# Copyright (c) 1996, 1997, 1998 The Regents of the University of California.
# All rights reserved.  See legal notices for full text and disclaimer.

"""
Conditions that are possibly time-dependent
Conditions available in this file are:
    OneTime(time)   #True when the time is reached.
    Cycles(start_cycle, stop_cycle=ForeverCycle, stride = 1) # True every so many cycles
    Times(start_time, stop_time=ForeverTime, stride = 1.0)   # True every so many "seconds"
    And (Condition1, Condition2)  #True if both are true
    Or (Condition1, Condition2)   #True if one of the conditions is true
    Not (Condition1)                #True if the condition is not true
    When ("expression to be evaluated", context)  # True if expression evaluates true
        The context can be (a) a dictionary, (b) a string naming a module, or (c) a module.
        In (b) and (c) the modules' __dict__ is used.

A condition can be tested at a given cycle/time via this
API:
        c.evaluate (cycle, time)
        if c.state:
            ...condition was true...
        if c.state_final:
            ...condition intended for use as "last" sample

To define a new kind of Condition, inherit from Condition and define
a method _value (self, cycle, time) which evaluates the condition at that time
and returns a tuple-pair of logical values. The first should be the value of the condition,
true or false, at the current time. The second should be true or false under assuming this is
the "last" timestep.

You can assume that _value (resp. _value_last) is called only once for a given cycle number, and
that these calls are in increasing order for both cycle and time. 

Your __init__ routine must call Condition.initialize (self)

It is recommended that you also redefine __repr__ and __str__,
and name any routine which resets the condition "set".
"""
import sys
import types
import contexts

# times and cycles for "never stopping"
ForeverCycle = 1000000
ForeverTime = 1.0 * ForeverCycle

# time and cycle that are smaller than any time and cycle
NeverCycle = - 1000000
NeverTime = - 1.0e30

class Condition:
    "Root of the family of conditions"
    def __init__ (self):
        self.initialize ()

    def initialize (self):
        self.evaluation_cycle = NeverCycle # cycle at last evaluation
        self.evaluation_time = NeverTime # time at last evaluation
        self.state = 0
        self.state_final = 0
        self.state_final_enabled = 1

    def evaluate (self, cycle, time):
        "Calculate state at given time."
        if cycle > self.evaluation_cycle:
            self.state, self.state_final = self._value (cycle, time)
            self.state_final = self.state_final and self.state_final_enabled
            self.evaluation_cycle = cycle
            self.evaluation_time = time  # due to roundoff, might not be strictly monotonic

    def _value (self, cycle, time):
        return (1, 1)

    def __add__ (self, other):
        "Return logical and of self and other."
        return And(self,other)

    def report (self):
        "String representation of internal state."
        result = `self` + '\n'
        result = result + "\tEvaluation cycle: " + `self.evaluation_cycle` + '\n'
        result = result + "\tEvaluation time: " + `self.evaluation_time` + '\n'
        result = result + "\tState: " + `self.state` + '\n'
        result = result + "\tState (final): " + `self.state_final` + '\n'
        result = result + "\tFinal state enabled: " + `self.state_final_enabled` + '\n'
        return result

    def __repr__ (self):
        return "Condition ()"

    __str__ = __repr__

EveryCycle = Condition()

class TimeList (Condition):
    "A condition that is true at a list of times."
    def __init__ (self, when):
        Condition.initialize (self)
        self.set (when)

    def set (self, when):
        self.timelist = list(when)
        self.timelist.sort ()
        self.cursor = 0

    def _value (self, cycle, time):
        try:
            c = self.timelist[self.cursor]
        except IndexError:
            return (0, 0)
        t = (self.evaluation_time < c) and (c <= time)
        if t:
            self._advance (time, self.timelist)
        return (t, 1)

    def _advance (self, t, tlist):
        c = tlist[self.cursor]
        while c <=t:
            self.cursor = self.cursor + 1
            try:
                c = tlist[self.cursor]
            except IndexError:
                return

    def __repr__ (self):
        return "TimeList: " + `self.timelist`

    __str__ = __repr__

class CycleList (Condition):
    "A condition that is true at a list of cycles."
    def __init__ (self, when):
        Condition.initialize (self)
        self.set (when)

    def set (self, when):
        self.cyclelist = list(when)
        self.cyclelist.sort ()
        self.cursor = 0

    def _value (self, cycle, time):
        try:
            c = self.cyclelist[self.cursor]
        except IndexError:
            return (0, 0)
        t = (self.evaluation_cycle < c) and (c <= cycle)
        if t:
            self._advance (cycle, self.cyclelist)
        return (t, 1)

    def _advance (self, t, tlist):
        c = tlist[self.cursor]
        while c <=t:
            self.cursor = self.cursor + 1
            try:
                c = tlist[self.cursor]
            except IndexError:
                return

    def __repr__ (self):
        return "Cyclelist: " + `self.cyclelist`

    __str__ = __repr__

