/*
    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 <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>


#include "tape.h"
#include "tape_smp.h"
#include "cpu.h"
#include "operaciones.h"
#include "debug.h"
#include "zx8081.h"
#include "snap.h"
#include "screen.h"
#include "zxvision.h"
#include "snap_zx8081.h"
#include "utils.h"
#include "settings.h"
#include "atomic.h"

FILE *ptr_mycinta_smp;

int lee_smp_ya_convertido;

//puntero de lectura de archivo generado en memoria
int spec_smp_read_index_tap;


//puntero de escritura de archivo generado en memoria
int spec_smp_write_index_tap;

//total de bytes de archivo generado en memoria
int spec_smp_total_read;

//donde se guarda el archivo generado en memoria
z80_byte *spec_smp_memory;

//int main_spec_smpatap(void);


//solo para decir que color de texto en oscuro o no
//int tape_guessing_parameters=0;

#define MAX_BYTES_READ (1024*1024)


//Archivo temporal
char inputfile_name_rwa[PATH_MAX];


//si archivo es rwa. lo abre tal cual
//si es smp u otros, lo convierte
int tape_smp_open_input_file(void)
{

    //Inicializar siempre esto. Si no se confunde si ha habido una conversion anterior
    ptr_mycinta_smp=NULL;

    int longitud_archivo_smp;

    if (!util_compare_file_extension(tapefile,"smp")) {
        if (lee_smp_ya_convertido==0) {
            convert_to_rwa_common_tmp(tapefile,inputfile_name_rwa);
            convert_smp_to_rwa(tapefile,inputfile_name_rwa);
        }

        ptr_mycinta_smp=fopen(inputfile_name_rwa,"rb");

        longitud_archivo_smp=get_file_size(inputfile_name_rwa);
        //printf ("convertido a rwa : %s\n",inputfile_name_rwa);
    }

    else if (!util_compare_file_extension(tapefile,"wav")) {
        if (lee_smp_ya_convertido==0) {
            convert_to_rwa_common_tmp(tapefile,inputfile_name_rwa);
            if (convert_wav_to_rwa(tapefile,inputfile_name_rwa)) {
                debug_printf (VERBOSE_ERR,"Error converting wav to rwa");
                return 0;
            }
        }
        ptr_mycinta_smp=fopen(inputfile_name_rwa,"rb");

        longitud_archivo_smp=get_file_size(inputfile_name_rwa);
    }


    else {
        //printf("cinta no es smp ni wav\n");
        ptr_mycinta_smp=fopen(tapefile,"rb");
        longitud_archivo_smp=get_file_size(tapefile);
    }


    lee_smp_ya_convertido=1;

    return longitud_archivo_smp;


}

//funcion para escribir un byte en memoria, comprobando si se sale de limite
//retorna 0 si ok
//1 si error
int spec_smp_write_mem_byte(int index,z80_byte valor)
{
    if (index>=MAX_BYTES_READ) {
        return 1;
    }

    spec_smp_memory[index]=valor;
    return 0;
}


int tape_block_smp_open(void)
{



    if (!(MACHINE_IS_SPECTRUM)) return 0;


    else {
        //avisar que no se ha abierto aun el archivo. Esto se hace porque en la rutina de Autodetectar,
        //cada vez se abre el archivo de nuevo, y evitar que se tenga que convertir (por ejemplo de wav) una y otra vez
        lee_smp_ya_convertido=0;

        int longitud_archivo_smp=tape_smp_open_input_file();

        if (!ptr_mycinta_smp)
        {
            debug_printf(VERBOSE_ERR,"Unable to open smp input file %s",tapefile);
            tapefile=0;
            return 1;
        }

        //Nota: esta rutina puede tardar un poco si el archivo de audio es muy grande
        main_spec_rwaatap(NULL,0,NULL,longitud_archivo_smp);


        return 0;
    }
}


int tape_block_smp_read(void *dir,int longitud)
{

    if (spec_smp_read_index_tap>=spec_smp_total_read) {
        debug_printf(VERBOSE_INFO,"End of file");
        return 0;
    }


    memcpy(dir,&spec_smp_memory[spec_smp_read_index_tap],longitud);

    spec_smp_read_index_tap +=longitud;

    return longitud;



}

int tape_block_smp_readlength(void)
{
    z80_byte buffer[2];
    if (spec_smp_read_index_tap>=spec_smp_total_read) {
        debug_printf(VERBOSE_INFO,"End of file");
        return 0;
    }

    memcpy(buffer,&spec_smp_memory[spec_smp_read_index_tap],2);
    spec_smp_read_index_tap +=2;

    //printf ("tape_block_smp_readlength: %d\n",value_8_to_16(buffer[1],buffer[0]));

    return value_8_to_16(buffer[1],buffer[0]);

}


int tape_block_smp_seek(int longitud,int direccion)
{

    switch (direccion) {
        case SEEK_CUR:
            spec_smp_read_index_tap +=longitud;
            break;

        default:
            debug_printf (VERBOSE_ERR,"tape_block_smp_seek. whence invalid : %d",direccion);
            return -1;
            break;
    }

    return 0;

}




//Cargar en RAM datos obtenidos del audio de SMP
void snap_load_zx80_zx81_load_smp(void)
{
    if (main_leezx81(NULL,NULL,1)==0) {
        debug_printf (VERBOSE_ERR,"Error: Program length is zero");
    }
}



//
//Rutina de conversion de audio de sonido de carga de spectrum a binario
//


/*
 * Ficheros .P del emulador XTENDER:
 * Contienen los bytes tal cual fueron grabados en el ZX81
 * exceptuando los bytes del nombre, que vienen al principio, y
 * el byte del nombre final tiene el bit 7 alzado
 */


