import math

from siege import game
from siege.util import seconds, Timer


class TweenSystem(object):
    NAME = "tween"

    def getName(self):
        return self.NAME

    def __init__(self):
        self.tweens = []
        self.defaultDuration = seconds(1)
        self.defaultTweening = None

    def update(self, frameTime):
        for tween in list(self.tweens):
            if tween.update(frameTime):
                self.tweens.remove(tween)

    def add(self, owner, **kwargs):
        duration = kwargs.pop("duration") if "duration" in kwargs else self.defaultDuration
        delay = kwargs.pop("delay") if "delay" in kwargs else 0
        tweening = kwargs.pop("tweening") if "tweening" in kwargs else self.defaultTweening
        onComplete = kwargs.pop("onComplete") if "onComplete" in kwargs else None
        self.tweens.append(Tween(owner, duration, delay, tweening, onComplete, **kwargs))

    def remove(self, tween):
        if tween in self.tweens:
            self.tweens.remove(tween)

    def getTweensForOwner(self, owner):
        tweens = []
        for tween in self.tweens:
            if tween.owner is owner:
                tweens.append(tween)
        return tweens

    def removeTweensFromOwner(self, owner):
        for tween in list(self.tweens):
            if tween.owner is owner:
                self.tweens.remove(tween)

    @staticmethod
    def register():
        game.registerSystem(TweenSystem.NAME, TweenSystem())


class Tween(object):
    def __init__(self, owner, duration, delay, tweening, onComplete, **kwargs):
        self.owner = owner
        self.duration = duration
        self.progress = Timer(duration)
        self.delay = Timer(delay)
        self.tweening = tweening
        self.onComplete = onComplete
        self.paused = not self.delay.expired()
        self.set = kwargs.pop("set") if "set" in kwargs else None
        self.get = kwargs.pop("get") if "get" in kwargs else None
        self.prop = kwargs.pop("prop") if "prop" in kwargs else ""
        if self.prop:
            self.get = self.getProp
            self.set = self.setProp
        if not (self.set and self.get):
            raise RuntimeWarning("Tween object was created with no property or no getter and setter methods.")
        self.start = self.get()
        if "change" in kwargs:
            self.target = None
            self.change = kwargs.pop("change")
        elif "target" in kwargs:
            self.target = kwargs.pop("target")
            self.change = self.target - self.start

    def update(self, frameTime):
        if self.paused:
            if not self.delay.expired():
                #Check for overflow incase we have a long frame and there is little delay left
                overflow = frameTime - self.delay.remaining()
                self.delay.update(frameTime)
                if self.delay.expired():
                    self.paused = True
                    #Set the starting value if we haven't started this tween yet
                    if self.progress.remaining() == self.duration:
                        self.start = self.get()
                        self.change = self.target - self.start
                self.paused = not self.delay.expired()
                if overflow > 0:
                    frameTime = overflow
                else:
                    return False

        self.progress.update(frameTime)
        delta = float(self.duration - self.progress.remaining())
        value = self.tweening(delta, self.start, self.change, self.duration)
        self.set(value)
        if self.progress.expired():
            if self.onComplete:
                self.onComplete()
            return True
        return False

    def getProp(self):
        return getattr(self.owner, self.prop)

    def setProp(self, value):
        setattr(self.owner, self.prop, value)

    def pause(self, duration=0):
        #If duration is 0 then tween will be paused indefinitely.
        self.paused = True
        if duration:
            self.delay.reset(duration)

    def resume(self):
        self.paused = False
        self.delay.reset()

    #Tweening functions
    @staticmethod
    def Linear(t, s, c, d):
        return c * t / d + s

    @staticmethod
    def QuadIn(t, s, c, d):
        t /= d
        return c * t * t + s

    @staticmethod
    def QuadOut(t, s, c, d):
        t /= d
        return -c * (t) * (t - 2) + s

    @staticmethod
    def QuadInOut(t, s, c, d):
        t /= d / 2
        if t < 1:
            return c / 2 * t * t * t + s
        t -= 2
        return c / 2 * (t * t * t + 2) + s

    @staticmethod
    def QuadOutIn(t, s, c, d):
        if t < d / 2:
            return Tween.QuadOut(t * 2, s, c / 2, d)
        return Tween.QuadIn(t * 2 - d, s + c / 2, c / 2)

    @staticmethod
    def CubicIn(t, s, c, d):
        t /= d
        return c * t * t * t + s

    @staticmethod
    def CubicOut(t, s, c, d):
        t = t / d - 1
        return c * (t * t * t + 1) + s

    @staticmethod
    def CubicInOut(t, s, c, d):
        t /= d / 2
        if t < 1:
            return c / 2 * t * t * t + s
        t -= 2
        return c / 2 * (t * t * t + 2) + s

    @staticmethod
    def CubicOutIn(t, s, c, d):
        if t < d / 2:
            return Tween.CubicOut(t * 2, s, c / 2, d)
        return Tween.CubicIn(t * 2 - d, s + c / 2, c / 2, d)

    @staticmethod
    def QuartIn(t, s, c, d):
        t /= d
        return c * t * t * t * t + s

    @staticmethod
    def QuartOut(t, s, c, d):
        t = t / d - 1
        return c * (t * t * t * t + 1) + s

    @staticmethod
    def QuartInOut(t, s, c, d):
        t /= d / 2
        if t < 1:
            return c / 2 * t * t * t * t + s
        t -= 2
        return c / 2 * (t * t * t * t + 2) + s

    @staticmethod
    def QuartOutIn(t, s, c, d):
        if t < d / 2:
            return Tween.QuartOut(t * 2, s, c / 2, d)
        return Tween.QuartIn(t * 2 - d, s + c / 2, c / 2, d)

    @staticmethod
    def ElasticIn(t, s, c, d):
        t /= d
        ts = t * t
        tc = ts * t
        return c * (33 * tc * ts - 59 * ts * ts + 32 * tc - 5 * ts) + s

    @staticmethod
    def ElasticOut(t, s, c, d):
        t /= d
        ts = t * t
        tc = ts * t
        return c * (33 * tc * ts - 106 * ts * ts + 126 * tc - 67 * ts + 15 * t) + s

    @staticmethod
    def square(f, t, s, c, d):
        t /= d
        return s if math.sin(2 * f * t * math.pi) >= 0 else d
