# This file is part of ROBOTGAME
#
# ROBOTGAME is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# ROBOTGAME is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# ROBOTGAME.  If not, see <http://www.gnu.org/licenses/>.
#
# ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
#
#                           level1.py
#                     --------------------
#       date created : Tue Aug 7 2012
#          copyright : (C) 2012 Sakse Dalum
#      maintained by : Sakse Dalum <don_s@hongabar.org>

"""
The first level.
"""

import os
import pygame
import random
import re
import itertools

import level
import player
import tile
import block
import boulder
import lever
import trigger
import worldobject

import logic.rollingstone
import logic.colourboxes

class Level1(level.Level):
    def __init__(self, game, graphics_dir, paused=False):
        level.Level.__init__(self, game, graphics_dir, size=(64 * 100,
                                                             48 * 100),
                             paused=paused)

        self.task_completions = []

        self.dimensions = 50, 50

        self.set_darkness(0)

        for i in range(self.dimensions[0]):
            for j in range(self.dimensions[1]):
                self.tiles.append(
                    tile.Tile(self, i*64, j*48, self.imgs['ground1']))

        self.player.set_pos(64 * 15, 48 * 15)
        self.player.set_init_pos()

        for i in range(self.dimensions[0]):
            self.objects.append(block.InvisBlock(self, i * 64,
                                                 0))
            self.objects.append(block.InvisBlock(self, i * 64,
                                                 (self.dimensions[1]) * 48))
        for i in range(self.dimensions[1]):
            self.objects.append(block.InvisBlock(self, -64,
                                                 i * 48))
            self.objects.append(block.InvisBlock(self, self.dimensions[0] * 64,
                                                 i * 48))

        ### Task 1: Wheels of fortune
        task1_pos = (64 * 15, 48 * 13)

        ws = []

        for i in range(5):
            w1 = Wheel(self, task1_pos[0] + 64 * i, task1_pos[1] - 48 * 2)
            w2 = Wheel(self, task1_pos[0] + 64 * i, task1_pos[1],
                       immitate=w1)
            self.objects.extend([w1, w2])
            self.objects.append(lever.Lever(self,
                                            task1_pos[0] + 64 * i,
                                            task1_pos[1] + 48 * 1,
                                            [w2.activate],
                                            anim='lever_updown'))
            ws.append(w2)
            self.add_tile(task1_pos[0] + 64 * i,
                          task1_pos[1] - 48 * 3,
                          'moat_horizontal')

        self.objects.append(
            lever.Lever(self,
                        task1_pos[0] + 64 * 5,
                        task1_pos[1] + 48 * 2,
                        [lambda *xs: self.complete_task(1)
                         if len(ws) == len([w for w in ws if w.on])
                         else lambda: None]))

        self.add_tile(task1_pos[0] - 64,
                      task1_pos[1] - 48 * 3,
                      'moat_corner_north')
        self.add_tile(task1_pos[0] + 64 * 5,
                      task1_pos[1] - 48 * 3,
                      'moat_corner_north_flip')
        for i in range(3):
            self.add_tile(task1_pos[0] - 64,
                          task1_pos[1] + 48 * (i - 2),
                          'moat_vertical')
            self.add_tile(task1_pos[0] + 64 * 5,
                          task1_pos[1] + 48 * (i - 2),
                          'moat_vertical')
        self.add_tile(task1_pos[0] - 64,
                      task1_pos[1] + 48,
                      'moat_corner_south_flip')
        self.add_tile(task1_pos[0] + 64 * 5,
                      task1_pos[1] + 48,
                      'moat_corner_south')
        self.add_tile(task1_pos[0] - 64 * 2,
                      task1_pos[1] + 48,
                      'moat_end_horizontal')
        self.add_tile(task1_pos[0] + 64 * 6,
                      task1_pos[1] + 48,
                      'moat_end_horizontal_flip')

        ### Task 2: Rolling stone

        task2_size = 15, 10

        task2_pos = (64 * 20, 48 * 20)

        playfield, nsteps, directions = (
            logic.rollingstone.generate_simple_unsolved_solvable_extra(
                task2_size[0], task2_size[1], 5, 50))

        n = 0
        c = len(playfield) / len(directions)
        ns = [k * c for k in range(len(directions))]
        arrow_blocks = []
        for i, j in playfield:
            if n in ns:
                arrow_blocks.append(
                    block.ArrowBlock(self,
                                     task2_pos[0] + 64 * i,
                                     task2_pos[1] + 48 * j,
                                     directions[n / c].next_pos((0, 0))))
            else:
                self.objects.append(
                    block.Block(self,
                                task2_pos[0] + 64 * i,
                                task2_pos[1] + 48 * j,
                                self.imgs['block1'],
                                movable=True)
                    if (random.randint(0, 2) or (i, j) == (4, 0)) else
                    block.Block(self,
                                task2_pos[0] + 64 * i,
                                task2_pos[1] + 48 * j,
                                self.imgs['block3'],
                                movable=False))
            n += 1

        # arrow_blocks = []
        # n = 0
        # for i in directions:
        #     arrow_blocks.append(
        #         block.ArrowBlock(self,
        #                          task2_pos[0] + 64 * (task2_size[0] - n),
        #                          task2_pos[1] + 48 * (task2_size[1] + 2),
        #                          i.next_pos((0, 0))))
        #     n += 1

        self.objects.extend(arrow_blocks)
        self.objects.append(lever.Lever(self,
                                        task2_pos[0] + 64 * 3,
                                        task2_pos[1] - 48 * 2,
                                        [arrow_block.activate
                                         for arrow_block in arrow_blocks],
                                        toggling=True,
                                        anim='lever_updown'))

        b = boulder.Boulder(self, task2_pos[0], task2_pos[1] - 48,
                            direction=(0, 1))
        self.objects.append(b)

        self.objects.append(lever.Lever(self,
                                        task2_pos[0] + 64 * 2,
                                        task2_pos[1] - 48 * 2,
                                        [b.activate],
                                        anim='lever_updown'))
        # Moat sides
        for i in range(-1, task2_size[1]):
            self.add_tile(task2_pos[0] - 64,
                          task2_pos[1] + 48 * i,
                          'moat_vertical')
        for i in range(task2_size[1] - 2):
            self.add_tile(task2_pos[0] + 64 * task2_size[0],
                          task2_pos[1] + 48 * i,
                          'moat_vertical')
        for i in range(6, task2_size[0]):
            self.add_tile(task2_pos[0] + 64 * i,
                          task2_pos[1] - 48,
                          'moat_horizontal')
        for i in range(task2_size[0] - 2):
            self.add_tile(task2_pos[0] + 64 * i,
                          task2_pos[1] + 48 * task2_size[1],
                          'moat_horizontal')
        # Corners
        self.add_tile(task2_pos[0] + 64 * task2_size[0],
                      task2_pos[1] - 48,
                      'moat_corner_north_flip')
        self.add_tile(task2_pos[0] - 64,
                      task2_pos[1] + 48 * task2_size[1],
                      'moat_corner_south')

        # Start
        self.add_tile(task2_pos[0] + 64 * 2,
                      task2_pos[1] - 48,
                      'moat_horizontal')
        self.add_tile(task2_pos[0] + 64 * 3,
                      task2_pos[1] - 48,
                      'moat_end_horizontal_flip')
        self.add_tile(task2_pos[0] + 64 * 5,
                      task2_pos[1] - 48,
                      'moat_end_horizontal')
        self.add_tile(task2_pos[0] - 64,
                      task2_pos[1] - 48 * 2,
                      'moat_corner_north')
        self.add_tile(task2_pos[0],
                      task2_pos[1] - 48 * 2,
                      'moat_horizontal')
        self.add_tile(task2_pos[0] + 64,
                      task2_pos[1] - 48 * 2,
                      'moat_corner_north_flip')
        self.add_tile(task2_pos[0] + 64,
                      task2_pos[1] - 48,
                      'moat_corner_south')

        # End
        self.add_tile(task2_pos[0] + 64 * task2_size[0],
                      task2_pos[1] + 48 * (task2_size[1] - 2),
                      'moat_corner_south')
        self.add_tile(task2_pos[0] + 64 * (task2_size[0] + 1),
                      task2_pos[1] + 48 * (task2_size[1] - 2),
                      'moat_corner_north_flip')
        self.add_tile(task2_pos[0] + 64 * (task2_size[0] + 1),
                      task2_pos[1] + 48 * (task2_size[1] - 1),
                      'moat_vertical')
        self.add_tile(task2_pos[0] + 64 * (task2_size[0] + 1),
                      task2_pos[1] + 48 * (task2_size[1]),
                      'moat_corner_south_flip')
        self.add_tile(task2_pos[0] + 64 * (task2_size[0]),
                      task2_pos[1] + 48 * (task2_size[1]),
                      'moat_end_horizontal')
        self.add_tile(task2_pos[0] + 64 * (task2_size[0] - 2),
                      task2_pos[1] + 48 * (task2_size[1]),
                      'moat_end_horizontal_flip')

        self.objects.append(
            trigger.Trigger(self, task2_pos[0] + 64 * (task2_size[0]),
                            task2_pos[1] + 48 * (task2_size[1] - 1),
                            [self.complete_task, b.fall],
                            self.imgs['hole'], [b],
                            signal=[0, 2]))


        ### Task 3: Colour blocks

        task3_pos = (64 * 15, 48 * 18)

        # Abstract "boxes", actually colour fields
        boxes = logic.colourboxes.generate_colour_boxes(1, 3)
        boxes += boxes
        boxes += [logic.colourboxes.generate_random_box(1) for _ in range(3)]
        random.shuffle(boxes)

        pos_colour = {}
        for box, (x, y) in zip(boxes, itertools.product(range(3), range(3))):
            # self.tiles.append(tile.Tile(self, x * 64 + task3_pos[0],
            #                             y * 48 + task3_pos[1],
            #                             self.imgs['ground1']))
            pos_colour[(x, y)] = box
            self.add_tile(task3_pos[0] + 64 * x, task3_pos[1] + 48 * (y + 1),
                          'indoor%d' % random.randint(1, 6), blocking=False)

        action_blocks = [block.ActionBlock(self, 64 * i + task3_pos[0],
                                           task3_pos[1], movable=True)
                         for i in range(3)]
        self.objects.extend(action_blocks)

        wells = [block.ColorWell(self, task3_pos[0] + 64,
                                 task3_pos[1] + 48 * 5)]
        self.objects.extend(wells)

        def update_wells(block):
            cur_boxes = []
            for block in action_blocks:
                box = pos_colour.get(((block.x - task3_pos[0]) / 64,
                                      (block.y - task3_pos[1] - 48) / 48))
                if box:
                    cur_boxes.append(box)

            if not cur_boxes:
                well_colours = [(0, 0, 0)] * len(wells)
            else:
                well_colours = logic.colourboxes.get_colours(cur_boxes)
            for i in range(len(wells)):
                wells[i].set_colour(*well_colours[i])

        for b in action_blocks:
            b.action = update_wells

        self.objects.append(
            lever.Lever(self,
                        task3_pos[0] + 64,
                        task3_pos[1] + 48 * 4,
                        [lambda *x: (self.complete_task(3)
                                     if (len([w for w in wells
                                              if (w.well_colour
                                                  == (255, 255, 255))
                                              ])
                                         == len(wells))
                                     else lambda: None)]))

        ### Task 4: Inverted bits

        task4_pos = (64 * 26, 48 * 18)

        b = boulder.Boulder(self, task4_pos[0] - 64, task4_pos[1] - 48 * 3,
                            direction=(1, 0))
        self.objects.append(b)

        self.objects.append(lever.Lever(self,
                                        task4_pos[0] - 64 * 3,
                                        task4_pos[1] - 48 * 3,
                                        [b.activate]))


        risingblocks = [block.RisingBlock(self, task4_pos[0] + 64 * i,
                                          task4_pos[1] - 48 * 3,
                                          is_up = False)
                        for i in range(8)]

        for i in range(8):
            r = range(8)
            r.remove(i)
            self.objects.append(risingblocks[i])
            n = random.choice(r)
            self.objects.append(lever.Lever(self,
                                            task4_pos[0] + 64 * i,
                                            task4_pos[1] - 48 * 2,
                                            [risingblocks[i].activate,
                                             risingblocks[n].activate],
                                            anim='lever_updown',
                                            toggling=True,
                                            signal=[0, 1]))
            for k in range(random.randint(0, 1)):
                self.objects[-1].use(self)
                self.objects[-1].set_init_pos()
            risingblocks[i].set_init_pos()
            risingblocks[n].set_init_pos()
            n = random.choice(r)
            self.objects.append(lever.Lever(self,
                                            task4_pos[0] + 64 * i,
                                            task4_pos[1],
                                            [risingblocks[i].activate,
                                             risingblocks[n].activate],
                                            anim='lever_updown',
                                            toggling=True,
                                            signal=[2, 3]))
            for k in range(random.randint(0, 1)):
                self.objects[-1].use(self)
                self.objects[-1].set_init_pos()
            risingblocks[i].set_init_pos()
            risingblocks[n].set_init_pos()

        self.objects.append(trigger.Trigger(self, task4_pos[0] + 64 * 8,
                                            task4_pos[1] - 48 * 3,
                                            [self.complete_task, b.fall],
                                            self.imgs['hole'], [b],
                                            signal=[0, 4]))

        # Moat
        self.add_tile(task4_pos[0] - 64 * 2, task4_pos[1] - 48 * 4,
                      'moat_corner_north')
        self.add_tile(task4_pos[0] - 64 * 2, task4_pos[1] - 48 * 3,
                      'moat_vertical')
        self.add_tile(task4_pos[0] - 64 * 2, task4_pos[1] - 48 * 2,
                      'moat_corner_south')
        self.add_tile(task4_pos[0] - 64, task4_pos[1] - 48 * 2,
                      'moat_end_horizontal_flip')
        for i in range(10):
            self.add_tile(task4_pos[0] + 64 * (i - 1), task4_pos[1] - 48 * 4,
                          'moat_horizontal')
        self.add_tile(task4_pos[0] + 64 * 9, task4_pos[1] - 48 * 4,
                      'moat_corner_north_flip')
        self.add_tile(task4_pos[0] + 64 * 9, task4_pos[1] - 48 * 3,
                      'moat_vertical')
        self.add_tile(task4_pos[0] + 64 * 9, task4_pos[1] - 48 * 2,
                      'moat_corner_south_flip')
        self.add_tile(task4_pos[0] + 64 * 8, task4_pos[1] - 48 * 2,
                      'moat_end_horizontal')


        ### Task 5: Teleporters

        task5_size = 5, 8 # y, x -- Note, inverted.
        task5_pos = (64 * 10, 48 * 10)

        task5_nturns = random.randint(2, 4) * 2 - 1

        playfield, nsteps, directions = (
            logic.rollingstone.generate_simple_unsolved_solvable_extra(
                task5_size[0], task5_size[1], task5_nturns,
                task5_size[0]*task5_size[1]))

        for x in range(task5_size[1]):
            for y in range(task5_size[0]):
                self.add_tile(task5_pos[0] - 64 * (x - 1),
                              task5_pos[1] - 48 * y,
                              'indoor%d' % random.randint(1, 6), blocking=False)


        for i, j in playfield:
            self.objects.append(
                trigger.Trigger(self,
                                task5_pos[0] - 64 * (j - 1),
                                task5_pos[1] - 48 * i,
                                [lambda x: self.player.set_pos(
                            task5_pos[0] + 2 * 64,
                            (task5_pos[1]
                             - random.randint(0, task5_size[0] - 1) * 48))],
                                self.imgs['hole'],
                                [self.player],
                                visible=False))

        for i in range(task5_size[1] + 1):
            self.add_tile(task5_pos[0] - 64 * i,
                          task5_pos[1] + 48,
                          'moat_horizontal')
            self.add_tile(task5_pos[0] - 64 * i,
                          task5_pos[1] - task5_size[0] * 48,
                          'moat_horizontal')
        self.add_tile(task5_pos[0] + 64,
                      task5_pos[1] + 48,
                      'moat_end_horizontal_flip')
        self.add_tile(task5_pos[0] + 64,
                      task5_pos[1] - task5_size[0] * 48,
                      'moat_end_horizontal_flip')
        self.add_tile(task5_pos[0] - 64 * (task5_size[1] + 1),
                      task5_pos[1] + 48,
                      'moat_corner_south')
        self.add_tile(task5_pos[0] - 64 * (task5_size[1] + 1),
                      task5_pos[1] - task5_size[0] * 48,
                      'moat_corner_north')
        for i in range(task5_size[0]):
            self.add_tile(task5_pos[0] - 64 * (task5_size[1] + 1),
                          task5_pos[1] - 48 * i,
                          'moat_vertical')

        self.objects.append(
            lever.Lever(self,
                        task5_pos[0] - 64 * (task5_size[1]),
                        task5_pos[1] - (task5_size[0] / 2) * 48,
                        [lambda *x: self.complete_task(5)]))
        # DRAW THE BACKGROUND

        self.draw_background()

    def add_tile(self, x, y, img, blocking=True):
        self.tiles.append(tile.Tile(self, x, y, self.imgs[img]))
        if blocking:
            self.objects.append(block.InvisBlock(self, x, y))

    def complete_task(self, task):
        if task == 0:
            return
        self.task_completions.append(task)
        if len(self.task_completions) > 3:
            self.task_completions = self.task_completions[-3:]
        print(self.task_completions)

    def restart(self):
        for obj in self.objects:
            obj.reset_pos()

