#!/usr/bin/env python
# -*- coding: utf-8 -*-

# pygmeu - PYthon GaMe Engine Unfinished

import pygame

class ProcessadorDeEventos(object):
    def processar_evento(self, e):
        pass


class Atualizavel(object):
    def atualizar(self, dt):
        pass


class Pintavel(object):
    def pintar(self, tela):
        pass


class Colidivel(object):
    def testar_colisao(self, outro):
        if self.get_rect() is None or outro.get_rect() is None:
            return False
        if self.get_rect().colliderect(outro.get_rect()):
            Jogo.jogo.enviar_evento(
                msg='colidiu.%s.%s' % (
                    self.__class__.__name__, outro.__class__.__name__),
                obj=self, outro=outro)
            self.ao_colidir(outro)
            outro.ao_colidir(self)
            return True
        else:
            return False

    def ao_colidir(self, outro):
        pass

    def get_rect(self):
        pass


class Animavel(Atualizavel):
    def __init__(self):
        Atualizavel.__init__(self)
        self.animacoes = []

    def atualizar(self, dt):
        Atualizavel.atualizar(self, dt)
        self.animar()

    def adicionar_animacao(self, animacao):
        if isinstance(animacao, Animacao):
            self.animacoes.append(animacao)
        elif isinstance(animacao, list):
            self.adicionar_animacao(AnimacaoEsperar(0) + animacao)
            return
        else:
            raise TypeError(
                'parametro animacao nao pode ser do tipo "%s"' % (
                    animacao.__class__.__name__))
        Jogo.jogo.enviar_evento(
            msg='animacao.%s.%s.%s.inicio' % (
                self.__class__.__name__,
                animacao.__class__.__name__,
                animacao.atributo),
            obj=self,
            animacao=animacao)

    def remover_animacao(self, animacao):
        self.animacoes.remove(animacao)
        Jogo.jogo.enviar_evento(
            msg='animacao.%s.%s.%s.fim' % (
                self.__class__.__name__,
                animacao.__class__.__name__,
                animacao.atributo),
            obj=self,
            animacao=animacao)

    def remover_todas_animacoes(self):
        self.animacoes = []

    def animar(self):
        for a in self.animacoes:
            if a.atributo:
                setattr(self, a.atributo, a.calcular())
            if a.temporizador.expirou():
                if a.vezes == 0:
                    a.temporizador.reiniciar()
                elif a.vezes == 1:
                    self.remover_animacao(a)
                    for p in a.proximas_animacoes:
                        p.temporizador.reiniciar()
                        self.adicionar_animacao(p)
                elif a.vezes >= 2:
                    a.vezes -= 1
                    a.temporizador.reiniciar()


class Animacao(object):
    def __init__(self, atributo, duracao, vezes=1):
        self.atributo = atributo
        self.duracao = duracao
        self.vezes = vezes
        self.temporizador = Temporizador(duracao)
        self.proximas_animacoes = []

    def calcular(self):
        return self.funcao(self.temporizador.transcorrido())

    def funcao(self, agora):
        pass

    def __add__(self, other):
        a = self
        while a.proximas_animacoes:
            a = a.proximas_animacoes[0]
        if isinstance(other, Animacao):
            a.proximas_animacoes.append(other)
        elif isinstance(other, list):
            a.proximas_animacoes.extend(other)
        else:
            raise TypeError(
                'nao pode concatenar objetos "%s" e "%s"' % (
                    self.__class__.__name__, other.__class__.__name__))
        return self

    def __radd__(self, other):
        return AnimacaoEsperar(0) + other + self


class AnimacaoEsperar(Animacao):
    def __init__(self, duracao, vezes=1):
        Animacao.__init__(self, None, duracao, vezes)


class AnimacaoValor(Animacao):
    def __init__(self, atributo, valor):
        Animacao.__init__(self, atributo, 0, 1)
        self.valor = valor

    def funcao(self, agora):
        return self.valor

class AnimacaoLinear(Animacao):
    def __init__(self, atributo, valor_inicial, valor_final, duracao, vezes=1):
        Animacao.__init__(self, atributo, duracao, vezes)
        self.valor_inicial = valor_inicial
        self.valor_final = valor_final

    def funcao(self, agora):
        r = self.valor_inicial + \
            (self.valor_final - self.valor_inicial) * agora / self.duracao
        if self.valor_inicial <= self.valor_final:
            r = min(r, self.valor_final)
        else:
            r = max(r, self.valor_final)
        return r


