//
// 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                               //
//////////////////////////////////////////////////////////////////////////////
// Lynx Cartridge Class                                                     //
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
// This class emulates the Lynx cartridge interface, given a filename it    //
// will contstruct a cartridge object via the constructor.                  //
//                                                                          //
//    K. Wilkins                                                            //
// August 1997                                                              //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////
// Revision History:                                                        //
// -----------------                                                        //
//                                                                          //
// 01Aug1997 KW Document header added & class documented.                   //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////

#define CART_CPP

#include "system.h"

#include <algorithm>
#include <string.h>
#include "cart.h"
#include <mednafen/state.h>
#include <mednafen/hash/md5.h>

LYNX_HEADER CCart::DecodeHeader(const uint8 *data)
{
 LYNX_HEADER header;

 memcpy(header.magic, data, 4);
 data += 4;

 header.page_size_bank0 = MDFN_de16lsb(data);
 data += 2;

 header.page_size_bank1 = MDFN_de16lsb(data);
 data += 2;

 header.version = MDFN_de16lsb(data);
 data += 2;

 memcpy(header.cartname, data, 32);
 data += 32;

 memcpy(header.manufname, data, 16);
 data += 16;

 header.rotation = *data;
 data++;

 memcpy(header.spare, data, 5);
 data += 5;

 return(header);
}

bool CCart::TestMagic(const uint8 *data, uint32 size)
{
 if(size < HEADER_RAW_SIZE)
  return(FALSE);

 if(memcmp(data, "LYNX", 4) || data[8] != 0x01)
  return(FALSE);

 return(TRUE);
}

CCart::CCart(Stream* fp)
{
	uint64 gamesize;
	uint8 raw_header[HEADER_RAW_SIZE];
	LYNX_HEADER	header;
	uint32 loop;

	mWriteEnableBank0 = FALSE;
	mWriteEnableBank1 = FALSE;
	mCartRAM = FALSE;

	if(fp)
	{
	 gamesize = fp->size();
	 // Checkout the header bytes
	 if(gamesize < HEADER_RAW_SIZE)
	 {
	  throw MDFN_Error(0, _("Lynx ROM image is too small: %llu bytes"), (unsigned long long)gamesize);
	 }

	 fp->read(raw_header, HEADER_RAW_SIZE);
	}
	else
	{
	 gamesize = HEADER_RAW_SIZE;
	 memset(raw_header, 0, sizeof(raw_header));
	 memcpy(raw_header, "LYNX", 4);
	 raw_header[8] = 0x01;
	}

	header = DecodeHeader(raw_header);
	gamesize -= HEADER_RAW_SIZE;

	InfoROMSize = gamesize;

	// Sanity checks on the header
	if(header.magic[0]!='L' || header.magic[1]!='Y' || header.magic[2]!='N' || header.magic[3]!='X' || header.version!=1)
	{
                throw MDFN_Error(0, _("Missing or corrupted \"LYNX\" header magic."));
	}

	// Setup name & manufacturer
	strncpy(mName,(char*)&header.cartname, 32);
	strncpy(mManufacturer,(char*)&header.manufname, 16);

	// Setup rotation
	mRotation=header.rotation;
	if(mRotation!=CART_NO_ROTATE && mRotation!=CART_ROTATE_LEFT && mRotation!=CART_ROTATE_RIGHT) mRotation=CART_NO_ROTATE;

	// Set the filetypes

	CTYPE banktype0,banktype1;

	switch(header.page_size_bank0)
	{
		case 0x000:
			banktype0=UNUSED;
			mMaskBank0=0;
			mShiftCount0=0;
			mCountMask0=0;
			break;
		case 0x100:
			banktype0=C64K;
			mMaskBank0=0x00ffff;
			mShiftCount0=8;
			mCountMask0=0x0ff;
			break;
		case 0x200:
			banktype0=C128K;
			mMaskBank0=0x01ffff;
			mShiftCount0=9;
			mCountMask0=0x1ff;
			break;
		case 0x400:
			banktype0=C256K;
			mMaskBank0=0x03ffff;
			mShiftCount0=10;
			mCountMask0=0x3ff;
			break;
		case 0x800:
			banktype0=C512K;
			mMaskBank0=0x07ffff;
			mShiftCount0=11;
			mCountMask0=0x7ff;
			break;
		default:
			throw MDFN_Error(0, _("Lynx file format invalid (Bank0)"));
			break;
	}

	switch(header.page_size_bank1)
	{
		case 0x000:
			banktype1=UNUSED;
			mMaskBank1=0;
			mShiftCount1=0;
			mCountMask1=0;
			break;
		case 0x100:
			banktype1=C64K;
			mMaskBank1=0x00ffff;
			mShiftCount1=8;
			mCountMask1=0x0ff;
			break;
		case 0x200:
			banktype1=C128K;
			mMaskBank1=0x01ffff;
			mShiftCount1=9;
			mCountMask1=0x1ff;
			break;
		case 0x400:
			banktype1=C256K;
			mMaskBank1=0x03ffff;
			mShiftCount1=10;
			mCountMask1=0x3ff;
			break;
		case 0x800:
			banktype1=C512K;
			mMaskBank1=0x07ffff;
			mShiftCount1=11;
			mCountMask1=0x7ff;
			break;
		default:
			throw MDFN_Error(0, _("Lynx file format invalid (Bank1)"));
			break;
	}

	// Make some space for the new carts

	mCartBank0.reset(new uint8[mMaskBank0+1]);
	mCartBank1.reset(new uint8[mMaskBank1+1]);

	// Set default bank

	mBank=bank0;

	// Initialiase

	for(loop=0;loop<mMaskBank0+1;loop++)
		mCartBank0[loop]=DEFAULT_CART_CONTENTS;

	for(loop=0;loop<mMaskBank1+1;loop++)
		mCartBank1[loop]=DEFAULT_CART_CONTENTS;

	// Read in the BANK0 bytes

        md5_context md5;
        md5.starts();

        if(mMaskBank0)
        {
         uint64 size = std::min<uint64>(gamesize, mMaskBank0+1);
         fp->read(mCartBank0.get(), size);
         md5.update(mCartBank0.get(), size);
         gamesize -= size;
        }

        // Read in the BANK0 bytes
        if(mMaskBank1)
        {
         uint64 size = std::min<uint64>(gamesize, mMaskBank1+1);
	 fp->read(mCartBank1.get(), size);
         md5.update(mCartBank1.get(), size);
        }

        md5.finish(MD5);

	// As this is a cartridge boot unset the boot address

	gCPUBootAddress=0;

	// Dont allow an empty Bank1 - Use it for shadow SRAM/EEPROM
	if(banktype1==UNUSED)
	{
		// Allocate some new memory for us
		banktype1=C64K;
		mMaskBank1=0x00ffff;
		mShiftCount1=8;
		mCountMask1=0x0ff;
		mCartBank1.reset(new uint8[mMaskBank1+1]);
		for(loop=0;loop<mMaskBank1+1;loop++) mCartBank1[loop]=DEFAULT_RAM_CONTENTS;
		mWriteEnableBank1=TRUE;
		mCartRAM=TRUE;
	}
}

