/*
    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 <string.h>

#include "zx8081.h"
#include "cpu.h"
#include "screen.h"
#include "debug.h"
#include "audio.h"
#include "core_zx8081.h"
#include "operaciones.h"
#include "zxvision.h"
#include "ula.h"
#include "zxpand.h"
#include "tape.h"
#include "tv.h"


/*

Información referencia

https://8bit-museum.de/heimcomputer-2/sinclair/sinclair-scans/scans-zx81-video-display-system/

https://github.com/Megatokio/zxsp

http://nocash.emubase.de/zxdocs.htm#zx80zx81videointerruptsintsandnmis

http://www.user.dccnet.com/wrigter/index_files/ZX%20Video%20Tutorial.htm

https://quix.us/timex/rigter/ZX@20Video@20Tutorial.html

http://blog.tynemouthsoftware.co.uk/2023/10/how-the-zx81-generates-video.html

http://www.fruitcake.plus.com/Sinclair/ZX80/FlickerFree/ZX80_DisplayMechanism.htm

http://searle.x10host.com/zx80/zx80nmiV3.html

https://k1.spdns.de/Vintage/Sinclair/80/Sinclair%20ZX81/ROMs/zx81%20version%202%20%27improved%27%20rom%20source.htm

*/

/*

Teclado:

Igual que spectrum excepto que no hay symbol shift. este esta reemplazado por "."
The ZX81/ZX80 Keyboard Matrix

  Port____Line____Bit__0____1____2____3____4__
  FEFEh  0  (A8)     SHIFT  Z    X    C    V
  FDFEh  1  (A9)       A    S    D    F    G
  FBFEh  2  (A10)      Q    W    E    R    T
  F7FEh  3  (A11)      1    2    3    4    5
  EFFEh  4  (A12)      0    9    8    7    6
  DFFEh  5  (A13)      P    O    I    U    Y
  BFFEh  6  (A14)    ENTER  L    K    J    H
  7FFEh  7  (A15)     SPC   .    M    N    B


*/


//Real
z80_byte   caracteres_zx80_no_artistic[]=" \"???????????$:?()-+*/=><;,."
                           "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

//Un poco mas artistico
z80_byte   caracteres_zx80[]=" \"|v''../#_^f$:?()-+*/=><;,."
                           "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";


//Real
z80_byte   caracteres_zx81_no_artistic[]=" ??????????\"?$:?()><=+-*/;,."
                           "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

//Un poco mas artistico
z80_byte   caracteres_zx81[]=" ''^.|/p#_^\"f$:?()><=+-*/;,."
                           "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";


//Caracteres sin interrogantes ni nada, para funciones ocr a speech
z80_byte   caracteres_zx80_solo_letras[]=
               " \"           $:?()-+*/=><;,."
                           "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

z80_byte   caracteres_zx81_solo_letras[]=
              //01234567890 12345678901234567
               "           \" $:?()><=+-*/;,."
              //890123456789012345678901234567890123
                           "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

//Pasar de ascii a zx81. Empezando por el 32

z80_byte caracteres_ascii_zx81[]={
//  !  "  #  $  %  &  '  (  )  *  +  ,  -  .  /  0  1  2  3  4  5  6  7  8  9
0, 15,11,15,13,15,15,15,16,17,23,21,26,22,27,24,28,29,30,31,32,33,34,35,36,37,

// :   ;  <  =  >  ?  @  A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z
   14,25,19,20,18,15,15,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,

//     [  \  ]  ^  _  `  a  b  c  d  e  f  g  h  i  j  k  l  m  n  o  p  q  r  s  t  u  v  w  x  y  z
      15,15,15,15,15,15,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,

// {  |  }  ~  127
   15,15,15,15,15
};


//Pasar de ascii a zx80. Empezando por el 32

z80_byte caracteres_ascii_zx80[]={
//  !  "  #  $  %  &  '  (  )  *  +  ,  -  .  /  0  1  2  3  4  5  6  7  8  9
0, 15,11,15,13,15,15,15,16,17,20,19,26,18,27,21,28,29,30,31,32,33,34,35,36,37,

// :   ;  <  =  >  ?  @  A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z
   14,25,24,22,23,15,15,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,

//     [  \  ]  ^  _  `  a  b  c  d  e  f  g  h  i  j  k  l  m  n  o  p  q  r  s  t  u  v  w  x  y  z
      15,15,15,15,15,15,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,

// {  |  }  ~  127
   15,15,15,15,15
};