class Estado(object):
    def __init__(self):
        self.maquina_de_estados = None

    def avancar(self, proximo_estado):
        self.maquina_de_estados.avancar(proximo_estado)

    def dono(self):
        return self.maquina_de_estados.dono

    def ao_entrar(self):
        pass

    def ao_sair(self):
        pass


class EstadoSprite(Estado, ProcessadorDeEventos, Atualizavel, Pintavel):
    def __init__(self):
        Estado.__init__(self)
        ProcessadorDeEventos.__init__(self)
        Atualizavel.__init__(self)
        Pintavel.__init__(self)


class MaquinaDeEstados(object):
    def __init__(self, estado_inicial, dono=None):
        self.estado_atual = None
        self.dono = dono
        self.avancar(estado_inicial)

    def avancar(self, proximo_estado):
        if self.estado_atual:
            self.estado_atual.ao_sair()
        if proximo_estado:
            proximo_estado.maquina_de_estados = self
            proximo_estado.ao_entrar()
        self.estado_atual = proximo_estado


class Pai(object):
    def __init__(self):
        self.filhos = []

    def adicionar_filho(self, filho):
        self.filhos.append(filho)

    def remover_filho(self, filho):
        self.filhos.remove(filho)

    def __getitem__(self, k):
        return self.filho_por_nome(k)

    def filho_por_nome(self, nome):
        cabeca, _, rabo = nome.partition('.')
        for f in self.filhos:
            if f.nome == nome:
                return f
            elif f.nome == cabeca:
                return f.filho_por_nome(rabo)
        return None

    def filhos_por_nome(self, nome):
        return [f for f in self.filhos if f.nome == nome]

    def debug_filhos(self, nivel=1):
        print '-' * nivel, self.nome
        for f in self.filhos:
            f.debug_filhos(nivel + 1)


class Filho(object):
    def __init__(self, pai, nome):
        self.pai = pai
        self.nome = nome
        if pai:
            pai.adicionar_filho(self)


class ObjetoGrafico(ProcessadorDeEventos, Animavel, Pintavel, Filho, Pai):
    def __init__(self, pai, nome):
        ProcessadorDeEventos.__init__(self)
        Animavel.__init__(self)
        Pintavel.__init__(self)
        Filho.__init__(self, pai, nome)
        Pai.__init__(self)
        self.maquina_de_estados = MaquinaDeEstados(EstadoSprite(), dono=self)
        self.lixo = False

    def processar_evento(self, e):
        ProcessadorDeEventos.processar_evento(self, e)
        return self.maquina_de_estados.estado_atual.processar_evento(e)

    def atualizar(self, dt):
        Animavel.atualizar(self, dt)
        return self.maquina_de_estados.estado_atual.atualizar(dt)

    def pintar(self, tela):
        Pintavel.pintar(self, tela)
        return self.maquina_de_estados.estado_atual.pintar(tela)

    def propagar_processar_evento(self, e):
        if not self.processar_evento(e):
            for f in self.filhos:
                f.propagar_processar_evento(e)

    def propagar_atualizar(self, dt):
        if not self.atualizar(dt):
            for f in self.filhos:
                f.propagar_atualizar(dt)

    def propagar_pintar(self, tela):
        if not self.pintar(tela):
            for f in self.filhos:
                f.propagar_pintar(tela)

    def propagar_remover_lixo(self):
        for f in self.filhos:
            f.propagar_remover_lixo()
            if f.lixo:
                self.filhos.remove(f)


class Sprite(ObjetoGrafico, Colidivel):
    def __init__(self, pai, nome, x, y):
        ObjetoGrafico.__init__(self, pai, nome)
        Colidivel.__init__(self)
        self.x = x
        self.y = y
        self.angulo = 0.0
        self.escala = 1.0
        self.opacidade = 255
        self._rect = None
        self.imagem = None

    def pintar(self, tela):
        ObjetoGrafico.pintar(self, tela)
        if self.imagem:
            if self.angulo == 0 and self.escala == 1.0:
                i = self.imagem
            else:
                i = pygame.transform.rotozoom(
                    self.imagem, self.angulo, self.escala)
            r = i.get_rect()
            r.center = (self.x, self.y)
            self._rect = r
            blit_alpha(tela, i, r.left, r.top, self.opacidade)

    def get_rect(self):
        return self._rect


