import pylase as ol
import wave, array, random, math

def clamp(x, y, z):
    if x < y:
        return y
    if x > z:
        return z
    return x

def clamp1(x):
    return clamp(x, 0.0, 1.0)

def unmapf(x, f, t):
    return (x - f) / float(t - f)

def mapf(x, f, t):
    return x * (t - f) + f

def shuf(t):
    return math.sin(t**1.1 * 31337)

def ss(x):
    return x * x * (3 - 2 * x)

def ssout(x):
    return ss(x * 0.5) * 2

def ssin(x):
    return ss(0.5 + x * 0.5) * 2 - 1

def rgb(r, g, b):
    return (int(r) << 16) | (int(g) << 8) | int(b)

def interp(t, l):
    for i, (t1, s1, v1) in enumerate(l):
        if t <= t1:
            if i > 0:
                t0, s0, v0 = l[i-1]
                if not isinstance(s0, tuple):
                    s0 = [s0]*len(v0)
                if not isinstance(s1, tuple):
                    s1 = [s1]*len(v1)
                k = clamp1(unmapf(t, t0, t1))
                vout = []
                for s0i, s1i, a, b in zip(s0, s1, v0, v1):
                    if s1i and s0i:
                        kl = ss(k)
                    elif s1i:
                        kl = ssin(k)
                    elif s0i:
                        kl = ssout(k)
                    else:
                        kl = k
                    vout.append(mapf(kl, a, b))
            else:
                vout = v1
            if len(vout) == 1:
                return vout[0]
            else:
                return vout
    vout = l[-1][2]
    if len(vout) == 1:
        return vout[0]
    else:
        return vout

def points_to_matrix(p0, p1, p2, p3):
    # Borrowed from Qt4: QTransform::squareToQuad
    dx0, dy0 = p0
    dx1, dy1 = p1
    dx2, dy2 = p2
    dx3, dy3 = p3

    ax  = dx0 - dx1 + dx2 - dx3
    ay  = dy0 - dy1 + dy2 - dy3

    ax1 = dx1 - dx2
    ax2 = dx3 - dx2
    ay1 = dy1 - dy2
    ay2 = dy3 - dy2

    gtop    =  ax  * ay2 - ax2 * ay
    htop    =  ax1 * ay  - ax  * ay1
    bottom  =  ax1 * ay2 - ax2 * ay1

    g = gtop / bottom
    h = htop / bottom

    a = dx1 - dx0 + g * dx1
    b = dx3 - dx0 + h * dx3
    c = dx0
    d = dy1 - dy0 + g * dy1
    e = dy3 - dy0 + h * dy3
    f = dy0

    return [a, d, g,
            b, e, h,
            c, f, 1.0]

class Effect(object):
    def __init__(self, demo, start, end):
        self.start = start * 4.0
        self.end = end * 4.0
        self.random = random.Random()
        self.random.seed(42)
        self.demo = demo

    def draw_fade(self, obj, curve):
        bright = interp(self.t, curve)
        if bright > 0:
            ol.pushColor()
            ol.multColor(ol.C_GREY(255 * bright))
            obj.draw()
            ol.popColor()

class Demo(object):
    RATE = 48000
    AUDIO = None
    ASPECT = 1.0
    MAX_FPS = 100

    def __init__(self):
        if self.ASPECT > 1:
            self.w = 1.0
            self.h = 1.0/self.ASPECT
        else:
            self.w = self.ASPECT
            self.h = 1.0
        self.effects = []
        self.param_stack = []

    def add_effect(self, effect):
        self.effects.append(effect)

    def push_params(self):
        self.param_stack.append(self.params.copy())

    def pop_params(self):
        self.params = self.param_stack.pop()
        ol.setRenderParams(self.params)

    def pop_params(self):
        self.params = self.param_stack.pop()
        ol.setRenderParams(self.params)

    def audiocb(self, samples):
        buf = self.audio[self.ap:self.ap + samples]
        self.ap += samples
        return buf

    def load_audio(self):
        wav = wave.open(self.AUDIO)
        assert wav.getnchannels() == 2
        assert wav.getsampwidth() == 2
        frames = wav.getnframes()
        samples = array.array('h', wav.readframes(frames))
        samples = [i/32768.0 for i in samples]
        self.audio = zip(*([iter(samples)]*2))

    @property
    def beat(self):
        return self.t * self.BPM / 60.0 

    def play(self):
        ol.init()

        try:
            for i in range(20):
                ol.renderFrame(self.MAX_FPS)
            time = 0
            frames = 0

            self.t = float(self.START_TIME)

            if self.audio:
                self.ap = int(self.t * self.RATE)
                ol.setAudioCallback(self.audiocb)

            while self.beat < (self.LEN * 4):
                ol.loadIdentity3()
                ol.loadIdentity()
                self.params = self.reset_params.copy()
                self.param_stack = []
                ol.setRenderParams(self.params)

                ol.setScissor((-self.w,-self.h),(self.w,self.h))
                if self.FRAME:
                    ol.rect((-self.w,-self.h),(self.w,self.h),ol.C_GREY(50))

                self.frame()

                for i in self.effects:
                    ol.loadIdentity3()
                    ol.loadIdentity()
                    ol.setRenderParams(self.params)
                    if i.start <= self.beat <= i.end:
                        i.t = self.beat - i.start
                        i.render(i.t)

                ft = ol.renderFrame(self.MAX_FPS)
                print "[%3d:%.2f] %3.1f FPS %5d pts" % (
                    int (self.beat / 4),
                    self.beat % 4,
                    1.0 / ft,
                    int(ft * self.RATE))
                self.t += ft
        finally:
            for i in range(10):
                ol.renderFrame(self.MAX_FPS)
            ol.shutdown()

    def load(self):
        if self.AUDIO is not None:
            print "Loading audio..."
            self.load_audio()
        else:
            self.audio = None
        print "Loading resources..."
        self.load_resources()
        print "Initializing effects..."
        self.init_effects()
        print "Ready."

        self.params = ol.RenderParams()
        self.init_params()
        self.reset_params = self.params.copy()
