//
// Copyright (c) 2004 K. Wilkins
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from
// the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgment in the product documentation would be
//    appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
//    be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//

//////////////////////////////////////////////////////////////////////////////
//                       Handy - An Atari Lynx Emulator                     //
//                          Copyright (c) 1996,1997                         //
//                                 K. Wilkins                               //
//////////////////////////////////////////////////////////////////////////////
// System object class                                                      //
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
// This class provides the glue to bind of of the emulation objects         //
// together via peek/poke handlers and pass thru interfaces to lower        //
// objects, all control of the emulator is done via this class. Update()    //
// does most of the work and each call emulates one CPU instruction and     //
// updates all of the relevant hardware if required. It must be remembered  //
// that if that instruction involves setting SPRGO then, it will cause a    //
// sprite painting operation and then a corresponding update of all of the  //
// hardware which will usually involve recursive calls to Update, see       //
// Mikey SPRGO code for more details.                                       //
//                                                                          //
//    K. Wilkins                                                            //
// August 1997                                                              //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////
// Revision History:                                                        //
// -----------------                                                        //
//                                                                          //
// 01Aug1997 KW Document header added & class documented.                   //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////

#define SYSTEM_CPP

//#include <crtdbg.h>
//#define	TRACE_SYSTEM

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "system.h"

#include "../movie.h"
#include "../general.h"
#include "../netplay.h"

CSystem::CSystem(uint8 *filememory, int32 filesize)
	:mCart(NULL),
	mRom(NULL),
	mMemMap(NULL),
	mRam(NULL),
	mCpu(NULL),
	mMikie(NULL),
	mSusie(NULL)
{
	mFileType=HANDY_FILETYPE_LNX;

	// Now try and determine the filetype we have opened
	if(filesize)
	{
		char clip[11];
		memcpy(clip,filememory,11);
		clip[4]=0;
		clip[10]=0;

		if(!strcmp(&clip[6],"BS93")) mFileType=HANDY_FILETYPE_HOMEBREW;
		else if(!strcmp(&clip[0],"LYNX")) mFileType=HANDY_FILETYPE_LNX;
		else
		{
			throw(-1);
			//CLynxException lynxerr;
			//delete filememory;
			//mFileType=HANDY_FILETYPE_ILLEGAL;
			//lynxerr.Message() << "Handy Error: File format invalid!";
			//lynxerr.Description()
			//	<< "The image you selected was not a recognised game cartridge format." << endl
			//	<< "(see the Handy User Guide for more information).";
			//throw(lynxerr);
		}
	}
	
// Create the system objects that we'll use

	// Attempt to load the cartridge errors caught above here...

	mRom = new CRom(MDFN_MakeFName(MDFNMKF_LYNXROM,0,0).c_str());

	// An exception from this will be caught by the level above

	switch(mFileType)
	{
		case HANDY_FILETYPE_LNX:
			mCart = new CCart(filememory,filesize);
			mRam = new CRam(0,0);
			break;
		case HANDY_FILETYPE_HOMEBREW:
			mCart = new CCart(0,0);
			mRam = new CRam(filememory,filesize);
			break;
		case HANDY_FILETYPE_SNAPSHOT:
		case HANDY_FILETYPE_ILLEGAL:
		default:
			mCart = new CCart(0,0);
			mRam = new CRam(0,0);
			break;
	}

	// These can generate exceptions

	mMikie = new CMikie(*this);
	mSusie = new CSusie(*this);

// Instantiate the memory map handler

	mMemMap = new CMemMap(*this);

// Now the handlers are set we can instantiate the CPU as is will use handlers on reset

	mCpu = new C65C02(*this);

// Now init is complete do a reset, this will cause many things to be reset twice
// but what the hell, who cares, I don't.....

	Reset();
}

CSystem::~CSystem()
{
	// Cleanup all our objects

	if(mCart!=NULL) delete mCart;
	if(mRom!=NULL) delete mRom;
	if(mRam!=NULL) delete mRam;
	if(mCpu!=NULL) delete mCpu;
	if(mMikie!=NULL) delete mMikie;
	if(mSusie!=NULL) delete mSusie;
	if(mMemMap!=NULL) delete mMemMap;
}