class Wheel(block.Block):
    def __init__(self, level, x, y, immitate=None):
        self.__dict__.update(locals())
        worldobject.WorldObject.__init__(self, level, x, y)

        self.frame = 0
        self.anim_speed = 15
        self.nsettings = 5
        self.setting = random.randint(0, self.nsettings - 1)
        if self.immitate:
            self.on = self.setting == self.immitate.setting

        self.init_setting = self.setting

        self.anim = 'elevating_column'

        self.anim_direction = 1

    def set_init_pos(self):
        worldobject.WorldObject.set_init_pos(self)
        self.init_setting = self.setting

    def reset_pos(self):
        worldobject.WorldObject.reset_pos(self)
        self.setting = self.init_setting

    def set_direction(self, setting):
        self.anim_direction = -1 if setting else 1

    def activate(self, setting):
        self.setting = (self.setting + 1 * self.anim_direction) % self.nsettings
        self.on = self.setting == self.immitate.setting

    def update(self, e, t, dt):
        # Update the animation
        l = len(self.level.imgs[self.anim])
        if not (int(self.frame) == self.setting * (l - 1)
                / (self.nsettings - 1)):
            self.frame = ((self.frame + self.anim_speed * dt) % l
                          if (self.setting > 0 and self.anim_direction > 0)
                          or (self.setting == self.nsettings - 1
                              and self.anim_direction < 0) else
                          (self.frame - self.anim_speed * dt) % l)

        worldobject.WorldObject.update(self, e, t, dt)

    def draw(self, window):
        self.img = self.level.imgs[self.anim][int(self.frame)]
        if self.visible:
            window.blit(self.img, (self.x - 32 - self.level.camera_x,
                                   self.y - self.img.get_size()[1] + 24
                                   - self.level.camera_y))
