/*************************************************************************

    p8000_8_abstr.c driver.
    P8000 8bit board abstraction layer for 16bit board emulation

   Copyright Matt Knoth 2009  p8000@knothusa.net

   - This source code is released as freeware for non-commercial purposes.
   - You are free to use and redistribute this code in modified or
     unmodified form, provided you list me in the credits.
   - If you modify this source code, you must add a notice to each modified
     source file that it has been changed.
   - The author of this copywritten work reserves the right to change the
     terms of its usage and license at any time, including retroactively
   - This entire notice must remain in the source code.

**************************************************************************/

#include <time.h>
#include "driver.h"
#include "deprecat.h"
#include "ui.h"
#include "uimenu.h"
#include "sound/beep.h"
#include "cpu/z8001/z8001.h"
#include "cpu/z8001/z8010.h"
#include "cpu/z80/z80daisy.h"
#include "machine/z80ctc.h"
#include "machine/z80pio.h"
#include "machine/z80sio.h"
#include "debugger.h"

#define CTC0 0
#define CTC1 1
#define PIO0 0
#define PIO1 1
#define PIO2 2
#define SIO0 0
#define SIO1 1

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 8bit board emulation - abstraction layer
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "machine/cqm_image.h"

// definition of abstration engine state
#define ABSTR_RESET  0x0000
#define ABSTR_TEST1  0x0001
#define ABSTR_IDLE   0x0002
#define ABSTR_WRITE  0x0003
#define ABSTR_READ   0x0004
#define ABSTR_FDREAD 0x0005
#define ABSTR_FDWRIT 0x0006
#define ABSTR_FDFORM 0x0007

// defintion of PIO0B status bits
#define PIO0B_STAT8_INT   0x01
#define PIO0B_STAT8_READ  0x02
#define PIO0B_STAT8_WRITE 0x04
#define PIO0B_STAT8_REMOT 0x10

// definition of abstract SIO interrupt reason
#define ABSTR_SIO_RINT 0x00
#define ABSTR_FDC_UDOS 0x11
#define ABSTR_SIO_XINT 0x20
#define ABSTR_SIO_EINT 0x40
#define ABSTR_SIO_SINT 0x60

void put_adm31_vt100( UINT8 term, UINT8 code );    // send a character to the terminal

struct __p8000_16_abstr_floppy {
   UINT8 dirty;                    // write/format took place
   UINT8 changed;                  // floppy has changed  [3] - changed [2]-status [1:0] drive number
   UINT8 *pRead[4];                // read pointer
   struct cqm_image* images[4];    // drive contents
};
typedef struct __p8000_16_abstr_floppy _p8000_16_abstr_floppy;

static UINT8 p8000_16_abstr_state;             // layer state
static UINT8 p8000_16_abstr_port;              // targetted port address
static UINT8 sio_wr1[4];                       // virtual SIO[0,1] WR1 register
static UINT8 sio_ctl_acc[4];                   // virtual SIO[0,1] access counter
static UINT8 fd_ctl_buf[8];                    // floppy input parameter
static UINT8 fd_ctl_idx;                       // floppy parameter index
static UINT16 fd_read_ctr;                     // floppy block read counter
static emu_timer* sio_timer[4];                // interrupt drummer

static UINT8 p8000_16_abstr_getdevdata(UINT8 port);
static void  p8000_16_abstr_setdevdata(UINT8 port, UINT8 data);
static void  p8000_16_abstr_fdread( void );

static _p8000_16_abstr_floppy p8000_16_abstr_floppy;


void p8000_16_abstr_keyboard(UINT8 sio, UINT8 channel, UINT8 data)
{
     if(p8000_16_abstr_state<ABSTR_IDLE) return;

//fprintf(stderr,"P8: KEY %d %d %02x\n",sio,channel,data);
     z80pio_p_w(PIO1, 0, data);
     z80pio_p_w(PIO1, 1, 0x07 & (((1+((sio<<1)|channel))<<1)|0x01));
}

