import pickle
from sets import Set
from math import hypot

import pygame

from pygsear.Drawable import Layer, Square, Stationary
from pygsear import Util
from pygsear.locals import *
from pygsear import conf

TS = 30     # Tile Size
WW = 2      # Wall Width


class Cell:
    def __init__(self, x, y):
        self.coord = (x, y)
        self.items = {}
        self.sides = {}

        dx, dy = x*TS, -y*TS
        self.rect = pygame.Rect(dx, dy, TS, TS)
        self.crect = self.rect

        self.special_funcs = Set()
        self.gen_topview()

        self.mode = None
        self.visible = False
        self.remembered = False

    def add_items(self, **kw):
        special_funcs = self.special_funcs
        for k in kw:
            sfunc = getattr(self, 'add_%s'%k, None)

            if kw[k] is None:
                special_funcs.discard(sfunc)

                try:
                    del(self.items[k])
                except KeyError:
                    pass

            else:
                #print k,'=',kw[k]
                self.items[k] = kw[k]
                if sfunc is not None:
                    special_funcs.add(sfunc)


        self.gen_topview()
        for func in special_funcs:
            #print 'func', func
            func()

    def gen_topview(self):
        self.gen_walls()

        layer = conf.game.level.layer

        self.clear_topdown_full()

        s = Square(w=layer, side=TS, color=LGRAY)
        self.draw_topdown_walls(s)
        s.center()

        dx, dy = self.rect.left, self.rect.top
        s.nudge(dx=dx, dy=dy)
        self.square = Stationary(w=layer, sprite=s)

    def add_Stairs(self):
        stairs = self.items.get('Stairs')
        #print 'adding stairs at', self.coord, stairs
        if stairs is not None:
            stairimage = Util.load_image('stairs.png')
            stairimage = Util.scale_image(stairimage, TS-3, TS-3)
            self.square.clear()
            self.square.sprite.image.blit(stairimage, (0, 0))
            self.square.prepare()
            self.square.draw()
        else:
            pass
            #print 'nosquare'

    def gen_walls(self):
        sides = {}
        blocks = []
        block_map = {}
        x, y = self.coord
        dx, dy = x*TS, -y*TS

        for side in self.items:
            #print 'side',side
            if not side in ('north', 'south', 'east', 'west'):
                continue

            wall = Wall(side=side)
            wall_type = self.items[side]
            if wall_type == 'Door':
                wall.add_door()
            elif wall_type == 'HDoor':
                wall.add_door()
                wall.door.hide()
            elif wall_type == 'HWall':
                wall.hide()
            sides[side] = wall

            if wall_type != 'HWall':
                block = pygame.Rect(wall.rect)
                block.left += dx
                block.top += dy
                blocks.append(block)

                block_map[(x, y, side)] = block

        self.sides = sides
        self.blocks = blocks
        self.block_map = block_map

    def draw_topdown(self, layer):
        #print 'topdown'
        if hasattr(self, 'square') and not self.visible:
            self.visible = True
            self.square.draw()

    def clear_topdown(self):
        if hasattr(self, 'square') and not self.remembered and self.visible:
            self.visible = False
            self.square.clear()
            #del(self.square)

    def clear_topdown_full(self):
        if hasattr(self, 'square'):
            self.visible = False
            self.square.clear()
            del(self.square)

    def draw_topdown_walls(self, s):
        i = s.image

        rect = pygame.draw.rect
        sides = self.sides
        for side in sides:
            wall = sides[side]
            if wall.visible:
                wallcolor = WHITE
            else:
                wallcolor = RED

            if wall.visible or self.mode is not None and self.mode.endswith('edit'):
                rect(i, wallcolor, wall.rect, 0)

            if wall.door is not None:
                if wall.door.visible:
                    doorcolor = WHITE
                else:
                    doorcolor = RED

                if wall.door.visible or self.mode is not None and self.mode.endswith('edit'):
                    rect(i, doorcolor, wall.door.rect, 1)


class Wall:
    def __init__(self, side):
        self.visible = True
        self.passable = False
        self.door = None

        self.side = side
        Rect = pygame.Rect
        if side == 'north':
            r = Rect((0, 0), (TS, WW))

        elif side == 'south':
            r = Rect((0, TS-WW), (TS, WW))

        elif side == 'west':
            r = Rect((0, 0), (WW, TS))

        elif side == 'east':
            r = Rect((TS-WW, 0), (WW, TS))

        self.rect = r
        self.crect = r

    def add_door(self):
        self.door = Door(self.side)

    def hide(self):
        self.visible = False


