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

"""Clase Guion."""

from __future__ import generators

from xml.dom import minidom
from random import shuffle
import os

from utils import *
import Guion
import Requisito
import Accion
import FechaHoraMine 
import CronoMine


class GuionTemporizado(Guion):
    """Esta clase representa un guion con pausas entre la ejecucion de acciones.

    a) "Parsear" un guion a partir de una lista de nodos XML contenidos en un fichero de
       descripci?n de sala, pnj, objeto, etc., o de un fichero XML propio.

    b) Ejecutar el guion, llamando por orden a todos los requisitos y acciones de la secuencia.
    
    c) Llamar a todos los callbacks registrados al terminar la secuencia
    """

    ACCIONES_TEMPORIZADAS = [
            "hablar",
        "susurrar",
        "gritar",
        "actuar",
        "actuar_individual",
        "actuar_uno_mismo"
    ]

    # Atributos públicos de la clase

    def __leer_repeticiones(self): return self.__repeticiones

    repeticiones = property(__leer_repeticiones,
                          doc="""(string): numero de ejecuciones permitidas antes de agotarse
                          
                              Invariantes:
                                  repeticiones != None
                              """)
    
    # Constructor

    def __init__(self, elem_guion, macros_tipos, macros_extra = {}):
        """Parsea un nodo conteniendo una lista de elementos <accion>, <requisito> o <guion>.
        
        macros_tipos ((string, string)[]) es una lista con los nombres y tipos de las
        macros permitidos en el contexto de este guion.
        
        macros_extra (string:any) es un diccionario de macros que ser?n pasados
        al guion cuando se ejecute, adem?s de los que se pasen al m?todo ejecutar().
        
        Requiere:
            elem_guion != None
            macros_tipos != None
	    macros_extra != None
        """
        
        # Atributos privados:
        #
        # __fecha_fichero (int): fecha de la ultima modificacion del fichero de descripci?n.
        # __macros_extra (string:any): macros que se pasan a la ejecuci?n del guion, desde
        #                              el sitio en el fichero ha sido invocado.
        #
        #                 Invariantes:
        #                     __id == None -> __fecha_fichero = None
        #                     __id != None -> __fecha_fichero >= 0
        
        if extraer_nombre_xml(elem_guion) == 'desc_guion':
            
            # Estamos leyendo un fichero XML de descripcion de guion.
            self.__id = extraer_atributo_xml(elem_guion, 'id')  
            trazar("iniciando guion temporizado %s" % self.__id, "pnj")
                
            version_desc_guion = extraer_atributo_xml(elem_guion,'version')
            if version_desc_guion != Guion.VERSION_DESC_GUION:
                raise ValueError("Version incorrecta del fichero descriptor de guion " +
                         self.__id + ": " + version_desc_guion + " cuando se esperaba " +
                         Guion.VERSION_DESC_GUION)
                     
            self.__version = Guion.VERSION_GUION
            
            self.__autor = extraer_atributo_xml(elem_guion,'autor')
            
            if elem_guion.hasAttribute('comentario'):
                self.__comentario = extraer_atributo_xml(elem_guion,'comentario')
            else:
                self.__comentario = ""
            
        else:
        
            # Estamos leyendo un guion incrustado dentro de otro fichero XML
            self.__id = None
            self.__version = None
            self.__autor = None
            self.__comentario = None
            self.__secuencia = None
            self.__fecha_fichero = None
            trazar("guion temporizado incrustado %s" % self.__id, "pnj")
                
        if elem_guion.hasAttribute('repetir'):
            self.__repeticiones = extraer_atributo_xml(elem_guion,'repetir')
            if self.__repeticiones != "siempre" and self.__repeticiones != "nunca":
                self.__repeticiones = int(self.__repeticiones)
        else:
            self.__repeticiones= 1
        
        if elem_guion.hasAttribute('secuencia'):
            self.__tipo_secuencia = extraer_atributo_xml(elem_guion,'secuencia')
        else:
            self.__tipo_secuencia = "lineal" # Posibles valores: aleatoria, instantanea, lineal
                
        if elem_guion.hasAttribute('velocidad'):
            self.__velocidad = extraer_atributo_xml(elem_guion,'velocidad') # (<int>)
        else:
            self.__velocidad = 30 # Un retardo razonable entre acciones
        
        
        self.__macros_extra = macros_extra
        self.__secuencia = []
        self.__macros_valores = []
        self.__temporizador = None
        self.__sucesos = None
        self.__callbacks = []
        
        for elem in elem_guion.childNodes:
            if extraer_nombre_xml(elem) == "accion":
                acc = Accion.nueva_xml(elem, macros_tipos)
                self.__secuencia.append(Accion.nueva_xml(elem, macros_tipos))
            elif extraer_nombre_xml(elem) == "requisito":
                self.__secuencia.append(Requisito.nuevo_xml(elem, macros_tipos))
            elif extraer_nombre_xml(elem) == "guion":
                self.__parsear_fichero(elem, macros_tipos, macros_extra)


    # M?todos privados

    def __generador_sucesos(self):
        """Construye un iterador sobre la secuencia de acciones"""
        if self.__tipo_secuencia == "aleatoria":
            sucesos = shuffle(self.__secuencia[:])
            for instancia in sucesos:
                yield instancia
        else: # if self.__tipo_secuencia == "lineal" or == "instantanea":
            for instancia in self.__secuencia:
                yield instancia
        
        # Posibles valores: aleatoria, instantanea, lineal
        
    def __retardo(self, instancia):
        
        texto = instancia.leer_parametro("texto")
        personaje = instancia.leer_parametro("id","$interlocutor")
        idioma = instancia.leer_parametro("idioma", "oestron")
        trazar("el texto vale: %s" % texto, "pnj", 2)
        trazar("el id es: %s" % personaje, "pnj", 2)
        trazar("el idioma es: %s" % idioma, "pnj", 2)
        trazar("el personaje es: %s" % self.__macros_valores[personaje], "pnj", 2)
        if personaje and personaje[0] == "$":
            fluidez = 5 - self.__macros_valores[personaje].idiomas.get(idioma, 4)
        else:
            sala = self.__macros_valores["$sala"]
            fluidez = 5 - sala.personajes[personaje].idiomas.get(idioma, 4)
        
        return ((1.5 * len(texto) + self.__velocidad ) / float(self.__velocidad)) + fluidez
        
    def __siguiente_suceso(self):
        """Ejecuta una accion y establece el temporizador para la siguiente"""
        try:
            try:
                self.__temporizador = None
                instancia = self.__sucesos.next()
                if instancia.__class__.__name__ == "Accion":
                    #Ejecuta la accion actual
                    if instancia.nombre == "aceptar":
                        self.__resultado = 1
                    elif instancia.nombre == "cancelar":
                        self.__resultado = 0
                    else:
                        instancia.ejecutar(self.__macros_valores)
                        self.__resultado = 1

                    #Determina si es una accion instantanea o requiere temporizacion
                    if self.__tipo_secuencia != "instantanea" and \
                       instancia.nombre in GuionTemporizado.ACCIONES_TEMPORIZADAS:
                        from mundo import Mundo
                        hora_siguiente = FechaHoraMine.crear_rel(segundos=self.__retardo(instancia))
                        trazar("siguiente suceso dentro de %s horas Mine" % repr(hora_siguiente), "pnj", 2)
                        self.__temporizador = CronoMine.crear(Mundo.cronoMine_control,
				                              hora_siguiente,
							      lambda x: self.__siguiente_suceso())
                    else:
                        self.__siguiente_suceso()
                elif instancia.__class__.__name__ == "Requisito":
                    #Ejecucion condicional de acciones
                    r = instancia.ejecutar(self.__macros_valores)
                    if r != None:
                        self.__resultado = r                    
                    self.__siguiente_suceso()
                elif instancia.__class__.__name__ == "Guion":
                    #Ejecucion de un subguion
                    self.__resultado = instancia.ejecutar(self.__macros_valores)
                    self.__siguiente_suceso()
            except StopIteration:
                #Final de la secuencia: limpia el estado y llama a los callbacks registrados
                self.__sucesos = []
                self.__macros_valores = []
                for finalizar in self.__callbacks:
                    finalizar(self, cancelado=self.__resultado)
                self.__callbacks = []
        except:
             escribir("Error inesperado al temporizar la conversacion")
             escribir_excepcion()
    
    
    
    # M?todos normales
    
    def ejecutar(self, macros_valores, finalizar=[]):
        """Comienza a ejecutar el guion en cuestion.
        
        macros_valores({String:any}) es un diccionario de los nombres de las macros a su valor
        final.
        
        Devuelve el resultado final de "aceptar" (1) o "cancelar" (0), o bien None
        si no se ha especificado (la serie no contiene ninguna de las dos acciones).
            
            Almacena el callback para llamarlo al terminar la secuencia
        
        Requiere:
            macros_valores != None
        """
        trazar("ejecutando guion temporizado %s" % self.__id, "pnj")
        resultado = None
        
        macros_total = macros_valores
        macros_total.update(self.__macros_extra)
        
        if finalizar:
            self.registrar_callback(finalizar)
            
        self.__sucesos = self.__generador_sucesos()
        self.__resultado = 0
        self.__macros_valores = macros_total
        self.__siguiente_suceso()
        
        
    def registrar_callback(self, finalizar):
        self.__callbacks.append(finalizar)
        
    def cancelar_callback(self, finalizar):
        if finalizar in self.__callbacks:
            self.__callbacks.remove(finalizar)        

    def cancelar_secuencia(self):
        """Cancela la ejecucion actual y llama a los callbacks registrados"""
        if not self.__sucesos:
            return
        
        self.__sucesos = None
        try:
            self.__temporizador.parar() 
        except:
            escribir("Error inesperado al cancelar el guion %s" % self.id)
        self.__temporizador = None
        for finalizar in self.__callbacks:
            finalizar(self, cancelado=1)
        self.__callbacks = []
    
    # M?todos especiales
 

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

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