# -*- coding: utf8 -*-

import sys, io, re, yaml, wx
from PIL import Image
from pprint import pprint # debugging dicts only
from struct import *

class MapInfo:

    def __init__(self, mapNumber):
        self.bubbleKey = None
        self.key = None
        self.chest = None
        self.loadMap(mapNumber)

    def loadMap(self, mapNumber):
        """
        Parses a map and fills Information object with properties:

        floor: floor number, zero-padded to three digits
        time: available time on floor
        map: dict of tiles (appearance, property) and their position (x, y) as key
        enemies: dict of enemy numbers and their position (x, y) as key
        items: dict of items and their position (x, y) as key
        moving:
        doors:
        """

        # define dictionary
        d = dict()

        if isinstance(mapNumber, basestring):
            self.floor = mapNumber
        else:
            # transform number into valid floor number
            self.floor = '{0:03d}'.format(mapNumber)

        # open specified map
        with open('./map/' + self.floor + '.map', 'rb') as f:

            # load content into byte string
            bs = f.read()

        # split bs into sections: main, post
        index = bs.find('\x2e\x6d\x70\x33') + 5 # matches .mp3
        pre = bs[:index]
        main = bs[index:][0:29328:4] # map dimension is always 78(76)x47 tiles with 8 bytes per tile, only every fourth byte is significant
        post = bs[index:][29952:] # one row after map is always \x00 only

        ###
        # process information in pre
        ###

        # process tileset filename
        pre = pre[4:]
        tilesetCharLength = ord(pre[0]) - 1
        self.tileset = pre[4:5+tilesetCharLength]

        # process background filename
        pre = pre[6+tilesetCharLength:]
        bgCharLength = ord(pre[0]) - 1
        self.bg = pre[4:5+bgCharLength]

        # process background music filename
        pre = pre[6+bgCharLength:]
        bgmCharLength = ord(pre[0]) - 1
        self.bgm = pre[4:5+bgmCharLength]

        # process information in main
        mappings = dict()
        appearances = main[0::2]
        properties  = main[1::2]
        columnsPerRow = 78 # the last two columns in a row are always /x00
        row = 0
        column = 0
        for a, p in zip(appearances, properties):
            if (ord(a), ord(p)) != (0, 0):
                mappings[column, row] = (ord(a), ord(p))
            if column < columnsPerRow - 1:
                column += 1
            else:
                column = 0
                row += 1
        self.map = mappings

        ##
        # process information in post
        ##

        # process enemies
        enemies = dict()
        post = post[1024:] # get the enemy part of post
        enemyCount = ord(post[0])
        for x in range(0, enemyCount):
            enemies[ord(post[x*12+4]), ord(post[x*12+8])] = ord(post[x*12+12])
        self.enemySet = ord(post[enemyCount*12+4])
        self.enemies = enemies

        # process items
        self.items = dict()
        post = post[enemyCount*12+8:]
        itemCount = ord(post[0])
        for x in range(0, itemCount):
            self.items[ord(post[x*12+4]), ord(post[x*12+8])] = ord(post[x*12+12])

        # process bubble key
        post = post[itemCount*12+4:]
        if ord(post[0]) == 1:
            self.bubbleKey = (ord(post[4]), ord(post[8]), ord(post[12]))

        # process key
        post = post[16:]
        if ord(post[0]) == 1:
            self.key = (ord(post[4]), ord(post[8]), ord(post[12]))

        # process chest
        post = post[16:]
        if ord(post[0]) == 1:
            self.chest = (ord(post[4]), ord(post[8]), ord(post[12]))

        # process moving platforms
        moving = dict()
        post = post[48:]
        movingCount = ord(post[0])
        for x in range(0, movingCount):
            moving[ord(post[x*12+4]), ord(post[x*12+8])] = ord(post[x*12+12])
        self.moving = moving

        # process door mapping
        doors = dict()
        post = post[movingCount*12+28:]
        for x in range(0, 4):
            doors[x] = ord(post[x*8+4]), ord(post[x*8+8])
        self.doors = doors

        # process time
        post = post[40:]
        self.time = ord(post[0]) + ord(post[1]) * 256

    def dumpMap(self, file):

        # dump pre
        file.write('\x00\x00\x00\x00')
        file.write(packInt(len(self.tileset)) + self.tileset + '\x00')
        file.write(packInt(len(self.bg)) + self.bg + '\x00')
        file.write(packInt(len(self.bgm)) + self.bgm + '\x00')


        # dump tiles
        for row in range(0, 48):
            for column in range(0, 78):
                tile = self.map.get((column, row), (0, 0))
                file.write(packInt(tile[0], tile[1]))

        # dump post
        file.write('\x00\x00\x00\x00' * 256) # this part doesn't seem to have any influence at all, so we're putting null bytes there instead

        # dump enemies
        file.write(packInt(len(self.enemies)))
        for position, enemy in self.enemies.items():
            file.write(packInt(position[0], position[1], enemy))
        file.write(packInt(self.enemySet))

        # dump items
        file.write(packInt(len(self.items)))
        for position, item in self.items.items():
            file.write(packInt(position[0], position[1], item))

        if self.bubbleKey != None:
            file.write(packInt(1, self.bubbleKey[0], self.bubbleKey[1], self.bubbleKey[2]))
        else:
            file.write(packInt(0, 0, 0, 0))

        if self.key != None:
            file.write(packInt(1, self.key[0], self.key[1], self.key[2]))
        else:
            file.write(packInt(0, 0, 0, 0))

        if self.chest != None:
            file.write(packInt(1, self.chest[0], self.chest[1], self.chest[2]))
        else:
            file.write(packInt(0, 0, 0, 0))

        # dump moving platforms
        file.write('\x00\x00\x00\x00' * 8)
        file.write(packInt(len(self.moving)))
        for position, moving in self.moving.items():
            file.write(packInt(osition[0], position[1], moving))

        # doors
        file.write('\x00\x00\x00\x00' * 7)
        for x in range(0, 4):
            file.write(packInt(self.doors[x][0], self.doors[x][1]))

        file.write(packInt(0, self.time))


