/*
    ZEsarUX  ZX Second-Emulator And Released for UniX
    Copyright (C) 2013 Cesar Hernandez Bano

    This file is part of ZEsarUX.

    ZEsarUX 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 3 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, see <http://www.gnu.org/licenses/>.

*/

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <dirent.h>


#if defined(__APPLE__)
        #include <sys/syslimits.h>
#endif

#include "cpu.h"
#include "snap_rzx.h"
#include "snap.h"
#include "debug.h"
#include "operaciones.h"
#include "utils.h"
#include "zxvision.h"
#include "screen.h"




z80_byte *rzx_file_mem=NULL;

int tamanyo_rzx=0;
int rzx_posicion_puntero=0;

int rzx_reproduciendo=0;

int rzx_elapsed_time=0;

int rzx_estimado_segundos;

//Princio del todo (o final de input recording) y hay que leer otro
int rzx_pendiente_leer_input_recording;

//Total frames del bloque de input recording
long long int rzx_frames_input_recording;

//Total frames leidos del bloque de input recording
long long int rzx_frames_input_recording_counter;

//Total lecturas i/o en el frame
z80_int rzx_in_reads_in_frame;

//Total lecturas realizadas en este frame
z80_int rzx_in_reads_in_frame_counter=0;

z80_byte rzx_get_byte(int posicion)
{
    if (posicion>=tamanyo_rzx) {
        debug_printf(VERBOSE_ERR,"Trying to read beyond rzx file. Asked: %d Size rzx: %d",posicion,tamanyo_rzx);
        eject_rzx_file();
        return 0;
    }

    return rzx_file_mem[posicion];
}

void eject_rzx_file(void)
{
    tamanyo_rzx=0;
    rzx_posicion_puntero=0;
    rzx_reproduciendo=0;
    rzx_delete_footer();
}

void rzx_create_temporary_file(char *extension, char *destino, int puntero, int longitud)
{
    sprintf (destino,"%s/tmp_rzxfile.%s",get_tmpdir_base(),extension);
    debug_printf (VERBOSE_INFO,"Creating temporary file %s",destino);

    FILE *ptr_destino;
    ptr_destino=fopen(destino,"wb");
    if (ptr_destino==NULL) {
        debug_printf (VERBOSE_ERR,"Error creating target file");
        return;
    }

    z80_byte byte_leido;
    for (;longitud;longitud--,puntero++) {
        byte_leido=rzx_get_byte(puntero);
        fwrite(&byte_leido,1,1,ptr_destino);
    }

    fclose(ptr_destino);

}


void rzx_create_temporary_gz_file(char *extension, char *nombre_base,char *destino, int puntero, int longitud)
{
    sprintf (destino,"%s/%s.%s.gz",get_tmpdir_base(),nombre_base,extension);
    debug_printf (VERBOSE_INFO,"Creating temporary file %s",destino);

    FILE *ptr_destino;
    ptr_destino=fopen(destino,"wb");
    if (ptr_destino==NULL) {
        debug_printf (VERBOSE_ERR,"Error creating target file");
        return;
    }

    //Escribir cabecera gz
    char gz_header[]="\x1f\x8b\x08\x00\x00\x00\x00\x00";
    fwrite(gz_header,1,8,ptr_destino);

    z80_byte byte_leido;
    for (;longitud;longitud--,puntero++) {
        byte_leido=rzx_get_byte(puntero);
        fwrite(&byte_leido,1,1,ptr_destino);
    }

    fclose(ptr_destino);

}

void rzx_load_z80_included_snapshot(char *s)
{
    //Llamamos a load_z80_snapshot en vez de snapshot_load para no alterar ultimo snapshot cargado
    load_z80_snapshot(s);
}

/*void load_rzx_snapshot(void)
{
  load_rzx_snapshot_file(snapfile);
}*/

