#!/usr/bin/env python3

# -*- coding: utf-8 -*-

###########################################################################
# :::~ Copyright (C) 2003-2010 by Claudio J. Tessone <claudio.tessone@uzh.ch>
#
# Modified on: v2.9.2 14 May 2011
# Modified on: v2.9.1 30 May 2010
# Modified on: v2.9   15 Sep 2008
# Created  on: v0.1   09 Jan 2005
#
# License: Distributed according to GNU/GPL Version 2
#          (see http://www.gnu.org)
#
###########################################################################


class Iterator:
    """class that implements a generic iterator for the spg framework
    **the implementation is minimal**. it is limited to known lists
    of values does not support changes on the fly of the list (!)
    """

    def __init__(self, name=None, data=[]):
        """name: the label to be assigned to this iterator
        data: the set of values can be assigned to this iterator
        """
        self.name = name
        self.data = data
        # self.type = "."
        self.reset()

    def __iter__(self):
        return self

    def __next__(self):
        if self.__index is None:
            self.__index = 0
        else:
            self.__index += 1

        try:
            self.value = self.data[self.__index]
        except:
            raise StopIteration

        return self.value

    def reset(self):
        try:
            self.value = self.data[0]
            self.__index = 0
        except:
            self.value = ""
            self.__index = None

    def __len__(self):
        return len(self.data)


class IterConstant(Iterator):
    """class that implements a generic iterator for the spg framework
    **the implementation is minimal**. it is limited to known lists
    of values does not support changes on the fly of the list (!)
    """

    def __init__(self, name=None, data=None):
        """name: the label to be assigned to this iterator
        data: the set of values can be assigned to this iterator
        """
        if isinstance(data, type([])):
            Iterator.__init__(self, name, data)
        else:
            Iterator.__init__(self, name, [data])
        self.name = name

        self.reset()

    def __iter__(self):
        return self

    def __next__(self):
        raise StopIteration

    def reset(self):
        try:
            self.value = self.data[0]
            self.__index = None
        except:
            self.value = ""
            self.__index = None


class IterOperator(Iterator):
    """this subclass generates the values for classes defined according to the rule
    @var_name val_min val_max step
    where @ is the operation defined for the data type.
    var_name is the variable name
    val_min, val_max the bounds
    step  the step
    the actualization process runs according to actual_value = actual_value @ step
    """

    def __init__(self, name, type, limits):
        """name: of the variable
        type: operator to be used in the operation
        limits: a 3-tuple with minimum, maximum and step values
        """
        self.name = name
        self.type = type
        data = self._parse(type, limits)
        Iterator.__init__(self, name, data)

    def _parse(self, it_type, limits):
        self.xmin, self.xmax, self.xstep = limits
        # xmin = eval(xmin)
        # xmax = eval(xmax)
        # xstep = eval(xstep)
        #######################################
        #   Block that raises exception in the case that iteration requested
        #   do not reaches xmax

        assert abs(self.xmax - self.xmin) > abs(
            self.xmax - eval(f"{self.xmin}{it_type}{self.xstep}")
        ), "on variable '{}': '{}{}{}' does not approach to {} ".format(
            self.name,
            str(self.xmin),
            str(it_type),
            str(self.xstep),
            str(self.xmax),
        )
        #
        #######################################

        lsTmp = []
        xact = self.xmin

        while (self.xmin > self.xmax) ^ (xact <= self.xmax):  # ^ is xor in python !
            lsTmp.append(xact)
            xact = eval(f"{xact}{it_type}{self.xstep}")

        return lsTmp


#:::~ ###################################################################
#:::~ ###################################################################