class Cycles (Condition):
    "Condition on cycle number of the form start stop stride."
    def __init__ (self, start=0, stop=ForeverCycle, stride=1):
        Condition.initialize (self)
        self.set (start, stop, stride)

    def set (self, start, stop, stride):
        "Set the condition to start:stop:stride"
        if (stop >= start) and (stride > 0):
            self.start = start
            self.stop = stop
            self.stride = stride
        else:
            raise ValueError, "Cycles specification illegal."

    def _value (self, cycle, time):
        t = (cycle <= self.stop) and (cycle >= self.start)
        if t:
            if (cycle - self.start) % self.stride == 0:
                return (1, 1)
            else:
                return (0, 1)
        else:
            return (0, 0)

    def __repr__ (self):
        s = "Cycles (" + `self.start` + ", " + `self.stop`
        if self.stride != 1:
            s = s + ", " + `self.stride`
        s = s + ')'
        return s

    __str__ = __repr__

class Times (Condition):
    "Condition on time of the form start stop stride"
    def __init__ (self, start=0.0, stop=ForeverTime, stride=1.0):
        Condition.initialize (self)
        self.set (start, stop, stride)

    def set (self, start, stop, stride):
        "Set the condition to start:stop:stride"
        if (stop >= start) and (stride > 0.0):
            self.start = float(start)
            self.stop = float(stop)
            self.stride = float(stride)
            self.next_picket = start
            self.picket_number = 0
        else:
            raise ValueError, "TimeIntervalCondition specification illegal."

    def _value (self, cycle, time):
        t = (time >= self.start) and (time <= self.stop)
        if t:
            if(time >= self.next_picket):
                start = self.start
                stride = self.stride
                picket_number = self.picket_number + 1
                next_picket = start + picket_number * stride
                while next_picket <= time:
                    picket_number = picket_number + 1
                    next_picket = start + picket_number * stride
                self.next_picket = next_picket
                self.picket_number = picket_number
                return (1, 1)
            else:
                return (0, 1)
        else:
            return (0, 0)

    def __repr__ (self):
        s = "Times (" + `self.start` + ", " + `self.stop`
        if self.stride != 1.0:
            s = s + ", " + `self.stride`
        s = s + ')'
        return s

    __str__ = __repr__

class And (Condition):
    "Logical and of two conditions"
    def __init__ (self, c1, c2):
        Condition.initialize (self)
        self.set (c1, c2)

    def set (self, c1, c2):
        "Set the condition to be true if c1 and c2 are true."
        self.c1 = c1
        self.c2 = c2

    def _value (self, cycle, time):
        "Are both the conditions true?"
        self.c1.evaluate (cycle, time)
        self.c2.evaluate (cycle, time)
        s1a = self.c1.state
        s1b = self.c1.state_final
        s2a = self.c2.state
        s2b = self.c2.state_final
        return (s1a and s2a, s1b and s2b)

    def __repr__ (self):
        return "And (" + `self.c1` + ", " + `self.c2` + ")"

    __str__ = __repr__

class Or (Condition):
    "Logical or of two conditions"
    def __init__ (self, c1, c2):
        Condition.initialize (self)
        self.set (c1, c2)

    def set (self, c1, c2):
        "Set the condition to be true if c1 or c2 (or both) are true."
        self.c1 = c1
        self.c2 = c2

    def _value (self, cycle, time):
        "Are one or both conditions true?"
        self.c1.evaluate (cycle, time)
        self.c2.evaluate (cycle, time)
        s1a = self.c1.state
        s1b = self.c1.state_final
        s2a = self.c2.state
        s2b = self.c2.state_final
        return (s1a or s2a, s1b or s2b)

    def __repr__ (self):
        return "Or (" + `self.c1` + ", " + `self.c2` + ")"

    __str__ = __repr__

class Not (Condition):
    "Logical negation of a condition"
    def __init__ (self, c1):
        Condition.initialize (self)
        self.set (c1)

    def set (self, c1):
        "Set the condition to be true iff c1 is false."
        self.c1 = c1

    def _value (self, cycle, time):
        "Is the target conditon false?"
        self.c1.evaluate (cycle, time)
        return (not self.c1.state, not self.c1.state_final)

    def __repr__ (self):
        return "Not (" + `self.c1` + ")"

    __str__ = __repr__


class When (Condition):
    "A logical condition, and the context dictionary in which to evaluate it."
    def __init__ (self, item, context = "__main__"):
        Condition.initialize (self)
        self.set (item, context)

    def set (self, item, context = "__main__"):
        "Set the condition and its context."
        if type(item) == types.StringType:
            self.item = item
        else:
            raise TypeError, (types.StringType, type(item))
        self.context = contexts.resolve_context (context)

    def _value (self, cycle, time):
        "Is this true at the time given by the clock?"
        t = eval(self.item, self.context)
        return (t, t)

    def __repr__ (self):
        return "When (" + self.item +  ")"

    __str__ = __repr__

if __name__ == "__main__":
    z = open("history_condition_out.txt", "w")
    import sys
    sys.stdout = z
    b = Cycles (2, 10, 4)
    c = Times (.3, 1.0, .3)
    ll = When ("xx > .3")
    t1 = TimeList ([.4, .7, .9])
    t2 = TimeList ([.4, .8, 1.5])
    t3 = CycleList ([5, 6, 8])
#    a = And(ll, c)
    a = ll + c
    e = Or(ll, b)
    d = Not(ll)
    for i in range(12):
        cycle = i
        time = i / 10.
        xx = time / 2.0
        print cycle, time, xx
        for x in (b,c,ll,a,e,d, t1, t2,t3):
            x.evaluate (cycle, time)
            print "\t", (x.state, x.state_final), x
    for x in (b,c,ll,a,e,d,t1,t2,t3):
        print x.report()
    raise SystemExit   