void load_rzx_snapshot_file(char *archivo)
{

    eject_rzx_file();

    rzx_elapsed_time=0;
    rzx_estimado_segundos=0;

    tamanyo_rzx=get_file_size(archivo);

    if (tamanyo_rzx==0) {
        debug_printf(VERBOSE_ERR,"RZX file is empty");
        return;
    }

    //Asignar memoria. Desasignando antes si es necesario
    if (rzx_file_mem!=NULL) {
        debug_printf(VERBOSE_DEBUG,"Freeing old memory used to read rzx file");
        free(rzx_file_mem);
    }

    //Asignar
    rzx_file_mem=malloc(tamanyo_rzx);

    if (rzx_file_mem==NULL) cpu_panic("Error allocating memory to read RZX file");

    //Leer archivo

    FILE *ptr_rzxfile;
    ptr_rzxfile=fopen(archivo,"rb");

    if (!ptr_rzxfile) {
        debug_printf(VERBOSE_ERR,"Unable to open rzx file");
        eject_rzx_file();
        return;
    }

    int leidos=fread(rzx_file_mem,1,tamanyo_rzx,ptr_rzxfile);

    if (leidos!=tamanyo_rzx) {
        debug_printf(VERBOSE_ERR,"Error reading RZX file");
        eject_rzx_file();
    }

    debug_printf (VERBOSE_DEBUG,"Read %d bytes of RZX file",leidos);

    fclose(ptr_rzxfile);

    //Leer primero firma de archivo
    if (rzx_get_byte(0)!='R' ||
        rzx_get_byte(1)!='Z' ||
        rzx_get_byte(2)!='X' ||
        rzx_get_byte(3)!='!') {
            debug_printf(VERBOSE_ERR,"RZX header invalid");
            eject_rzx_file();
            return;
    }

    debug_printf (VERBOSE_INFO,"RZX file version %d.%d",rzx_get_byte(4),rzx_get_byte(5));
    //z80_byte rzx_flags=rzx_get_byte(6);

    rzx_posicion_puntero=10;

    //Gestionar tipos de ids
    z80_byte block_type;

    int final=0;
    z80_long_int aux_block_length;
    int i;

    while (!final) {
        block_type=rzx_get_byte(rzx_posicion_puntero);

        debug_printf (VERBOSE_DEBUG,"Block type %d",block_type);

        switch (block_type) {

            //Creator information block
            case 0x10:
                aux_block_length=rzx_get_byte(rzx_posicion_puntero+1) + 256*rzx_get_byte(rzx_posicion_puntero+2) +
                            65536*rzx_get_byte(rzx_posicion_puntero+3) + 16777216*rzx_get_byte(rzx_posicion_puntero+4);

                debug_printf (VERBOSE_DEBUG,"Creator information block. Length: %d",aux_block_length);
                char creator_name[21];

                for (i=0;i<20;i++) {
                    creator_name[i]=rzx_get_byte(rzx_posicion_puntero+5+i);
                    //printf("%d %d\n",i,creator_name[i]);
                }

                //Acabar la string con 0
                creator_name[i]=0;

                debug_printf (VERBOSE_INFO,"Creator : %s. Version: %d.%d",creator_name,rzx_get_byte(rzx_posicion_puntero+25),rzx_get_byte(rzx_posicion_puntero+26) );

                rzx_posicion_puntero+=aux_block_length;
            break;


            //Snapshot block
            case 0x30:
                aux_block_length=rzx_get_byte(rzx_posicion_puntero+1) + 256*rzx_get_byte(rzx_posicion_puntero+2) +
                            65536*rzx_get_byte(rzx_posicion_puntero+3) + 16777216*rzx_get_byte(rzx_posicion_puntero+4);

                debug_printf (VERBOSE_DEBUG,"Snapshot block. Length: %d",aux_block_length);

                z80_byte snapshot_flags=rzx_get_byte(rzx_posicion_puntero+5);

                debug_printf (VERBOSE_DEBUG,"First snapshot flags byte: %d",snapshot_flags);

                char snapshot_extension[5];

                for (i=0;i<4;i++) snapshot_extension[i]=rzx_get_byte(rzx_posicion_puntero+9+i);

                //Acabar la string con 0
                snapshot_extension[i]=0;

                debug_printf (VERBOSE_INFO,"Snapshot extension : %s",snapshot_extension );


                z80_long_int uncompressed_snapshot_length=rzx_get_byte(rzx_posicion_puntero+13) + 256*rzx_get_byte(rzx_posicion_puntero+14) +
                            65536*rzx_get_byte(rzx_posicion_puntero+15) + 16777216*rzx_get_byte(rzx_posicion_puntero+16);

                debug_printf (VERBOSE_DEBUG,"Uncompressed snapshot length: %d",uncompressed_snapshot_length);

                //Cargar snapshot a partir de rzx_posicion_puntero+0x11
                //De momento comprobar tipo snapshot
                if (!strcasecmp(snapshot_extension,"z80")) {
                    debug_printf (VERBOSE_INFO,"Loading z80 snapshot");
                    //Dado que actualmente la lectura de snap z80 lo hace mediante ruta a archivo, creamos archivo temporal
                    //con contenido del snapshot contenido en el archivo rzx y se lo enviamos
                    //Si esta comprimido

                    char nombre_final[PATH_MAX];

                    if (snapshot_flags&2) {

                        //Suponemos descompresion con GZ
                        rzx_create_temporary_gz_file("z80", "tmp_rzxfile", nombre_final, rzx_posicion_puntero+0x11, aux_block_length-17);

                        //Y descomprimimos ese archivo
                        char descomprimido[PATH_MAX];
                        char tmpdir[PATH_MAX];
                        sprintf (tmpdir,"%s",get_tmpdir_base());
                        sprintf (descomprimido,"%s/rzx_snapshot.z80",tmpdir);

                        if (uncompress_gz(nombre_final,descomprimido)) {
                            debug_printf (VERBOSE_ERR,"RZX: Error uncompressing snapshot");
                            eject_rzx_file();
                            final=1;
                        }



                        //sprintf (tmpdir,"%s/%s",get_tmpdir_base(),archivo);
                        rzx_load_z80_included_snapshot(descomprimido);
                    }

                    else {
                        rzx_create_temporary_file("z80", nombre_final, rzx_posicion_puntero+0x11, aux_block_length-17);
                        rzx_load_z80_included_snapshot(nombre_final);
                    }
                }




                else {
                    debug_printf (VERBOSE_ERR,"Unknown snapshot type %s",snapshot_extension);
                    eject_rzx_file();
                    final=1;
                }

                rzx_posicion_puntero+=aux_block_length;

            break;


            case 0x80:
                //Asumimos que a partir de aqui todo son frames 0x80. Reproducir juego
                final=1;
                rzx_pendiente_leer_input_recording=1;
                rzx_reproduciendo=1;
                rzx_in_reads_in_frame_counter=0;
            break;


            default:
                debug_printf (VERBOSE_ERR,"Unknown RZX block type %d",block_type);
                eject_rzx_file();
                final=1;
            break;
        }
    }



}


