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

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

from gamelib.platform import Platform, SortingRules

COLORS = (
        ("Red", (1,0,0)),
        ("Blue", (0,0,1)),
        ("Violet", (1,0,1)),
        ("Green", (0,1,0)))
_COLORS_LOOKUP = dict(COLORS)
COLOR_NAMES = [c[0] for c in COLORS]
FIGURES = (
        "cube",
        "ball",
        "triangle"
        )

class Item(rabbyt.Sprite):
    load_speed = .2
    def __init__(self, world, figure, color):
        # figure is like "square" or "circle".  I would use the name "shape"
        # but it's already taken by the Sprite class.
        rabbyt.Sprite.__init__(self, figure+".png")
        self.world = world
        self.figure = figure
        self.color = color
        self.gondola = None
        self.platform = None

    def _set_color(self, color):
        self._color = color
        self.rgb = _COLORS_LOOKUP[color]
    def _get_color(self):
        return self._color
    color = property(_get_color, _set_color)

    def _set_gondola(self, gondola):
        self._gondola = gondola
        if gondola:
            self.platform = None
            self.xy = rabbyt.lerp(self.xy, gondola.attrgetter("xy"),
                    dt=self.load_speed)
            # This is due to a bug in rabbyt.  After a lerp is finished it is 
            # replaced by the ending static value, even if the end is an Anim.
            def set_xy(dt):
                self.xy = gondola.attrgetter("xy")
            self.world.clock.schedule_once(set_xy, self.load_speed)
            self.rot = gondola.attrgetter("rot")
    gondola = property((lambda self:self._gondola), _set_gondola)

    def _set_platform(self, platform):
        self._platform = platform
        if platform:
            self.gondola = None
            if self.xy == (0,0) or not platform.smooth_entry:
                self.xy = platform.attrgetter("xy")
            else:
                self.xy = rabbyt.lerp(end=platform.attrgetter("xy"), dt=.3)
            self.rot = platform.attrgetter("rot")
    platform = property((lambda self:self._platform), _set_platform)

    def drop_in_sea(self):
        if not self.world.ui.dropped_items.has_key((self.figure, self.color)):
            self.world.ui.dropped_items[(self.figure, self.color)] = 0
        self.world.ui.dropped_items[(self.figure, self.color)] += 1
        self.world.ui.update_stats()

        self.gondola = None
        if self.platform:
            self.platform.remove_fade()

        s = rabbyt.Sprite(self.texture, rgb=self.rgb)
        s.rot = random.random()*360
        d = random.randint(80,180)
        rot = random.random()*360
        s.x = self.x+math.sin(rot)*d
        s.y = self.y+math.cos(rot)*d
        s.scale = rabbyt.chain(
            rabbyt.ease_in(start=0, end=.6, dt=1),
            rabbyt.ease_in(start=.6, end=.7, dt=2,
                extend="reverse")
            )

        wake = []
        for i in range(3):
            s1 = rabbyt.Sprite("wake.png", xy=s.xy)
            s1.rot = random.random()*360
            wake.append(s1)
            s1.scale = rabbyt.chain(
                rabbyt.ease_in(start=0, end=.6, dt=1),
                rabbyt.ease_in(start=.6, end=1, dt=random.random()*1+.5,
                    extend="reverse")
                )
            s1.alpha = rabbyt.ease_out(start=.3, end=1,
                    dt=random.random()*1+.5, extend="reverse")
            self.world.renderables["floats"].append(s1)

        self.world.renderables["floats"].append(s)

        def remove(dt):
            s.scale = rabbyt.ease_in(end=0, dt=1)
            for s1 in wake:
                s1.alpha = rabbyt.ease_in(end=0, dt=1)
            def f(dt):
                self.world.renderables["floats"].remove(s)
                for s1 in wake:
                    self.world.renderables["floats"].remove(s1)
            self.world.clock.schedule_once(f, 1)
        self.world.clock.schedule_once(remove, random.randint(6,13))

        p =self.world.sound_manager.play("splash", self.xy)
        if p:
            p.volume += random.random()*.4 - .2

