import pygame
from pygame.locals import *
import math
import time
import os

import obj
import zombie
import spawn
from locals import *

import renderer

class Tile:
    tilesets = []
    wallsets = []
    def __init__(self, tileset, type, leftwall, rightwall):
        self.tileset = tileset
        self.type = type
        self.leftwall = leftwall
        self.rightwall = rightwall

def load_tilesets():
    f = open("tiles/tiles.txt")
    num_tilesets = int(f.readline().strip())

    for i in range(num_tilesets):
        line = f.readline().strip()
        Tile.tilesets.append(pygame.image.load(os.path.join("tiles", line + "floor.png")).convert_alpha())
        Tile.wallsets.append(pygame.image.load(os.path.join("tiles", line + "wall.png")).convert_alpha())

class Map:
    def __init__(self, width, height):
        # Make sure the width and height are positive integers.

        try:
            width = int(width)
        except:
            width = 1
        if width < 1:
            width = 1

        try:
            height = int(height)
        except:
            height = 1
        if height < 1:
            height = 1

        self.width = int(width)
        self.height = int(height)

        # grid stores the basic tiles, objects stores all game objects. Spawnpoints stores routes to other levels.
        self.grid = []
        self.objects = []
        self.spawnpoints = []

        for i in range(width*height):
            self.grid.append(Tile(0,0,0,0))

    def get_tile(self, pos):
        x = int(math.floor(pos[0]))
        y = int(math.floor(pos[1]))
        if x < 0 or y < 0 or x >= self.width or y >= self.height:
            return Tile(0,0,0,0)
        return self.grid[int(math.floor(pos[0])) + int(math.floor(pos[1])) * self.width]

    def draw(self, screen, offset, characters):
        i = 0

        rend = renderer.Renderer()

        for y in range(self.height):
            pos = [offset[0] - y * TILEWIDTH/2, offset[1] + y * TILEHEIGHT/2]
            for x in range(self.width):
                if self.grid[i].type > 0:
                    rect = pygame.Rect((self.grid[i].type - 1) * TILEWIDTH, 0, TILEWIDTH, TILEHEIGHT)
                    if (Variables.psychomode):
                       screen.blit(Tile.tilesets[5], pos, rect)
                    else:
                       screen.blit(Tile.tilesets[self.grid[i].tileset], pos, rect)

                pos[0] += TILEWIDTH/2
                pos[1] += TILEHEIGHT/2
                i += 1

        x,y = 0,0
        while y < self.height or x < self.width:
            if y < self.height and x < self.width:
                rend.add_walls(self.get_tile((x,y)), (x, y))
            x += 1
            y -= 1
            if y < 0 or x > self.width:
                y = y + x + 1
                x = 0

        for object in self.objects:
            rend.add_object(object)

        rend.draw(screen, offset)

    def update(self):
        self.check_collisions()

        for object in self.objects:
            object.update(self)

# I've got lots of crap here
    """
# Dunno if this is a bit hackish, but it works ok.
# We calculate the last tile that is behind the object
        for object in self.objects:
            cornerpos = [object.pos[0]+object.size[0]/2,
                         object.pos[1]+object.size[1]/2]

# This is just terrible... Apparently int(-0.5) is 0 instead of -1
# So we need to floor the values first
            object.draw_at = (int(math.floor(object.pos[0])),# - 0.5)),
                              int(math.floor(object.pos[1])))# + 0.5)))

        i = 0
        for y in range(self.height):
            pos = [offset[0] - y * TILEWIDTH/2, offset[1] + y * TILEHEIGHT/2]
            for x in range(self.width):
                if self.grid[i].leftwall > 0:
                    rect = ((self.grid[i].leftwall - 1) * WALLWIDTH, 0, WALLWIDTH, WALLHEIGHT)
                    pos2 = (pos[0], pos[1] + TILEHEIGHT/2 - WALLHEIGHT)
                    screen.blit(Tile.wallsets[self.grid[i].tileset], pos2, rect)
                if self.grid[i].rightwall > 0:
                    rect = ((self.grid[i].rightwall - 1 + 7) * WALLWIDTH, 0, WALLWIDTH, WALLHEIGHT)
                    pos2 = (pos[0] + WALLWIDTH, pos[1] + TILEHEIGHT/2 - WALLHEIGHT)
                    screen.blit(Tile.wallsets[self.grid[i].tileset], pos2, rect)

# Rendering objects (and updating them too).
                for object in self.objects:
                    if object.draw_at == (x,y):
                        object.draw(screen, offset)
                        object.update()
                        #rect = (object.size[0] * object.frame, 0,
                        #        object.size[0], object.size[1])
                        #objectscreenpos = get_screen_pos((offset[0] - object.centerx, offset[1] - object.centery), object.pos)
                        #object.draw(screen, objectscreenpos)
                        #object.update()

                pos[0] += TILEWIDTH/2
                pos[1] += TILEHEIGHT/2
                i += 1
    """


    def check_collisions(self):
        for o in self.objects:
            if o.objtype == TYPE_OBJECT or o.dead or o.is_walkover():
               continue

            rect = o.get_rect()
            
            rect.bottom += o.dir[1]
            rect.top += o.dir[1]
            rect.left += o.dir[0]
            rect.right += o.dir[0]

            stopped = False

	    # Tile 7 is a door. You don't collide with doors.

            for x in range(int(math.ceil(rect.left)),int(math.ceil(rect.right))):
		tile = self.get_tile((x, int(math.floor(rect.top))))
                if not tile.type or (tile.leftwall and tile.leftwall != 7):
