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

See main.py for full notice.
"""
from __future__ import division

import math
import rabbyt
from math import cos, sin, pi, radians, degrees, hypot

from gamelib.platform import Platform, SortingRules
from gamelib.station import Sorter, Item, COLOR_NAMES, _COLORS_LOOKUP

import random


def _platform_setter(attr_name):
    def _get(self):
        return getattr(self, "_"+attr_name)
    def _set(self, value):
        setattr(self, "_"+attr_name, value)
        for p in self.platforms:
            setattr(p, attr_name, value)
    return property(_get, _set)

class Ship(rabbyt.Sprite):
    forbidden = _platform_setter("forbidden")

    def __init__(self, world, **kwargs):
        rabbyt.Sprite.__init__(self, "barge.png", **kwargs)
        self.world = world
        self.world.clock.schedule_interval(self.add_wake, 2)
        self.world.renderables["ships"].append(self)
        self.rules = SortingRules(starting_weight=7)
        self.platforms = []
        for row in range(3):
            for column in range(6):
                p = Platform(self.world, rules=self.rules, smooth_entry=False, 
                        render_layer=None)
                p.ship_offset = column*12-25, row*16-16
                p.x = self.attrgetter("x") + p.ship_offset[1]
                p.y = self.attrgetter("y") - p.ship_offset[0]
                p.rot = self.attrgetter("rot") + 90
                self.platforms.append(p)


        self.forbidden = set(self.platforms)

        self.sorter = Sorter(self.world, self.xy, list(self.platforms), 
                rest_rot=self.attrgetter("rot"))

        self.update_platform_xy()

        self.dock = None
        self.target_xy = (0,0)
        self.target_callback = None
        self.target_range = 30
        self.target_distance = 100000
        self.speed = 0
        self.max_speed = 30

        self.state = "sailing"
        self.sorter.enabled = False

        self.world.clock.schedule_interval(self.update, 1/30.)

    def add_wake(self, dt):
        if self.state != "sailing":
            return

        w1 = rabbyt.Sprite("barge_wake_left.png", y=self.y+30)
        w2 = rabbyt.Sprite("barge_wake_right.png", y=self.y+30)

        w2.alpha = w1.alpha = rabbyt.chain(
                rabbyt.lerp(start=0, end=1, dt=1),
                rabbyt.lerp(start=1, end=0, dt=2)
                )

        w1.x = rabbyt.ease_out(start=self.x-20, end=self.x-50, dt=3)
        w2.x = rabbyt.ease_out(start=self.x+20, end=self.x+50, dt=3)

        w1.rot = 90
        w2.rot = 90

        self.world.renderables["floats"].append(w1)
        self.world.renderables["floats"].append(w2)

        def remove(dt):
            self.world.renderables["floats"].remove(w1)
            self.world.renderables["floats"].remove(w2)
        self.world.clock.schedule_once(remove, 3)


    def update_platform_xy(self):
        return
        for p in self.platforms:
            p.xy = self.convert_offset(p.ship_offset)

    def render(self):
        rabbyt.Sprite.render(self)
        if self.state != "docked":
            self.update_platform_xy()
        rabbyt.render_unsorted(self.platforms)

    def connect_dock(self, dock):
        self.dock = dock
        self.sorter.platforms.extend(dock.platforms)
        self.sorter.enabled = True
        self.state = "docked"
        self.update_platform_xy()

    def disconnect_dock(self):
        self.sorter.platforms = list(self.platforms)
        self.dock.ship = None
        self.dock = None
        self.sorter.enabled = False

        num_filled = 0
        for p in self.platforms:
            if p.contents and self.rules.get_weight(p.contents) > 0:
                num_filled += 1

        if num_filled == 18:
            # Bonus for filling up.
            num_filled = 24
        self.world.ui.score += num_filled
        self.world.ui.cash += num_filled * 30

    def update(self, dt):
        if self.state == "sailing":
            self.sorter.enabled = False
            dist = hypot(self.target_xy[1]-self.y, self.target_xy[0]-self.x)
            self.target_distance = dist
            if dist > 100:
                self.speed = self.max_speed
            elif dist > self.target_range:
                self.speed = dist/100*self.max_speed
            else:
                self.state = "stopped"
                if self.target_callback: self.target_callback(self)
                return
            self.y += self.speed*dt

        self.sorter.xy = self.convert_offset((-57,0))

    def sail_to(self, xy, callback=None):
        self.target_xy = xy
        self.target_callback = callback
        if self.dock:
            self.disconnect_dock()
        self.state = "sailing"
        self.speed = 0

    def remove(self):
        self.world.clock.unschedule(self.update)
        self.world.renderables["ships"].remove(self)
        for p in self.platforms:
            p.remove()
        self.sorter.remove()
        self.world.clock.unschedule(self.add_wake)

    def dump_cargo(self, rules):
        def dump(dt):
            platforms = list(self.platforms)
            random.shuffle(platforms)
            for p in platforms:
                if p.contents and rules.get_weight(p.contents):
                    p.contents.drop_in_sea()
                    self.world.clock.schedule_once(dump, .2)
                    break
        self.world.clock.schedule_once(dump, .2)


class DockPlatform(Platform):
    def __init__(self, dock, **kwargs):
        self.dock = dock
        Platform.__init__(self, dock.world, **kwargs)

    def get_weight(self, item, sorter, other_platform):
        if sorter is self.dock.station.sorter:
            rules = self.dock.station_rules
        elif self.dock.ship and sorter is self.dock.ship.sorter:
            rules = self.dock.ship_rules
        else:
            rules = self.rules

        # We don't want the either the station or the ship to fill up 
        # the dock so that the other cannot unload, so we check to 
        # make sure there is at least one other platform that is either 
        # empty or containts an item other going in the opposite direction.
        for p in self.dock.platforms:
            if p is self:
                continue
            if not p.contents:
                break
            if rules.get_weight(p.contents) == 0:
                break
        else:
            return 0

        return Platform.get_weight(self, item, sorter, other_platform)

    def get_allow_pickup(self, sorter):
        # If our item matches the ship, we don't want the station picking it up.
        if sorter is self.dock.station.sorter:
            weight= self.get_rules(sorter).get_weight(self.contents)
            if weight:
                return False
        return Platform.get_allow_pickup(self, sorter)



class Dock(object):
    forbidden = _platform_setter("forbidden")
    def __init__(self, world, station, 
            input_figure=None, output_figure=None,
            input_color=None, output_color=None):
        self.world = world
        self.world.stepables.append(self)
        self.station = station
        self.station_rules = SortingRules()
        self.ship_rules = SortingRules()
        self.sorter_rules = {self.station.sorter:self.station_rules}
        self.platforms = []
        for i in range(6):
            p = DockPlatform(self)
            p.x = self.station.x + 30 
            p.y = self.station.y + i *12 - 30
            p.sorter_rules = self.sorter_rules
            self.platforms.append(p)

        # The type of figure accepted, or None for any figure.
        self.input_figure = input_figure
        # The type of figure outputted, or None for no change from input.
        self.output_figure = output_figure

        self.input_color = input_color
        self.output_color = output_color

        self.ship_rules.match_color = self.output_color
        self.ship_rules.match_figure = self.output_figure

        self.station_rules.match_color = self.input_color
        self.station_rules.match_figure = self.input_figure

        self.forbidden = set(self.platforms)
        self.station.platforms.extend(self.platforms)
        #self.allow_drop = True
        #self.allow_pickup = False

        self.ship = None
        self.next_ship = self.make_ship(500 + random.random()*100)
        
        # Icon setup
        xoffset = -30
        yoffset = 40

        if not self.input_figure:
            sp = "all.png"
        else:
            sp = self.input_figure+".png"
        s1 = rabbyt.Sprite(sp, xy=self.station.xy)
        if self.input_color:
            s1.rgb =_COLORS_LOOKUP[self.input_color]
        s1.x += xoffset - 20
        s1.y += yoffset
        s1.rot = 90

        if not self.output_figure:
            sp = "all.png"
        else:
            sp = self.output_figure+".png"
        s2 = rabbyt.Sprite(sp, xy=self.station.xy)
        if self.output_color:
            s2.rgb =_COLORS_LOOKUP[self.output_color]
        s2.x += xoffset + 20
        s2.y += yoffset
        s2.rot = 90

        ar = rabbyt.Sprite("arrow.png", xy=self.station.xy)
        ar.x += xoffset - 9
        ar.y += yoffset 
        ar.scale = .5
        ar2 = rabbyt.Sprite("arrow.png", xy=self.station.xy)
        ar2.x += xoffset + 9
        ar2.y += yoffset
        ar2.scale = .5

        b = rabbyt.Sprite("barge.png", xy=self.station.xy)
        b.x += xoffset
        b.y += yoffset
        b.scale = .15
        b.rot = 90

        self.filter_sprite_bg = rabbyt.Sprite("transformer_bg.png",
                xy=self.station.xy)
        self.filter_sprite_bg.x += xoffset
        self.filter_sprite_bg.y += yoffset

        self.station.world.renderables["hud"].append(self.filter_sprite_bg)
        self.station.world.renderables["hud"].append(s1)
        self.station.world.renderables["hud"].append(s2)
        self.station.world.renderables["hud"].append(b)
        self.station.world.renderables["hud"].append(ar)
        self.station.world.renderables["hud"].append(ar2)

    def make_ship(self, distance):
        s = Ship(self.world, x=self.station.x+10, y=self.station.y-100)
        s.rot = 90
        if self.output_color:
            colors = [self.output_color]
        else:
            colors = COLOR_NAMES
        if self.output_figure:
            figures = [self.output_figure]
        else:
            figures = ["cube", "ball", "triangle"]
        for p in s.platforms:
            p.contents = Item(self.world, random.choice(figures), random.choice(colors))

        s.xy = self.station.x+70, self.station.y-distance
        s.target_xy = s.x, self.station.y+40
        s.target_callback = self.ship_arrived

        return s

    def ship_arrived(self, ship):
        self.connect_ship(ship)
        self.next_ship = self.make_ship(2000 + random.random()*1000)

    def connect_ship(self, ship):
        self.ship = ship
        self.ship.connect_dock(self)
        self.sorter_rules[self.ship.sorter] = self.ship_rules
        self.ship.rules.match_color = self.input_color
        self.ship.rules.match_figure = self.input_figure

    def step(self):
        if self.ship:
            if self.next_ship and self.next_ship.target_distance < 300:
                self.depart_ship()
            else:
                for p in self.ship.platforms:
                    if not p.contents or not self.ship.rules.get_weight(p.contents):
                        break
                else:
                    self.depart_ship()

    def depart_ship(self):
        self.ship.dump_cargo(self.ship_rules)
        self.ship.sail_to((self.ship.x, self.ship.y+2000, lambda s:s.remove()))