void p8000_16_abstr_put0A(UINT8 data)
{

   switch(p8000_16_abstr_state) {
   case ABSTR_RESET:  
                      z80pio_p_w(PIO1, 0, 0x55);
                      z80pio_p_w(PIO1, 1, 0x55);
                      p8000_16_abstr_state=ABSTR_TEST1;
                      break;
   case ABSTR_TEST1:  
                      z80pio_p_w(PIO1, 0, 0x02);                  // Choice 1/2 UDOS (not)avail
                      z80pio_p_w(PIO1, 1, 0x00);
                      p8000_16_abstr_state=ABSTR_IDLE;
                      break;
   case ABSTR_IDLE:   
                      p8000_16_abstr_port=data;
//                    fprintf(stderr,"P8: IDLE: put0A %02x STATUS: %d %c\n",data,p8000_16_abstr_state,data);
                      break;
   case ABSTR_WRITE:
//                    fprintf(stderr,"P8: WRITE: put0A %02x STATUS: %d %c\n",data,p8000_16_abstr_state,data);
                      p8000_16_abstr_setdevdata(p8000_16_abstr_port,data);
                      if(z80pio_p_r(PIO0, 1)==0) p8000_16_abstr_state=ABSTR_IDLE;
                      break;
   case ABSTR_FDREAD:
//                    fprintf(stderr,"P8: FDREAD: put0A %02x STATUS: %d %c\n",data,p8000_16_abstr_state,data);
                      if(fd_ctl_idx<6) {
                         fd_ctl_buf[fd_ctl_idx++]=data;
                         if(fd_ctl_idx==6) { 
                            p8000_16_abstr_fdread();
                            fd_ctl_idx=0;
                            fd_read_ctr=0;
                      }  }
                      break;
   case ABSTR_FDWRIT:
//                    fprintf(stderr,"P8: FDWRITE: put0A %02x STATUS: %d %c\n",data,p8000_16_abstr_state,data);
                      fd_ctl_buf[fd_ctl_idx++]=data;
                      break;
   case ABSTR_FDFORM:
//                    fprintf(stderr,"P8: FDFORMAT: put0A %02x STATUS: %d %c\n",data,p8000_16_abstr_state,data);
                      fd_ctl_buf[fd_ctl_idx++]=data;
                      break;
   }

}

void p8000_16_abstr_put0B(UINT8 data)
{  UINT8 res;

   switch(p8000_16_abstr_state) {
   case ABSTR_IDLE: if(data & PIO0B_STAT8_INT) {
                       if(data & PIO0B_STAT8_READ)   {
//                      fprintf(stderr,"P8: READ: put0B %02x STATUS: %d %c\n",data,p8000_16_abstr_state,data);
                          res=p8000_16_abstr_getdevdata(p8000_16_abstr_port);
                          z80pio_p_w(PIO1, 0, res );   // Data Byte
                          z80pio_p_w(PIO1, 1, 0x00);   // enable status
                          return;
                       }
                       if(data & PIO0B_STAT8_WRITE)  {
//                      fprintf(stderr,"P8: WRITE: put0B %02x STATUS: %d %c\n",data,p8000_16_abstr_state,data);

                          p8000_16_abstr_state=ABSTR_WRITE;
                          z80pio_p_w(PIO1, 1, 0x00);   // enable status
                       }
                       if(data & PIO0B_STAT8_REMOT)   {
//                        fprintf(stderr,"P8: REMOTE: put0B %02x STATUS: %d %c\n",data,p8000_16_abstr_state,data);
                               if(p8000_16_abstr_port=='R') p8000_16_abstr_state=ABSTR_FDREAD;
                          else if(p8000_16_abstr_port=='W') p8000_16_abstr_state=ABSTR_FDWRIT;
                          else if(p8000_16_abstr_port=='F') p8000_16_abstr_state=ABSTR_FDFORM;
                          memset(fd_ctl_buf,0,8);
                          fd_ctl_buf[0]=p8000_16_abstr_port;
                          fd_ctl_idx=1;
                          z80pio_p_w(PIO1, 1, 0x00);   // enable status
                       }
                    }
                    break;
   case ABSTR_FDREAD:
//                    fprintf(stderr,"P8: FDREAD: put0B %02x STATUS: %d %c\n",data,p8000_16_abstr_state,data);
                      break;
   }
}

void  p8000_16_abstr_get1A(UINT8 cnl)         // prepare for block read data (FD)
{
  if(cnl || p8000_16_abstr_state!=ABSTR_FDREAD) return;

  if(fd_read_ctr==0xffff) {  
     p8000_16_abstr_floppy.pRead[fd_ctl_buf[1] & 0x01]=qm_image_get_block_rw_ptr(fd_ctl_buf[1] & 0x01, (fd_ctl_buf[2]<<8) | fd_ctl_buf[3]);
     fd_read_ctr=fd_ctl_buf[4]<<8 | fd_ctl_buf[5];
     z80pio_p_w(PIO1, 0, 0x06);
  } else {
     z80pio_p_w(PIO1, 0, *p8000_16_abstr_floppy.pRead[fd_ctl_buf[1] & 0x01]++); 
//   fprintf(stderr,"P8: FD: GET1A %d\n",fd_read_ctr);
     if(fd_read_ctr) fd_read_ctr--;
  }

  // block read done, also unset Floppy Drive Lamps
  if(fd_read_ctr==0) {
     p8000_16_abstr_state=ABSTR_IDLE;

     if(fd_ctl_buf[1] & 0x01) output_set_value("lamp1",0);
     else                     output_set_value("lamp0",0);
  }
}