class SpriteMatrix:
    """ wrapper for sprites """

    def __init__(self, img, fieldSize = 'height', flip = False):

        # load or assign sprite
        if isinstance(img, basestring):
            self.sprite = Image.open(img)
        else:
            self.sprite = img

        self.flip = flip

        # convert to RGBA
        if self.sprite.mode != 'RGBA':
            rgba = Image.new('RGBA', self.sprite.size)
            rgba.paste(self.sprite, (0, 0))
            self.sprite = rgba
            self.sprite.convert('RGBA')

        # make fieldSize into tuple (x, y) or set it depending on string command
        if isinstance(fieldSize, int):
            fieldSize = (fieldSize, fieldSize)
        if isinstance(fieldSize, str):
            if fieldSize == 'width':
                self.fieldSize = self.sprite.size[0], self.sprite.size[0]
            elif fieldSize == 'height':
                self.fieldSize = self.sprite.size[1], self.sprite.size[1]
        else:
            self.fieldSize = fieldSize

        # sprite size in number of fields (x, y)
        self.size = (int(self.sprite.size[0]/self.fieldSize[0]), int(self.sprite.size[1]/self.fieldSize[1]))

    def __getitem__(self, index):
        """ returns field out of loaded sprite based on one- or two-dimensional index"""
        
        # if not tuple, bound index to tuple
        if isinstance(index, int):
            t = index
            index = (int(index / self.size[0]), index - (int(index / self.size[0]) * self.size[0]))
        
        # crop field out of sprite
        top  = index[0] * self.fieldSize[0]
        left = index[1] * self.fieldSize[1]
        sprite = self.sprite.crop((left, top, left + self.fieldSize[1], top + self.fieldSize[0]))
        if self.flip == True:
            return sprite.transpose(Image.FLIP_LEFT_RIGHT)
        return sprite

class Enemy:
    spriteNumber = None
    additionalProperty = None
    scale = None

    def __init__(self, path, spriteNumber = 0, scale = 1, addP = None, spriteSize = 'height', flip = True):
        self.sprite = SpriteMatrix('./data/enemy_' + path + '.png', spriteSize, flip)
        self.spriteNumber = spriteNumber
        self.scale = scale
        self.additionalProperty = addP