CCart::~CCart()
{

}


void CCart::Reset(void)
{
	mCounter=0;
	mShifter=0;
	mAddrData=0;
	mStrobe=0;
	last_strobe = 0;
}

INLINE void CCart::Poke(uint32 addr, uint8 data)
{
	if(mBank==bank0)
	{
		if(mWriteEnableBank0) mCartBank0[addr&mMaskBank0]=data;
	}
	else
	{
		if(mWriteEnableBank1) mCartBank1[addr&mMaskBank1]=data;
	}
}


INLINE uint8 CCart::Peek(uint32 addr)
{
	if(mBank==bank0)
	{
		return(mCartBank0[addr&mMaskBank0]);
	}
	else
	{
		return(mCartBank1[addr&mMaskBank1]);
	}
}


void CCart::CartAddressStrobe(bool strobe)
{
	mStrobe=strobe;

	if(mStrobe) mCounter=0;

	//
	// Either of the two below seem to work OK.
	//
	// if(!strobe && last_strobe)
	//
	if(mStrobe && !last_strobe)
	{
		// Clock a bit into the shifter
		mShifter=mShifter<<1;
		mShifter+=mAddrData?1:0;
		mShifter&=0xff;
	}
	last_strobe=mStrobe;
}

void CCart::CartAddressData(bool data)
{
	mAddrData=data;
}


void CCart::Poke0(uint8 data)
{
	if(mWriteEnableBank0)
	{
		uint32 address=(mShifter<<mShiftCount0)+(mCounter&mCountMask0);
		mCartBank0[address&mMaskBank0]=data;		
	}
	if(!mStrobe)
	{
		mCounter++;
		mCounter&=0x07ff;
	}
}

void CCart::Poke1(uint8 data)
{
	if(mWriteEnableBank1)
	{
		uint32 address=(mShifter<<mShiftCount1)+(mCounter&mCountMask1);
		mCartBank1[address&mMaskBank1]=data;		
	}
	if(!mStrobe)
	{
		mCounter++;
		mCounter&=0x07ff;
	}
}


uint8 CCart::Peek0(void)
{
	uint32 address=(mShifter<<mShiftCount0)+(mCounter&mCountMask0);
	uint8 data=mCartBank0[address&mMaskBank0];		

	if(!mStrobe)
	{
		mCounter++;
		mCounter&=0x07ff;
	}

	return data;
}

uint8 CCart::Peek1(void)
{
	uint32 address=(mShifter<<mShiftCount1)+(mCounter&mCountMask1);
	uint8 data=mCartBank1[address&mMaskBank1];		

	if(!mStrobe)
	{
		mCounter++;
		mCounter&=0x07ff;
	}

	return data;
}


void CCart::StateAction(StateMem *sm, const unsigned load, const bool data_only)
{
 SFORMAT CartRegs[] =
 {
        SFVAR(mCounter),
        SFVAR(mShifter),
        SFVAR(mAddrData),
        SFVAR(mStrobe),
        SFVAR(mShiftCount0),
        SFVAR(mCountMask0),
        SFVAR(mShiftCount1),
        SFVAR(mCountMask1),
        SFVAR(mBank),
        SFVAR(mWriteEnableBank0),
        SFVAR(mWriteEnableBank1),
	SFVAR(last_strobe),
	SFARRAYN(mCartBank1.get(), mCartRAM ? mMaskBank1 + 1 : 0, "mCartBank1"),
	SFEND
 };

 MDFNSS_StateAction(sm, load, data_only, CartRegs, "CART");

 if(load)
 {

 }
}