# This "try:" is a hack to make characters stop, but there's nothing like this
# for objects in general yet. Perhaps I'll make a general "you collided"
# function
                    try:
                        o.stop()
                        stopped = True
                        break
                    except:
                        pass
                tile = self.get_tile((x, int(math.floor(rect.bottom))))
                if not tile.type or (tile.leftwall and tile.leftwall != 7):
                    try:
                        o.stop()
                        stopped = True
                        break
                    except:
                        pass

            if stopped:
                continue

            for y in range(int(math.ceil(rect.top)), int(math.ceil(rect.bottom))):
                tile = self.get_tile((int(math.floor(rect.left)), y))
                if not tile.type or (tile.rightwall and tile.rightwall != 7):
                    try:
                        o.stop()
                        stopped = True
                        break
                    except:
                        pass
                tile = self.get_tile((int(math.floor(rect.right)), y))
                if not tile.type or (tile.rightwall and tile.rightwall != 7):
                    try:
                        o.stop()
                        stopped = True
                        break
                    except:
                        pass

            if stopped:
                continue

            for o2 in self.objects:
                if o2.is_walkover(): # or o2.objtype == TYPE_CHARACTER or o2.objtype == TYPE_ENEMY:
                    continue
                if o != o2:
                    rect2 = o2.get_rect()
                    rect2.bottom += o2.dir[1]
                    rect2.top += o2.dir[1]
                    rect2.left += o2.dir[0]
                    rect2.right += o2.dir[0]
                    if (rect.left >= rect2.left and rect.left <= rect2.right) or (rect.right >= rect2.left and rect.right <= rect2.right):
                        if rect.bottom >= rect2.top and rect.bottom <= rect2.bottom:
                            try:
                                o.stop()
                                stopped = True
                            except:
                                pass
                        elif rect.top >= rect2.top and rect.top <= rect2.bottom:
                            try:
                                o.stop()
                                stopped = True
                            except:
                                pass

    #Selects an object pointed by the mouse. First prefers characters, then zombies, then ordinary items.
    def select_object(self, offset, pos):
        lastfound = None
        lastenemy = None
        lastchar  = None

        for o in self.objects:
            spos = get_screen_pos(offset, o.pos)
            #print spos
            #print pos

            rect = pygame.Rect(spos[0] - o.center[0], spos[1] - o.center[1],
                    o.current_animation.surface.get_width() / o.current_animation.frames, o.current_animation.surface.get_height())

            if rect.collidepoint(pos[0], pos[1]):
                if o.objtype == TYPE_CHARACTER:
                    lastchar = o
                if o.objtype == TYPE_ENEMY:
                    lastenemy = o
                if o.objtype == TYPE_SPAWNPOINT:
                    return o
                lastfound = o

        if lastchar:
            return lastchar

        if lastenemy:
            return lastenemy

        if lastfound:
            return lastfound

        return None

    #This function returns a spawn point from a map
    def getspawn(self, name):
        result = (0,0)

        for sp in self.spawnpoints:
            if sp.spawn_to == name:
                return sp.pos

        return (0,0)



# This function takes parameters x and y in the tile coordinates, and returns coordinates on the screen.
# Offset is just a pair of numbers that is added to the result.
def get_screen_pos(offset, pos):
    x,y = pos
    return [offset[0] + x * TILEWIDTH/2 - y * TILEWIDTH/2 + TILEWIDTH/2, offset[1] + x * TILEHEIGHT/2 + y * TILEHEIGHT/2]

def get_map_pos(offset, pos):
    x,y = pos
    pos = (float(x - offset[0]), float(y - offset[1]))