class Station(rabbyt.Sprite):
    radius = 18
    speed = 25 # Pixels per second

    def __init__(self, world, placeholder, fade_in=0):
        self.world = world
        world.stations.append(self)
        world.stepables.append(self)
        world.renderables["stations"].append(self)
        self.world = world

        self.placeholder = placeholder
        placeholder.station = self
        rabbyt.Sprite.__init__(self, "station.png", xy=self.placeholder.xy)
        self.rot = rabbyt.lerp(start=0, end=math.degrees(self.speed/self.radius),
                dt=1, extend="extrapolate")
        self.alpha = rabbyt.lerp(0, 1, dt=fade_in)

        self.connections = []
        self.platforms = []

        self.sorter = Sorter(self.world, self.xy, self.platforms)

    def add_connection(self, connection):
        self.connections.append(connection)
        self.platforms.extend(connection.entry_platforms)
        self.platforms.extend(connection.exit_platforms)

    def step(self):
        pass


class SorterClaw(Platform):
    def __init__(self, world, **kwargs):
        Platform.__init__(self, world, **kwargs)
        self.open()

    def open(self):
        self.texture = "sorter_claw_open.png"

    def close(self):
        self.texture = "sorter_claw_close.png"

    def render(self):
        if self.fading_out_sprite:
            self.fading_out_sprite.render()
        if self.contents:
            self.contents.render()
        rabbyt.Sprite.render(self)

    def _set_contents(self, value):
        Platform._set_contents(self, value)
        if value:
            self.close()
        else:
            self.open()
    contents = property(lambda self:self._contents, _set_contents)



class Sorter(rabbyt.Sprite):
    length = rabbyt.anim_slot()
    def __init__(self, world, xy, platforms, debug=False, 
            rest_rot=None, render_layer="sorters"):
        self.debug = debug
        rabbyt.Sprite.__init__(self, "sorter_base.png", xy=xy)
        self.platforms = platforms
        self.world = world
        self._render_layer = render_layer
        if self._render_layer:
            self.world.renderables[self._render_layer].append(self)
        self.world.stepables.append(self)

        self.claw = SorterClaw(world, smooth_entry=False, render_layer=None)

        self.claw.rot = self.attrgetter("rot")
        self.claw.x = (lambda:  self.x+
                self.length*math.cos(math.radians(self.rot)))
        self.claw.y = (lambda:  self.y+
                self.length*math.sin(math.radians(self.rot)))

        self.arm_sprite = rabbyt.Sprite("sorter_arm.png", xy=self.attrgetter("xy"),
                rot=self.attrgetter("rot"), shape=(0, 8, 1, -8), 
                scale_x=self.attrgetter("length")-8)

        # Tuple of platforms (from_, to)
        self.current_job = None

        self.rot = random.random()*360

        self.rest_rot = rest_rot

        self._move_to_rest_position()

        self.enabled = True

        self.job_find_wait = 2

        if hasattr(self.world, "sound_manager"):
            p = self.world.sound_manager.play("squeak"+random.choice("1234"), self.xy)
            if p:
                p.volume = 1.2

    def remove(self):
        if self._render_layer:
            self.world.renderables[self._render_layer].remove(self)
        self.claw.remove()
        self.world.stepables.remove(self)


    def _d(self, *items):
        if not self.debug: return
        print "* ",
        for i in items:
            print i,
        print

    def step(self):
        if not self.enabled:
            return
        if not self.current_job:
            self.job_find_wait -= 1
            if self.job_find_wait <=0:
                self.current_job = self._find_job()
                if self.current_job:
                    self._start_job()
                else:
                    # If we didn't find a job right away, don't just try again
                    # next_frame... wait a bit.
                    self.job_find_wait = random.randint(10, 20)

    def _start_job(self):
        self.current_job[0].acquire_block(self)
        d = self._move_to_xy(self.current_job[0].xy)
        self.world.clock.schedule_once(self._pickup_item, d)

    def _end_job(self):
        self.current_job[0].clear_block(self)
        self.current_job[1].clear_block(self)
        self.current_job = None
        self._move_to_rest_position()

    def _pickup_item(self, dt):
        if not self.current_job:
            # Something crazy happened.
            return
        start, end = self.current_job
        if not start.contents:
            self._end_job()
            return

        # By the time we move to pick up an item, a better ending location
        # might have become available.  So we check again:
        end = self._dispatch_platform(start)
        if not end:
            # There is no longer any available platform.  *abort*
            self._end_job()
            return
        self.current_job = start, end
        start.clear_block(self)
        end.acquire_block(self)

        self.claw.contents = start.contents
        start.contents = None

        d = self._move_to_xy(end.xy)
        self.world.clock.schedule_once(self._drop_item, d)

    def _drop_item(self, dt):
        if not self.current_job:
            # Something crazy happened.
            return
        start, end = self.current_job
        if end.contents:
            # Whoa, someone else got here first!
            # (With the new blocking code, this shouldn't happen...
            self.claw.contents.drop_in_sea()
            self._end_job()
            return

        end.contents = self.claw.contents
        self.claw.contents = None

        self._end_job()

        self._move_to_rest_position()

    def _move_to_rest_position(self):
        self.length = rabbyt.ease(end=20, dt=.5)
        if self.rest_rot is not None:
            r = self.rest_rot
        else:
            r = self.rot+random.random()*60-30
        self.rot = rabbyt.ease(end=r, dt=.5)

    def _move_to_xy(self, xy):
        """
        Starts the animation to go to xy.  Returns the amount of seconds it 
        will take.
        """
        x, y = xy
        d = .5
        self.rot = self.rot % 360
        needed_angle = math.degrees(math.atan2(y-self.y, x-self.x))
        if needed_angle > self.rot + 180:
            needed_angle -= 360
        if needed_angle < self.rot - 180:
            needed_angle += 360
        self.rot = rabbyt.ease(end=needed_angle, dt=d)
        needed_length = math.hypot(y-self.y, x-self.x)
        self.length = rabbyt.ease(end=needed_length, dt=d)

        diff = abs(needed_angle-self.rot)
        if diff >140:
            choices = "14"
        elif diff > 60:
            choices = "23"
        else:
            choices = "2"
        self.world.sound_manager.play("squeak"+random.choice(choices), self.xy,
                random_pitch=.5)

        return d

    def _dispatch_platform(self, platform):
        """
        Returns a new platform on which to place the item on the given platform.
        """
        legal = [p for p in self.platforms if not p.contents]

        options = {}
        for p in self.platforms:
            if not p.contents:
                weight = p.get_weight(platform.contents, self, platform)
                options.setdefault(weight, []).append(p)

        self._d(options and max(options.keys()))

        if options and max(options.keys()) > platform.min_move_weight:
            return random.choice(options[max(options.keys())])
        else:
            return None

    def _find_job(self):
        platforms = list(self.platforms)
        random.shuffle(platforms)
        for p in platforms:
            if p.contents and p.get_allow_pickup(self):
                new_p = self._dispatch_platform(p)
                if new_p:
                    return p, new_p

    def render(self):
        self.arm_sprite.render()
        rabbyt.Sprite.render(self)
        self.claw.render()