def enemyLUT(enemySet):
    balloonSlime = Enemy(u'バルーンスライム_青', scale = 3)
    batH = Enemy(u'バット_青', spriteNumber = 2)
    batV = Enemy(u'バット_黄', spriteNumber = 2)
    beeH = Enemy(u'ビー_赤')
    beeV = Enemy(u'ビー_黄')
    bomber = Enemy(u'ボマー_赤', scale = 2)
    bubbleSkull = Enemy(u'ウィスプボス_灰', scale = 3, flip = False)
    butanukiB = Enemy(u'ブタヌキ_青')
    butanukiR = Enemy(u'ブタヌキ_赤', scale = 2)
    butanukiY = Enemy(u'ブタヌキ_黄')
    butterfly = Enemy(u'バタフライ', scale = 3)
    dragon = Enemy(u'ドラゴンヘッド_青', scale = 5)
    eyeball = Enemy(u'目玉_紫') 
    fireballB = Enemy(u'火の玉_青')
    fireballR = Enemy(u'火の玉_赤')
    guardianR = Enemy(u'ガーディアン_火', scale = 4)
    guardianG = Enemy(u'ガーディアン_風', scale = 4)
    guardianB = Enemy(u'ガーディアン_水', scale = 4)
    guardianO = Enemy(u'ガーディアン_土', scale = 4)
    laserU = Enemy(u'レーザートラップU', flip = False)
    laserD = Enemy(u'レーザートラップD', flip = False)
    laserL = Enemy(u'レーザートラップL', flip = False)
    laserR = Enemy(u'レーザートラップR', flip = False)
    laserUM = Enemy(u'レーザートラップU', addP = 'moving', flip = False)
    laserDM = Enemy(u'レーザートラップD', addP = 'moving', flip = False)
    laserLM = Enemy(u'レーザートラップL', addP = 'moving', flip = False)
    laserRM = Enemy(u'レーザートラップR', addP = 'moving', flip = False)
    lizard = Enemy(u'リザード_黄')
    magicBallR = Enemy(u'マジックボール_赤')
    magicBallG = Enemy(u'マジックボール_青')
    magicBallB = Enemy(u'マジックボール_緑')
    mole = Enemy(u'モグラ_茶')
    moon1 = Enemy(u'ムーン01', scale = 4)
    moon2 = Enemy(u'ムーン02')
    moon3 = Enemy(u'ムーン03')
    mushroom = Enemy(u'きのこ_緑', flip = False)
    needle = Enemy(u'ニードルトラップ', flip = False)
    needleHS = Enemy(u'ニードルトラップ', addP = 'slow', flip = False)
    needleHF = Enemy(u'ニードルトラップ', addP = 'fast', flip = False)
    needleVS = Enemy(u'ニードルトラップ', addP = 'slow', flip = False)
    needleVF = Enemy(u'ニードルトラップ', addP = 'fast', flip = False)
    needleCW = Enemy(u'ニードルトラップ', addP = 'clockwise', flip = False)
    needleCC = Enemy(u'ニードルトラップ', addP = 'counterclockwise', flip = False)
    penguin = Enemy(u'ペンギン_青')
    rat = Enemy(u'ラット_茶')
    shellderG = Enemy(u'シェルダー_緑')
    shellderW = Enemy(u'シェルダー_白', scale = 3)
    shooterR = Enemy(u'シューター_赤', flip = False)
    shooterG = Enemy(u'シューター_緑', flip = False)
    shooterB = Enemy(u'シューター_青', flip = False)
    slimeR = Enemy(u'スライム_赤', spriteNumber = 2)
    slimeG = Enemy(u'スライム_緑', spriteNumber = 2)
    slimeB = Enemy(u'スライム_青', spriteNumber = 2)
    slimeY = Enemy(u'スライム_金', spriteNumber = 2)
    slimeK = Enemy(u'スライム_黒', scale = 2, spriteNumber = 2)
    slimeBoss = Enemy(u'スライム_黒', scale = 3, spriteNumber = 2)
    snake = Enemy(u'スネーク_青')
    sun = Enemy(u'サン')
    witch1 = Enemy(u'ウィッチ')
    witch2 = Enemy(u'ウィッチ')
    witch3 = Enemy(u'ウィッチ2', spriteSize = (96, 64), flip = False)

    common = {
         8: needle,
         9: needleHS,
        10: needleVS,
        11: needleHF,
        12: needleVF,
        13: laserL,
        14: laserR,
        15: laserD,
        16: laserU,
        17: laserLM,
        18: laserRM,
        19: laserDM,
        20: laserUM,
        21: shooterR,
        22: shooterB,
        23: shooterG,
        24: magicBallR,
        25: magicBallG,
        26: magicBallB,
        27: needleCW,
        28: needleCC
    }
    if enemySet == 1:
        d = {
        0: slimeB,
        1: slimeG,
        2: slimeR,
        3: slimeY,
        4: slimeK,
        5: slimeBoss
        }
    elif enemySet == 2:
        d = {
        0: slimeG,
        1: slimeR,
        2: slimeY,
        3: slimeK,
        4: beeV,
        5: beeH,
        6: bubbleSkull
        }
    elif enemySet == 3:
        d = {
        0: batH,
        1: batV,
        2: butanukiB,
        3: butanukiY,
        4: butanukiR,
        5: witch1,
        6: witch2,
        7: witch3
        }
    elif enemySet == 4:
        d = {
        0: batH,
        1: batV,
        2: butanukiY,
        3: butanukiR,
        4: mole,
        5: mushroom,
        6: butterfly
        }
    elif enemySet == 5:
        d = {
        0: batH,
        1: batV,
        2: shellderG,
        3: shellderW,
        4: penguin,
        5: fireballR,
        6: fireballB,
        7: balloonSlime
        }
    elif enemySet == 6:
        d = {
        0: batH,
        1: batV,
        2: snake,
        3: rat,
        4: butanukiB,
        5: butanukiR,
        6: fireballB,
        7: dragon
        }
    elif enemySet == 7:
        d = {
        0: batH,
        1: batV,
        2: slimeG,
        3: snake,
        4: rat,
        5: lizard,
        6: bomber,
        7: eyeball
        }
    elif enemySet == 8:
        d = {
        0: guardianG,
        1: guardianB,
        2: guardianR,
        3: guardianO
        }
    else: # enemySet == 9
        d = {
        0: moon1,
        1: moon2,
        2: moon3,
        3: sun,
        4: eyeball
        }
    d.update(common)
    return d

