# Path: fito/operation_runner.py
# class OperationRunner(Spec):
#     execute_cache_size = NumericField(default=0)
#     verbose = PrimitiveField(default=False)
# 
#     # Whether to force execution and ignore caches
#     # Helps encapsulate the behaviour so the Operation.apply remains simple
#     force = PrimitiveField(serialize=False, default=False)
# 
#     def __init__(self, *args, **kwargs):
#         super(OperationRunner, self).__init__(*args, **kwargs)
#         if self.execute_cache_size == 0:
#             self.execute_cache = None
#         else:
#             self.execute_cache = FifoCache(self.execute_cache_size, self.verbose)
# 
#     def alias(self, **kwargs):
#         """
#         Same as self.replace, but keeps the same execute_cache
#         """
#         res = self.replace(**kwargs)
#         if res.execute_cache is not None:
#             res.execute_cache = self.execute_cache
#         return res
# 
#     # TODO: The FifoCache can be casted into a FifoDataStore, and make this function an @autosave
#     def execute(self, operation, force=False):
#         """
#         Executes an operation using this data store as input
#         If this data store was configured to use an execute cache, it will be used
# 
#         :param force: Whether to ignore the current cached value of this operation
#         """
#         force = force or self.force
#         if not force:
#             # if not force, then check the caches out
#             functions = [
#                 lambda: self._get_memory_cache(operation),
#                 lambda: self._get_data_store_cache(operation),
#             ]
#         else:
#             functions = []
# 
#         functions.append(
#             lambda: operation.apply(
#                 self.alias(force=force)
#             )
#         )
# 
#         for func in functions:
#             try:
#                 res = func()
#                 break
#             except NotFoundError:
#                 pass
# 
#         if self.execute_cache is not None:
#             self.execute_cache.set(operation, res)
# 
#         out_data_store = operation.get_out_data_store()
#         if out_data_store is not None:
#             out_data_store[operation] = res
# 
#         return res
# 
#     def _get_memory_cache(self, operation):
#         if self.execute_cache is not None:
#             try: return self.execute_cache[operation]
#             except KeyError: raise NotFoundError()
#         else:
#             raise NotFoundError()
# 
#     def _get_data_store_cache(self, operation):
#         out_data_store = operation.get_out_data_store()
#         if out_data_store is not None:
#             try:
#                 return out_data_store[operation]
#             except KeyError:
#                 raise NotFoundError()
#         else:
#             raise NotFoundError()
# 
# Path: fito/operations/operation.py
# class Operation(Spec):
#     out_data_store = SpecField(default=None, serialize=False)
#     default_data_store = None
# 
#     def execute(self, force=False):
#         return OperationRunner().execute(self, force=force)
# 
#     def apply(self, runner):
#         raise NotImplementedError()
# 
#     def get_out_data_store(self):
#         if self.out_data_store is not None:
#             return self.out_data_store
#         else:
#             return type(self).default_data_store
# 
# Path: fito/specs/fields.py
# class NumericField(PrimitiveField):
#     def __lt__(self, _): return
# 
#     def __add__(self, _): return
# 
#     def __sub__(self, other): return
# 
#     def __mul__(self, other): return
# 
#     def __floordiv__(self, other): return
# 
#     def __mod__(self, other): return
# 
#     def __divmod__(self, other): return
# 
#     def __pow__(self, _, modulo=None): return
# 
#     def __lshift__(self, other): return
# 
#     def __rshift__(self, other): return
# 
#     def __and__(self, other): return
# 
#     def __xor__(self, other): return
# 
#     def __or__(self, other): return
# 
#     @property
#     def allowed_types(self):
#         return int, float
# 
# def SpecField(pos=None, default=_no_default, base_type=None, serialize=True, spec_field_subclass=None):
#     """
#     Builds a SpecField
# 
#     :param pos: Position on *args
#     :param default: Default value
#     :param base_type: Base type, it does some type checkig + avoids some warnings from IntelliJ
#     :param serialize: Whether this spec field should be included in the serialization of the object
#     :param spec_field_subclass: Sublcass of BaseSpecField, useful to extend the lib
# 
#     :return:
#     """
#     if not serialize and default is _no_default:
#         raise RuntimeError("If serialize == False, the field should have a default value")
# 
#     spec_field_subclass = spec_field_subclass or BaseSpecField
# 
#     if base_type is not None:
#         assert issubclass(base_type, base.Spec)
#         return_type = type(
#             'SpecFieldFor{}'.format(base_type.__name__),
#             (spec_field_subclass, base_type),
#             {}
#         )
#     else:
#         return_type = spec_field_subclass
# 
#     return return_type(pos=pos, default=default, base_type=base_type, serialize=serialize)


from collections import defaultdict
from random import Random
from fito.operation_runner import OperationRunner
from fito.operations.operation import Operation
from fito.specs.fields import NumericField, SpecField
import inspect
import unittest


class MultiplyOperation(Numeric):
    a = SpecField(0)
    b = SpecField(1)

    def apply(self, runner):
        super(MultiplyOperation, self).apply(runner)
        return runner.execute(self.a) * runner.execute(self.b)

    def __repr__(self):
        return '{} * {}'.format(self.a, self.b)


class TestOperation(unittest.TestCase):
    def setUp(self):
        # create some numbers
        numbers = [GetNumber(i) for i in xrange(10)]

        # multiply random numbers
        rnd = Random(42)
        for i in xrange(15):
            a = rnd.choice(numbers)
            b = rnd.choice(numbers)
            numbers.append(a * b)

        self.operations = numbers

    def test_base(self):
        # test whether everything works without caching