class Producer(object):
    def __init__(self, world, station, figures=("cube", "ball"), 
            colors=COLOR_NAMES):
        world.producers.append(self)
        world.renderables["buildings"].append(self)
        world.stepables.append(self)
        self.world = world

        self.figures = list(figures)
        self.colors = list(colors)

        self.station = station

        xy = (station.x + 42, station.y)
        self.platforms = [Platform(self.world, xy=xy, allow_drop=False)]
        self.station.platforms.extend(self.platforms)
        self.interval = 3
        self._last_produce_time = rabbyt.get_time()-random.random()

        self.sprite1 = rabbyt.Sprite("producer.png", x=self.station.x+50,
                y=self.station.y)
        self.sprite2 = rabbyt.Sprite("gear.png", x=self.station.x+40,
                y=self.station.y-20)
        self.sprite2.rot = rabbyt.lerp(start=0, end=365, dt=1.5,
                extend="repeat")
        self.sprite3 = rabbyt.Sprite("gear.png", x=self.station.x+54,
                y=self.station.y-16, scale=.5)
        self.sprite3.rot = rabbyt.lerp(start=365, end=0, dt=1,
                extend="repeat")


        self.icon = rabbyt.Sprite("station_icon_gear.png", x=self.station.x,
                y=self.station.y+60, scale=1, rgb=(1,.3,.4))
        self.icon.alpha = rabbyt.lerp(start=1, end=.5, dt=1,
                extend="reverse")

    def render(self):
        self.sprite3.render()
        self.sprite2.render()
        self.sprite1.render()
        if not self.station.connections:
            self.icon.render()

    def step(self):
        if rabbyt.get_time() - self._last_produce_time > self.interval:
            self.produce()
            self._last_produce_time = rabbyt.get_time()+ random.random()-.5

    def produce(self):
        item = Item(self.world, random.choice(self.figures), random.choice(self.colors))
        for p in self.platforms:
            if not p.contents:
                p.contents = item
                break
        else:
            p = random.choice(self.platforms)
            p.contents.drop_in_sea()
            p.contents = item
        item.alpha = rabbyt.lerp(0, 1, dt=.5)
        p.temporarily_block(dt=.5)