void CSystem::Reset(void)
{
	gSystemCycleCount=0;
	gNextTimerEvent=0;
	gCPUBootAddress=0;
	gSingleStepMode=FALSE;
	gSingleStepModeSprites=FALSE;
	gSystemIRQ=FALSE;
	gSystemNMI=FALSE;
	gSystemCPUSleep=FALSE;
	gSystemHalt=FALSE;
	gSuzieDoneTime = 0;

	mMemMap->Reset();
	mCart->Reset();
	mRom->Reset();
	mRam->Reset();
	mMikie->Reset();
	mSusie->Reset();
	mCpu->Reset();

	// Homebrew hashup

	if(mFileType==HANDY_FILETYPE_HOMEBREW)
	{
		mMikie->PresetForHomebrew();

		C6502_REGS regs;
		mCpu->GetRegs(regs);
		regs.PC=(UWORD)gCPUBootAddress;
		mCpu->SetRegs(regs);
	}
}

static CSystem *lynxie;
extern MDFNGI EmulatedLynx;

#include <fidlib.h>

static FidFilter *fid = NULL;
static FidRun *fidrun = NULL;
static FidFunc *fidfuncp = NULL;
static void *fidbuf = NULL;

static void FreeFid(void)
{
 if(fidbuf)
 {
  fid_run_freebuf(fidbuf);
  fidbuf = 0;
 }
 if(fidrun)
 {
  fid_run_free(fidrun);
  fidrun = 0;
 }
 if(fid)
 {
  free(fid);
  fid = 0;
 }
}

static void FidInit(uint32 rate)
{
 char *spec = "LpBuZ1/4000";
 fid_parse(rate, &spec, &fid);
 fidrun = fid_run_new(fid, &fidfuncp);
 fidbuf = fid_run_newbuf(fidrun);
}

static int Load(const char *name, MDFNFILE *fp)
{
 try
 {
  lynxie = new CSystem(fp->data, fp->size);
 }
 catch(int i)
 {
  // FIXME:  erhm, free memory here? 
 return(i);
 }


 lynxie->DisplaySetAttributes(FSettings.rshift, FSettings.gshift, FSettings.bshift, 256 * sizeof(uint32));

 lynxie->mMikie->mikbuf.set_sample_rate(FSettings.SndRate? FSettings.SndRate : 44100, 60);
 lynxie->mMikie->mikbuf.clock_rate(16000000 / 4);
 lynxie->mMikie->mikbuf.bass_freq(60);

 lynxie->mMikie->miksynth.volume(0.50);

 MDFNGameInfo->soundchan = 1;

 int rot = lynxie->CartGetRotate();
 if(rot == CART_ROTATE_LEFT) MDFNGameInfo->rotated = -1;
 else if(rot == CART_ROTATE_RIGHT) MDFNGameInfo->rotated = 1;

 gAudioEnabled = 1;

 if(FSettings.SndRate)
  FidInit(FSettings.SndRate);


 MDFNGameInfo->fps = (uint32)(59.8 * 65536 * 256);
 return(1);
}

static void CloseGame(void)
{

}
static int needrew = 0;
static void DoRewind(void)
{
 needrew = 1;
}

static uint16 *chee;