unsigned int zx8081_fic_leido;

int zx8081_sensibilidad_cambio,zx8081_longitud_cambio;


char *fichero,*fichero_p;

//SMP 11111 hz
//char zx8081_ceros=18,zx8081_unos=33;

//RWA 15600 hz
char zx8081_ceros=25,zx8081_unos=46;

unsigned char zx8081_byte_cambio;
char zx8081_final_fichero=0;

int zx8081_debugonda=0;




int zx8081_da_abs(int valor)
{
    if (valor>=0) return valor;
    else return -valor;
}

int zx8081_lee_byte(void)
//Funcion que lee byte del fichero
//Pone zx8081_final_fichero a 1 si se llega al final del fichero
{

    zx8081_byte_cambio=fgetc(ptr_mycinta_smp);
    zx8081_fic_leido++;
    //printf (" %d\n",zx8081_fic_leido);

    if (feof(ptr_mycinta_smp)) {
        zx8081_final_fichero=1;
        return 0;
    }

    return zx8081_byte_cambio;
}

int zx8081_lee_onda(unsigned char *longitud)
//Funcion que lee una onda completa de sonido
//Devuelve -1 si se llega al final del fichero
//Se lee a 11111hz, 8 bit, mono, unsigned
{


    //int debug_leidos=0;
    unsigned char veces;
    int byte,byte_ant;

    //Primero posicionarse en una onda de sonido
    //Ver si la onda cambia bruscamente (mas de zx8081_sensibilidad_cambio) en mas de zx8081_longitud_cambio bytes

    //printf ("Sensibilidad cambio: %d\n",zx8081_sensibilidad_cambio);


    byte_ant=zx8081_lee_byte();

    veces=0;

    do {
        if (zx8081_debugonda) printf ("S");
        if (zx8081_final_fichero) {
            debug_printf (VERBOSE_DEBUG,"End audio input file waiting audio value high change");
            //printf ("longitud esperando cambio: %d leidos: %d\n",*longitud,debug_leidos);
            return -1;
        }
        byte=zx8081_lee_byte();
        //debug_leidos++;

        //Parche para soportar conversiones no muy buenas de smp a rwa, en que se repite el ultimo byte de vez en cuando
        //Desactivado Parche, pues entonces lo que sucede es que con archivos rwa
        //generados mediante save del emulador en zx81, los silencios entre bits tienen mismo valor, y se interpretarian
        //como parte de este parche
        //if (byte==byte_ant) {
        //}

        //else {
        int diferencia;
        diferencia=zx8081_da_abs(byte-byte_ant);
        //printf ("esperando cambio brusco: byte antes: %d byte despues: %d diferencia: %d sensibilidad cambio: %d veces: %d zx8081_longitud_cambio: %d\n",byte_ant,byte,diferencia,zx8081_sensibilidad_cambio,veces,zx8081_longitud_cambio);

        if (diferencia>=zx8081_sensibilidad_cambio) veces++;
        else {
            //en vez de invalidar esto (veces=0) decir que "la ultima" no cuenta
            if (veces) veces--;
        }

        //}


        byte_ant=byte;
    } while (veces<zx8081_longitud_cambio);



    *longitud=veces+1;
    //printf ("longitud despues de esperar cambio: %d leidos: %d\n",*longitud,debug_leidos);
    //debug_leidos=0;

    //A partir de ahora leer la longitud hasta que el cambio no sea brusco

    //valor zx8081_longitud_cambio depende de frecuencia muestreo
    //valor zx8081_sensibilidad_cambio depende del volumen y/o bits (8 o 16)

    //Se tiene byte
    veces=0;

    byte_ant=byte;
    do {
        if (zx8081_debugonda) printf ("O");
        byte=zx8081_lee_byte();
        if (zx8081_final_fichero) {
            debug_printf (VERBOSE_DEBUG,"End file reading data. Length: %d",*longitud);
            //printf ("fin de archivo. longitud: %d leidos: %d\n",*longitud,debug_leidos);

            return -1;
        }

        //Parche para soportar conversiones no muy buenas de smp a rwa, en que se repite el ultimo byte de vez en cuando
        //Desactivado Parche, pues entonces lo que sucede es que con archivos rwa
        //generados mediante save del emulador en zx81, los silencios entre bits tienen mismo valor, y se interpretarian
        //como parte de este parche
        //if (byte==byte_ant) {
        //	(*longitud)--;
        //}

        //else {
        int diferencia;
        diferencia=zx8081_da_abs(byte-byte_ant);
        //printf ("esperando cambio no brusco byte antes: %d byte despues: %d diferencia: %d sensibilidad cambio: %d veces: %d zx8081_longitud_cambio: %d\n",byte_ant,byte,diferencia,zx8081_sensibilidad_cambio,veces,zx8081_longitud_cambio);
        if (diferencia<zx8081_sensibilidad_cambio) {
            veces++;
            if (veces>=zx8081_longitud_cambio) break;
        }
        else {
            //en vez de invalidar esto (veces=0) decir que "la ultima" no cuenta
            if (veces) veces--;
        }

        //}

        (*longitud)++;
        byte_ant=byte;
        //debug_leidos++;
    } while (1);


    //printf ("longitud despues de cambio no brusco: %d leidos: %d\n",*longitud,debug_leidos);

    return 0;

}