/*
Diferencias entre set de caracteres del zx81 y zx80
zx80 tiene en la posicion 1 , el "
Despues de ?(), en posicion 18:

       18 19 20 21 22 23 24
-zx80: -  +  *  /  =  >  <

-zx81: >  <  =  +  -  *  /
*/


//
//Inicio variables para generación de video
//
z80_bit nmi_generator_active;
//z80_bit hsync_generator_active;
z80_bit zx8081_vsync_generator={0};

//Tiempo total de hsync:
//12 microsegundos:
//1.65 front porch
//4.7 hsync pulse
//5.7 back porch
//TODO: esto deberia ser 39 t-estados (12 microsec)  pero entonces algunos juegos están muy a la izquierda
//tiempo en t-estados. Para calcularla: (207/64)*valor en microsegundos
//int hsync_total_duration=39;

int hsync_duration_counter=0;

//linea actual (scan 0-7) del caracter mostrado
int video_zx8081_lcntr=0;


//el siguiente valor que tendra una linea entera segun la ULA
//zx8081_ula_video_in_sync=1 : con sync , voltaje 0v, color negro
//zx8081_ula_video_in_sync=0 : sin sync , voltaje "normal" (1V), color blanco
//sync (ya sea hsync o vsync) es 0V. Depende de lo que dure ese 0V será hsync o vsync (esta duración no la implemento
//ni aqui ni en el módulo de emulación de tv)
int zx8081_ula_video_in_sync=0;

z80_byte zx80801_last_sprite_video=0;
int zx80801_last_sprite_video_tinta=0;
int zx80801_last_sprite_video_papel=0;

int ula_zx81_time_event_t_estados=0;


z80_bit force_zx81_chr_128={0};


z80_bit autodetect_wrx;

//WRX hi-res mode
z80_bit wrx_present;

//chroma enabled
z80_bit chroma81={0};

z80_bit autodetect_chroma81={1};

//chroma port
z80_byte chroma81_port_7FEF;

//
//Fin variables para generación de video
//

//Opcion para simular perdida de vsync
z80_bit simulate_lost_vsync;

//Opcion para simular perdida de hsync
z80_bit simulate_lost_hsync;

//indica si se simula la pantalla negra del modo fast
z80_bit video_fast_mode_emulation;

//indica si el siguiente frame estara en negro. es un contador con un limite
z80_byte video_fast_mode_next_frame_black;

//Esto solo sirve para mostrar en menu debug i/o ports
z80_byte zx8081_last_port_write_value;

//Para que al hacer hotswap de ZX80 a ZX81, el ZX81 resultante no tenga nmi generator
//Eso permite que juegos como Breakout y otros se pueda hacer hotswap a ZX81
z80_bit hotswapped_zx80_to_zx81={0};

z80_int ramtop_zx8081;

//forzar habilitar sonido de vsync/cinta en zx80/81
z80_bit zx8081_vsync_sound;


//deteccion sonido vsync
//TODO: da falsos positivos en modo fast, en manic miner, en pacman, nolimits, sllc
z80_bit zx8081_detect_vsync_sound={0};


int zx8081_detect_vsync_sound_counter_debe_incrementar=0;
int zx8081_detect_vsync_sound_counter=0;



//8k de RAM en 8192-16383
z80_bit ram_in_8192;

//16k de RAM en 49152-65535
z80_bit ram_in_49152;

//16k de RAM en 32767-49152
z80_bit ram_in_32768;



int vsync_per_second;
int last_vsync_per_second;

z80_byte ascii_to_zx81(z80_byte c)
{
    //?
    if (c<32 || c>127) return 15;

    else return caracteres_ascii_zx81[c-32];
}

z80_byte ascii_to_zx80(z80_byte c)
{
    //?
    if (c<32 || c>127) return 15;

    else return caracteres_ascii_zx80[c-32];
}



