#include <stdio.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <festival.h>
#include <sstream>
#include <string>
#include <termios.h>
#include <fcntl.h>

#include <xalanc/Include/PlatformDefinitions.hpp>
#include <xercesc/util/PlatformUtils.hpp>
#include <xalanc/XalanTransformer/XalanTransformer.hpp>
#include <xalanc/XalanTransformer/XalanCAPI.h>

XALAN_USING_XERCES(XMLPlatformUtils)
XALAN_USING_XALAN(XalanTransformer)
XALAN_USING_XALAN(XSLTInputSource)
XALAN_USING_XALAN(XSLTResultTarget)

void ObtenerPagina(), InicializarControlador(), ModificarPagina(), TransformarPagina(), LeerPagina();
bool AntCaracteres(FILE*,string), ProxCaracteres(FILE*,string);
char BuscaControlador();

int heap_size = 210000;
int load_init_files = 1;

int main(int argc, char **argv)
{
    festival_initialize(load_init_files,heap_size);

    festival_eval_command("(voice_kal_diphone)");	//Carga la voz por defecto de Festival.

    InicializarControlador();	//Cargar el controlador de sonido adecuado

    ObtenerPagina();		//Descargar el RSS de slashdot.org

    ModificarPagina();		//Meter las etiquetas <text> para reconocer más fácilmente las noticias

    TransformarPagina();	//XML ---XSL--> SABLE

    LeerPagina();		//Reproducir el aschivo SABLE resultante
}

//------------------------------------------------
//Función para obtener el fichero RSS de slashdot.
//------------------------------------------------
void ObtenerPagina()
{
    remove("./Archiweb/slashdot");
    system("wget -i -D http://rss.slashdot.org/Slashdot/slashdot -o ./Archiaux/log.txt --directory-prefix=Archiweb");
}

//--------------------------------------------------------------------------
//Función que realiza las modificaciones necesarias en la página descargada.
//--------------------------------------------------------------------------
void ModificarPagina()
{
    FILE * forigen;
    FILE * fdestino;
    char caracter;
    bool abiertaText = false;	//indicará si se ha puesto una etiqueta de <text> y estamos en espera del cierre </text>

    forigen = fopen("./Archiweb/slashdot","r");

    if (forigen != NULL)
    {
	fdestino = fopen("./Archiaux/paginamod.xml","w");
	caracter = fgetc(forigen);
	do{
	    if (ProxCaracteres(forigen,"&amp;mdash; "))	//linea que elimina los &amp;mdash; que aparecen en las noticias
	    {
		fputc(' ',fdestino);
	    }
	    else if (ProxCaracteres(forigen,"&amp;mdash;"))	//y aqui lo mismo pero poniendo un espacio si no lo tenía
	    {
		fputc(' ',fdestino);
	    }

	    else if (!abiertaText)
	    {
		if (AntCaracteres(forigen,"<description>"))	//Cuando se encuentra una etiqueta <description> se coloca la etiqueta <text>
		{
		    fputs("<text>",fdestino);
		    abiertaText = true;
		}
		    fputc(caracter,fdestino);
	    }
	    else if (abiertaText)
	    {
		if (ProxCaracteres(forigen,"&lt;p&gt;"))	//y cuando se encuentra &lt;p&gt; 
		{
		    fputs("</text>&lt;p&gt;",fdestino);
		    abiertaText = false;
		}
		else if (ProxCaracteres(forigen,"</description>"))	//o </description>
		{
		    fputs("</text></description>",fdestino);		//se añade </text> antes (se cierra)
		    abiertaText = false;
		}
		else
		    fputc(caracter,fdestino);
	    }
	    caracter = fgetc(forigen);
	} while(caracter != EOF);	//hasta encontrar el fin de linea.

	fflush(fdestino);
	fclose(forigen);
	fclose(fdestino);
    }    
}

//----------------------------------------------------------------------
//Función que obtiene un archivo SABKE a partir del XML de las noticias.
//----------------------------------------------------------------------
void TransformarPagina()
{
    XMLPlatformUtils::Initialize();
    XalanTransformer::initialize();
    {
    XalanTransformer theXalanTransformer;
    const char* xmlIn = "./Archiaux/paginamod.xml";
    const char* xslIn = "XML2SABLE.xsl";
    const char* xmlOut = "./Archiaux/pagina.sable";
    int theResult = theXalanTransformer.transform(xmlIn,xslIn,xmlOut);
    }
    XalanTransformer::terminate();
    XMLPlatformUtils::Terminate();
    XalanTransformer::ICUCleanUp();
}

//--------------------------------------------------
//Función que procede a leer el fichero SABLE final.
//--------------------------------------------------
void LeerPagina()
{
    printf("Reading news...\n\n");
    fflush(stdout);
    festival_say_text("reading news");
    festival_say_file("./Archiaux/pagina.sable");
}

//-------------------------------------------------------------------------------------------------------------
//Función que comprueba si en las anteriores posiciones al puntero del fichero indicado se encuentra la cadena.
//-------------------------------------------------------------------------------------------------------------
bool AntCaracteres(FILE * fdesc, string laetiqueta)
{
    char proxcaracter;
    char *etiqueta = strdup(laetiqueta.c_str());
    int posCursorInicial = ftell(fdesc);
 
    fseek(fdesc,-(strlen(etiqueta)+1),SEEK_CUR);
    for (int i=0; i <= strlen(etiqueta)-1; i++)
    {
	proxcaracter = fgetc(fdesc);
	if (proxcaracter != etiqueta[i])
	{
	    fseek(fdesc,posCursorInicial,SEEK_SET);
	    return false;
	}
    }
    fseek(fdesc,posCursorInicial,SEEK_SET);
    return true;
}