# Path: fito/operation_runner.py
# class OperationRunner(Spec):
#     execute_cache_size = NumericField(default=0)
#     verbose = PrimitiveField(default=False)
# 
#     # Whether to force execution and ignore caches
#     # Helps encapsulate the behaviour so the Operation.apply remains simple
#     force = PrimitiveField(serialize=False, default=False)
# 
#     def __init__(self, *args, **kwargs):
#         super(OperationRunner, self).__init__(*args, **kwargs)
#         if self.execute_cache_size == 0:
#             self.execute_cache = None
#         else:
#             self.execute_cache = FifoCache(self.execute_cache_size, self.verbose)
# 
#     def alias(self, **kwargs):
#         """
#         Same as self.replace, but keeps the same execute_cache
#         """
#         res = self.replace(**kwargs)
#         if res.execute_cache is not None:
#             res.execute_cache = self.execute_cache
#         return res
# 
#     # TODO: The FifoCache can be casted into a FifoDataStore, and make this function an @autosave
#     def execute(self, operation, force=False):
#         """
#         Executes an operation using this data store as input
#         If this data store was configured to use an execute cache, it will be used
# 
#         :param force: Whether to ignore the current cached value of this operation
#         """
#         force = force or self.force
#         if not force:
#             # if not force, then check the caches out
#             functions = [
#                 lambda: self._get_memory_cache(operation),
#                 lambda: self._get_data_store_cache(operation),
#             ]
#         else:
#             functions = []
# 
#         functions.append(
#             lambda: operation.apply(
#                 self.alias(force=force)
#             )
#         )
# 
#         for func in functions:
#             try:
#                 res = func()
#                 break
#             except NotFoundError:
#                 pass
# 
#         if self.execute_cache is not None:
#             self.execute_cache.set(operation, res)
# 
#         out_data_store = operation.get_out_data_store()
#         if out_data_store is not None:
#             out_data_store[operation] = res
# 
#         return res
# 
#     def _get_memory_cache(self, operation):
#         if self.execute_cache is not None:
#             try: return self.execute_cache[operation]
#             except KeyError: raise NotFoundError()
#         else:
#             raise NotFoundError()
# 
#     def _get_data_store_cache(self, operation):
#         out_data_store = operation.get_out_data_store()
#         if out_data_store is not None:
#             try:
#                 return out_data_store[operation]
#             except KeyError:
#                 raise NotFoundError()
#         else:
#             raise NotFoundError()
# 
# Path: fito/operations/operation.py
# class Operation(Spec):
#     out_data_store = SpecField(default=None, serialize=False)
#     default_data_store = None
# 
#     def execute(self, force=False):
#         return OperationRunner().execute(self, force=force)
# 
#     def apply(self, runner):
#         raise NotImplementedError()
# 
#     def get_out_data_store(self):
#         if self.out_data_store is not None:
#             return self.out_data_store
#         else:
#             return type(self).default_data_store
# 
# Path: fito/specs/fields.py
# class NumericField(PrimitiveField):
#     def __lt__(self, _): return
# 
#     def __add__(self, _): return
# 
#     def __sub__(self, other): return
# 
#     def __mul__(self, other): return
# 
#     def __floordiv__(self, other): return
# 
#     def __mod__(self, other): return
# 
#     def __divmod__(self, other): return
# 
#     def __pow__(self, _, modulo=None): return
# 
#     def __lshift__(self, other): return
# 
#     def __rshift__(self, other): return
# 
#     def __and__(self, other): return
# 
#     def __xor__(self, other): return
# 
#     def __or__(self, other): return
# 
#     @property
#     def allowed_types(self):
#         return int, float
# 
# def SpecField(pos=None, default=_no_default, base_type=None, serialize=True, spec_field_subclass=None):
#     """
#     Builds a SpecField
# 
#     :param pos: Position on *args
#     :param default: Default value
#     :param base_type: Base type, it does some type checkig + avoids some warnings from IntelliJ
#     :param serialize: Whether this spec field should be included in the serialization of the object
#     :param spec_field_subclass: Sublcass of BaseSpecField, useful to extend the lib
# 
#     :return:
#     """
#     if not serialize and default is _no_default:
#         raise RuntimeError("If serialize == False, the field should have a default value")
# 
#     spec_field_subclass = spec_field_subclass or BaseSpecField
# 
#     if base_type is not None:
#         assert issubclass(base_type, base.Spec)
#         return_type = type(
#             'SpecFieldFor{}'.format(base_type.__name__),
#             (spec_field_subclass, base_type),
#             {}
#         )
#     else:
#         return_type = spec_field_subclass
# 
#     return return_type(pos=pos, default=default, base_type=base_type, serialize=serialize)


# Path: tests/test_operation_runner.py
from collections import defaultdict
from random import Random
from fito.operation_runner import OperationRunner
from fito.operations.operation import Operation
from fito.specs.fields import NumericField, SpecField
import inspect
import unittest



class MultiplyOperation(Numeric):
    a = SpecField(0)
    b = SpecField(1)

    def apply(self, runner):
        super(MultiplyOperation, self).apply(runner)
        return runner.execute(self.a) * runner.execute(self.b)

    def __repr__(self):
        return '{} * {}'.format(self.a, self.b)


class TestOperation(unittest.TestCase):
    def setUp(self):
        # create some numbers
        numbers = [GetNumber(i) for i in xrange(10)]

        # multiply random numbers
        rnd = Random(42)
        for i in xrange(15):
            a = rnd.choice(numbers)
            b = rnd.choice(numbers)
            numbers.append(a * b)

        self.operations = numbers

    def test_base(self):
        # test whether everything works without caching

        runner = OperationRunner(execute_cache_size=0)