static TIMER_CALLBACK( abstract_sio_timer )
{
   switch(param) {
   case 0x00:
       z80pio_p_w(PIO1, 0, 0x00);                       // clear data port
       z80pio_p_w(PIO1, 1, ABSTR_SIO_XINT | 0x03);      // SIO0A Interrupt
       break;
   case 0x01:
       if(!(sio_wr1[1] & 0x02)) return;
       z80pio_p_w(PIO1, 0, 0x00);                       // clear data port
       z80pio_p_w(PIO1, 1, ABSTR_SIO_XINT | 0x05);      // SIO0B Interrupt
       break;
   case 0x02:
       z80pio_p_w(PIO1, 0, 0x00);                       // clear data port
       z80pio_p_w(PIO1, 1, ABSTR_SIO_XINT | 0x07);      // SIO0A Interrupt
       break;
   case 0x03:
       z80pio_p_w(PIO1, 0, 0x00);                       // clear data port
       z80pio_p_w(PIO1, 1, ABSTR_SIO_XINT | 0x09);      // SIO0B Interrupt
       break;
   case 0x10:                                           // FD read access
       z80pio_p_w(PIO1, 0, 0x06);                       // ACK OK FDC read // FIXME - depends on disk read
       z80pio_p_w(PIO1, 1, ABSTR_FDC_UDOS);             // SIO0B Interrupt
       fd_read_ctr=0xffff;                              // mark as busy
//fprintf(stderr,"FDC: KOPPEL_UDOS sent\n");
       break;
   }
}

void p8000_16_abstr_init(void)                  // initialize 8bit abstraction layer
{  int i;

   p8000_16_abstr_state=ABSTR_RESET;
   z80pio_p_w(PIO1, 0, 0xaa);
   z80pio_p_w(PIO1, 1, 0x2a);

   for(i=0; i<4; i++) {
       sio_wr1[i]=0;                                          // virtual SIO[0,1][A,B] WR1 register
       sio_ctl_acc[i]=0;                                      // virtual SIO[0,1][A,B] access counter
       sio_timer[i] = timer_alloc(abstract_sio_timer, NULL);
   }
}

static UINT8 p8000_16_abstr_getdevdata(UINT8 port)
{  UINT8 res;

   switch(port) {
   case 0x27:   //////////////////////////// SIO0B Control /////
   case 0x2b:   //////////////////////////// SIO1B Control /////
                res=0x04;  // RR0 - RECV empty
                break;
   default:     fprintf(stderr,"ERROR: UNKNOWN device read access in 8bit abstraction layer %02x\n",port);
   }

   return 0x04;
}

static void  p8000_16_abstr_setdevdata(UINT8 port, UINT8 data)
{  

   switch(port) {
   /////////////////////////////////////////////////////////////////////////////////////////////////////// SIO0 8bit board ////////
   case 0x24:   //////////////////////////// SIO0A Data    /////
//              fprintf(stderr,"P8: TTY0 = %02x\n",data);
                put_adm31_vt100( 0, data);
                timer_set(ATTOTIME_IN_USEC(1000), NULL, 0, abstract_sio_timer);
                break;
   case 0x25:   //////////////////////////// SIO0A Control /////
//              fprintf(stderr,"P8: SIO0A Control := %02x\n",data);
                if(sio_ctl_acc[0]==0x00) sio_ctl_acc[0]=data & 0x07;
                else { if(sio_ctl_acc[0]==0x01) sio_wr1[0]=data;
                       sio_ctl_acc[0]=0;
                }
                break;
   case 0x26:   //////////////////////////// SIO0B Data    /////
//            fprintf(stderr,"P8: TTY1 = %02x\n",data);
                put_adm31_vt100( 1, data);
                if(sio_wr1[1] & 0x02) timer_set(ATTOTIME_IN_USEC(1000), NULL, 1, abstract_sio_timer);
                break;
   case 0x27:   //////////////////////////// SIO0B Control /////
//              fprintf(stderr,"P8: SIO0B Control := %02x\n",data);
                if(sio_ctl_acc[1]==0x00) sio_ctl_acc[1]=data & 0x07;
                else { if(sio_ctl_acc[1]==0x01) sio_wr1[1]=data;
                       sio_ctl_acc[1]=0;
                }
                break;
   /////////////////////////////////////////////////////////////////////////////////////////////////////// SIO1 8bit board ////////
   case 0x28:   //////////////////////////// SIO1A Data    /////
//              fprintf(stderr,"P8: TTY2 = %02x\n",data);
                put_adm31_vt100( 2, data);
                /*if(sio_wr1[2] & 0x02)*/ timer_set(ATTOTIME_IN_USEC(1000), NULL, 2, abstract_sio_timer);
                break;
   case 0x29:   //////////////////////////// SIO0A Control /////
//              fprintf(stderr,"P8: SIO1A Control := %02x\n",data);
                if(sio_ctl_acc[2]==0x00) sio_ctl_acc[2]=data & 0x07;
                else { if(sio_ctl_acc[2]==0x01) sio_wr1[2]=data;
                       sio_ctl_acc[2]=0;
                }
                break;
   case 0x2a:   //////////////////////////// SIO1B Data    /////
//              fprintf(stderr,"P8: TTY3 = %02x\n",data);
                put_adm31_vt100( 3, data);
                /*if(sio_wr1[3] & 0x02)*/ timer_set(ATTOTIME_IN_USEC(1000), NULL, 3, abstract_sio_timer);
                break;
   case 0x2b:   //////////////////////////// SIO1B Control /////
//              fprintf(stderr,"P8: SIO1B Control := %02x\n",data);
                if(sio_ctl_acc[3]==0x00) sio_ctl_acc[3]=data & 0x07;
                else { if(sio_ctl_acc[3]==0x01) sio_wr1[3]=data;
                       sio_ctl_acc[3]=0;
                }
                break;
   case 0x08:   //////////////////////////// CTC0 programming /////////////
   case 0x09: case 0x0a: case 0x0b:
   case 0x2c:   //////////////////////////// CTC1 programming /////////////
   case 0x2d: case 0x2e: case 0x2f:   
                break;
   default:     fprintf(stderr,"ERROR: UNKNOWN device write access in 8bit abstraction layer %02x\n",port);
   }
}