//Activar wrx y offset de t_estados
void enable_wrx(void)
{
  if (!MACHINE_IS_ZX8081) {
        debug_printf (VERBOSE_INFO,"ZXpand can only be enabled on ZX80/81");
        return;
    }

    if (wrx_present.v==0) {
        screen_print_splash_text_center_no_if_previous(ESTILO_GUI_TINTA_NORMAL,ESTILO_GUI_PAPEL_NORMAL,"Enabling WRX video mode");
        debug_printf (VERBOSE_INFO,"Enabling WRX video mode");
    }

    wrx_present.v=1;
}

//Desactivar wrx
void disable_wrx(void)
{
    if (wrx_present.v==1) {
            wrx_present.v=0;
        debug_printf (VERBOSE_INFO,"Disabling WRX video mode");
    }
}




void enable_ram_in_32768(void)
{
    ram_in_32768.v=1;

    //decimos la RAM para BASIC
    //debug_printf (VERBOSE_INFO,"Setting BASIC RAMTOP to 49152");
    //memoria_spectrum[16389]=192;
}

void enable_ram_in_49152(void)
{

    //si habilitamos esta, tambien la de 32768
    ram_in_32768.v=1;
    enable_ram_in_32768();
        ram_in_49152.v=1;

        //decimos la RAM para BASIC
    //debug_printf (VERBOSE_INFO,"Setting BASIC RAMTOP to 65535");
        //memoria_spectrum[16389]=255;
}

z80_int zx8081_get_standard_ram(void)
{
  return (ramtop_zx8081-16383)/1024;
}

z80_int zx8081_get_total_ram_with_rampacks(void)
{
    z80_int total_ram=zx8081_get_standard_ram();

    if (ram_in_8192.v) total_ram +=8;
    if (ram_in_49152.v==1) total_ram +=16;
    if (ram_in_32768.v==1) total_ram +=16;

    return total_ram;

}

z80_int get_ramtop_with_rampacks(void)
{
    //retorna la ramtop teniendo en cuenta los rampack
    if (ram_in_49152.v==1) return 65535;
    if (ram_in_32768.v==1) return 49151;
    return ramtop_zx8081;
}


//ajusta variable ramtop del sistema si hay algun pack activo
//esto se hace al cargar programas zx81 solo
void set_ramtop_with_rampacks(void)
{

    z80_int r;

    if (MACHINE_IS_ZX80_TYPE) return;

    //Solo si hay expansiones de memoria
    if (ram_in_32768.v==1 || ram_in_49152.v==1) {
        r=get_ramtop_with_rampacks();
        r++;
        if (r==0) r=65535;

        debug_printf (VERBOSE_INFO,"Setting BASIC RAMTOP to %d",r);
        memoria_spectrum[16389]=value_16_to_8h(r);
        memoria_spectrum[16388]=value_16_to_8l(r);
    }
}


z80_byte da_codigo81(z80_byte codigo,z80_bit *inverse)
{

  if (codigo>127) {
        inverse->v=1;
        codigo-=128;
  }
  else inverse->v=0;

  if (MACHINE_IS_ZX80_TYPE) {
    //zx80
    if (texto_artistico.v==1) return (codigo<64 ? caracteres_zx80[codigo] : '~');
    else return (codigo<64 ? caracteres_zx80_no_artistic[codigo] : '~');
    }
  else {
    //zx81
    if (texto_artistico.v==1) return (codigo<64 ? caracteres_zx81[codigo] : '~');
    else return (codigo<64 ? caracteres_zx81_no_artistic[codigo] : '~');
  }

}



z80_byte da_codigo81_solo_letras(z80_byte codigo,z80_bit *inverse)
{

  if (codigo>127) {
        inverse->v=1;
        codigo-=128;
  }
  else inverse->v=0;

  if (MACHINE_IS_ZX80_TYPE) {
        //zx80
    return (codigo<64 ? caracteres_zx80_solo_letras[codigo] : ' ');
        }
  else {
        //zx81
    return (codigo<64 ? caracteres_zx81_solo_letras[codigo] : ' ');
  }

}

z80_byte da_codigo_zx80_no_artistic(z80_byte codigo)
{
    return caracteres_zx80_no_artistic[codigo];
}