int zx8081_dice_margen(int n,int valor,int izq,int der)
//Funcion que dice si el valor n esta entre [valor-izq,valor+der]
{

    return (n>=valor-izq && n<=valor+der);

}

int zx8081_dice_bit(char numero)
//Dice si el bit es 0 o 1 segun su numero de ondas
//Devuelve -1 si no es un bit aceptado
{
    if (zx8081_dice_margen(numero,zx8081_ceros,12,12)) {
        if (zx8081_debugonda) printf ("0");
        return 0;
    }
    if (zx8081_dice_margen(numero,zx8081_unos,12,12)) {
        if (zx8081_debugonda) printf ("1");
        return 1;
    }
    unsigned int n=(unsigned int)numero;
    debug_printf (VERBOSE_DEBUG,"Value %d for a bit length not accepted",n);
    return -1;

}

int zx8081_lee_1_bit(void)
//Funcion que lee 1 bit
{

    unsigned char longitud;

    if (zx8081_lee_onda(&longitud)==-1) return -1;

    return zx8081_dice_bit(longitud);
}

int zx8081_lee_8_bits(void)
//Devuelve 8 bits leidos
//Devuelve -1 si se llega al final de los datos
{
    char bit;
    int n,byte=0;

    for (n=0;n<8;n++) {
        bit=zx8081_lee_1_bit();
        //printf ("bit: %d ",bit);
        if (bit==-1) return -1;
        byte=byte*2+bit;
    }
    return byte;
}

int zx8081_escribe_nombre(unsigned char *m,int leidos)
//Funcion que escribe el nombre del fichero y retorna la longitud del nombre
{
    unsigned char n;
    int l=0;
    z80_bit inverse;

    do {
        if (!leidos) break;
        n=*m++;
        leidos--;
        l++;
        putchar(da_codigo81(n,&inverse));
    } while (n<128);


    return l;

}

int zx8081_escribe_nombre_to_string(unsigned char *m,unsigned char *s,int leidos)
//Funcion que escribe el nombre del fichero en una string y retorna la longitud del nombre
{
    unsigned char n;
    int l=0;
    z80_bit inverse;
    z80_byte caracter;

    do {
        if (!leidos) break;
        n=*m++;
        leidos--;
        l++;
        if (l>255) {
            debug_printf (VERBOSE_INFO,"Error. Name is bigger than 255 bytes");
            return l;
        }

        caracter=da_codigo81(n,&inverse);
        *s++=caracter;
        //putchar(da_codigo81(n,&inverse));
    } while (n<128);

    *s=0;
    return l;

}



int zx8081_lee_todos_bytes(unsigned char *m)
{




    tape_smp_open_input_file();

    if (!ptr_mycinta_smp)
    {
        debug_printf(VERBOSE_ERR,"Unable to open smp input file (on zx8081_lee_todos_bytes) %s",tapefile);
        tapefile=0;
        return -1;
    }



    zx8081_fic_leido=0;
    zx8081_final_fichero=0;


    int retorno;
    int bytes_leidos=0;
    unsigned char byte_leido;

    do {
        retorno=zx8081_lee_8_bits(/*-1*/);
        if (retorno==-1) break;

        byte_leido=retorno;

        *m++=byte_leido;
        bytes_leidos++;
    } while (1);


    fclose(ptr_mycinta_smp);

    return bytes_leidos;

}

z_atomic_semaphore main_leezx81_semaphore;

void main_leezx81_init_semaphore(void)
{
    //printf("Init semaforo\n");
    z_atomic_reset(&main_leezx81_semaphore);
}


//temporal
//int temp_bloqueo=0;

/*
No permitir dos ejecuciones a la vez
Ejemplo:
maquina zx81
Ventana visual real tape visible
Insertar real tape orquesta_zx81.rwa
y rapidamente abrir tape viewer
eso si se hace rapido con visual real tape abierto y antes de que visual real tape haya mostrado datos del programa
*/
int main_leezx81_inicio_bloqueo(void)
{

    //En este caso gestiono el bloqueo diferente.
    //Si esta bloqueado, retorno aviso al usuario, no me quedo esperando a liberar el bloqueo

    if (z_atomic_test_and_set(&main_leezx81_semaphore)) {
        debug_printf(VERBOSE_ERR,"Another conversion from audio tape to ZX81 data is already running (inserted Real Tape and/or open Tape Viewer). "
                                "Please try it again some seconds later");
        //printf("Another conversion from audio tape to ZX81 data is already running\n");
        return 1;
    }

    /*while(z_atomic_test_and_set(&main_leezx81_semaphore)) {
        //Pausa de 0.05 segundo
        usleep(50000);
        printf("Esperando a liberar lock en zrcp_handle_new_connection\n");
    }*/

    //printf("activar bloqueo\n");

    //temp_bloqueo=1;
    return 0;
}

void main_leezx81_liberar_bloqueo(void)
{
    //temp_bloqueo=0;
    //printf("liberar bloqueo\n");

    //Liberar lock
    z_atomic_reset(&main_leezx81_semaphore);
}