//-------------------------------------------------------------------------------------------------------------
//Función que comprueba si en las siguientes posiciones al puntero del fichero indicado se encuentra la cadena.
//-------------------------------------------------------------------------------------------------------------
bool ProxCaracteres(FILE * fdesc, string laetiqueta)
{
    char proxcaracter;
    char *etiqueta = strdup(laetiqueta.c_str());
    int posCursorInicial = ftell(fdesc);
 
    fseek(fdesc,-1,SEEK_CUR);
    for (int i=0; i <= strlen(etiqueta)-1; i++)
    {
	proxcaracter = fgetc(fdesc);
	if (proxcaracter != etiqueta[i])
	{
	    fseek(fdesc,posCursorInicial,SEEK_SET);
	    return false;
	}
    }

    return true;
}

//-----------------------------------------------------------------------------------------------------------------
//Función que carga el controlador de sonido indicado en el fichero configaudio, que en caso de no existir lo crea.
//-----------------------------------------------------------------------------------------------------------------
void InicializarControlador()
{
    FILE * archiconfig;
    char codigodriver;

    archiconfig = fopen("configaudio","r");
    if (archiconfig == NULL)
    {
	codigodriver = BuscaControlador();
	sleep(1);
	if (codigodriver == '0')
	{
	    cout << "\nUnable to find a proper audio device!\nExiting aplication...\n\n";
	    exit(1);
	}
	archiconfig = fopen("configaudio","w");
	fputc(codigodriver,archiconfig);
	fclose(archiconfig);
    }
    else
    {
	codigodriver = fgetc(archiconfig);
	fclose(archiconfig);
	switch (codigodriver)
	{
	    case '1': festival_eval_command("(Parameter.set 'Audio_Command \"aplay -D plug:dmix -q -c 1 -t raw -f S16_BE -r $SR $FILE\")"); break;
	    case '2': festival_eval_command("(Parameter.set 'Audio_Command \"esdaudio $FILE\")"); break;
	    case '3': festival_eval_command("(Parameter.set 'Audio_Command \"sun16audio $FILE\")"); break;
	    case '4': festival_eval_command("(Parameter.set 'Audio_Command \"sunaudio $FILE\")"); break;
	    case '5': festival_eval_command("(Parameter.set 'Audio_Command \"freebsd16audio $FILE\")"); break;
	    case '6': festival_eval_command("(Parameter.set 'Audio_Command \"linux16audio $FILE\")"); break;
	}
	festival_eval_command("(Parameter.set 'Audio_Method 'Audio_Command)");
	festival_eval_command("(Parameter.set 'Audio_Required_Format 'snd)");	
    }
    system("clear");
}

//-------------------------------------------------------------------------------------------------------------
//Función que se encarga de ir probando los controladores uno a uno hasta que encuentra uno válido y lo guarda.
//-------------------------------------------------------------------------------------------------------------
char BuscaControlador()
{
    time_t Tantes, Tdespues;

    system("clear");
    cout << "First execution detected. Searching the proper audio device...";
    fflush(stdout);
    sleep(3);

    festival_eval_command("(Parameter.set 'Audio_Command \"aplay -D plug:dmix -q -c 1 -t raw -f S16_BE -r $SR $FILE\")");
    festival_eval_command("(Parameter.set 'Audio_Method 'Audio_Command)");
    festival_eval_command("(Parameter.set 'Audio_Required_Format 'snd)");
    time(&Tantes);
    festival_say_text("ALSA controller detected");
    system("clear");
    time(&Tdespues);
    if (difftime(Tdespues,Tantes) >= 2)
	return '1';

    festival_eval_command("(Parameter.set 'Audio_Method 'esdaudio)");
    festival_eval_command("(Parameter.set 'Audio_Required_Format 'snd)");
    time(&Tantes);
    festival_say_text("E S D controller detected");
    system("clear");
    time(&Tdespues);
    if (difftime(Tdespues,Tantes) >= 2)
	return '2';

    festival_eval_command("(Parameter.set 'Audio_Method 'sun16audio)");
    festival_eval_command("(Parameter.set 'Audio_Required_Format 'snd)");
    time(&Tantes);
    festival_say_text("sun audio 16 bits controller detected");
    system("clear");
    time(&Tdespues);
    if (difftime(Tdespues,Tantes) >= 2)
	return '3';

    festival_eval_command("(Parameter.set 'Audio_Method 'sunaudio)");
    festival_eval_command("(Parameter.set 'Audio_Required_Format 'snd)");
    time(&Tantes);
    festival_say_text("sun audio controller detected");
    system("clear");
    time(&Tdespues);
    if (difftime(Tdespues,Tantes) >= 2)
	return '4';

    festival_eval_command("(Parameter.set 'Audio_Method 'freebsd16audio)");
    festival_eval_command("(Parameter.set 'Audio_Required_Format 'snd)");
    time(&Tantes);
    festival_say_text("free B S D audio 16 bits controller detected");
    system("clear");
    time(&Tdespues);
    if (difftime(Tdespues,Tantes) >= 2)
	return '5';

    festival_eval_command("(Parameter.set 'Audio_Method 'linux16audio)");
    festival_eval_command("(Parameter.set 'Audio_Required_Format 'snd)");
    time(&Tantes);
    festival_say_text("Linux audio 16 bits controller detected");
    system("clear");
    time(&Tdespues);
    if (difftime(Tdespues,Tantes) >= 2)
	return '6';

    return '0';
}