z80_byte da_codigo_zx81_no_artistic(z80_byte codigo)
{
    return caracteres_zx81_no_artistic[codigo];
}



int da_amplitud_speaker_zx8081(void)
{
    if (bit_salida_sonido_zx8081.v) return amplitud_speaker_actual_zx8081;
    else return -amplitud_speaker_actual_zx8081;
}


//Establece tamanyo ram (sin contar ram packs) de zx80/81. Valor entre 1 y 16
void set_zx8081_ramtop(z80_byte valor)
{
    if (valor<1 || valor>16) {
        cpu_panic("Cannot set ZX80/81 RAM");
    }

    ramtop_zx8081=16383+1024*valor;
}


void enable_chroma81(void)
{

    if (!MACHINE_IS_ZX8081) return;


    //en drivers stdout o curses, no habilitar colores, aunque dejamos si que dejamos habilitar las paginas de ram y el rainbow
    if (!strcmp(scr_new_driver_name,"curses") || !strcmp(scr_new_driver_name,"stdout")) {
        debug_printf (VERBOSE_WARN,"Chroma 81 is not supported on curses or stdout drivers");
    }
    else {
        if (chroma81.v==0) screen_print_splash_text_center_no_if_previous(ESTILO_GUI_TINTA_NORMAL,ESTILO_GUI_PAPEL_NORMAL,"Enabling Chroma81 video mode");
        chroma81.v=1;
    }


    ram_in_8192.v=1;
    enable_ram_in_49152();
    enable_rainbow();
}

void disable_chroma81(void)
{
    chroma81.v=0;
}


void chroma81_return_mode1_colour(z80_int dir,int *colortinta,int *colorpapel)
{
    //1 attribute file
    z80_byte c=peek_byte_no_time(dir|0x8000);
    *colortinta=c&15;
    *colorpapel=(c>>4)&15;
}

int color_es_chroma(void)
{
    return (chroma81.v && (chroma81_port_7FEF & 32) ? 1 : 0);
}




void screen_store_scanline_char_zx8081(z80_byte byte_leido,z80_byte caracter,int inverse)
{

    zx80801_last_sprite_video_tinta=0;
    zx80801_last_sprite_video_papel=15;



    //Si modo chroma81 y lo ha activado

    if (color_es_chroma() ) {
        //ver modo
        if ((chroma81_port_7FEF&16)!=0) {
            //1 attribute file
            chroma81_return_mode1_colour(reg_pc,&zx80801_last_sprite_video_tinta,&zx80801_last_sprite_video_papel);
            //printf ("modo 1\n");
        }
        else {
            //0 character code
            //tablas colores van primero para los 64 normales y luego para los 64 inversos
            if (inverse) {
                caracter +=64;
            }

            z80_int d=caracter*8+0xc000;
            d=d+(video_zx8081_lcntr&7);
            z80_byte leido=peek_byte_no_time(d);
            zx80801_last_sprite_video_tinta=leido&15;
            zx80801_last_sprite_video_papel=(leido>>4)&15;

            //printf ("modo 0\n");

        }
    }


    zx80801_last_sprite_video=byte_leido;
    return;

}