//Si archivo_destino==NULL, lo carga en memoria de la maquina
//Si no, escribe archivo en cinta
//Quien llama debe indicar si quiere extension P u O, aunque el contenido final es el mismo, solo cambia la extension
//texto_info_output es para obtener la descripcion de la cinta, NULL si no se obtiene
//si_load: si es 0, indica que no hay que cargar nada en la memoria del zx80/81
//retorna bytes leidos
int main_leezx81(char *archivo_destino, char *texto_info_output,int si_load)
//int main_leezx81(int argc,char *argv[])
{

    if (main_leezx81_inicio_bloqueo()) return 0;

    int bytes_leidos;
    int auto_parametros=0;

    unsigned char *buffer_memoria;
    unsigned char *buffer_memoria_orig;


    zx8081_sensibilidad_cambio=3;

    //11111 hz
    //zx8081_longitud_cambio=3;

    //15600 hz
    //zx8081_longitud_cambio=4;
    //temp
    zx8081_longitud_cambio=3;




    auto_parametros=1;


    debug_printf (VERBOSE_INFO,"Reading SMP audio data and converting to ZX80/ZX81 file in memory");

    debug_printf (VERBOSE_INFO,"Routine based on original program LEEZX81 V1.1. (c) Cesar Hernandez Bano (10/09/1998), (02/09/2013)");


    //Mensaje de aviso que se esta procesando

    //borrar texto             01234567890123456789012345678901
    menu_putstring_footer(0,2,"                                ",WINDOW_FOOTER_INK,WINDOW_FOOTER_PAPER);

    //color inverso
    menu_putstring_footer(0,2,"Guessing Loading Parameters...",WINDOW_FOOTER_PAPER,WINDOW_FOOTER_INK);



    //si no hay este cpuloop, no se refresca la pantalla en xwindows
    int conta;
    if (archivo_destino==NULL && si_load) {
        for (conta=0;conta<20000;conta++) {
            new_snap_load_zx8081_simulate_cpuloop();
        }
        scr_refresca_pantalla();
    }



    //Asignar memoria
    if ((buffer_memoria=(unsigned char *)malloc(65536L))==NULL) {
        cpu_panic ("Error allocating memory when reading smp file");
    }

    buffer_memoria_orig=buffer_memoria;


    debug_printf (VERBOSE_DEBUG,"Reading smp audio data...");


    //avisar que no se ha abierto aun el archivo. Esto se hace porque en la rutina de Autodetectar,
    //cada vez se abre el archivo de nuevo, y evitar que se tenga que convertir (por ejemplo de wav) una y otra vez
    lee_smp_ya_convertido=0;

    if (auto_parametros==0) {
        bytes_leidos=zx8081_lee_todos_bytes(buffer_memoria);
        if (bytes_leidos==-1) {
            main_leezx81_liberar_bloqueo();
            //Error
            return 0;
        }
    }


    else {
        int i;
        int mejor_zx8081_sensibilidad_cambio=2;
        int mejor_bytes_leidos=0;
        int mejor_zx8081_fic_leido=0;

        zx8081_sensibilidad_cambio=2;

        debug_printf (VERBOSE_INFO,"Autodetecting best loading parameters...");

        //30 diferentes valores de zx8081_sensibilidad_cambio
        for (i=0;i<30;i++) {
            debug_printf (VERBOSE_DEBUG,"Testing with Threshold of wave change: %d",zx8081_sensibilidad_cambio);
            bytes_leidos=zx8081_lee_todos_bytes(buffer_memoria);

            if (bytes_leidos==-1) {
                main_leezx81_liberar_bloqueo();
                //Error
                return 0;
            }

            debug_printf (VERBOSE_DEBUG,"Bytes read: %d",bytes_leidos);
            if (bytes_leidos>mejor_bytes_leidos) {
                mejor_bytes_leidos=bytes_leidos;
                mejor_zx8081_sensibilidad_cambio=zx8081_sensibilidad_cambio;
                mejor_zx8081_fic_leido=zx8081_fic_leido;
            }

            zx8081_sensibilidad_cambio++;
        }

        debug_printf (VERBOSE_DEBUG,"Best Threshold of wave change: %d Bytes read: %d Sound Bytes read: %d",mejor_zx8081_sensibilidad_cambio,mejor_bytes_leidos,mejor_zx8081_fic_leido);

        //Relanzamos lectura con el mejor parametro de sensibilidad
        zx8081_sensibilidad_cambio=mejor_zx8081_sensibilidad_cambio;
        //printf ("sensi: %d\n",zx8081_sensibilidad_cambio);
        bytes_leidos=zx8081_lee_todos_bytes(buffer_memoria);
        debug_printf (VERBOSE_DEBUG,"Bytes read: %d",bytes_leidos);


    }



    //borrar texto             01234567890123456789012345678901
    menu_putstring_footer(0,2,"                                ",WINDOW_FOOTER_INK,WINDOW_FOOTER_PAPER);

    menu_footer_bottom_line();



    if (bytes_leidos) {


        if (verbose_level>=VERBOSE_DEBUG) {
            //mostrar por consola
            int i;
            z80_bit inverse;

            printf ("Data loaded:\n");

            for (i=0;i<bytes_leidos;i++) printf ("%c",da_codigo81(buffer_memoria[i],&inverse));

            printf ("\n");

        }

        //asumimos zx81
        int es_zx81=1;

        //las detecciones de abajo un tanto redundantes, por defecto es zx81, pero las dejo por si cambio el de defecto
        //en el caso de tape browser, depende exclusivamente de la maquina que tengamos seleccionada,
        //dado que no indicamos extension destino y por tanto:
        //Si el usuario tiene seleccionada maquina zx80, hara browse suponiendo que es zx80
        //Si el usuario tiene seleccionada cualquier otra maquina, hara browse suponiendo que es zx81
        if (archivo_destino==NULL) {
            if (MACHINE_IS_ZX81_TYPE) es_zx81=1;
            if (MACHINE_IS_ZX80_TYPE) es_zx81=0;
        }
        else {
            //Si indicamos .p, asumimos que sera zx81
            if (!util_compare_file_extension(archivo_destino,"p")) {
                es_zx81=1;
            }

            if (!util_compare_file_extension(archivo_destino,"o")) {
                es_zx81=0;
            }
        }

        if (es_zx81) {
            //Si carga en memoria, saltar cabecera con el nombre

            z80_byte buffer_nombre[257];
            int longitud_nombre=zx8081_escribe_nombre_to_string(buffer_memoria,buffer_nombre,bytes_leidos);
            debug_printf (VERBOSE_INFO,"Total bytes read: %d Program name length: %d Program name: %s",bytes_leidos,longitud_nombre,buffer_nombre);

            if (texto_info_output!=NULL) sprintf(texto_info_output,"ZX81 Tape\n\nTotal bytes read: %d\nProgram name: %s\n",bytes_leidos,buffer_nombre);

            //Descartar nombre
            bytes_leidos -=longitud_nombre;
            buffer_memoria +=longitud_nombre;

        }

        else {
            debug_printf (VERBOSE_INFO,"Total bytes read: %d",bytes_leidos);

            if (texto_info_output!=NULL) {
                sprintf(texto_info_output,"ZX80 Tape\n\nTotal bytes read: %d\n",bytes_leidos);
            }
        }


        debug_printf (VERBOSE_INFO,"Sound Bytes read: %u Program length (without the name):%u ",
                  zx8081_fic_leido,bytes_leidos);
        if (bytes_leidos) {

            z80_int offset_destino;

            offset_destino=0;

            if (MACHINE_IS_ZX81_TYPE) offset_destino=0x4009;
            if (MACHINE_IS_ZX80_TYPE) offset_destino=0x4000;

            if (offset_destino==0 && si_load) cpu_panic ("Destination dir is zero");


            if (offset_destino+bytes_leidos>ramtop_zx8081) debug_printf (VERBOSE_ERR,"Read bytes (%d) over ramtop (%d)",bytes_leidos,ramtop_zx8081);
            //printf ("offset_destino: %d\n",offset_destino);



            if (tape_loading_simulate.v==1 && archivo_destino==NULL && si_load) {
                new_snap_load_zx80_zx81_simulate_loading(memoria_spectrum+offset_destino,buffer_memoria,bytes_leidos);
            }

            //Igualmente lo leemos, aunque traspase ramtop
            if (archivo_destino==NULL && si_load) {
                memcpy(memoria_spectrum+offset_destino,buffer_memoria,bytes_leidos);
            }
        }
    }
    //if (!bytes_leidos) debug_printf (VERBOSE_ERR,"Error: Program length is zero");

    if (archivo_destino!=NULL) {
        util_save_file(buffer_memoria,bytes_leidos,archivo_destino);
    }

    free(buffer_memoria_orig);

    main_leezx81_liberar_bloqueo();
    return bytes_leidos;

}





