# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/07_errors.ipynb (unless otherwise specified).

__all__ = ['BaseErrorType', 'RootSumSquaredError', 'RootMeanSquareError', 'BaseErrorCollector', 'TotalError',
           'InputsError', 'ErrorFactory']

# Cell
import numpy as np
from abc import ABC, abstractmethod
from .hierarchy import PCTHierarchy
from .functions import IndexedParameter

# Cell
class BaseErrorType(ABC):
    "Base class of a type error response. This class is not used direclty by developers, but defines the interface common to all."
    def __init__(self):
        self.error_response=0

    @abstractmethod
    def __call__(self):
        pass

    def get_error_response(self):
        return self.error_response

# Cell
class RootSumSquaredError(BaseErrorType):
    "The square root of the sum of the square of the errors."
    def __init__(self):
        self.sum=0

    def __call__(self, error):
        self.sum+=error*error
        self.error_response=np.sqrt(self.sum)

    class Factory:
        def create(self): return RootSumSquaredError()

# Cell
class RootMeanSquareError(BaseErrorType):
    "The square root of the sum of the square of the errors."
    def __init__(self):
        self.sum=0
        self.num=0

    def __call__(self, error):
        self.num+=1
        self.sum+=error*error
        self.error_response=np.sqrt(self.sum/self.num)

    class Factory:
        def create(self): return RootMeanSquareError()

# Cell
class BaseErrorCollector(ABC):
    "Base class of an error collector. This class is not used direclty by developers, but defines the interface common to all."
    'Parameters:'
    'limit - the limit of valid error response'
    'error_response - the type of error response'

    def __init__(self, limit,error_response):
        self.limit=limit
        self.limit_exceeded=False
        self.error_response=error_response

    def set_limit(self, limit):
        self.limit=limit
        self.limit_exceeded=False

    def set_error_response(self, error_response):
        self.error_response=error_response

    def error(self):
        return self.error_response.get_error_response()

    def add_error_data(self, data=[]):
        for datum in data:
            self.error_response(datum)

    def is_limit_exceeded(self):
        return self.limit_exceeded

    @classmethod
    def collector(cls, error_type, error_collector, limit):
        et = ErrorFactory.createError(error_type)
        ec = ErrorFactory.createError(error_collector)
        ec.set_limit(limit)
        ec.set_error_response(et)

        return ec

# Cell
class TotalError(BaseErrorCollector):
    "A class to collect all the errors of the control system run."
    def __init__(self, limit=1000, error_response=None, **cargs):
        super().__init__(limit, error_response)

    def add_data(self, hpct=None):
        for level in range(len(hpct.hierarchy)):
             for col in range(len(hpct.hierarchy[level])):
                node  = hpct.hierarchy[level][col]
                self.add_error_data( [node.get_function("comparator").get_value()])
                if self.error_response.get_error_response() > self.limit:
                    self.limit_exceeded=True
                    return
    class Factory:
        def create(self): return TotalError()

# Cell
class InputsError(BaseErrorCollector):
    "A class to collect all the errors of the control system run."
    def __init__(self, limit=1000, error_response=None, **cargs):
        super().__init__(limit, error_response)

    def add_data(self, hpct=None):
        data = []
        pre = hpct.get_preprocessor()
        for func in pre:
            if isinstance(func, IndexedParameter):
                data.append(func.get_value())
        self.add_error_data( data )
        if self.error_response.get_error_response() > self.limit:
            self.limit_exceeded=True
            return
    class Factory:
        def create(self): return InputsError()

# Cell
class ErrorFactory:
    factories = {}
    def addFactory(id, errorFactory):
        ErrorFactory.factories.put[id] = errorFactory
    addFactory = staticmethod(addFactory)
    # A Template Method:
    def createError(id):
        if not ErrorFactory.factories.__contains__(id):
            ErrorFactory.factories[id] = \
              eval(id + '.Factory()')
        return ErrorFactory.factories[id].create()
    createError = staticmethod(createError)