//Tratamiento de los gráficos zx81 al hacer fetch
z80_byte fetch_opcode_zx81_graphics(void)
{

    z80_byte op;

    op=peek_byte_zx80_no_time(reg_pc&0x7fff);

    if( (reg_pc&0x8000) ) {
        //se esta ejecutando la zona de pantalla


        z80_byte caracter;

        if ( (op & 64) || z80_halt_signal.v) {
            //caracter=0;
            //Otros caracteres no validos (y el HALT) generan video display a 0

            return op;
        }

        else {
            //ejecutará NOP
            caracter=op;
            op=0;
        }


        z80_byte sprite;



        //poner caracter en pantalla de video highmem
        z80_bit caracter_inverse;
        z80_int direccion_sprite;


        if (caracter&128) {
            caracter_inverse.v=1;
            caracter=caracter&127;
        }
        else caracter_inverse.v=0;




        //Posible modo wrx, y excluir valores de I usados en chr$128 y udg
        if (reg_i>=33 && reg_i!=0x31 && reg_i!=0x30 && wrx_present.v==0 && autodetect_wrx.v) {
            //posible modo wrx
            debug_printf(VERBOSE_INFO,"Autoenabling wrx so the program seems to need it (I register>32). Also enable 8K RAM in 2000H");
            enable_wrx();

            //algunos juegos requieren que este ram pack este presente antes de activar wrx... sino no funcionara
            //pero igualmente, por si acaso, lo activamos aqui
            ram_in_8192.v=1;

        }


        //Modos WRX
        if (wrx_present.v==1 && reg_i>=32 ) {

            //printf ("reg_i en zona WRX\n");

            direccion_sprite=(reg_i<<8) | (reg_r_bit7 & 128) | ((reg_r) & 127);

            sprite=memoria_spectrum[direccion_sprite];

            if (caracter_inverse.v) sprite=sprite^255;

        }


        else {

            //chr$128
            if (reg_i==0x31) {
                if (caracter_inverse.v) {
                    //El bit de inverse es para acceder a los 64 caracteres siguientes
                    caracter=caracter | 64;
                    //Pero sigue indicando inverse
                    //caracter_inverse.v=0;

                }
            }

            //Otros interfaces que tambien hacen 128 caracteres aunque no siguen la norma del registro I,
            //como el SD81 Booster de Alejandro Valero
            if (caracter_inverse.v && force_zx81_chr_128.v) {
                caracter=caracter | 64;
            }


            direccion_sprite=((reg_i&254)*256)+caracter*8+( (video_zx8081_lcntr) & 7);


            //Obteniendo tipo de letra de rom de zxpand en el caso del zx80
            if (zxpand_enabled.v && MACHINE_IS_ZX80_TYPE && direccion_sprite<8192) {
                sprite=zxpand_memory_pointer[direccion_sprite];
            }

            else {
                sprite=memoria_spectrum[direccion_sprite];
            }

            //aunque este en modo zxpand, la tabla de caracteres siempre sale de la rom principal
            //por eso hacemos sprite=memoria_spectrum[direccion_sprite]; y zxpand rom esta en otro puntero de memoria
            //sprite=peek_byte_zx80_no_time(direccion_sprite);

            if (caracter_inverse.v) sprite=sprite^255;

        }


        screen_store_scanline_char_zx8081(sprite,caracter,caracter_inverse.v);


        //Si no modo real video
        if (rainbow_enabled.v==0) {
            //Intentar autodetectar si hay que activar realvideo
            if (autodetect_rainbow.v) {
                if (MACHINE_IS_ZX80_TYPE) {
                    //ZX80
                    if (reg_i!=0x0e) {
                        debug_printf(VERBOSE_INFO,"Autoenabling realvideo so the program seems to need it (I register on ZX80 != 0x0e)");
                        enable_rainbow();
                    }
                }
                if (MACHINE_IS_ZX81_TYPE) {
                    //ZX81
                    if (reg_i!=0x1e) {
                        debug_printf(VERBOSE_INFO,"Autoenabling realvideo so the program seems to need it (I register on ZX81 != 0x1e)");
                        enable_rainbow();
                    }
                }
            }

        }
    }

    return op;

}



void generar_zx80_hsync(void)
{

    if (!hsync_duration_counter) {
        tv_enable_hsync();
    }

    hsync_duration_counter=48;


}

void ula_zx80_time_event(int delta)
{

    int i;

    for (i=0;i<delta;i++) {

        if (hsync_duration_counter) {
            hsync_duration_counter--;
            if (!hsync_duration_counter) {
                tv_disable_hsync();
            }
        }


        if (zx8081_vsync_generator.v) video_zx8081_lcntr=0;


        tv_time_event(1);


    }


}

//
// Start waitmap code
// Note: code borrowed from ZXSP emulator. Thanks!
// But is not in use yet
//
int WAITMAP_POS = +1; // +1 wg. test position in Z80.run()
int NMI_POS     = +2; // +2 = waitmap_pos+1 => max. 13 wait cycles

int waitmap[WAITMAP_SIZE];
int cc_hsync_period = 207;


int cc_hsync_next=16;

void clear_waitmap(void)
{
    memset(waitmap, 0, WAITMAP_SIZE);
}