//
//Rutina de conversion de audio de sonido de carga de spectrum a binario
//

//de smpatap spectrum

//11111 hz
#define SPEC_NO_RUIDO 		2


#define SPEC_ONDAS_GUIA 	10


char *spec_tipos_fichero[]={
    "Program",  //0
    "Number Array",
    "Character Array",
    "Bytes",  //3
    "Flag",  //4
    "Unknown" //5
};

//int spec_ondas_leidas;



unsigned char spec_carry;

//unsigned char *memoria;
//unsigned char *memoria_original;
unsigned int spec_bytes_leidos;

//char fichero_smp[1024],fichero_tap[1024];
char *fichero_smp,*fichero_tap;

//Para smp 11111
//char spec_tono_guia=14,spec_ceros=6,spec_unos=12,spec_mitad_onda_falsa=6;
//char margen_spec_tono_guia=2;

//Para rwa 15600
char spec_tono_guia=20,spec_ceros=8,spec_unos=16,spec_mitad_onda_falsa=8;
char margen_spec_tono_guia=3;

//Para smp 11111
//char margen_spec_ceros=2;
//char margen_spec_unos=2;

//Para rwa 15600
char margen_spec_ceros=3;
char margen_spec_unos=3;



char spec_byte_cambio,spec_cambio=0;
char spec_final_fichero=0;

z80_byte *memoria_archivo_smp;
int memoria_archivo_smp_puntero;

int spec_da_ascii(int codigo)
{
    return (codigo<127 && codigo>31 ? codigo : '.');
}


#define spec_da_abs(X)    ((X>=0 ? X : -X))

/*
int spec_da_abs(int valor)
{
    if (valor>=0) return valor;
    else return -valor;

}
*/


//int tempp=0;

int spec_longitud_total_archivo_smp;

int spec_lee_byte(void)
//Funcion que lee byte del fichero
//Mira si hay un byte de cambio de onda, en cuyo caso lo devuelve
//Pone spec_final_fichero a 1 si se llega al final del fichero
{

    //printf("antes spec_lee_byte memoria_archivo_smp_puntero %d spec_longitud_total_archivo_smp %d final %d\n",
    //        memoria_archivo_smp_puntero,spec_longitud_total_archivo_smp,spec_final_fichero);

    //Por si acaso se llama a leer cuando ya se ha llegado al final
    if (memoria_archivo_smp_puntero>=spec_longitud_total_archivo_smp) {
        spec_final_fichero=1;
        return 0;
    }


    if (spec_cambio) {
        spec_cambio=0;
        return spec_byte_cambio;
    }

    else {
        //spec_byte_cambio=fgetc(ptr_mycinta_smp);
        //Es mas rapido leer de memoria que no andar haciendo fgetc
        spec_byte_cambio=memoria_archivo_smp[memoria_archivo_smp_puntero++];
    }

    if (memoria_archivo_smp_puntero>=spec_longitud_total_archivo_smp) {

    //if (feof(ptr_mycinta_smp)) {
        spec_final_fichero=1;
        return 0;
    }

    //printf ("leido: %d\n",spec_byte_cambio);

    return spec_byte_cambio;
}

