"""
Copyright (C) 2008  Matthew and Joey Marshall <joey@arcticpaint.com>

See main.py for full notice.
"""
from __future__ import division
import rabbyt
import math
from math import sin, cos, pi, degrees, radians
import random

from gamelib.connection import Connection
from gamelib.station import Station

class Cable(rabbyt.Animable):
    """
    A Cable has a length, two endpoints, and gondolas.
    """
    offset = rabbyt.anim_slot()
    speed = Station.speed # pixels per second.

    def __init__(self, world, endpoints, fade_in=0):
        rabbyt.Animable.__init__(self)
        self.world = world
        self.world.cables.append(self)
        self.world.renderables["cables"].append(self)
        self.endpoints = endpoints

        self.angle_r = math.atan2(self.endpoints[1].y-self.endpoints[0].y,
                                  self.endpoints[1].x-self.endpoints[0].x)
        self.angle_d = math.degrees(self.angle_r)

        self.length = math.hypot(self.endpoints[1].y-self.endpoints[0].y,
                                 self.endpoints[1].x-self.endpoints[0].x)
        self.cable_length = 2*self.length + 2*math.pi*Station.radius

        self._direction_offsets = {}
        angle = self.angle_r - math.pi/2
        self._direction_offsets[0] = (Station.radius*math.cos(angle),
                                      Station.radius*math.sin(angle))
        angle = self.angle_r + math.pi/2
        self._direction_offsets[1] = (Station.radius*math.cos(angle),
                                      Station.radius*math.sin(angle))


        self.offset = rabbyt.lerp(0,self.speed,dt=1,
                                  extend="extrapolate") + random.random()*self.speed

        min_gondola_spacing = 50


        gondola_count = int(math.floor(self.cable_length/min_gondola_spacing))
        gondola_spacing = self.cable_length/gondola_count

        self.gondolas = []
        for i in range(gondola_count):
            self.gondolas.append(Gondola(self, i*gondola_spacing, fade_in=fade_in))

        self.items = []

        self.connections = (
                Connection(endpoints[0], self, endpoints[1], "a"),
                Connection(endpoints[1], self, endpoints[0], "b"))

        self.endpoints[0].add_connection(self.connections[0])
        self.endpoints[1].add_connection(self.connections[1])


        self.cable_sprites = (
                rabbyt.Sprite("cable.png",
                    shape=(0,2,self.length,-2), 
                    tex_shape=(self.length/4, 1),
                    u = rabbyt.lerp(self.speed/4, 0, dt=1, extend="extrapolate"),
                    x=self.endpoints[0].x+self._direction_offsets[0][0],
                    y=self.endpoints[0].y+self._direction_offsets[0][1], 
                    rot=self.angle_d,
                    alpha=rabbyt.lerp(0,1,dt=fade_in)),
                rabbyt.Sprite("cable.png",
                    shape=(0,2,self.length,-2), 
                    tex_shape=(self.length/4, 1),
                    u = rabbyt.lerp(0,self.speed/4, dt=1, extend="extrapolate"),
                    x=self.endpoints[0].x+self._direction_offsets[1][0], 
                    y=self.endpoints[0].y+self._direction_offsets[1][1], 
                    rot=self.angle_d,
                    alpha=rabbyt.lerp(0,1,dt=fade_in)))

        self.error_sprite = rabbyt.Sprite(
                    shape=(0,20,self.length,-20), 
                    xy=self.endpoints[0].xy,
                    rot=self.angle_d,
                    rgb = (1,0,0),
                    alpha=0)


        # This if for rendering culling:
        a,b = self.endpoints
        self.left = min(a.left, b.left) - 30
        self.top = max(a.top, b.top) + 30
        self.right = max(a.right, b.right) + 30
        self.bottom = min(a.bottom, b.bottom) - 30

    def flash_error(self):
        self.error_sprite.alpha = rabbyt.lerp(.5,0, dt=.5)

    def render(self):
        self.error_sprite.render()
        rabbyt.render_unsorted(self.gondolas)
        rabbyt.render_unsorted(self.items)
        rabbyt.render_unsorted(self.cable_sprites)

    def get_gondola_at_offset(self, offset, deviance=2):
        """
        Return a gondola that is at the given offset or None.
        """
        for g in self.gondolas:
            if abs(g.offset - offset) <= deviance:
                return g
        return None



class Gondola(rabbyt.Sprite):
    """
    The thing that moves
    """
    def __init__(self, cable, starting_offset, fade_in=0):
        rabbyt.Sprite.__init__(self, "gondola.png")
        self.alpha = rabbyt.lerp(0,1, dt=fade_in)
        self.cable = cable
        self.starting_offset = starting_offset
        self.contents = None
        self.rot = self.cable.angle_d

        self.set_anims()

    @property
    def offset(self):
        return (self.cable.offset + self.starting_offset) % self.cable.cable_length

    def set_anims(self, dt=None):
        offset = self.offset
        a,b = self.cable.endpoints

        def do_stretch(start_offset, end_offset, station_offset):
            start_x = a.x + start_offset*math.cos(self.cable.angle_r) + \
                    station_offset[0]
            end_x = a.x + end_offset*math.cos(self.cable.angle_r) + \
                    station_offset[0]
            start_y = a.y + start_offset*math.sin(self.cable.angle_r) + \
                    station_offset[1]
            end_y = a.y + end_offset*math.sin(self.cable.angle_r) + \
                    station_offset[1]
            dt = abs(end_offset-start_offset)/self.cable.speed
            self.x = rabbyt.lerp(start_x, end_x, dt=dt)
            self.y = rabbyt.lerp(start_y, end_y, dt=dt)
            self.cable.world.clock.schedule_once(self.set_anims, dt)

        def do_wheel(start, end, xy):
            while end < start:
                end += 2*pi
            dt = (end-start)*Station.radius/self.cable.speed
            self.rot = rabbyt.lerp(degrees(start)+90, degrees(end)+90, dt=dt)
            x, y = xy
            r = Station.radius
            angle = rabbyt.lerp(start, end, dt=dt).force_complete()
            self.x = lambda: x + r*cos(angle.get())
            self.y = lambda: y + r*sin(angle.get())
            self.cable.world.clock.schedule_once(self.set_anims, dt)

        if offset <= self.cable.length:
            # From a to b:
            self.rot = self.cable.angle_d
            do_stretch(offset, self.cable.length,
                    self.cable._direction_offsets[0])
        elif offset <= self.cable.length + Station.radius*math.pi:
            # Going around the wheel:
            start = (offset - self.cable.length)/Station.radius + \
                    self.cable.angle_r - math.pi/2
            end = self.cable.angle_r + math.pi/2
            do_wheel(start, end, self.cable.endpoints[1].xy)
        elif offset <= self.cable.cable_length - Station.radius*math.pi:
            # From b to a:
            self.rot = self.cable.angle_d + 180
            o = (self.cable.cable_length - Station.radius*math.pi) - offset
            do_stretch(o, 0, self.cable._direction_offsets[1])
        else:
            # Going around the other wheel:
            start = (offset - 2*self.cable.length - 
                        Station.radius*math.pi)/Station.radius + \
                            self.cable.angle_r + math.pi/2
            end = self.cable.angle_r - pi/2
            do_wheel(start, end, self.cable.endpoints[0].xy)

    def _set_contents(self, value):
        if getattr(self, "_contents", None):
            self.cable.items.remove(self._contents)
        self._contents = value
        if self._contents:
            self.cable.items.append(self._contents)
    def _get_contents(self):
        return self._contents
    contents = property(_get_contents, _set_contents)

