#!/usr/bin/env python
# encoding: iso-8859-15
#
#Minë. Mundo Interactivo-Narrativo en Español [Tierra-Media]
#Copyright (C) 2002  Pablo Ruiz Múzquiz
#
#
#This program is free software; you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation; either version 2 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#
#Fichero: cronomine.py

"""Módulo para manejar cronómetros basados en el calendario de Minë.

Contiene dos clases:
 - CronoMine
 - CronoMineControl
"""

import FechaHoraMine


class CronoMine(object):
    """Cronometro que puede ser programado para llamar a una función al cabo de cierto tiempo.

    No es necesario guardar un CronoMine en una variable local. Él mismo se guarda dentro
    de su CronoMineControl, y es automáticamente destruido cuando vence.
    """
    
    def __init__(self, control, vencimiento, vencimiento_func):
	"""Crea un cronometro en funcion de los datos ofrecidos."""
        
        self.__control = control
        self.__vencimiento_func = vencimiento_func
    
	# No guardamos el intervalo de tiempos, sino la fecha en la que la
	# cuenta termina.
        self.__fecha_vencimiento = FechaHoraMine.ahora() + vencimiento

        self.__control._CronoMineControl__lista_cronos.append(self)


    def tiempo_restante(self):
	"""Devuelve un FechaHoraMineRel con el tiempo que le queda hasta el vencimiento.
	
	Requiere:
	    Cronometro no está en pausa.
	"""
        return self.__fecha_vencimiento - FechaHoraMine.ahora()
                   
            
    def parar(self):
	"""Detiene la cuenta y destruye el crono."""
        self.__control._CronoMineControl__lista_cronos.remove(self)


    def pausar(self):
	"""Detiene temporalmente el crono, con posibilidad de reanudarlo después.

	Es necesario guardarlo en algún puntero mientras está detenido.

	Requiere:
	    Cronometro no está en pausa.
	"""
	self.__control._CronoMineControl__lista_cronos.remove(self)
	self.__tiempo_restante = self.__fecha_vencimiento - FechaHoraMine.ahora()
	del self.__fecha_vencimiento


    def reanudar(self):
	"""Prosigue la cuenta de tiempo, con el tiempo restante cuando se pausó.

	Requiere:
	    Cronometro está en pausa.
	"""
	self.__fecha_vencimiento = FechaHoraMine.ahora() + self.__tiempo_restante
	del self.__tiempo_restante
	self.__control._CronoMineControl__lista_cronos.append(self)


    def __avanzar(self, fecha_ahora):
	"""Para ser llamada sólo desde CronoMineControl.

	Compara la fecha actual con la de vencimiento, y si es igual o superior,
	llama a la función de vencimiento y destruye el crono.
        """
        if fecha_ahora >= self.__fecha_vencimiento:
            self.__vencimiento_func(self)
            self.__control._CronoMineControl__lista_cronos.remove(self)


    def __getstate__(self):
        """Método llamado para preguntar qué hay que salvar en un pickle."""
	atributos = self.__dict__.copy()

        # La funcion de callback no se puede salvar en un pickle, asi que
	# guardamos información suficiente para reconstruirla después.
	del atributos["_CronoMine__vencimiento_func"]
	if getattr(self.__vencimiento_func, "im_self", None) != None:
	    # Es un método de clase
	    atributos["_CronoMine__instancia_cb"] = self.__vencimiento_func.im_self
	    fname = self.__vencimiento_func.im_func.func_name
	    if fname[0:2] == "__":
		# Los metodos privados hay que renombrarlos a _Clase__nombre_metodo
		fname = "_" + self.__vencimiento_func.im_self.__class__.__name__ + fname
	    atributos["_CronoMine__funcion_cb"] = fname
	else:
	    # Es una función normal
	    atributos["_CronoMine__instancia_cb"] = None
	    atributos["_CronoMine__funcion_cb"] = self.__vencimiento_func.func_name
	del atributos["_CronoMine__control"] # PIFIA GORDA: ¿como restauramos esto ahora?
	return atributos


    def __setstate__(self, atributos):
        """Método llamado para restaurar los atributos provinientes de un pickle."""
	if atributos["_CronoMine__instancia_cb"] != None:
	    # Es un método de clase
	    instancia_cb = atributos["_CronoMine__instancia_cb"]
	    funcion_cb = atributos["_CronoMine__funcion_cb"]
	    func_cb = getattr(instancia_cb, funcion_cb)
	else:
	    # %%Komo kohone recupero una funcion normal global????
	    func_cb = atributos["_CronoMine__funcion_cb"]
	atributos["_CronoMine__vencimiento_func"] = func_cb
	del atributos["_CronoMine__instancia_cb"]
	del atributos["_CronoMine__funcion_cb"]
	self.__dict__ = atributos
	import Mundo
	self.__control = Mundo.cronoMine_control # CHAPUZA: funciona pero no es nada legal...
	if getattr(self, "_CronoMine__fecha_vencimiento", None) != None:
            self.__control._CronoMineControl__lista_cronos.append(self)


class CronoMineControl(object):
    """Controlador que contiene y avanza automáticamente a los cronómetros."""
    
    def __init__(self):
        self.__lista_cronos = []


    def run(self):
	"""Visitar cíclicamente los cronos, llamando a las funciones de vencimiento
	que correspondan.

	Esta función es llamada desde dentro de un bucle en el programa principal.
	"""
        fecha_ahora = FechaHoraMine.ahora()
        for crono in self.__lista_cronos:
            crono._CronoMine__avanzar(fecha_ahora)


def control():
    """Crea un controlador de cronos."""
    return CronoMineControl()
  

def crear(control, vencimiento, vencimiento_func):
    """Crea un cronometro que empieza a correr inmediatamente.

    Cuando el tiempo ha vencido, la función es llamada.
    
    control -- el CronoMineControl que controlará a este cronometro.
    vencimiento -- el intervalo de tiempo en forma de FechaHoraMineRel.
    vencimiento_func -- la función que será llamada al vencimiento. La función
			recibirá un parámetro: este crono.
    """
    return CronoMine(control, vencimiento, vencimiento_func)
       
        

# Código para pruebas unitarias del módulo.

if (__name__ == '__main__'):

    from random import random

    def venc_f(crono):
        print "vencido"
        nuevo_venc = FechaHoraMine.crear_rel(segundos = random() * 5 + 1)
        print "creado crono a " + "%0.2f" % nuevo_venc.segundos() + " segundos"
        #nuevo_venc = FechaHoraMine.crear_rel(horas = (random() * 5 + 1) / 20)
        t = crear(c_control, nuevo_venc, venc_f)
        print "creado crono con duracion " + str(t.tiempo_restante())
    
    c_control = control()
    venc = FechaHoraMine.crear_rel(segundos = random() * 5 + 1)
    print "creado crono a " + "%0.2f" % venc.segundos() + " segundos"
    #venc = FechaHoraMine.crear_rel(horas = (random() * 5 + 1) / 20)
    t = crear(c_control, venc, venc_f)
    print "creado crono con duracion " + str(t.tiempo_restante())
    
    while 1:
        c_control.run()