#define spec_da_signo(X) (X>=0 ? 1 : -1)

/*
char spec_da_signo(char valor)
//Devuelve el signo de valor: -1,+1 o 0
{

    if (valor>=0) return 1;
    if (valor<0) return -1;

    //TODO: aqui se llega alguna vez?
    return 0;
}
*/

int spec_lee_onda(unsigned char *longitud,unsigned char *amplitud)
//Funcion que lee una onda completa de sonido
//Da la maxima amplitud (en positivo) y la longitud
//de esa onda
//Devuelve -1 si se llega al final del fichero
{
    char byte,byte_anterior,veces=0;

    *longitud=1;
    *amplitud=0;

    byte_anterior=spec_lee_byte();
    *amplitud=spec_da_abs(byte_anterior);


    if (spec_final_fichero) return -1;

    do {
        byte=spec_lee_byte();
        if (spec_final_fichero) return -1;

        if (spec_da_abs(byte)>(*amplitud)) *amplitud=spec_da_abs(byte);

        //printf ("amplitud: %d\n",*amplitud);

        if (spec_da_signo(byte)!=spec_da_signo(byte_anterior)
            //&& spec_da_abs(byte)>=SPEC_NO_RUIDO
        ) {

            if (veces==1) {
                spec_cambio=1;

                //printf ("cambio signo con longitud: %u\n",*longitud);

                return 0;
            }

            veces++;
        }
        (*longitud)++;
        byte_anterior=byte;
    } while (1);
}

/*
int spec_dice_bit(char longitud)
//Dice si el bit es 0 o 1 segun su amplitud
//Devuelve -1 si no es un bit aceptado
{
    if (longitud>=spec_ceros-margen_spec_ceros && longitud<=spec_ceros+margen_spec_ceros) return 0;
    if (longitud>=spec_unos-margen_spec_unos && longitud<=spec_unos+margen_spec_unos) return 1;
    debug_printf (VERBOSE_DEBUG,"Invalid length for bit: %d",longitud);
    return -1;
}
*/

int spec_lee_8_bits(void)
//Devuelve 8 bits leidos


//No usado:
//Se puede entrar la longitud anterior leida, si no entrarlo con -1

//Devuelve -1 si se llega al final del fichero
//Devuelve -2 si se encuentra ruido
//Devuelve -3 si se encuentran datos sin sentido
{
    unsigned char longitud,amplitud;
    char bit;
    int n,byte=0;

    for (n=0;n<8;n++) {

        if (spec_lee_onda(&longitud,&amplitud)==-1) return -1;

        if (amplitud<SPEC_NO_RUIDO) return -2;


        if (longitud>=spec_ceros-margen_spec_ceros && longitud<=spec_ceros+margen_spec_ceros) bit=0;
        else if (longitud>=spec_unos-margen_spec_unos && longitud<=spec_unos+margen_spec_unos) bit=1;
        else {
            debug_printf (VERBOSE_DEBUG,"Invalid length for bit: %d",longitud);
            return -3;
        }

        //bit=spec_dice_bit(longitud);
        //if (bit==-1) return -3;



        byte=byte*2+bit;
    }
    return byte;
}

char *main_spec_rwaatap_pointer_print=NULL;
int main_spec_rwaatap_pointer_print_max=0;

//posicion anterior al leer el bloque
long spec_last_file_position;

//puntero a array de enteros donde se almacenaran las posiciones de cada bloque. Finaliza con 0
//indicar NULL si no se quiere
long *spec_array_block_positions;

//maximo posible a indicar de arrays
int spec_max_array_block_positions;

//conteo actual de posiciones escritas
int spec_current_block_positions;

