#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>

#ifdef VMS
#include <unixio.h>
#include <file.h>
#else
#include <fcntl.h>
#endif

#ifdef DJGPP
#include "djgpp.h"
#endif

static char *rcsid = "$Id: atari.c,v 1.60 1998/02/17 thor,david Exp $";

#define FALSE   0
#define TRUE    1

#include "atari.h"
#include "cpu.h"
#include "mem.h"
#include "antic.h"
#include "gtia.h"
#include "pia.h"
#include "pokey.h"
#include "pokey11.h"
#include "supercart.h"
#include "devices.h"
#include "sio.h"
#include "monitor.h"
#include "platform.h"
#include "prompts.h"
#include "rt-config.h"
#include "ui.h"
#include "patch.h"

TVmode tv_mode = PAL;
Machine machine = Atari;
int verbose = FALSE;

static void Atari800_Hardware (void);

int load_cart (char *filename, int type);

void sigint_handler (int num)
{
  Atari800_Exit (TRUE, 0);

  signal (SIGINT, sigint_handler);
  return;
}


void InitHW(int *argc,char **argv)
{
  SetBlank(0xd000,0xd7ff);
  Init_PIA(argc,argv,0xd300);
  Init_Pokey(argc,argv,0xd200);
  Init_GTIA(argc,argv,0xd000);
  Init_Antic(argc,argv,0xd400);
  Init_Super(argc,argv,0xd500);
}

void Init5200HW(int *argc,char **argv)
{
  SetBlank(0xc000,0xf7ff);
  Init_GTIA(argc,argv,0xc000);     /* 5200 GTIA chip */
  Init_Antic(argc,argv,0xd400);
  Init_Pokey(argc,argv,0xe800);
  Init_Pokey(argc,argv,0xeb00);
  EnableOs();
}

void HW_Reset(void)
{

  if (machine==Atari5200)
    Init5200HW(NULL,NULL);
  else
    InitHW(NULL,NULL);
}



/*
 * This removes any loaded cartridge ROM files from the emulator
 * It doesn't remove either the OS, FFP or character set ROMS.
 */

int Remove_ROM (void)
{
  RemoveCurrent();
  return TRUE;
}

#define STD_8K 1
#define STD_16K 2
#define OSS 3
#define AGS 4
#define AGS_16 5

int Insert_Cartridge (char *filename)
{
  int status = FALSE;
  int fd;

  fd = open (filename, O_RDONLY, 0777);
  if (fd != -1)
    {
      UBYTE header[16];

      read (fd, header, sizeof(header));
      if ((header[0] == 'C') &&
          (header[1] == 'A') &&
          (header[2] == 'R') &&
          (header[3] == 'T'))
        {
          int type;
          int checksum;

          type = (header[4] << 24) |
                 (header[5] << 16) |
                 (header[6] << 8) |
                 header[7];

          checksum = (header[4] << 24) |
                     (header[5] << 16) |
                     (header[6] << 8) |
                     header[7];

          switch (type)
            {
             case STD_8K :
		status = Insert_8K_ROM (NULL,fd);
                break;
              case STD_16K :
		status = Insert_16K_ROM (NULL,fd);
                break;
              case OSS :
		status = Insert_OSS_ROM (NULL,fd);
                break;
              case AGS :
		status = Insert_32K_5200ROM(NULL,fd);
                break;
	      case AGS_16:
		status = Insert_16K_5200ROM(NULL,fd);
		break;
              default :
                printf ("%s is in unsupported cartridge format %d\n",
                        filename, type);
                break;
            }
        }
      else
        {
          printf ("%s is not a cartridge\n", filename);
        }
      close (fd);
    }

  return status;
}

void Coldstart (void)
{

  HW_Reset ();
  Poke(0x244,1);
  Poke(0x9,0);
  Poke(0x33d,0);
  Poke(0x33e,0);
  Poke(0x33f,0);
  CPU_Reset ();

  if (hold_option)
    next_console_value = 0x03; /* Hold Option During Reboot */

}

void Warmstart (void)
{
  HW_Reset ();
  CPU_Reset ();
}

int Initialise_AtariOSA (int *argc,char **argv)
{
int status;

  FreeCarts();
  if ((status = ReadOSABRom(atari_osa_filename,0))!=0) {
    machine = Atari;
    PatchOsA ();
    SetRAM (0x0000, 0xbfff);
    if (enable_c000_ram)
      SetRAM (0xc000, 0xcfff);
    else
      SetROM (0xc000, 0xcfff);
    SetROM (0xd800, 0xffff);
    InitHW(argc,argv);
    Coldstart ();
  }

  return status;
}