"""
def propertyLUT():
    return {
      0: blank
      1: signTop
     #2: unconfirmed blank
      3: shopTop
      4: door0Top
      5: door1Top
      6: door2Top
      7: door3Top
      8: bottom
      9: blankSwitchMarble
     10: blankSwitchPlatform
     11: blankSwitchPush
     12: blankSwitchJump
     13: blankSwitchIce
     14: blankSwitchTeleport
     15: blankSwitchTubeDR
     16: blankSwitchTubeDL
     17: blankSwitchTubeUR
     18: blankSwitchTubeUL
     19: blankSwitchTubeLR
     20: blankSwitchTubeUD
     21: blankSwitchDoor1Top
     22: blankSwitchDoor2Top
     23: blankSwitchDoor3Top
     24: blankSwitchDoorBottom
     25: blankSwitchShopTop
     26: blankSwitchShopBottom
     27: entry0
     28: entry1
     29: entry2 
     30: entry3
     31: checkpoint
    #32: unconfirmed blank

     39: magicBlockBD
     40: magicBlockRD
     41: magicBlockGD
     42: oneWayL
     43: oneWayR
     44: trueEye
     45: hiddenR
     46: hiddenU
    #47: unconfirmed blank
    #48: unconfirmed blank
    #49: unconfirmed blank
     50: platform
     51: push
     52: spikeU
     53: spikeD
     54: spikeL
     55: spikeR
     56: vanish
     57: tubeDR
     58: tubeDL
     59: tubeUR
     60: tubeUL
     61: tubeLR
     62: tubeUD
     63: jump
     64: ice
     65: teleport
     66: magicB
     67: magicR
     68: magicG
     69: magicRB
     70: magicRG
     71: magicGB
     72: magicRGB
     73: magicBlockBA
     74: magicBlockRA
     75: magicBlockGA
     76: breakBlank
     77: breakPlatform
     78: breakSpikeU
     79: breakOneWayL
     80: breakOneWayR
     81: breakMpRestore
     82: breakDoor0Top
     83: breakDoor1Top
     84: breakDoor2Top
     85: breakDoorBottom

     89: breakTeleport
     90: solidSwitchBlank
     91: solidSwitchPlatform
     92: solidSwitchSpikeU
     93: solidSwitchSpikeD
     94: solidSwitchSpikeL
     95: solidSwitchSpikeR
     96: solidSwitchJump
     97: solidSwitchPush
     98: solidSwitchTeleport
     99: solidSwitchDoor0Top
    100: solidSwitchDoor1Top
    101: solidSwitchDoor2Top
    102: solidSwitchDoorBottom
    103: solidSwitchShopTop
    104: solidSwitchShopBottom
    105: touchBlank
    106: touchPlatform
    107: touchSpikeU
    108: touchSpikeD
    109: touchSpikeL
    110: touchSpikeR
    111: touchJump
    114: touchDoor1Top
    115: touchDoor2Top
    116: touchDoor3Top
    117: touchDoorBottom
    118: touchShopTop
    119: touchShopBottom
    120: solid
    }
"""