void spec_debug_cabecera(int indice,int leidos)
//Escribe tipo de fichero
{
    int n;
    z80_int len,parm1,parm2;
    unsigned char tipo;

    char buffer_nombre[11];

    char buffer_string[1024];

    //Indicar posicion del archivo en array. Contar que haya que poner el del final, de ahi a comparar  -1
    if (spec_array_block_positions!=NULL) {
        if (spec_current_block_positions<spec_max_array_block_positions-1) {
            //printf("Previous position in file: %ld\n",spec_last_file_position);
            spec_array_block_positions[spec_current_block_positions++]=spec_last_file_position;
        }
    }

    if (leidos!=19) {
        debug_printf (VERBOSE_INFO,"Read tape block. %s:%d . Length: %d",
                  spec_tipos_fichero[4],spec_smp_memory[indice],  ( leidos>2 ? leidos-2 : leidos  )  );


        if (main_spec_rwaatap_pointer_print!=NULL) {
            sprintf (buffer_string,"Tape block. %s: %d. Length: %d\n\n",
                  spec_tipos_fichero[4],spec_smp_memory[indice],  ( leidos>2 ? leidos-2 : leidos  )  );
            int nocabe=util_concat_string(main_spec_rwaatap_pointer_print,buffer_string,main_spec_rwaatap_pointer_print_max);
            if (nocabe) return;
        }

        //if (leidos>2) printf ("%u+2\n",leidos-2);
        //else printf ("%u\n",leidos);
        return;
    }

    tipo=spec_smp_memory[indice+1];

    if (tipo>3) tipo=5; //Desconocido

    for (n=0;n<10;n++) buffer_nombre[n]=spec_da_ascii(spec_smp_memory[indice+2+n]);
    buffer_nombre[10]=0;
    debug_printf (VERBOSE_INFO,"Read tape block. Standard Header - %s: %s",spec_tipos_fichero[tipo],buffer_nombre);


    if (main_spec_rwaatap_pointer_print!=NULL) {
        sprintf (buffer_string,"Tape block. Standard Header - %s: %s\n",spec_tipos_fichero[tipo],buffer_nombre);
        int nocabe=util_concat_string(main_spec_rwaatap_pointer_print,buffer_string,main_spec_rwaatap_pointer_print_max);
        if (nocabe) return;
    }

    len=value_8_to_16(spec_smp_memory[indice+13],spec_smp_memory[indice+12]);
    parm1=value_8_to_16(spec_smp_memory[indice+15],spec_smp_memory[indice+14]);
    parm2=value_8_to_16(spec_smp_memory[indice+17],spec_smp_memory[indice+16]);

    debug_printf (VERBOSE_INFO,"- Length: %u Parm1: %u Parm2: %u",len,parm1,parm2);

    if (main_spec_rwaatap_pointer_print!=NULL) {
        sprintf (buffer_string,"- Length: %u Parm1: %u Parm2: %u\n",len,parm1,parm2);
        int nocabe=util_concat_string(main_spec_rwaatap_pointer_print,buffer_string,main_spec_rwaatap_pointer_print_max);
        if (nocabe) return;
    }


    int variables=len-parm2;
    if (variables<0) variables=0;

    if (tipo==3) {
        debug_printf (VERBOSE_INFO,"- Start: %u",parm1);

        if (main_spec_rwaatap_pointer_print!=NULL) {
            sprintf (buffer_string,"- Start: %u\n",parm1);
            int nocabe=util_concat_string(main_spec_rwaatap_pointer_print,buffer_string,main_spec_rwaatap_pointer_print_max);
            if (nocabe) return;
        }
    }

    if (!tipo) {
        if (parm1<=32767) {
            debug_printf (VERBOSE_INFO,"- Variables: %u . Autorun: %d",variables,parm1);

            if (main_spec_rwaatap_pointer_print!=NULL) {
                sprintf (buffer_string,"- Variables: %u . Autorun: %d\n",variables,parm1);

                int nocabe=util_concat_string(main_spec_rwaatap_pointer_print,buffer_string,main_spec_rwaatap_pointer_print_max);
                if (nocabe) return;
            }
        }
        else {
            debug_printf (VERBOSE_INFO,"- Variables:%u . Autorun: None",variables);

            if (main_spec_rwaatap_pointer_print!=NULL) {
                sprintf (buffer_string,"- Variables:%u . Autorun: None\n",variables);
                int nocabe=util_concat_string(main_spec_rwaatap_pointer_print,buffer_string,main_spec_rwaatap_pointer_print_max);
                if (nocabe) return;
            }
        }
    }


    if (main_spec_rwaatap_pointer_print!=NULL) {
        sprintf (buffer_string,"\n");
        int nocabe=util_concat_string(main_spec_rwaatap_pointer_print,buffer_string,main_spec_rwaatap_pointer_print_max);
        if (nocabe) return;
    }


}