int Initialise_AtariOSB (int *argc,char **argv)
{
int status;

  FreeCarts();
  if ((status = ReadOSABRom(atari_osb_filename,0))!=0) {
      machine = Atari;
      PatchOsB ();
      SetRAM (0x0000, 0xbfff);
      if (enable_c000_ram)
	SetRAM (0xc000, 0xcfff);
      else
	SetROM (0xc000, 0xcfff);
      SetROM (0xd800, 0xffff);
      InitHW(argc,argv);
      Coldstart ();
    }

  return status;
}

int Initialise_AtariXL (int *argc,char **argv)
{
int status;

  FreeCarts();
  if ((status = ReadXLRom(atari_xlxe_filename,0))!=0) {
      machine = AtariXL;
      PatchOsXL ();
      SetRAM (0x0000,0xbfff);
      status = ReadBasic(atari_basic_filename,0);
      InitHW(argc,argv);     
      Coldstart ();
    }

  return status;
}

int Initialise_AtariXE (int *argc,char **argv)
{
int status;

  FreeCarts();
  if ((status = Build_XE_Banks())!=0) {
    status = Initialise_AtariXL (argc,argv);
    machine = AtariXE;
  }
  return status;
}

int Initialise_Atari5200 (int *argc,char **argv)
{
int status;

  FreeCarts();
  if ((status = Read5200Rom (atari_5200_filename,0))!=0) {
      machine = Atari5200;
      SetRAM (0x0000, 0x3fff);
      SetROM (0x4000, 0xffff);
      Init5200HW (argc,argv);  /* This call installes the hardware */
      Coldstart ();
    }

  return status;
}