static void p8000_16_abstr_fdread( void )
{
//   fprintf(stderr,"FD: %c dr: %02x, typ: %02x block: %02x len %02x\n",fd_ctl_buf[0],fd_ctl_buf[1] & 0x0f,(fd_ctl_buf[1] & 0xf0)>>4,
//                  (fd_ctl_buf[2]<<8)|fd_ctl_buf[3],(fd_ctl_buf[4]<<8)|fd_ctl_buf[5]);

   // set Floppy Drive Lamps
   if(fd_ctl_buf[1] & 0x01) output_set_value("lamp1",1);
   else                     output_set_value("lamp0",1);

   // start seek time
   timer_set(ATTOTIME_IN_MSEC(1000), NULL, 0x10, abstract_sio_timer);
}

void p8000_16_abstr_floppy_change_ready(  UINT8 which, const char* fname, UINT8 wrten )
{
    if(which > 3)
       return;

    if(fname==NULL) {

       if(p8000_16_abstr_floppy.images[which]!=NULL)  {  // remove disk or set write enable

             p8000_16_abstr_floppy.images[which]=NULL;
             if(p8000_16_abstr_floppy.dirty & (1<<which)) {  // check if modified
                qm_image_save( which );
                p8000_16_abstr_floppy.dirty &= ~(1<<which);
             }

// fprintf(stderr,"REMOVE FLOPPY %d\n",which);
         qm_image_close( which );
       }
    } else if((int)fname == -1) {                            // create new disk

               if(p8000_16_abstr_floppy.images[which]!=NULL)  {  // remove disk
                  qm_image_close( which );
               }

// fprintf(stderr,"New FLOPPY %d\n",which);
               p8000_16_abstr_floppy.images[which]=qm_image_new( which, strcpy(auto_malloc(4),"New"));
               p8000_16_abstr_floppy.dirty &= (1<<which);

    } else {                                                   // open existing disk

         if(p8000_16_abstr_floppy.images[which]!=NULL) {

// fprintf(stderr,"REMOVE FLOPPY %d %s\n",which,(char*)p8000_16_abstr_floppy.images[which]);
            p8000_16_abstr_floppy.images[which]=NULL;
            if(p8000_16_abstr_floppy.dirty & (1<<which)) {  // check if modified
               qm_image_save( which );
               p8000_16_abstr_floppy.dirty &= ~(1<<which);
            }

            qm_image_close( which );
         }

// fprintf(stderr,"INSERT FLOPPY %d %s\n",which,fname);
         p8000_16_abstr_floppy.images[which] = qm_image_open( which, fname);
    }

    p8000_16_abstr_floppy.changed = which | ((p8000_16_abstr_floppy.images[which])? 0x0c : 0x08);  // floppy status
//    if(i8272_floppy.intr) (*i8272_floppy.intr)(1);                                                 // set change interrupt
}