class Consumer(object):
    def __init__(self, world, station, match_figure=None, match_color=None):
        world.renderables["stations"].append(self)
        self.world = world
        self.world.stepables.append(self)
        # TODO add a "consumers" list to the world?  I think we need to make consumers
        # and producers into subclasses of a single class.  (Or just a single class
        # period.)
        self.station = station
        xy = (station.x + 27, station.y)
        self.rules = SortingRules(allow_pickup=False, starting_weight=7)
        self.platforms = [Platform(self.world, xy=xy, rules=self.rules)]
        self.station.platforms.extend(self.platforms)

        self.sprite1 = rabbyt.Sprite("consumer.png", x=self.station.x+16,
                y=self.station.y+19)

        self.icon = rabbyt.Sprite("station_icon_consumer.png", x=self.station.x,
                y=self.station.y+65, rgb=(1,.3,.4))
        self.icon.alpha = rabbyt.lerp(start=1, end=.5, dt=1,
                extend="reverse")

        xoffset = -30
        yoffset = 30

        self.filter_sprite = rabbyt.Sprite("all.png", xy=self.station.xy)
        self.filter_sprite.x += xoffset
        self.filter_sprite.y += yoffset
        self.filter_sprite.rot = 90

        self.filter_sprite_bg = rabbyt.Sprite("consumer_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(self.filter_sprite)


        self.match_figure = match_figure
        self.match_color = match_color

    def _set_match_figure(self, value):
        self.filter_sprite.texture = value+".png"
        if value == "all":
            value = None
        self.rules.match_figure = value
    match_figure = property((lambda self:self.rules.match_figure), _set_match_figure)

    def _set_match_color(self, value):
        if value == "all":
            value = None
        self.rules.match_color = value
        if not value:
            self.filter_sprite.rgb = (1,1,1)
        else:
            self.filter_sprite.rgb = _COLORS_LOOKUP[value]
    match_color = property((lambda self:self.rules.match_color), _set_match_color)

    def render(self):
        self.sprite1.render()
        if not self.station.connections:
            self.icon.render()

    def step(self):
        for p in self.platforms:
            if p.contents:
                self.consume(p)

    def consume(self, platform):
        platform.remove_fade()
        self.world.ui.cash += 20
        self.world.ui.score += 1


class Transformer(object):
    def __init__(self, world, station, 
            input_figure=None, output_figure=None,
            input_color=None, output_color=None):

        self.world = world
        self.station = station

        world.stepables.append(self)

        # 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.input_rules = SortingRules(allow_pickup=False, match_color=input_color,
                match_figure=input_figure)
        self.output_rules = SortingRules(allow_drop=False)

        self.input_platform = Platform(self.world, self.station.xy, 
                rules=self.input_rules)
        self.input_platform.match_figure = self.input_figure
        self.input_platform.match_color = self.input_color
        self.input_platform.x += 50
        self.input_platform.y += 10

        self.output_platform = Platform(self.world, self.station.xy, 
                rules=self.output_rules)
        self.output_platform.x += 50
        self.output_platform.y -= 10

        self.output_platform.forbidden.add(self.input_platform)

        self.station.platforms.extend([self.input_platform,
                self.output_platform])


        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
        ar.y += yoffset
        ar.scale = .5

        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(ar)
        self.station.world.renderables["hud"].append(s2)

        t = rabbyt.Sprite("transformer.png", xy=self.station.xy)
        t.x += 65
        self.station.world.renderables["buildings"].append(t)

        self.input_platform.discouraged.add(self.output_platform)

        self.station.platforms.extend([self.input_platform, self.output_platform])

    def step(self):
        if self.input_platform.contents and not self.output_platform.contents:
            old = self.input_platform.contents
            new = Item(self.world, 
                    figure=self.output_figure or old.figure,
                    color=self.output_color or old.color)
            fade_time = .5
            self.input_platform.remove_fade(fade_time)
            self.output_platform.add_fade(new, fade_time)
            self.world.ui.score += 1


class StationPlaceholder(rabbyt.Sprite):
    def __init__(self, world, xy):
        rabbyt.Sprite.__init__(self, "station_placeholder.png", xy=xy)
        self.world = world
        self.world.station_placeholders.append(self)
        self.station = None