int main (int argc, char *argv[])
{
int status = FALSE;
int error;
int diskno = 1;
int i;
int j;
char *rom_filename = NULL;
int cart_type = CARTRIDGE;
int os = 1;

  char *rtconfig_filename = NULL;
  int config = FALSE;

  error = FALSE;

  for (i=j=1;i<argc;i++)
    {
      if (strcmp(argv[i],"-configure") == 0)
        config = TRUE;
      else if (strcmp(argv[i],"-config") == 0)
        rtconfig_filename = argv[++i];
      else if (strcmp(argv[i],"-v") == 0)
	{
	  printf ("%s\n", ATARI_TITLE);
	  exit (1);
	}
      else if (strcmp(argv[i],"-verbose") == 0)
        verbose = TRUE;
      else
	argv[j++] = argv[i];
    }

  argc = j;

  if (!RtConfigLoad (rtconfig_filename))
    config = TRUE;

  if (config)
    {
      RtConfigUpdate ();
      RtConfigSave ();
    }

  switch (default_system)
    {
      case 1 :
        machine = Atari;
        os = 1;
        break;
      case 2 :
        machine = Atari;
        os = 2;
        break;
      case 3 :
        machine = AtariXL;
        break;
      case 4 :
        machine = AtariXE;
        break;
      case 5 :
        machine = Atari5200;
        break;
      default :
        machine = AtariXL;
        break;
     } 

  switch (default_tv_mode)
    {
      case 1 :
      default :
        tv_mode = PAL;
        break;
      case 2 :
        tv_mode = NTSC;
        break;
    }

  for (i=j=1;i<argc;i++)
    {
      if (strcmp(argv[i],"-atari") == 0)
	machine = Atari;
      else if (strcmp(argv[i],"-xl") == 0)
	machine = AtariXL;
      else if (strcmp(argv[i],"-xe") == 0)
	machine = AtariXE;
      else if (strcmp(argv[i],"-5200") == 0)
	machine = Atari5200;
      else if (strcmp(argv[i],"-nobasic") == 0)
        hold_option = TRUE;
      else if (strcmp(argv[i],"-nopatch") == 0)
	enable_sio_patch = FALSE;
      else if (strcmp(argv[i],"-pal") == 0)
	tv_mode = PAL;
      else if (strcmp(argv[i],"-ntsc") == 0)
	tv_mode = NTSC;
      else if (strcmp(argv[i],"-osa_rom") == 0)
        strcpy (atari_osa_filename, argv[++i]);
      else if (strcmp(argv[i],"-osb_rom") == 0)
        strcpy (atari_osb_filename, argv[++i]);
      else if (strcmp(argv[i],"-xlxe_rom") == 0)
        strcpy (atari_xlxe_filename, argv[++i]);
      else if (strcmp(argv[i],"-5200_rom") == 0)
        strcpy (atari_5200_filename, argv[++i]);
      else if (strcmp(argv[i],"-basic_rom") == 0)
        strcpy (atari_basic_filename, argv[++i]);
      else if (strcmp(argv[i],"-cart") == 0)
        {
	  rom_filename = argv[++i];
	  cart_type = CARTRIDGE;
        }
      else if (strcmp(argv[i],"-rom") == 0)
	{
	  rom_filename = argv[++i];
	  cart_type = NORMAL8_CART;
	}
      else if (strcmp(argv[i],"-rom16") == 0)
	{
	  rom_filename = argv[++i];
	  cart_type = NORMAL16_CART;
	}
      else if (strcmp(argv[i],"-ags32") == 0)   /* 5200 32K rom */
	{
	  rom_filename = argv[++i];
	  cart_type = AGS32_CART;
	}
      else if (strcmp(argv[i],"-ags16") == 0)   /* 5200 16K rom */
	{
	  rom_filename = argv[++i];
	  cart_type = AGS16_CART;
	}
      else if (strcmp(argv[i],"-oss") == 0)
	{
	  rom_filename = argv[++i];
	  cart_type = OSS_SUPERCART;
	}
      else if (strcmp(argv[i],"-db") == 0)	/* db 16/32 superduper cart */
	{
	  rom_filename = argv[++i];
	  cart_type = DB_SUPERCART;
	}
      else if (strcmp(argv[i],"-refresh") == 0)
	{
	  sscanf (argv[++i],"%d", &refresh_rate);
	  if (refresh_rate < 1)
	    refresh_rate = 1;
	}
      else if (strcmp(argv[i],"-maxmiss") == 0)
	{
	  sscanf (argv[++i],"%d", &maxmiss);
	  if (maxmiss < 0)
	    maxmiss = 0;
	}
      else if (strcmp(argv[i],"-help") == 0)
	{
          printf ("\t-configure    Update Configuration File\n"
                  "\t-config fnm   Specify Alternate Configuration File\n"
                  "\t-atari        Standard Atari 800 mode\n"
                  "\t-xl           Atari XL mode\n"
                  "\t-xe           Atari XE mode (Currently same as -xl)\n"
                  "\t-5200         Atari 5200 Games System\n"
                  "\t-pal          Enable PAL TV mode\n"
                  "\t-ntsc         Enable NTSC TV mode\n"
                  "\t-rom fnm      Install standard 8K Cartridge\n"
	          "\t-rom16 fnm    Install standard 16K Cartridge\n"
		  "\t-ags32 fnm    Install 5200/AGS 32K Cartridge\n"
		  "\t-ags16 fnm    Install 5200/AGS 16K Cartridge\n"
	          "\t-oss fnm      Install OSS Super Cartridge\n"
	          "\t-db fnm       Install DB's 16/32K Cartridge (not for normal use)\n"
	          "\t-refresh num  Specify screen refresh rate\n"
	          "\t-maxmiss num  Specify maximal number of missing frames\n"
		  "\t-artefacts    Generate artefacts for half color clock graphics\n"
	          "\t-nopatch      Don't patch SIO routine in OS\n"
	          "\t-a            Use A OS\n"
	          "\t-b            Use B OS\n"
                  "\t-c            Enable RAM between 0xc000 and 0xd000\n"
	          "\t-v            Show version/release number\n");
	  argv[j++] = argv[i];
	}
      else if (strcmp(argv[i],"-a") == 0)
	os = 1;
      else if (strcmp(argv[i],"-b") == 0)
	os = 2;
      else if (strcmp(argv[i],"-c") == 0)
	enable_c000_ram = TRUE;
      else
	argv[j++] = argv[i];
    }

  argc = j;

  Device_Initialise (&argc, argv);
  SIO_Initialise (&argc, argv);

  Atari_Initialise (&argc, argv); /* Platform Specific Initialisation */

  if (!atari_screen)
    {
      atari_screen = (ULONG*)malloc((ATARI_HEIGHT+16) * ATARI_MODULO);
      for (i=0;i<256;i++)
	colour_translation_table[i] = i;
    }

  /*
   * Configure Atari System
   */

  switch (machine)
    {
    case Atari :
      if (os == 1)
	status = Initialise_AtariOSA (&argc,argv);
      else
	status = Initialise_AtariOSB (&argc,argv);
      break;
    case AtariXL :
      status = Initialise_AtariXL (&argc,argv);
      break;
    case AtariXE :
      status = Initialise_AtariXE (&argc,argv);
      break;
    case Atari5200 :
      status = Initialise_Atari5200 (&argc,argv);
      break;
    default :
      printf ("Fatal Error in atari.c\n");
      Atari800_Exit (FALSE, 1);
    }

  /*
   * Any parameters left on the command line must be disk images.
   */

  for (i=1;i<argc;i++)
    {
      if (!SIO_Mount (diskno++, argv[i]))
	{
	  printf ("Disk File %s not found\n", argv[i]);
	  error = TRUE;
	}
    }

  if (error)
    {
      printf ("Usage: %s [-rom filename] [-oss filename] [diskfile1...diskfile8]\n", argv[0]);
      printf ("\t-help         Extended Help\n");
      Atari800_Exit (FALSE, 1);
    }

  /*
   * Install CTRL-C Handler
   */

  signal (SIGINT, sigint_handler);

  if (!status)
    {
      printf ("Operating System not available\n");
      Atari800_Exit (FALSE, 1);
    }
/*
 * ================================
 * Install requested ROM cartridges
 * ================================
 */
  if (rom_filename)
    {
      switch (cart_type)
	{
        case CARTRIDGE :
          status = Insert_Cartridge (rom_filename);
          break;
	case OSS_SUPERCART :
	  status = Insert_OSS_ROM (rom_filename,0);
	  break;
	case DB_SUPERCART :
	  status = Insert_DB_ROM (rom_filename,0);
	  break;
	case NORMAL8_CART :
	  status = Insert_8K_ROM (rom_filename,0);
	  break;
	case NORMAL16_CART :
	  status = Insert_16K_ROM (rom_filename,0);
	  break;
	case AGS32_CART :
	  status = Insert_32K_5200ROM (rom_filename,0);
	  break;
	case AGS16_CART :
	  status = Insert_16K_5200ROM (rom_filename,0);
	  break;
	}
    }

/*
 * ======================================
 * Reset CPU and start hardware emulation
 * ======================================
 */

  Atari800_Hardware ();
  printf("Fatal error: Atari800_Hardware() returned\n");
  Atari800_Exit(FALSE, 1);
  return 1;
}