#x = offset[0] + retx * TILEWIDTH/2 - rety * TILEWIDTH/2 + TILEWIDTH/2
#y = offset[1] + retx * TILEHEIGHT/2 + rety * TILEHEIGHT/2
#retx = 2*x/TILEWIDTH - 2*offset[0]/TILEWIDTH + rety - 1
#rety = 2*y/TILEHEIGHT - 2*offset[1]/TILEHEIGHT - retx
# retx = pos[0]/TILEWIDTH + pos[1]/TILEHEIGHT
    retx = pos[1]/TILEHEIGHT + pos[0]/TILEWIDTH - 0.5
    rety = -pos[0]/TILEWIDTH + pos[1]/TILEHEIGHT + 0.5
    return (retx, rety)

# This function reads and parses a map file
def load_map(name):
    filename = "maps/" + name + ".txt"
    f = open(filename)

    str = f.readline()
    strs = str.split()
    #print strs
    width = int(strs[0])
    height = int(strs[1])

    map = Map(width, height)

    map.name = name

    # Loads the spawnpoints from the file
    num_spawnpoints = int(f.readline().strip())

    for i in range(num_spawnpoints):
        line = f.readline()
        parts = line.split(" ")
        spname,rx,ry = parts
        spx = float(rx)
        spy = float(ry)
        sp = spawn.Spawnpoint(spname, (spx, spy))
        map.spawnpoints.append(sp)
        if sp.spawn_to != "default":
            map.objects.append(sp)

    num_enemies = int(f.readline().strip())

    for i in range(num_enemies):
        line = f.readline()
        parts = line.split(" ")
        objectname,rx,ry = parts
        objectx = float(rx)
        objecty = float(ry)
        map.objects.append(zombie.Zombie(objectname, (objectx, objecty)))

    # Loads the objects from the file and adds them to the objects array
    num_objects = int(f.readline().strip())

    for i in range(num_objects):
        line = f.readline()
        parts = line.split(" ")
        objectname,rx,ry = parts
        objectx = float(rx)
        objecty = float(ry)
        map.objects.append(obj.Object(objectname, (objectx, objecty)))

    # Loads tiles from the file and adds them to the grid
    i = 0
    #print map.width, map.height
    for tile in f.read().split():
        map.grid[i].tileset = ord(tile[0])-65
        map.grid[i].leftwall = int(tile[1])
        map.grid[i].type = int(tile[2])
        map.grid[i].rightwall = int(tile[3])

        i += 1

    f.close()

    return map

def main():
    import random

# Random map generation - not needed anymore?
#    map = Map(10, 10)

#    for i in range(100):
#        map.grid[i].tileset = 2
#        map.grid[i].type = random.randint(1,8)
#        wall = random.randint(1,50)
#        if wall <= 7:
#            map.grid[i].leftwall = wall
#        wall = random.randint(8,59)
#        if wall <= 14:
#            map.grid[i].rightwall = wall

    pygame.init()

    screen = pygame.display.set_mode((SCREENWIDTH,SCREENHEIGHT))

    mode = 0
    
    map = load_map("maps/testingfield.txt")

    map.objects[-1].set_animation("west")

    ui = pygame.image.load("ui/game_lower_bg.png").convert_alpha()
    ui_rect = ui.get_rect()
    ui_rect.bottom = screen.get_height()

    load_tilesets()

    done = False

    dy = 0
    dx = 0
    x =300
    y = 50
    
    lastupdate = pygame.time.get_ticks()
    while not done:
        screen.fill((0,0,0))

        for e in pygame.event.get():
            if e.type == QUIT:
                done = True
            if e.type == KEYDOWN and e.key == K_ESCAPE:
                done = True
            if e.type == MOUSEBUTTONDOWN:
                if e.button == 1:
                    print get_map_pos((x,y), e.pos)
            if e.type == MOUSEMOTION:
                if pygame.mouse.get_pressed()[1]:
                    x += e.rel[0]
                    y += e.rel[1]
            if e.type is KEYDOWN and e.key == K_F10:
                tmp = pygame.Surface((SCREENWIDTH,SCREENHEIGHT)).convert()
                tmp.blit(screen,(0,0))
                mode ^= FULLSCREEN
                screen = pygame.display.set_mode((SCREENWIDTH,SCREENHEIGHT),mode)
                screen.blit(tmp,(0,0))
                pygame.display.flip()

        map.draw(screen, (x,y))

        screen.blit(ui, ui_rect)

        pygame.display.flip()
        sleeptime = (1000 / FPS - (pygame.time.get_ticks() - lastupdate)) * 0.001
        if sleeptime > 0:
           time.sleep(sleeptime)
        lastupdate = pygame.time.get_ticks()

if __name__ == "__main__":
    main()