class Door:
    def __init__(self, side):
        self.visible = True
        self.passable = True

        self.side = side
        Rect = pygame.Rect
        if side == 'north':
            r = Rect((int(TS/3.0), 0), (int(TS/3.0), WW*2))

        elif side == 'south':
            r = Rect((int(TS/3.0), TS-WW*2), (int(TS/3.0), WW*2))

        elif side == 'west':
            r = Rect((0, int(TS/3.0)), (WW*2, int(TS/3.0)))

        elif side == 'east':
            r = Rect((TS-WW*2, int(TS/3.0)), (WW*2, int(TS/3.0)))

        self.rect = r
        self.crect = r

    def lock(self):
        self.passable = False

    def hide(self):
        self.visible = False


class Stairs:
    def __init__(self, direction, destination):
        self.direction = direction
        self.destination = destination


class Level:
    def __init__(self):
        self.cells = {}
        self.blocks = {}

        self.mode = None

        self.layer = conf.game.addLayer()
        self.layer.center()
        self.layer.set_background(color=(125, 125, 175))

    def add_cell(self, cell):
        coord = cell.coord
        self.cells[coord] = cell
        cell.mode = self.mode

    def dump(self):
        cells = {}
        for coord in self.cells:
            cells[coord] = self.cells[coord].items

        return cells

    def draw(self):
        pass

    def draw_area_nudge(self, d):
        pass

    def set_mode_topdown(self):
        self.mode = 'topdown'

        conf.game.sprites.add(self.layer)
        #self.slayer = Stationary(sprite=self.layer)
        self.draw_area_nudge = self.draw_area_nudge_topdown

    def set_mode_topdown_full(self):
        self.mode = 'topdown_full'
        self.draw = self.draw_topdown_full

    def draw_topdown_full(self):
        for coord in self.cells:
            cell = self.cells[coord]
            cell.draw_topdown(self.layer)

        #self.slayer.draw()

    def set_mode_topdown_cansee(self, party):
        self.mode = 'topdown_cansee'
        self.party = party
        self.draw = self.draw_topdown_cansee

    def draw_topdown_cansee(self):
        party = self.party
        x, y = party.coord

        blocks = []
        for coord, cell in self.cells.items():
            cblocks = cell.blocks[:]
            cbmap = cell.block_map

            for cx, cy, side in cbmap:
                #print cx, cy, side, cbmap[(cx, cy, side)]
                if cx > x and side == 'west':
                    #print x, y, cx, cy, 'west'
                    try:
                        cblocks.remove(cbmap[(cx, cy, 'west')])
                    except (KeyError, ValueError):
                        pass
                elif cx < x and side == 'east':
                    #print x, y, cx, cy, 'east'
                    try:
                        cblocks.remove(cbmap[(cx, cy, 'east')])
                    except (KeyError, ValueError):
                        pass
                elif cy > y and side == 'south':
                    #print x, y, cx, cy, 'south', cbmap
                    try:
                        cblocks.remove(cbmap[(cx, cy, 'south')])
                    except (KeyError, ValueError):
                        pass
                elif cy < y and side == 'north':
                    #print x, y, cx, cy, 'north'
                    try:
                        cblocks.remove(cbmap[(cx, cy, 'north')])
                    except (KeyError, ValueError):
                        pass

            blocks.extend(cblocks)

        for coord in self.cells:
            cell = self.cells[coord]
            cx, cy = coord
            dx, dy = cx-x, cy-y
            distance = hypot(dx, dy)
            if distance <= party.see_distance and party.can_see(cell, blocks):
                cell.draw_topdown(self.layer)
            else:
                cell.clear_topdown()

        #self.party.vis.udraw()
        #self.slayer.clear()
        #self.slayer.prepare()
        #self.slayer.draw()

    def set_mode_topdown_edit(self):
        self.mode = 'topdown_edit'
        self.draw = self.draw_topdown_full


    def draw_area_nudge_topdown(self, d):
        dx, dy = d
        dx *= TS
        dy *= TS
        #self.slayer.nudge(dx=dx, dy=dy)
        self.layer.nudge(dx=dx, dy=dy)


    def save(self, filename):
        cells = self.dump()

        f = file(filename, 'w')
        pickle.dump(cells, f)
        f.close()

    def load(self, filename=None, cells=None):
        if filename is not None:
            f = file(filename)
            cells = pickle.load(f)

            #print  cells

        for coord in cells:
            x, y = coord
            c = Cell(x, y)
            c.add_items(**cells[coord])
            self.add_cell(c)
            #c.add_items(**{'north': 'Door'})