/*
Logica de operacion:

rzx_in_reads_in_frame_counter se pone a 0 al principio de cada frame, llamando a rzx_siguiente_frame_recording
Operacion de lectura de puerto, llama a rzx_lee_puerto
Si retorna 0, quiere decir que no devolvemos lectura puerto y se lee puerto de manera habitual
Si no retorna 0, retorna en valor_puerto valor de lectura puerto del frame.
Cuando el rzx_in_reads_in_frame_counter llega a rzx_in_reads_in_frame, devolvemos 0

Cuando hay cambio de frame, se salta a siguiente frame de recording.
Si siguiente frame de recording excede rzx_frames_input_recording, hay que leer nuevo bloque recording

Cada bloque recording puede estar comprimido. Al leerlo se guarda en un buffer de memoria aparte, que hay que asignar memoria


*/

z80_byte *rzx_io_block_mem=NULL;

//Memoria usada por el bloque de io
z80_long_int memory_to_assign_io_block;

int rzx_puntero_io_block=0;

void free_rzx_io_block_mem(void)
{
    if (rzx_io_block_mem!=NULL) {
        debug_printf(VERBOSE_DEBUG,"Freeing previous io block memory");
        free(rzx_io_block_mem);
    }
}

z80_byte rzx_get_io_block_byte(z80_long_int posicion)
{
    if (posicion>=memory_to_assign_io_block) {
        debug_printf(VERBOSE_ERR,"Trying to read beyond rzx file. Asked: %d Total memory: %d",posicion,memory_to_assign_io_block);
        eject_rzx_file();
        return 0;
    }

    return rzx_io_block_mem[posicion];
}