//array_block_positions, max_array_block_positions usados para guardar las posiciones de los bloques
//codigo_retorno: si es NULL, si hay error de carga se genera mensaje de error por VERBOSE_ERR
//si no es NULL, no hay mensaje por VERBOSE_ERR y se almacena en codigo retorno: 0: ok, 1: error
int main_spec_rwaatap(long *array_block_positions,int max_array_block_positions,int *codigo_retorno,int longitud_archivo_smp)
{


    spec_longitud_total_archivo_smp=longitud_archivo_smp;

    spec_array_block_positions=array_block_positions;
    spec_max_array_block_positions=max_array_block_positions;

    spec_smp_write_index_tap=0;
    spec_smp_read_index_tap=0;

    spec_smp_total_read=0;

    spec_last_file_position=0;

    spec_current_block_positions=0;



    unsigned char amplitud,longitud;
    int byte,byte2;
    unsigned int n;

    if (codigo_retorno!=NULL) *codigo_retorno=0; //asumimos ok carga


    //apunta al principio de cada bloque TAP
    int spec_smp_write_index_tap_start;

    debug_printf (VERBOSE_INFO,"Reading SMP audio data and converting to TAP file in memory");

    debug_printf (VERBOSE_INFO,"Routine based on original program SMPATAP V1.1. (c) Cesar Hernandez Bano (10/09/1998), (31/03/2014)");


    spec_smp_write_index_tap_start=spec_smp_write_index_tap;

    //dejamos espacio para los 2 bytes que indican longitud
    spec_smp_write_index_tap +=2;

    //cuando hay browse, dice que ya se ha agregado el texto de "ZX Spectrum tape"
    int agregado_info_inicio=0;


    //Asignar memoria. 1 MB maximo
    if (spec_smp_memory==NULL) {
        debug_printf (VERBOSE_INFO,"Allocating %d bytes for tape buffer",MAX_BYTES_READ);

        spec_smp_memory=malloc(MAX_BYTES_READ);
        if (spec_smp_memory==NULL) {
            cpu_panic ("Error allocating memory for tape buffer");
        }
    }

    //Asignar memoria para archivo de entrada


    memoria_archivo_smp=util_malloc(longitud_archivo_smp,"Can not allocate memory for smp read");
    fread(memoria_archivo_smp,1,longitud_archivo_smp,ptr_mycinta_smp);
    fclose(ptr_mycinta_smp);

    memoria_archivo_smp_puntero=0;

    do {
        spec_carry=0;

        spec_cambio=0;
        spec_final_fichero=0;

        spec_bytes_leidos=0;

        //printf ("antes spec_smp_write_index_tap: %d spec_smp_write_index_tap_start: %d spec_smp_total_read: %d\n",spec_smp_write_index_tap,spec_smp_write_index_tap_start,spec_smp_total_read);



        //Leer unas ondas de tono guia
        n=0;
        do {
            if (spec_lee_onda(&longitud,&amplitud)==-1) {
                //printf ("spec_lee_onda == -1 antes de leer pilot tone. n=%d\n",n);
                goto fin;
            }
            if (amplitud<SPEC_NO_RUIDO || (!(longitud>=spec_tono_guia-margen_spec_tono_guia && longitud<=spec_tono_guia+margen_spec_tono_guia))
            ) {
                //printf ("reset n. amplitud: %u longitud: %u\n",amplitud,longitud);
                n=0;
                continue;
            }
            n++;
            //printf ("ondas guia: %d\n",n);
        } while (n<SPEC_ONDAS_GUIA);

        debug_printf (VERBOSE_DEBUG,"Reading pilot tone...");
        //printf("Reading pilot tone...\n");

        do {
            if (spec_lee_onda(&longitud,&amplitud)==-1) goto fin;
        } while (amplitud>=SPEC_NO_RUIDO && (longitud>=spec_tono_guia-margen_spec_tono_guia &&
        longitud<=spec_tono_guia+margen_spec_tono_guia));

        //Hay que saber si se esta en mitad o al final de la onda falsa
        if (longitud>spec_mitad_onda_falsa) { //en mitad de la onda falsa
            spec_cambio=0;
            byte=spec_byte_cambio;
            do {
                byte2=spec_lee_byte();
                if (spec_final_fichero) goto fin;
            } while (spec_da_signo(byte)==spec_da_signo(byte2));
        }

        debug_printf (VERBOSE_DEBUG,"Reading data...");

        //printf("Reading data...\n");


        //Despues del tono guia viene una onda falsa, no utilizable,
        //parecida a un bit 0

        do {
            byte=spec_lee_8_bits(/*-1*/);
            //if (byte==-1) goto fin;

            //if (byte==-1) break;
            if (byte<0) break;

            if (spec_smp_write_mem_byte(spec_smp_write_index_tap,byte)) {
                debug_printf (VERBOSE_ERR,"Memory buffer full");
                return 0;
            }

            spec_smp_write_index_tap++;


            spec_carry^=byte;
            spec_bytes_leidos++;
        } while (1);


        if (spec_bytes_leidos) {

            if (!agregado_info_inicio) {
                agregado_info_inicio=1;

                if (main_spec_rwaatap_pointer_print!=NULL) {
                    int nocabe=util_concat_string(main_spec_rwaatap_pointer_print,"ZX Spectrum Tape\n\n",main_spec_rwaatap_pointer_print_max);
                    if (nocabe) return 0;
                }
            }

            spec_debug_cabecera(spec_smp_write_index_tap_start+2,spec_bytes_leidos);



            //leer posicion dentro del archivo
            //spec_last_file_position=ftell(ptr_mycinta_smp);

            spec_last_file_position=memoria_archivo_smp_puntero;



            n=spec_bytes_leidos;


        }

        //En convertir cinta, o en browser cinta, interesa tener en el error con VERBOSE_ERR
        //En Visual Tape Browser, no queremos esto. Lo tendremos en cuenta en la funcion de salida
        if (spec_carry) {
            if (codigo_retorno!=NULL) *codigo_retorno=1; //error de carga
            else debug_printf (VERBOSE_ERR,"Error converting audio block to tape. Invalid end carry");
        }


        if (spec_bytes_leidos) {

            debug_printf (VERBOSE_DEBUG,"Writing %d bytes to memory buffer",spec_bytes_leidos);

            spec_smp_write_mem_byte(spec_smp_write_index_tap_start,value_16_to_8l(spec_bytes_leidos));
            spec_smp_write_index_tap_start++;


            //comprobamos solo el ultimo byte, ya es suficiente
            if (spec_smp_write_mem_byte(spec_smp_write_index_tap_start,value_16_to_8h(spec_bytes_leidos) )) {
                debug_printf (VERBOSE_ERR,"Memory buffer full");
                return 0;
            }


            spec_smp_total_read+=spec_bytes_leidos+2;



            spec_smp_write_index_tap_start=spec_smp_write_index_tap;

            //dejamos espacio para los 2 bytes que indican longitud
            spec_smp_write_index_tap +=2;


        }
        else {
            debug_printf (VERBOSE_DEBUG,"0 bytes read");
        }
        //printf ("despues spec_smp_write_index_tap: %d spec_smp_write_index_tap_start: %d spec_smp_total_read: %d\n",spec_smp_write_index_tap,spec_smp_write_index_tap_start,spec_smp_total_read);


        debug_printf (VERBOSE_INFO,"----------------");


    } while (!spec_final_fichero);

    fin:


    free(memoria_archivo_smp);

    if (spec_smp_total_read==0) {
        debug_printf(VERBOSE_INFO,"Converted Zero bytes of data from SMP file. May be a corrupted file or unsupported format");
    }

    //Indicar -1 al final del array de posiciones
    if (spec_array_block_positions!=NULL) {
        spec_array_block_positions[spec_current_block_positions]=-1;
    }


    return 0;

}