class Texto(Sprite):
    def __init__(self, pai, nome, x, y, fonte, tamanho, cor, texto):
        Sprite.__init__(self, pai, nome, x, y)
        self.fonte = pygame.font.Font(fonte, tamanho)
        self.tamanho = tamanho
        self.cor = cor
        self.imagem = None
        self.definir_texto(texto)

    def definir_texto(self, texto):
        self.texto = texto
        self._atualizar_imagem()
        self.ao_definir_texto()

    def ao_definir_texto(self):
        pass

    def _atualizar_imagem(self):
        self.imagem = self.fonte.render(self.texto, True, self.cor).convert_alpha()


class Camada(ObjetoGrafico):
    pass


class Cena(ObjetoGrafico):
    def __init__(self, pai, nome, largura, altura):
        ObjetoGrafico.__init__(self, pai, nome)
        self.largura = largura
        self.altura = altura


class Jogo(ObjetoGrafico):
    jogo = None

    def __init__(self, nome, largura, altura, fps, titulo, flags=0):
        ObjetoGrafico.__init__(self, None, nome)
        pygame.init()
        self._tela = pygame.display.set_mode((largura, altura), flags)
        pygame.display.set_caption(titulo)
        self.largura = largura
        self.altura = altura
        self._fps = fps
        self._eventos = []
        Jogo.jogo = self
        self.ticks = 0
        self.ticks_0 = pygame.time.get_ticks()
        self.continuar = True
        self.pausa = False

    def update_ticks(self):
        dt = 0
        if not self.pausa:
            dt = (pygame.time.get_ticks() - self.ticks_0)
            self.ticks += dt
        self.ticks_0 = pygame.time.get_ticks()
        return dt

    def get_ticks(self):
        return self.ticks

    def executar(self):
        clock = pygame.time.Clock()
        while self.continuar:
            dt = self.update_ticks()
            for e in pygame.event.get():
                if e.type == pygame.QUIT:
                    self.continuar = False
                elif e.type == pygame.KEYDOWN:
                    if e.key == pygame.K_q:
                        if e.mod & pygame.KMOD_CTRL:
                            self.continuar = False
                self.propagar_processar_evento(e)
            while len(self._eventos) > 0:
                self.propagar_processar_evento(self._eventos.pop(0))
            self.propagar_atualizar(dt)
            self.propagar_pintar(self._tela)
            pygame.display.flip()
            self.propagar_remover_lixo()
            clock.tick(self._fps)
        pygame.quit()

    def enviar_evento(self, **kwargs):
        e = pygame.event.Event(pygame.USEREVENT, kwargs)
        self._eventos.append(e)


class GerenciadorDeSom(ObjetoGrafico):
    def __init__(self, pai, nome):
        ObjetoGrafico.__init__(self, pai, nome)
        self._sons = {}

    def processar_evento(self, e):
        ObjetoGrafico.processar_evento(self, e)
        if e.type == pygame.USEREVENT:
            if e.msg in self._sons.keys():
                som, parar = self._sons[e.msg]
                if parar:
                    som.stop()
                som.play()

    def adicionar_som(self, evento_msg, som, parar=False):
        self._sons[evento_msg] = (pygame.mixer.Sound(som), parar)


class Temporizador(object):
    def __init__(self, duracao):
        self.duracao = duracao
        self.reiniciar()

    def reiniciar(self):
        self.t0 = Jogo.jogo.get_ticks()

    def transcorrido(self):
        return Jogo.jogo.get_ticks() - self.t0

    def expirou(self):
        if self.transcorrido() > self.duracao:
            return True
        else:
            return False

#http://nerdparadise.com/tech/python/pygame/blitopacity/
def blit_alpha(tela, imagem, x, y, opacidade):
    if False:
        pygame.draw.rect(
            tela,
            pygame.Color(150, 0, 0),
            imagem.get_rect().move(x, y), 1)
        return
    if opacidade == 0:
        return
    if opacidade == 255:
        tela.blit(imagem, (x, y))
    else:
        temp = pygame.Surface(
            (imagem.get_width(), imagem.get_height())).convert()
        temp.blit(tela, (-x, -y))
        temp.blit(imagem, (0, 0))
        temp.set_alpha(opacidade)
        tela.blit(temp, (x, y))