//Ultimo bloque con longitud diferente de 65535
int rzx_puntero_io_block_last=0;

//Longitud del frame del ultimo diferente de 65535
z80_int rzx_in_reads_in_frame_last;

//De donde lee la operacion de i/o
int rzx_puntero_io_block_lectura=0;


//Puntero al siguiente frame
int rzx_next_puntero_io_block=0;


//Estos dos realmente no los uso, aunque los leo y los incremento cuando toca
z80_int rzx_in_fetch_counter_til_next_int;
z80_int rzx_in_fetch_counter_til_next_int_counter;

void rzx_next_frame_recording(void)
{

    debug_printf(VERBOSE_DEBUG,"RZX: Begin rzx frame. Total frames in this input recording block: %d. Current frame: %d",rzx_frames_input_recording,rzx_frames_input_recording_counter);

    if (rzx_frames_input_recording_counter>=rzx_frames_input_recording) {
        debug_printf(VERBOSE_DEBUG,"End of frames in this input recording block. TODO: Be able to read next frame");
        eject_rzx_file();
        return;
    }

    rzx_puntero_io_block=rzx_next_puntero_io_block;


    rzx_in_fetch_counter_til_next_int=rzx_get_io_block_byte(rzx_puntero_io_block+0)+256*rzx_get_io_block_byte(rzx_puntero_io_block+1);

    debug_printf(VERBOSE_DEBUG,"RZX: Fetch counter til next interrupt: %d",rzx_in_fetch_counter_til_next_int);

    rzx_in_reads_in_frame=rzx_get_io_block_byte(rzx_puntero_io_block+2)+256*rzx_get_io_block_byte(rzx_puntero_io_block+3);

    rzx_puntero_io_block+=4;

    debug_printf(VERBOSE_DEBUG,"RZX: In reads in this frame: %d, pointer: %d",rzx_in_reads_in_frame,rzx_puntero_io_block);


    if (rzx_in_reads_in_frame==65535) {
        rzx_puntero_io_block_lectura=rzx_puntero_io_block_last;
        rzx_in_reads_in_frame=rzx_in_reads_in_frame_last;

        rzx_next_puntero_io_block=rzx_puntero_io_block;

        debug_printf(VERBOSE_DEBUG,"RZX: Repeated frame: In reads in this frame: %d, pointer: %d, read pointer: %d",rzx_in_reads_in_frame,rzx_puntero_io_block,rzx_puntero_io_block_lectura);
    }

    else {

        rzx_puntero_io_block_lectura=rzx_puntero_io_block;

        rzx_puntero_io_block_last=rzx_puntero_io_block;
        rzx_in_reads_in_frame_last=rzx_in_reads_in_frame;

        rzx_next_puntero_io_block=rzx_puntero_io_block+rzx_in_reads_in_frame;
    }

    rzx_in_reads_in_frame_counter=0;

    rzx_frames_input_recording_counter++;

    rzx_in_fetch_counter_til_next_int_counter=0;

}

z80_byte rzx_ultimo_puerto_retornado;