void setup_waitmap(int cc_hsync)
{
        // when NMI is enabled then /NMI and /WAIT are asserted for 16cc at the same time as HSYNC.
        // the Z80 emulation will use cc % 207 to peek into the waitmap[].
        // the NMI will start at cc_nmi => waitmap[cc_nmi%207 to cc_nmi%207+15] must be set.

        // the waitmap must be updated
        // - when the NMI is enabled
        // - when the cc timebase is shifted in videoFrameEnd()
        // - when the HSYNC generator is restarted in interruptAtCycle()

        cc_hsync += WAITMAP_SIZE;

        cc_hsync %= WAITMAP_SIZE;

        if (get_waitmap_value(cc_hsync) == 16) // quick test whether it is already set as required
                return;
        clear_waitmap(); // clear old wait positions

        // circularly fill in 16cc delay for the first cc of the NMI pulse to 1cc for the last:
        int i = 0;
        for (; i < util_min(WAITMAP_SIZE - cc_hsync, 16); i++) {
            //printf("valor %d\n",16-i);
            int valor=16-i;
            waitmap[cc_hsync + i] = valor;
        }
        for (; i < 16; i++) {
            int valor=16-i;
            waitmap[cc_hsync - WAITMAP_SIZE + i] = valor;
            //printf("valor %d\n",16-i);
        }


    for (i=0;i<WAITMAP_SIZE;i++) {
        //printf("Waitmap %3d: %d\n",i,waitmap[i]);
    }


}

void reset_hsync_setup_waitmap(int cc, int dur)
{
        // reset the HSYNC counter.
        // the reset input is activated by an INT acknowledge cycle of the cpu.
        // the HSYNC counter is kept in reset while the INTACK signal is active.
        // => the alignment of the NMI changes => the waitmap needs to be updated


        cc_hsync_next = cc + dur + 16;

        if (nmi_generator_active.v)
        {
                setup_waitmap(cc_hsync_next + WAITMAP_POS);
        }
}

int get_waitmap_value(int pos)
{
    //TODO. no sumamos waitmap. no funciona bien
    return 0;

    return waitmap[pos % WAITMAP_SIZE];
}

//
// End waitmap code
//

int delayed_hsync_after=0;
int delayed_hsync_enable_for_states=0;

void generate_zx81_delayed_hsync(int tiempo,int generate_after)
{
    delayed_hsync_enable_for_states=tiempo;
    delayed_hsync_after=generate_after;

    /*
    printf("Definir delayed sync durante %d despues de %d. "
            "ula_zx81_time_event_t_estados=%3d tv_x=%3d tv_y=%3d zx8081_vsync_generator.v=%d nmi_generator.v=%d\n",tiempo,generate_after,
            ula_zx81_time_event_t_estados,tv_get_x(),tv_get_y(),zx8081_vsync_generator.v,nmi_generator_active.v);
    */
}

void generar_zx81_hsync(int tiempo)
{

    //Si electron esta hacia la izquierda, no hacer hsync
    if (tv_get_x()<100) return;


    if (!hsync_duration_counter) {

        //printf("Enable hsync con ula_zx81_time_event_t_estados=%3d tv_x=%3d tv_y=%3d zx8081_vsync_generator.v=%d nmi_generator.v=%d\n",
        //    ula_zx81_time_event_t_estados,tv_get_x(),tv_get_y(),zx8081_vsync_generator.v,nmi_generator_active.v);

        tv_enable_hsync();

    }

    hsync_duration_counter=tiempo;


}