static void Emulate(uint32 *pXBuf, MDFN_Rect *LineWidths, float **SoundBuf, int32 *SoundBufSize, int skip)
{
 SFORMAT JoyRegs[] =
 {
  SFVAR(*chee),
  SFEND
 };
 MDFNGameInfo->fb = pXBuf;

 MDFNMOV_AddJoy(JoyRegs);
 lynxie->SetButtonData(*chee);

 MDFN_StateEvil(needrew);
 needrew = 0;

 lynxie->mMikie->mpSkipFrame = skip;
 lynxie->mMikie->mpDisplayCurrent = (UBYTE *)pXBuf;
 lynxie->mMikie->startTS = gSystemCycleCount;

 while(lynxie->mMikie->mpDisplayCurrent && (gSystemCycleCount - lynxie->mMikie->startTS) < 700000)
 {
  lynxie->Update();
//  printf("%d ", gSystemCycleCount - lynxie->mMikie->startTS);
 }

 if(FSettings.SndRate)
 {
  static float yaybuf[8000];
  int love;

  lynxie->mMikie->mikbuf.end_frame((gSystemCycleCount - lynxie->mMikie->startTS) >> 2);
  love = lynxie->mMikie->mikbuf.read_samples(yaybuf, 8000);

  fidfuncp(fidbuf, yaybuf, love, 1);
  //printf("%d %d\n",love, gSystemCycleCount - lynxie->mMikie->startTS);
  *SoundBufSize = love;
  *SoundBuf = yaybuf;
 }
 else
 {
  *SoundBufSize = 0;
  *SoundBuf = NULL;
 }
}

static void SetPixelFormat(int rs, int gs, int bs)
{
 lynxie->DisplaySetAttributes(rs, gs, bs, 256 * sizeof(uint32));
}

static void SetInput(int port, int type, void *ptr, int attrib)
{
 if(!port) chee = (uint16 *)ptr;
}

static void SetSoundMultiplier(double mult)
{
 lynxie->mMikie->mikbuf.set_sample_rate(FSettings.SndRate? FSettings.SndRate : 44100, 60);
 lynxie->mMikie->mikbuf.clock_rate((long int)(16000000 * mult / 4));
}

static void SetSoundVolume(uint32 volume)
{

}

static void Sound(int32 rate)
{
 lynxie->mMikie->mikbuf.set_sample_rate(rate? rate : 44100, 60);
 lynxie->mMikie->mikbuf.clock_rate((long int)(16000000 * FSettings.soundmultiplier / 4));

 FreeFid();
 if(rate)
  FidInit(rate);
}

static int StateAction(StateMem *sm, int load, int data_only)
{
 SFORMAT SystemRegs[] =
 {
	SFVAR(gSuzieDoneTime),
        SFVAR(gSystemCycleCount),
        SFVAR(gNextTimerEvent),
        SFVAR(gCPUBootAddress),
        SFVAR(gSingleStepMode),
        SFVAR(gSystemIRQ),
        SFVAR(gSystemNMI),
        SFVAR(gSystemCPUSleep),
        SFVAR(gSystemHalt),
	{lynxie->GetRamPointer(), RAM_SIZE, "RAM"},
	SFEND
 };
 std::vector <SSDescriptor> love;

 love.push_back(SSDescriptor(SystemRegs, "SYST"));
 MDFNSS_StateAction(sm, load, data_only, love);

 if(!lynxie->mSusie->StateAction(sm, load, data_only))
  return(0);
 if(!lynxie->mMemMap->StateAction(sm, load, data_only))
  return(0);

 if(!lynxie->mCart->StateAction(sm, load, data_only))
  return(0);

 if(!lynxie->mMikie->StateAction(sm, load, data_only))
  return(0);

 if(!lynxie->mCpu->StateAction(sm, load, data_only))
  return(0);

 return(1);
}

static bool ToggleLayer(int which)
{

 return(1);
}

static void DoSimpleCommand(int cmd)
{
 switch(cmd)
 {
  case MDFNNPCMD_POWER:
  case MDFNNPCMD_RESET: lynxie->Reset(); break;
 }
}

MDFNGI EmulatedLynx =
{
 GISYS_LYNX,
 "lynx",
 Load,
 NULL,
 CloseGame,
 ToggleLayer,
 "",
 StateAction,
 DoRewind,
 Emulate,
 SetPixelFormat,
 SetInput,
 SetSoundMultiplier,
 SetSoundVolume,
 Sound,
 DoSimpleCommand,
 NULL, // No netplay
 NULL,
 0,
 NULL,
 160,
 102,
 160,
 256 * sizeof(uint32),
 {0, 0, 160, 102},
};