int rzx_lee_puerto(z80_byte *valor_puerto)
{
    /*
    //Principio del todo (o final de input recording) y hay que leer otro
    int rzx_pendiente_leer_input_recording;

    //Total frames del bloque de input recording
    long long int rzx_frames_input_recording;

    //Total frames leidos del bloque de input recording
    long long int rzx_frames_input_recording_counter;

    //Total lecturas i/o en el frame
    z80_int rzx_in_reads_in_frame;

    //Total lecturas realizadas en este frame
    z80_int rzx_in_reads_in_frame_counter=0;
    */


    if (rzx_pendiente_leer_input_recording) {
        //Leer input recording
        z80_byte block_type=rzx_get_byte(rzx_posicion_puntero);
        if (block_type!=0x80) {
        debug_printf(VERBOSE_ERR,"RZX: Can't support reading non 0x80 blocks when the first has appeared");
        eject_rzx_file();
        return 0;
        }


        z80_long_int compressed_iorecording_length=rzx_get_byte(rzx_posicion_puntero+1) + 256*rzx_get_byte(rzx_posicion_puntero+2) +
                    65536*rzx_get_byte(rzx_posicion_puntero+3) + 16777216*rzx_get_byte(rzx_posicion_puntero+4);

        rzx_frames_input_recording=rzx_get_byte(rzx_posicion_puntero+5) + 256*rzx_get_byte(rzx_posicion_puntero+6) +
                    65536*rzx_get_byte(rzx_posicion_puntero+7) + 16777216*rzx_get_byte(rzx_posicion_puntero+8);

    debug_printf(VERBOSE_DEBUG,"RZX: Total frames in this input recording block: %d",rzx_frames_input_recording);
    //sleep(3);


/*
  Este valor suele ser 0 o menor de 20. No tiene sentido usarlo
    z80_long_int rzx_t_estados_al_inicio=rzx_get_byte(rzx_posicion_puntero+0x0A) + 256*rzx_get_byte(rzx_posicion_puntero+0x0B) +
                  65536*rzx_get_byte(rzx_posicion_puntero+0x0C) + 16777216*rzx_get_byte(rzx_posicion_puntero+0x0D);

    printf ("t_estados al inicio: %d\n",rzx_t_estados_al_inicio);
    */

    z80_byte ioblock_flags=rzx_get_byte(rzx_posicion_puntero+0x0E);

    memory_to_assign_io_block=compressed_iorecording_length;

    //Si comprimido
    /*if (ioblock_flags&2) {
        //Cuanta memoria asignamos???
        memory_to_assign_io_block *=10;
    }*/

    //Si encriptado
    if (ioblock_flags&1) {
        debug_printf (VERBOSE_ERR,"RZX: Can not read encrypted block");
        eject_rzx_file();
        return 0;
    }

    //liberar bloque anterior
    free_rzx_io_block_mem();


    if (ioblock_flags&2) {
        //Leerlo comprimido
        char nombre_final[PATH_MAX];
        rzx_create_temporary_gz_file("raw", "tmp_rzxfile", nombre_final, rzx_posicion_puntero+0x12, compressed_iorecording_length-18);

        //Descomprimir

        char descomprimido[PATH_MAX];
        char tmpdir[PATH_MAX];
        sprintf (tmpdir,"%s",get_tmpdir_base());
        sprintf (descomprimido,"%s/uncompressed_rzx.raw",tmpdir);


        if (uncompress_gz(nombre_final,descomprimido)) {
        debug_printf (VERBOSE_ERR,"RZX: Error uncompressing data");
        eject_rzx_file();
        return 0;
        }

        //Leer archivo en memoria de rzx_io_block_mem
        memory_to_assign_io_block=get_file_size(descomprimido);

        rzx_io_block_mem=malloc(memory_to_assign_io_block);

        FILE *ptr_rzxfile;
        ptr_rzxfile=fopen(descomprimido,"rb");

        if (!ptr_rzxfile) {
            debug_printf(VERBOSE_ERR,"Unable to open rzx file");
            eject_rzx_file();
            return 0;
        }

        int leidos=fread(rzx_io_block_mem,1,memory_to_assign_io_block,ptr_rzxfile);


        debug_printf (VERBOSE_DEBUG,"RZX: Read %d bytes of RZX file",leidos);

        fclose(ptr_rzxfile);

    }

        else {
            debug_printf(VERBOSE_ERR,"RZX: Can not read uncompressed block");
            eject_rzx_file();
            return 0;
        }

        rzx_frames_input_recording_counter=0;
        rzx_puntero_io_block=0;
        rzx_next_puntero_io_block=0;

        rzx_next_frame_recording();


        rzx_pendiente_leer_input_recording=0;

    }


    debug_printf (VERBOSE_PARANOID,"RZX: Reading port. rzx_in_fetch_counter_til_next_int_counter: %d rzx_in_fetch_counter_til_next_int: %d",rzx_in_fetch_counter_til_next_int_counter,rzx_in_fetch_counter_til_next_int);

    if (rzx_in_reads_in_frame_counter>=rzx_in_reads_in_frame) {
        debug_printf (VERBOSE_PARANOID,"RZX: Reading port beyond frame. rzx_in_reads_in_frame_counter: %d rzx_in_reads_in_frame: %d",rzx_in_reads_in_frame_counter,rzx_in_reads_in_frame);

        //No hay suficiente info en este frame. Ir al siguiente frame
        while (rzx_in_reads_in_frame_counter>=rzx_in_reads_in_frame && rzx_reproduciendo) {
            //usleep(100000); //0.1 segundo
            rzx_next_frame_recording();
        }

        if (!rzx_reproduciendo) {
            return 0;
        }

        //*valor_puerto=0xFF;
        //return 1;


        //return rzx_ultimo_puerto_retornado;
    }

    z80_byte rzx_ultimo_puerto_retornado=rzx_get_io_block_byte(rzx_puntero_io_block_lectura++);

    debug_printf (VERBOSE_PARANOID,"RZX: Returning %02XH to port read",rzx_ultimo_puerto_retornado);

    //if (rzx_in_reads_in_frame!=65535) rzx_puntero_io_block++;



    *valor_puerto=rzx_ultimo_puerto_retornado;

    rzx_in_reads_in_frame_counter++;


    //Meter byte de i/o
    return 1;

}