void ula_zx81_time_event(int delta)
{

    int i;

    for (i=0;i<delta;i++) {

        ula_zx81_time_event_t_estados++;

        if (delayed_hsync_after) {
            delayed_hsync_after--;
            if (!delayed_hsync_after) {

                //printf("Generar delayed hsync. ula_zx81_time_event_t_estados=%3d tv_x=%3d tv_y=%3d zx8081_vsync_generator.v=%d nmi_generator.v=%d\n",
                //    ula_zx81_time_event_t_estados,tv_get_x(),tv_get_y(),zx8081_vsync_generator.v,nmi_generator_active.v);

                generar_zx81_hsync(delayed_hsync_enable_for_states);
                video_zx8081_lcntr++;
                ula_zx81_time_event_t_estados=0;
            }
        }


        if (hsync_duration_counter) {
            hsync_duration_counter--;
            if (!hsync_duration_counter) {
                tv_disable_hsync();
                //printf("Fin hsync\n");
            }
        }

        if (ula_zx81_time_event_t_estados>=screen_testados_linea) {
            ula_zx81_time_event_t_estados -=screen_testados_linea;


            if (nmi_generator_active.v) {
                //printf("Generate nmi\n");
                generate_nmi();
                reset_hsync_setup_waitmap(t_estados+2,2);
            }


            //printf("hsync desde generator\n");
            generar_zx81_hsync(16);
            video_zx8081_lcntr++;

        }


        if (zx8081_vsync_generator.v) {
            video_zx8081_lcntr=0;
        }

        tv_time_event(1);

    }


}

void zx81_enable_nmi_generator(void)
{
    if (hotswapped_zx80_to_zx81.v) return;

    //printf("   nmi on   en t_estados %6d y: %4d\n",t_estados,tv_get_y());
    nmi_generator_active.v=1;

    int cc_hsync = hsync_duration_counter ? cc_hsync_next - 16 + cc_hsync_period : cc_hsync_next;
    setup_waitmap(cc_hsync + WAITMAP_POS);

}

void zx81_disable_nmi_generator(void)
{
    //printf("   nmi off  en t_estados %6d y: %4d\n",t_estados,tv_get_y());
    nmi_generator_active.v=0;

    clear_waitmap();

}


//con vsync , voltaje 0v, color negro, este valor a 255

void zx8081_set_video_voltage_sync(void)
{
    zx8081_ula_video_in_sync=1;
}

void zx8081_set_video_voltage_no_sync(void)
{
    zx8081_ula_video_in_sync=0;
}

int zx8081_is_video_voltage_in_sync(void)
{
    return (zx8081_ula_video_in_sync ? 1 : 0);
}

int zx8081_read_port_a0_low(z80_byte puerto_h)
{

    if (MACHINE_IS_ZX81_TYPE) {


        //necesario? Manic y otros tiembla la imagen
        //if (nmi_generator_active.v) {
        //    generar_zx81_hsync(16);
        //}
        //testeado con demo sllc.81

    }

    z80_byte valor;

    //Solo se lanzan vsync cuando el nmi generator está off. Si no fuese así, se pretendería enviar vsync
    //en algunos juegos cuando se lee el teclado y el electrón está en la zona del borde superior o inferior
    //Eso pasaba en Mazogs por ejemplo
    if (nmi_generator_active.v==0) {
        tv_enable_vsync();
        zx8081_vsync_generator.v=1;
        //printf("Set vsync generator tv_y=%d\n",tv_get_y() );
        zx8081_set_video_voltage_sync();

        if (zx8081_detect_vsync_sound.v) {
            //si hay vsync en zonas no habituales (principio o fin) asumimos que puede generar sonido
            int y=tv_get_y();

            //maximo 5 segundos activos desde ultima deteccion de sonido
            if (y>50 && y<200) zx8081_detect_vsync_sound_counter_debe_incrementar=1;
        }

    }

    if (MACHINE_IS_ZX81_TYPE && nmi_generator_active.v)
    {
        int d = get_waitmap_value((t_estados + 2 + WAITMAP_POS) % WAITMAP_SIZE);
        t_estados += d;
    }




    /*
    if (nmi_generator_active.v==0 && hsync_generator_active.v) {
        hsync_generator_active.v=0;
        //printf("hsync generator off en t_scanline_draw=%d t_estados: %d\n",t_scanline_draw,t_estados);
    }
    */


    //printf("Disabling the HSYNC generator t_scanline_draw=%d\n",t_scanline_draw);

    //printf("Disabling the HSYNC generator on t-state %d t-states %d scanline_draw %d contador_segundo %d\n",
    //    t_estados % screen_testados_linea,t_estados,t_scanline_draw,contador_segundo);

    modificado_border.v=1;


    //y ponemos a low la salida del altavoz
    bit_salida_sonido_zx8081.v=0;

    set_value_beeper_on_array(da_amplitud_speaker_zx8081() );


    if (zx8081_vsync_sound.v) {
        //solo resetea contador de silencio cuando esta activo el vsync sound - beeper
        reset_beeper_silence_detection_counter();
    }

    //Teclado

    //probamos a enviar lo mismo que con teclado de spectrum
    valor=lee_puerto_teclado(puerto_h);

/*
Bit  Expl.
0-4  Keyboard column bits (0=Pressed)
5    Not used             (1)
6    Display Refresh Rate (0=60Hz, 1=50Hz)
7    Cassette input       (0=Normal, 1=Pulse)
*/
    //decimos que no hay pulso de carga. 50 Hz refresco

    valor = (valor & 31) | (32+64);


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

    int leer_cinta_real=0;

    if (realtape_inserted.v && realtape_playing.v) leer_cinta_real=1;

    if (audio_can_record_input()) {
        if (audio_is_recording_input) {
            leer_cinta_real=1;
        }
    }

    if (leer_cinta_real) {
        if (realtape_get_current_bit_playing()) {
            valor=valor|128;
            //printf ("1 ");
        }
        else {
            valor=(valor & (255-128));
            //printf ("0 ");
        }
    }

    return valor;


}

