#!/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: Opcion.py

"""Clase Opcion."""

import logging
from random import random
import os

from utils import *
import Guion
import FechaHoraMine
import CronoMine
import xmlutils

log = logging.getLogger("Modulos")

class Opcion(object):
    """Esta clase representa una opción de módulo, es decir, un guión con algunos atributos
    añadidos para definir cuándo y cómo se debe activar este guión y no otro:
    
    - Recuerda cuantas veces ha sido utilizado recientemente.
    - Tambien se ocupa de "olvidar" el uso del guion con el paso del tiempo.
    - Lee y almacena los "contextos" a los que pertenece el guion.

    La misión de esta clase es doble:

    a) "Parsear" una opción a partir de un nodo XML contenido en un módulo.
    b) Ejecutar el guión, recordando las veces que se ha ejecutado.
    """

    # Atributos públicos de la clase

    def __leer_contexto(self): return self.__contexto 

    contexto = property(__leer_contexto,
                  doc="""(string): permite asociar diferentes comportamientos, sirve para
		      saber si los interlocutores o usuarios de la entidad propietaria de este
		      módulo siguen una secuencia lógica de acciones o no.
                      """)

    def __leer_duracion_recuerdo(self): return self.__duracion_recuerdo 

    duracion_recuerdo = property(__leer_duracion_recuerdo,
                  doc="""(string): es el número de horas Minë que transcurren hasta que los
		      usos del guión se "olvidan" y se puede activar el guión de nuevo.
                      """)

    def __leer_repeticiones(self): return self.__repeticiones 

    repeticiones = property(__leer_repeticiones,
                  doc="""("<número>" | "siempre" | "nunca"): Un valor numérico indica la cantidad de
		      veces seguidas que se puede repetir el guión. Cuando se gastan todas, no
		      se puede activar de nuevo hasta que los usos se olvidan.

		      Si vale "siempre", siempre se puede activar el guión (no se gasta).

		      Si vale "nunca", el guión es de UN SÓLO USO, sólo se puede activar una
		      vez y ésta activación nunca se olvida.
                      """)
    
    def __leer_guion(self): return self.__guion 

    guion = property(__leer_guion,
                     doc="""(Guion): El guión correspondiente.
		         
		         Invariantes:
			   guion != None
			 """)


    # Constructor

    def __init__(self, contexto, duracion_recuerdo, repeticiones, guion):
        """Crea una nueva opción a partir de sus atributos.

        Requiere:
            Los parámetros satisfacen las invariantes de los atributos.
        """

	# Atributos privados:
	#
	# __repeticiones_iniciales: guarda el valor inicial de repeticiones, para restaurarlo
	#                           cuando los usos se olvidan.
	# __temporizador: un crono para marcar el momento en que se olvidarán los usos.
	# __cb_finalizar (funcion): función llamada al terminar el guión.

	self.__contexto = contexto
	self.__duracion_recuerdo = duracion_recuerdo
	self.__repeticiones = repeticiones
	self.__guion = guion
	self.__repeticiones_iniciales = repeticiones
	self.__temporizador = None
	self.__cb_finalizar = None


    # Métodos normales
    
    def agotada(self):
	"""Indica si se han consumido el máximo de repeticiones."""
	if self.__repeticiones == "siempre" or self.__repeticiones == "nunca":
	    return 0
     
	return (int(self.__repeticiones) == 0)

	
    def ejecutar(self, macros_valores, cb_finalizar = None):
	"""Activa el guion si le quedan usos disponibles, y lleva la cuenta del
	número de repeticiones.
	"""
	if self.__repeticiones != "siempre":
	    if self.__repeticiones == "nunca":
		self.__repeticiones = "0"
		# Al no activar el temporizador, nunca volvera a haber repeticiones disponibles.
	    elif self.__repeticiones > 0:
		self.__repeticiones = str(int(self.__repeticiones) - 1)
		self.__activar_temporizador()
	    else:
		return 0    # Repeticiones agotadas
	
	log.debug("Una repeticion gastada, quedan" + repr(self.__repeticiones))

	if cb_finalizar == None:
	    # Ejecución instantánea
	    return self.__guion.ejecutar(macros_valores)
	else:
	    self.__cb_finalizar = cb_finalizar
	    self.__guion.ejecutar(macros_valores, self.__fin_ejecucion)


    def cancelar(self):
	"""Cancela la ejecución. No se llama a la función de callback."""
	self.__guion.cancelar()
	self.__cb_finalizar = None


    # Métodos privados

    def __fin_ejecucion(self):
	"""Registra que ha terminado la ejecución y llama al callback."""
	if self.__cb_finalizar != None:
	    self.__cb_finalizar()
	self.__cb_finalizar = None

    
    def __activar_temporizador(self):
	"""Establece el tiempo necesario para "olvidar" los usos del guion"""
	import Mundo
	
	if self.__temporizador:
	    self.__temporizador.parar() 

	horas_olvido = FechaHoraMine.crear_rel( segundos = (random() + 0.5) * \
		                                           self.__duracion_recuerdo * \
		                                           FechaHoraMine.SEGUNDOS_POR_HORA )

	log.debug("Activado temporizador para dentro de %s horas Mine" % repr(horas_olvido))
	self.__temporizador = CronoMine.crear(Mundo.cronoMine_control, horas_olvido, 
					      self.__olvidar_repeticiones)

	
    def __olvidar_repeticiones(self, crono):
	if self.__guion.id == None:
	    log.debug("Olvidado guion inline")
	else:
	    log.debug("Olvidado guion %s" % self.__guion.id)

	self.__repeticiones = self.__repeticiones_iniciales
	self.__temporizador = None
    

# Funciones públicas

def nueva_xml(elem_opcion, macros_tipos, macros_extra):
    """Crea una opción a partir de un nodo XML de opción.

    La estructura del nodo es:

      ...
      <opcion contexto="xxx" duracion_recuerdo="xxx" repeticiones="xxx">
        ...guion...
      </opcion>
      ...

    Las macros serán pasadas tal cual al guión interno.

    Requiere:
      elem_opcion != None
      macros_tipos != None
      macros_extra != None
    """
    log.debug("Creando opción")
    contexto = xmlutils.atributo_def(elem_opcion, 'contexto', 'inicial')
    duracion_recuerdo = xmlutils.atributo_def(elem_opcion, 'duracion_recuerdo', 24)
    repeticiones = xmlutils.atributo_def(elem_opcion, 'repeticiones', 1)
    guion = Guion.nuevo_xml(elem_opcion, macros_tipos, macros_extra)

    opcion = Opcion(contexto, duracion_recuerdo, repeticiones, guion)
    log.debug("Opcion creada correctamente")

    return opcion


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

if (__name__ == '__main__'):
    print "No hay prueba unitaria de módulo. Prueba con objeto.py" 