void rzx_delete_footer(void)
{
                           //01234567890123456789012345678901
  menu_putstring_footer(0,2,"                                ",WINDOW_FOOTER_INK,WINDOW_FOOTER_PAPER);
  menu_footer_bottom_line();
}

void rzx_retorna_minutos_segundos(int segundos, int *m, int *s)
{
  *s=segundos%60;

  int min=segundos/60;

  if (min>99) min=99;

  *m=min;
}



void rzx_print_footer(void)
{
    if (rzx_reproduciendo) {

        //debug_printf(VERBOSE_DEBUG,"RZX: Begin rzx frame. Total frames in this input recording block: %d. Current frame: %d",rzx_frames_input_recording,rzx_frames_input_recording_counter);

        int total;

        if (rzx_frames_input_recording==0) return;

        //if (rzx_frames_input_recording==0) total=100;
        //else

        total=(rzx_frames_input_recording_counter*100)/rzx_frames_input_recording;

        if (total>100) total=100;

        int elapsed_min,elapsed_sec;
        rzx_retorna_minutos_segundos(rzx_elapsed_time,&elapsed_min,&elapsed_sec);

        //Calculo aproximado del total de segundos
        //Sabemos total frames, frame actual, segundos actuales
        //Teniendo un total de porcentaje de 1,
        //Si ha transcurrido un 0.1 en 5 segundos,tiempo total=5*10=5*(1/0.1)=elapsed*(1/porcentaje transcurrido)
        //porcentaje transcurrido=(frame_actual/total frames)
        //tiempo total=elapsed*(1/(frame_actual/total frames))=(elapsed*total frames)/frame actual
        //controlar division por 0 para frame actual=0


        //Recalcular tiempo estimado solo cada 10 segundos
        if ((rzx_elapsed_time%10)==0) {
            if (rzx_frames_input_recording_counter==0) rzx_estimado_segundos=60*99; //el maximo
            else {
                rzx_estimado_segundos=(rzx_elapsed_time*rzx_frames_input_recording)/rzx_frames_input_recording_counter;
            }
        }

        int est_sec,est_min;

        rzx_retorna_minutos_segundos(rzx_estimado_segundos,&est_min,&est_sec);

        char buffer_tiempo_estimado[6];
        //Escribir mm:ss pero el : parpadea

        //Hasta que no hayan pasado unos 10 segundos, no mostrar tiempo estimado
        if (rzx_elapsed_time>=10) sprintf (buffer_tiempo_estimado,"%02d:%02d",est_min,est_sec);
        else sprintf (buffer_tiempo_estimado,"%s","UNK");

        char buffer_texto[33];

        //Escribir mm:ss pero el : parpadea
        sprintf (buffer_texto,"RZX Playing: %02d%c%02d/%s (%d%%)",elapsed_min,(elapsed_sec&1 ? ':' : ' '),elapsed_sec,
                buffer_tiempo_estimado,total);
        //color inverso
        menu_putstring_footer(0,2,buffer_texto,WINDOW_FOOTER_PAPER,WINDOW_FOOTER_INK);

        rzx_elapsed_time++;
    }
}