def packInt(*args):
    return pack('i' * len(args), *args)

def makeImage(info, layers = None, merge = False):
    """ creates an image from a map"""

    # define standard tile size in px
    tSize = 32

    # create image dictionary
    imgs = dict()

    if layers == None or 0 in layers:

        imgs[0] = Image.new('RGBA', (tSize * 76, tSize * 47))

        # open tileset
        tSet = SpriteMatrix('./data/' + info.tileset, tSize)
        # draw mapped tiles
        for key, value in info.map.items():

            # draw cropped tile on base image
            imgs[0].paste(tSet[value[0]], (key[0] * tSize, key[1] * tSize))

    if layers == None or 1 in layers:

        imgs[1] = Image.new('RGBA', (tSize * 76, tSize * 47))

        # draw moving tiles
        for key, value in info.moving.items():
            
            # draw cropped tile on base image
            imgs[1].paste(tSet[int(value/4)+2], (key[0] * tSize, key[1] * tSize))

    if layers == None or 2 in layers:

        imgs[2] = Image.new('RGBA', (tSize * 76, tSize * 47))

        # draw enemies
        elut = enemyLUT(info.enemySet)
        for key, value in info.enemies.items():

            # look up enemy
            enemy = elut.get(value, None)

            # draw enemy on base image
            resizedEnemy = enemy.sprite[enemy.spriteNumber].resize((enemy.scale * enemy.sprite.fieldSize[1], enemy.scale * enemy.sprite.fieldSize[0]))

            imgs[2].paste(resizedEnemy, (key[0] * tSize, key[1] * tSize), resizedEnemy)

    if layers == None or 3 in layers:

        imgs[3] = Image.new('RGBA', (tSize * 76, tSize * 47))

        # open itemset and chest sprite with transparency 
        iSet = Image.new('RGBA', (tSize * 6, tSize * 7))
        iSet.paste(Image.open('./data/pat_item.png'), (0, 0))
        iSet.paste(Image.open('./data/chip_itembox.png'), (0, tSize * 6))
        iSet = SpriteMatrix(iSet, tSize)

        # add bubbleKey, key and chest to items
        if info.bubbleKey != None:
            info.items[info.bubbleKey[0], info.bubbleKey[1]] = info.bubbleKey[2]
        if info.key != None:
            info.items[info.key[0], info.key[1]] = info.key[2]
        if info.chest != None:
            info.items[info.chest[0], info.chest[1]] = info.chest[2]

        # draw items
        for key, value in info.items.items():

            # bound value
            v = value if value < 6 else 6
            
            # draw item on base image
            imgs[3].paste(iSet[v, 0],(key[0] * tSize, key[1] * tSize), iSet[v, 0])
   
    if layers == None or 4 in layers:

        imgs[4] = Image.new('RGBA', (tSize * 76, tSize * 47))
        row = 0
        while row < imgs[4].size[1]:
            imgs[4].paste((255, 0, 0, 63), (0, row-1, imgs[4].size[0], row+1))
            row += tSize
        column = 0
        while column < imgs[4].size[0]:
            imgs[4].paste((255, 0, 0, 63), (column-1, 0, column+1, imgs[4].size[1]))
            column += tSize

    if merge == False:
        return imgs
    else:
        img = Image.new('RGBA', (tSize * 76, tSize * 47))
        for key, image in imgs:
            img.paste(image)
        return img

def PILImageToWxImage(pilImage):
    image = wx.EmptyImage(pilImage.size[0], pilImage.size[1])
    image.SetData(pilImage.convert('RGB').tostring())
    image.SetAlphaData(pilImage.convert('RGBA').tostring()[3::4])
    return image