void zx8081_out_any_port_video_stuff(void)
{

    if (MACHINE_IS_ZX81_TYPE) {

        generar_zx81_hsync(16);

    }



    //printf("Enabling the HSYNC generator t_scanline_draw=%d\n",t_scanline_draw);

    /*
    if (hsync_generator_active.v==0) {
        hsync_generator_active.v=1;
    }
    */


    if (zx8081_vsync_generator.v) {

        //printf("Disable vsync con ula_zx81_time_event_t_estados=%3d tv_x=%3d tv_y=%3d zx8081_vsync_generator.v=%d nmi_generator.v=%d\n",
        //    ula_zx81_time_event_t_estados,tv_get_x(),tv_get_y(),zx8081_vsync_generator.v,nmi_generator_active.v);


        if (MACHINE_IS_ZX81_TYPE) {
            //Para que la imagen esté centrada
            ula_zx81_time_event_t_estados=0;
            reset_hsync_setup_waitmap(t_estados+2,2);
        }

        tv_disable_vsync();
        zx8081_vsync_generator.v=0;
        zx8081_set_video_voltage_no_sync();
    }

    if (MACHINE_IS_ZX81_TYPE && nmi_generator_active.v)
    {
        int d = get_waitmap_value((t_estados + 2 + WAITMAP_POS) % WAITMAP_SIZE);
        t_estados += d;
    }

    modificado_border.v=1;


    //reseteamos contador de deteccion de modo fast-pantalla negra. Para modo no-realvideo
    video_fast_mode_next_frame_black=0;


    //y ponemos a high la salida del altavoz
    bit_salida_sonido_zx8081.v=1;

    set_value_beeper_on_array(da_amplitud_speaker_zx8081() );


    if (zx8081_vsync_sound.v) {
        //solo resetea contador de silencio cuando esta activo el vsync sound - beeper
        reset_beeper_silence_detection_counter();
    }


}


void zx8081_timer_vsync_detection(void)
{
    if (zx8081_detect_vsync_sound.v) {

        //para que solo se tengan en cuenta los vsync 1 por cada segundo
        //y un maximo de x segundos
        if (zx8081_detect_vsync_sound_counter_debe_incrementar) {
            if (zx8081_detect_vsync_sound_counter<10) {
                zx8081_detect_vsync_sound_counter++;
            }
            debug_printf(VERBOSE_DEBUG,"Detect vsync sound. Counter=%d",zx8081_detect_vsync_sound_counter);
        }
        else {

            if (zx8081_detect_vsync_sound_counter>0) {
                zx8081_detect_vsync_sound_counter--;
                debug_printf(VERBOSE_DEBUG,"Detect vsync sound. Counter=%d",zx8081_detect_vsync_sound_counter);
            }

        }

        zx8081_detect_vsync_sound_counter_debe_incrementar=0;

        //printf("counter: %d\n",zx8081_detect_vsync_sound_counter);
    }
}