class IterMutable(Iterator):
    """this subclass generates the values for classes defined according to the rule
    @var_name val_min val_max step
    where @ is the operation defined for the data type.
    var_name is the variable name
    val_min, val_max the bounds
    step  the step
    the actualization process runs according to actual_value = actual_value @ step
    """

    def __init__(self, name, type, limit_commands, external_dict):
        """name: of the variable
        type: operator to be used in the operation
        limits: a 3-tuple with minimum, maximum and step values
        """
        self.name = name
        self.limit_commands = limit_commands
        self.external_dict = external_dict
        self.type = type
        self.data = None

    #       Iterator.__init__(self,name,data)

    def _parse(self, it_type, limits):
        self.xmin, self.xmax, self.xstep = limits
        # xmin = eval(xmin)
        # xmax = eval(xmax)
        # xstep = eval(xstep)
        #######################################
        #   Block that raises exception in the case that iteration requested
        #   do not reaches xmax

        assert abs(self.xmax - self.xmin) > abs(
            self.xmax - eval(f"{self.xmin}{it_type}{self.xstep}")
        ), "on variable '{}': '{}{}{}' does not approach to {} ".format(
            self.name,
            str(self.xmin),
            str(it_type),
            str(self.xstep),
            str(self.xmax),
        )
        #
        #######################################

        lsTmp = []
        xact = self.xmin

        while (self.xmin > self.xmax) ^ (xact <= self.xmax):  # ^ is xor in python !
            #            print(f"{self.type}({xact})")
            try:
                lsTmp.append(eval(f"{self.type}({xact})"))
            except:
                lsTmp.append(xact)

            xact = eval(f"{xact}{it_type}{self.xstep}")

        return lsTmp

    def reset(self):
        self._parse()
        super().reset()


#:

#:::~ ###################################################################
#:::~ ###################################################################


#:::~ ###################################################################
#:::~ ###################################################################
#:::~ ###################################################################
#:::~ ###################################################################
#:::~ ###################################################################
#:::~ ###################################################################
#:::~ ###################################################################
#:::~ ###################################################################
#:::~ ###################################################################
#:::~ ###################################################################
#:::~ ###################################################################
#:::~ ###################################################################
#:::~ ###################################################################
#:::~ ###################################################################


class MultIterator:
    """class that implements a generic multiterator for the spg framework.
    the values are supossed to be accessed **only** through the
    operator[]
    """

    def __init__(self):
        self.data = []
        self.__dict = {}
        self.names = []  # ordered items
        self.__is_reset = True

    def add(self, spg_it):
        """raises an AssertionError if the key is duplicated"""
        assert spg_it.name not in self.names, "duplicate key '%s'" % spg_it.name

        assert self.__is_reset, "multiterator already initiated"

        self.names.append(spg_it.name)
        self.data.append(spg_it)
        self.__dict[spg_it.name] = spg_it.value

    def __iter__(self):
        return self

    def __len__(self):
        ret = 1
        for it in self.data:
            ret *= len(it)
        return ret

    def reset(self):
        for i in self.data:
            i.reset()
            self.__dict[i.name] = i.value
        self.__is_reset = True

    def __next__(self):
        if self.__is_reset:
            self.__is_reset = False
            return self.__dict

        index = len(self.data) - 1

        while index >= 0:
            try:
                next(self.data[index])
                self.__dict[self.data[index].name] = self.data[index].value
                return self.__dict
            except StopIteration:
                self.data[index].reset()
                self.__dict[self.data[index].name] = self.data[index].value
                index -= 1
        raise StopIteration

    def __getitem__(self, name):
        """the values of the multiterator are supposed to be accessed
        only by the operator[] (or by the returned value of next()
        """
        #      print name, self.names
        #      if name == "id":
        #          return self.current_iteration_id

        assert name in self.names, (
            "the requested variable '%s' was not found in the multiterator" % name
        )
        return self.__dict[name]

    def get_dict(self):
        return self.__dict.copy()

    #  def items(self):
    #      return self.names

    def get_variables(self):
        return [i.name for i in self.data if i.__class__ != IterConstant]

    def constant_parameters(self):
        return [i.name for i in self.data if i.__class__ == IterConstant]

    def reorder(self, new_order):
        """the ordered list of spgiterator's names can be reshuffled with this"""
        assert set(self.names) == set(
            new_order
        ), "the origin and destination set of variables differ"

        self.names = new_order

    def position_of(self, var):
        """returns the position in the ordered list of a given variable"""
        assert (
            var in self.names
        ), "the requested variable was not found in the multiterator"

        return self.names.index(var)


if __name__ == "__main__":

    mu = MultIterator()
    mu.add(IterOperator(name="a", type="*", limits=(1, 8, 2)))
    mu.add(IterOperator(name="b", type="+", limits=(0, 2, 0.5)))
    mu.add(IterConstant(name="const", data="3"))
    mu.add(IterConstant(name="konst", data=[3]))
    mu.add(Iterator(name="c", data=[0, -1]))
    #    mu.add(SPGIterator(name = "b", data=[2,4,8]))
    for i in mu:
        print(i)
#    m2 = SPGIterator(name = "a", data=[1,2,4,8])