class MainWindow(wx.Frame):

    def __init__(self, parent, info = None, id = -1, title = 'Untitled'):
        wx.Frame.__init__(self, parent, id, title, (-1, -1))

        # make sizers
        self.sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.leftSizer = wx.BoxSizer(wx.VERTICAL)
        mapControlSizer = wx.BoxSizer(wx.HORIZONTAL)
        rightSizer = wx.BoxSizer(wx.VERTICAL)
        informationSizer = wx.FlexGridSizer(cols = 2)
        visibilitySizer = wx.BoxSizer(wx.VERTICAL)

        # make elements
        self.mapPanel = MapPanel(self, id = -1)

        # buttons
        resetScalingButton = wx.Button(self, -1, 'Reset scaling')
        saveFileButton = wx.Button(self, -1, 'Save')
        openFileButton = wx.Button(self, -1, 'Open')
        closeFileButton = wx.Button(self, -1, 'Close')
        # checkboxes
        showTilesCheckbox = wx.CheckBox(self, 0, 'Tiles')
        showMovingCheckbox = wx.CheckBox(self, 1, 'Moving')
        showEnemiesCheckbox = wx.CheckBox(self, 2, 'Enemies')
        showItemsCheckbox = wx.CheckBox(self, 3, 'Items')
        showGridCheckbox = wx.CheckBox(self, 4, 'Grid')

        self.tilesetControl = wx.TextCtrl(self, -1)
        tilesetControlLabel = wx.StaticText(self, -1, 'Tileset: ')
        self.backgroundControl = wx.TextCtrl(self, -1)
        backgroundControlLabel = wx.StaticText(self, -1, 'Background: ')
        self.musicControl = wx.TextCtrl(self, -1)
        musicControlLabel = wx.StaticText(self, -1, 'Music: ')

        self.timeControl = wx.SpinCtrl(self, -1)
        self.timeControl.SetRange(0, 2147483647)
        timeControlLabel = wx.StaticText(self, -1, 'Time: ')

        # set some defaults
        showTilesCheckbox.SetValue(True)
        showMovingCheckbox.SetValue(True)
        showEnemiesCheckbox.SetValue(True)
        showItemsCheckbox.SetValue(True)
        showGridCheckbox.SetValue(True)

        # add elements to sizer
        self.sizer.Add(self.leftSizer, 1, wx.EXPAND)
        self.sizer.AddSpacer(2)
        self.sizer.Add(rightSizer, 0, wx.EXPAND)
        self.leftSizer.Add(self.mapPanel, 1, wx.EXPAND)
        self.leftSizer.Add(mapControlSizer, 0, wx.EXPAND)
        mapControlSizer.Add(resetScalingButton, 1, wx.EXPAND)
        rightSizer.AddMany([(informationSizer, 0, wx.EXPAND),
                            (visibilitySizer, 1, wx.EXPAND),
                            (saveFileButton, 1, wx.EXPAND),
                            (openFileButton, 1, wx.EXPAND),
                            (closeFileButton, 1, wx.EXPAND)])
        informationSizer.AddMany([(timeControlLabel),(self.timeControl, 1, wx.EXPAND),
                                  (tilesetControlLabel), (self.tilesetControl, 1, wx.EXPAND),
                                  (backgroundControlLabel), (self.backgroundControl, 1, wx.EXPAND),
                                  (musicControlLabel), (self.musicControl, 1, wx.EXPAND)])
        visibilitySizer.AddMany([(showTilesCheckbox, 1, wx.EXPAND),
                                 (showMovingCheckbox, 1, wx.EXPAND),
                                 (showEnemiesCheckbox, 1, wx.EXPAND),
                                 (showItemsCheckbox, 1, wx.EXPAND),
                                 (showGridCheckbox, 1, wx.EXPAND)])


        # do event handling
        self.Bind(wx.EVT_CLOSE, self.OnClose)

        resetScalingButton.Bind(wx.EVT_BUTTON, self.OnResetScalingButton)
        saveFileButton.Bind(wx.EVT_BUTTON, self.OnSaveFileButton)
        openFileButton.Bind(wx.EVT_BUTTON, self.OnOpenFileButton)
        closeFileButton.Bind(wx.EVT_BUTTON, self.OnCloseFileButton)

        showTilesCheckbox.Bind  (wx.EVT_CHECKBOX, self.OnShowCheckbox)
        showMovingCheckbox.Bind (wx.EVT_CHECKBOX, self.OnShowCheckbox)
        showEnemiesCheckbox.Bind(wx.EVT_CHECKBOX, self.OnShowCheckbox)
        showItemsCheckbox.Bind  (wx.EVT_CHECKBOX, self.OnShowCheckbox)
        showGridCheckbox.Bind   (wx.EVT_CHECKBOX, self.OnShowCheckbox)

        # set sizer
        self.SetSizer(self.sizer)
        self.SetAutoLayout(1)
        self.sizer.Fit(self)

        # show window
        self.Show()

    def OnResetScalingButton(self, event):
        self.mapPanel.setScale(1.0)

    def OnSaveFileButton(self, event):
        # copy info to object
        self.mapPanel.info.time = self.timeControl.GetValue()
        self.mapPanel.info.tileset = self.tilesetControl.GetValue().encode('ascii')
        self.mapPanel.info.bg = self.backgroundControl.GetValue().encode('ascii')
        self.mapPanel.info.bgm = self.musicControl.GetValue().encode('ascii')

        saveFileDialog = wx.FileDialog(self, 'Save MAP', './', self.mapPanel.info.floor, 'MAP (*.map)|*.map|YAML Map (*.yml)|*.yml', wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
        if saveFileDialog.ShowModal() == wx.ID_CANCEL:
            return  # Cancel
        with io.open(saveFileDialog.GetPath(), 'wb') as f:
            self.mapPanel.info.dumpMap(f)

    def OnOpenFileButton(self, event):
        openFileDialog = wx.FileDialog(self, 'Open map file', './', '', 'MAP (*.map)|*.map|YAML Map (*.yml)|*.yml', wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
        if openFileDialog.ShowModal() == wx.ID_CANCEL:
            return  # Cancel
        ext = openFileDialog.GetPath()[-4:]
        if ext == '.map':
            self.setInfo(MapInfo(openFileDialog.GetPath()[-7:-4]))
            print 'opened F%s' % openFileDialog.GetPath()[-7:-4]
        else:
            print 'something should happen here'

    def OnCloseFileButton(self, event):
        size = self.mapPanel.GetSize()
        self.mapPanel.Destroy()
        self.mapPanel = MapPanel(self, id = 1, size = size)
        self.leftSizer.Insert(0, self.mapPanel, 1, wx.EXPAND)

        self.resetInfo()

    def OnClose(self, event):
        self.Show(False)
        self.Destroy()

    def OnShowCheckbox(self, event):
        if event.Checked():
            self.mapPanel.layersDisplayed.add(event.GetId())
        else:
            self.mapPanel.layersDisplayed.remove(event.GetId())
        self.mapPanel.Refresh()

    def setInfo(self, info):
        self.timeControl.SetValue(info.time)
        self.tilesetControl.SetValue(info.tileset)
        self.backgroundControl.SetValue(info.bg)
        self.musicControl.SetValue(info.bgm)

        self.mapPanel.setInfo(info)

    def resetInfo(self):
        self.timeControl.SetValue(0)
        self.tilesetControl.SetValue('')
        self.backgroundControl.SetValue('')        
        self.musicControl.SetValue('')

class MapPanel(wx.Panel):

    def __init__(self, parent, info = None, tileSize = 32, id = -1, size = (640, 480), scale = 1.0):
        
        # init parent
        wx.Panel.__init__(self, parent, id, size = size)
        self.tileSize = tileSize
        self.offsetX = 0
        self.offsetY = 0
        self.dragging = False
        self.leftWhileDragging = False
        self.draggingX = 0
        self.draggingY = 0
        self.info = self.setInfo(info)
        self.layersDisplayed = set([0, 1, 2, 3, 4])
        self.layers = dict()
        self.scaledLayers = dict()
        self.setScale(scale)

        # events are binded when setInfo is called

    def BindEvents(self):
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_MOTION, self.OnMotion)
        self.Bind(wx.EVT_ENTER_WINDOW, self.OnWindowEnter)
        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnWindowLeave)
        self.Bind(wx.EVT_LEFT_DOWN, self.OnLDown)
        self.Bind(wx.EVT_LEFT_UP, self.OnLUp)
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnRDown)
        self.Bind(wx.EVT_MOUSEWHEEL, self.OnScroll)

    def UnbindEvents(self):
        self.Unbind(wx.EVT_PAINT, self.OnPaint)
        self.Unbind(wx.EVT_SIZE, self.OnSize)
        self.Unbind(wx.EVT_MOTION, self.OnMotion)
        self.Unbind(wx.EVT_ENTER_WINDOW, self.OnWindowEnter)
        self.Unbind(wx.EVT_LEAVE_WINDOW, self.OnWindowLeave)
        self.Unbind(wx.EVT_LEFT_DOWN, self.OnLDown)
        self.Unbind(wx.EVT_LEFT_UP, self.OnLUp)
        self.Unbind(wx.EVT_RIGHT_DOWN, self.OnRDown)
        self.Unbind(wx.EVT_MOUSEWHEEL, self.OnScroll)

    def OnPaint(self, event):
        dc = wx.AutoBufferedPaintDC(self)
        dc.SetBackground(wx.Brush("black"))
        dc.Clear()
        for x in self.layersDisplayed:
            dc.DrawBitmap(self.scaledLayers[x], -self.offsetX*self.scale, -self.offsetY*self.scale)

    def OnSize(self, event):

        self.updateOffset()

        self.Refresh()

    def OnMotion(self, event):
        if self.dragging == True:
            x = self.offsetX
            y = self.offsetY
            self.offsetX += (self.draggingX - event.GetX()) / self.scale
            self.offsetY += (self.draggingY - event.GetY()) / self.scale

            # stop if we hit a border
            self.updateOffset(x, y)

            self.Refresh()
            self.draggingX = event.GetX()
            self.draggingY = event.GetY()

    def OnWindowEnter(self, event):
        if self.leftWhileDragging == True and event.LeftIsDown() == True:
            self.dragging = True
        self.leftWhileDragging = False
        self.SetFocus()

    def OnWindowLeave(self, event):
        if event.LeftIsDown() == True:
            self.dragging = False
            self.leftWhileDragging = True

    def OnLDown(self, event):
        self.draggingX = event.GetX()
        self.draggingY = event.GetY()
        self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
        self.dragging = True

    def OnLUp(self, event):
        self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
        self.dragging = False

    def OnRDown(self, event):
        position = self.boundToGrid(event.GetPosition())
        print position
        propertyAtPosition = self.info.map.get(position, (0, 0))[1]
        if propertyAtPosition in range(4, 8):
            doorNumber = propertyAtPosition - 4
            print 'this is door %s which should lead to floor %s' % (str(doorNumber), self.info.doors[doorNumber][1]) 
            if event.ShiftDown():
                self.GetParent().setInfo(MapInfo(self.info.doors[doorNumber][1]))
        elif propertyAtPosition in range(27, 31):
            entryNumber = propertyAtPosition - 27
            print 'this entry point %s' % entryNumber
        else:
            print 'this isn\'t interesting'


    def OnScroll(self, event):
        scale = self.scale
        self.setScale(self.scale + float(event.GetWheelRotation()) / 12000 * 2)
        self.updateOffset()
        self.Refresh()

    def boundToGrid(self, position):
        return (int((position[0]/self.scale + self.offsetX)/self.tileSize), int((position[1]/self.scale + self.offsetY)/self.tileSize))

    # bounds offset to fit to borders if necessary
    def updateOffset(self, x = 0, y = 0):
        rightBorder = (self.scaledLayers[0].GetSize()[0] - self.GetSizeTuple()[0]) / self.scale
        bottomBorder = (self.scaledLayers[0].GetSize()[1] - self.GetSizeTuple()[1]) / self.scale

        if self.offsetX <= 0:
            self.offsetX = 0
        elif rightBorder < 0 and x != None:
            if x != None:
                self.offsetX = x
            else:
                self.offsetX = 0
        elif self.offsetX > rightBorder:
            self.offsetX = rightBorder

        if self.offsetY <= 0:
            self.offsetY = 0
        elif bottomBorder < 0:
            self.offsetY = y
        elif self.offsetY > bottomBorder:
            self.offsetY = bottomBorder

    # sets scale 
    def setScale(self, scale):
        if scale > 0.0:
            self.scale = scale
            self.scaleLayers()

    def setInfo(self, info, layers = None):
        if info != None:
            self.info = info

            self.BindEvents()
            # avoid flickering
            self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)

            self.updateLayers(layers)

    # updates layers with info
    def updateLayers(self, layers = None):

        # only get layers requested
        images = makeImage(self.info, layers = layers)

        for key, image in images.items():
            self.layers[key] = PILImageToWxImage(image)
            self.scaleLayers()

    # scales all layers in layers with scale to scaledLayers
    def scaleLayers(self, layers = None):
        if layers != None:
            self.layers = layers
        if self.layers != {}:
            self.scaledLayers = dict()
            for key, image in self.layers.items():
                self.scaledLayers[key] = image.Scale(image.GetSize()[0] * self.scale, image.GetSize()[1] * self.scale, wx.IMAGE_QUALITY_NORMAL).ConvertToBitmap()
        self.Refresh()

app = wx.App(0)
frame = MainWindow(None, title = "Asakura! P Map Editor")
app.MainLoop()