# vim:encoding=utf-8
from Tkinter import *
import tkMessageBox

import os
import re
import random

SNAKE = "snake"
WALL = "wall"
APPLE = "apple"

LEFT = "left"
RIGHT = "right"
UP = "up"
DOWN = "down"

WIN = "win"
FAIL = "fail"
EAT = "eat"
TICK = "tick"

def opposite(dir1, dir2):
    return set([dir1, dir2]) in [set([UP, DOWN]), set([LEFT, RIGHT])]

def headed(head, neck):
    if head == neck.left(): return LEFT
    if head == neck.right(): return RIGHT
    if head == neck.up(): return UP
    if head == neck.down() : return DOWN
    assert False

def regIndex(text, regex):
    result = {}
    for match in re.finditer(regex, text):
        result[match.start()] = match.group(0)
    return result

class Point(tuple):
    def __new__(cls, x, y):
        return tuple.__new__(cls, (x, y))

    def __getattr__(self, attr):
        if attr == 'x': return self[0]
        elif attr == 'y': return self[1]
        else: raise AttributeError, attr

    def up(self): return Point(self.x, self.y - 1)
    def down(self): return Point(self.x, self.y + 1)
    def left(self): return Point(self.x - 1, self.y)
    def right(self): return Point(self.x + 1, self.y)

class World(object):

    def __init__(self, width, height, apples, wall, snake):
        self._wall = wall
        self._snake = snake
        self._apple = None
        self._apples = self._toEat = apples
        self._width = width
        self._height = height
        self._completed = None
        self.generateApple()

    def __getitem__(self, point):
        if point in self._wall: return WALL
        elif point in self._snake: return SNAKE
        elif point == self.apple(): return APPLE
        else: return None

    def width(self): return self._width
    def height(self): return self._height
    def snake(self): return self._snake[:]
    def wall(self): return self._wall[:]
    def head(self): return self._snake[0]
    def tail(self): return self._snake[-1]
    def apple(self): return self._apple

    def putApple(self, point):
        self._apple = point

    def generateApple(self):
        while True:
            point = Point(
                random.randint(0, self._width - 1),
                random.randint(0, self._height - 1))
            if self[point] == None:
                self._apple = point
                return point

    def advance(self, direction = None):
        if self._completed: return
        if not direction or opposite(direction, headed(self.head(), self._snake[1])):
            direction = headed(self.head(), self._snake[1])

        nextPoint = getattr(self.head(), direction)()
        next = self[nextPoint]
        if not self.withinWorld(nextPoint):
            self._completed = FAIL
            return (FAIL, self.head())
        if next in [SNAKE, WALL]:
            self._completed = FAIL
            return (FAIL, nextPoint)

        if next == APPLE:
            self._snake.insert(0, nextPoint)
            self._apples -= 1
            if self._apples:
                self.generateApple()
                return (EAT, nextPoint)
            else:
                self._completed = WIN
                return (WIN, nextPoint)
        else:
            self._snake.insert(0, nextPoint)
            self._snake.pop()
            return (TICK, nextPoint)
        
    def withinWorld(self, point):
        return 0 <= point.x < self.width() and 0 <= point.y < self.height()

    def load(file):
        lines = file.readlines()
        lines = map(str.rstrip, lines)
        info, rows = lines[0], lines[1:]

        match = re.match(r'(\d+)x(\d+) (\d+)', info)
        width, height, apples = map(int, match.groups())

        wall = []
        for row, text in enumerate(rows):
            for i in regIndex(text, '#').keys():
                wall.append(Point(i, row))

        snakeDict = {}
        for row, text in enumerate(rows):
            for column, segment in regIndex(text, '\d').items():
                snakeDict[int(segment)] = Point(column, row)

        snake = [None] * len(snakeDict)
        for key, value in snakeDict.items():
            snake[key] = value

        return World(width, height, apples, wall, snake)
    load = staticmethod(load)

class Game(object):
    def __init__(self):
        self.tk = Tk()

        self.init_menu()
        self.board = None
        self.welcome = Label(self.tk, padx = 100, pady = 40, text = "Змията хапе!")
        self.welcome.pack()

        self.tk.mainloop()

    def init_menu(self):
        menu = Menu(self.tk)

        file = Menu(menu)
        file.add_command(label = "За ЗХ", command = lambda: tkMessageBox.showinfo("За ЗХ", "Змията хапе, бе!\nhttp://fmy.py-bg.net/"))
        file.add_separator()
        file.add_command(label = "Изход", command = self.tk.quit)
        menu.add_cascade(label = "Файл", menu = file)

        levels = Menu(menu)
        for name in [file for file in os.listdir('./levels') if file.endswith('.snake')]:
            levels.add_command(label = name[0:-len('.snake')], 
                                command = self.level_opener(name))
        menu.add_cascade(label = "Нива", menu = levels)

        self.tk.config(menu = menu)

    def level_opener(self, level_name):
        return lambda: self.load_level(level_name)

    def load_level(self, name):
        if self.welcome:
            self.welcome.pack_forget()
            self.welcome.destroy()
        if self.board:
            self.board.hide()

        world = World.load(file("./levels/" + name))
        self.board = Board(world, self.tk)
        self.board.pack()
        self.tk.after(1000, self.board.next_frame)

        

SIZE = 20
class Board(Frame):
    def __init__(self, world, root):
        Frame.__init__(self, root)
        self.world = world
        self.direction = None
        
        self.canvas = Canvas(root, width=world.width()*SIZE, height=world.height()*SIZE)
        self.canvas.pack()
        
        self.redraw()

        root.bind("<Down>", lambda _: self.key_press(DOWN))
        root.bind("<Up>", lambda _: self.key_press(UP))
        root.bind("<Left>", lambda _: self.key_press(LEFT))
        root.bind("<Right>", lambda _: self.key_press(RIGHT))

    def hide(self):
        self.canvas.pack_forget()
        self.canvas.destroy()
        self.pack_forget()
        self.destroy()

    def put_point(self, point, **kwargs):
        x, y = point.x * SIZE, point.y * SIZE
        self.canvas.create_rectangle(x, y, x + SIZE - 1, y + SIZE - 1, **kwargs)


    def redraw(self):
        for i in self.canvas.find_all(): self.canvas.delete(i)

        for segment in self.world.snake():
            self.put_point(segment, fill='green')

        for segment in self.world.wall():
            self.put_point(segment, fill='brown')

        if self.world.apple():
            self.put_point(self.world.apple(), fill='red')

    def key_press(self, direction):
        self.direction = direction

    def next_frame(self):
        status, _ = self.world.advance(self.direction)
        self.direction = None
        self.redraw()

        if status == WIN:
            tkMessageBox.showinfo('Победа!', 'Ти победи. Чувстваш ли се по-специален бе, юнак?')
        elif status == FAIL:
            tkMessageBox.showerror("Ти загуби :'(", "Нищо, повече късмет следващия път... шегувам се. Смешник.")
        else:
            self.after(150, self.next_frame)


if __name__ == "__main__":            
    Game() 