/* This is now the global exit routine.
	run_monitor = TRUE : run the monitor, exit only if QUIT was chosen
	exitcode :  The global return
   Note that the function only returns if run_monitor=TRUE and "CONT" was
   given.  Return will be 1. */
int Atari800_Exit (int run_monitor, int exitcode)
{

  int ret=Atari_Exit (run_monitor);

  if (!ret)
	{
  	int diskno;

	for (diskno=1;diskno<8;diskno++)
		SIO_Dismount (diskno);

	FreeCarts();
	Free_XE_Banks();
	exit(exitcode);
	}

  return ret;
}


/* Get the time as number of micros */
#ifndef DJGPP
unsigned long PreciseTime(void)
{      
static struct timeval tp;
static struct timezone tzp;

  gettimeofday (&tp, &tzp);
  /* printf("%d,%d\n",tp.tv_sec,tp.tv_usec); */
  return (tp.tv_sec * 1000000) + tp.tv_usec;
}
#else
unsigned long PreciseTime(void)
{
// for dos, count ticks and use the ticks_per_second global variable
// Use the high speed clock tick function uclock()
uclock_t curtime;
unsigned int time;

  curtime = uclock();
  time = (unsigned long) curtime;
  time *= 1000000/UCLOCKS_PER_SEC;
  
  return time;
}
#endif


void Atari800_Hardware (void)
{
int	        pil_on = FALSE;
ULONG           scanrate=1000000/60;
ULONG           lasttime=0;
ULONG           thistime=0;
int             keycode;
int             unref_cnt = 0;

  if (tv_mode == PAL)
    scanrate = 1000000/50;

   scanrate *= refresh_rate;

  while (TRUE)
    {
#ifndef BASIC
/*
      colour_lookup[8] = colour_translation_table[COLBK];
*/

      keycode = Atari_Keyboard ();

      switch (keycode)
	{
	case AKEY_COLDSTART :
	  Coldstart ();
	  break;
	case AKEY_WARMSTART :
	  Warmstart ();
	  break;
	case AKEY_EXIT :
	  Atari800_Exit (FALSE, 1);
	  break;
	case AKEY_BREAK :
	  SendBRK();
	  break;
        case AKEY_UI :
          ui ((UBYTE *)atari_screen);
          break;
	case AKEY_PIL :
	  if (pil_on)
	    {
	      SetRAM (0x8000, 0xbfff);
	      pil_on = FALSE;
	    }
	  else
	    {
	      SetROM (0x8000, 0xbfff);
	      pil_on = TRUE;
	    }
	  break;
	case AKEY_NONE :
	  break;
	default :
	  SendKey(keycode);
	  break;
	}
#endif

      /*
       * Generate Screen
       */

      ANTIC_RunDisplayList();
      thistime=PreciseTime();
      /* printf("%lu,%lu,%lu\n",thistime,lasttime,lasttime+scanrate); */
      if (unref_cnt>=maxmiss || thistime<=lasttime+scanrate) {
	Atari_DisplayScreen ((UBYTE*)atari_screen);
	unref_cnt = 0;
      } else {
	unref_cnt++;
      }
      lasttime=thistime;
    }
}

