#include "common.h"

Disc* chd_parse(const wchar* file);
Disc* gdi_parse(const wchar* file);
Disc* cdi_parse(const wchar* file);

u32 NullDriveDiscType;
Disc* disc;

Disc*(*drivers[])(const wchar* path)=
{
	chd_parse,
	gdi_parse,
	cdi_parse,
	0
};

u8 q_subchannel[96];

static void PatchRegion_0(u8* sector,int size)
{
#ifndef NOT_REICAST
	if (settings.imgread.PatchRegion==0)
		return;
#else
	return;
#endif

	u8* usersect=sector;

	if (size!=2048)
	{
		printf("PatchRegion_0 -> sector size %d , skipping patch\n",size);
	}

	//patch meta info
	u8* p_area_symbol=&usersect[0x30];
	memcpy(p_area_symbol,"JUE     ",8);
}

static void PatchRegion_6(u8* sector,int size)
{
#ifndef NOT_REICAST
	if (settings.imgread.PatchRegion==0)
		return;
#else
	return;
#endif

	u8* usersect=sector;

	if (size!=2048)
		printf("PatchRegion_6 -> sector size %d , skipping patch\n",size);

	//patch area symbols
	u8* p_area_text=&usersect[0x700];
	memcpy(&p_area_text[4],"For JAPAN,TAIWAN,PHILIPINES.",28);
	memcpy(&p_area_text[4 + 32],"For USA and CANADA.         ",28);
	memcpy(&p_area_text[4 + 32 + 32],"For EUROPE.                 ",28);
}

bool ConvertSector(u8* in_buff , u8* out_buff , int from , int to,int sector)
{
   //get subchannel data, if any
   if (from==2448)
   {
      memcpy(q_subchannel,in_buff+2352,96);
      from-=96;
   }
   //if no conversion
   if (to==from)
   {
      memcpy(out_buff,in_buff,to);
      return true;
   }
   switch (to)
   {
      case 2340:
         verify((from==2352));
         memcpy(out_buff,&in_buff[12],2340);
         break;
      case 2328:
         verify((from==2352));
         memcpy(out_buff,&in_buff[24],2328);
         break;
      case 2336:
         verify(from>=2336);
         verify((from==2352));
         memcpy(out_buff,&in_buff[0x10],2336);
         break;
      case 2048:
         {
            verify(from>=2048);
            verify((from==2448) || (from==2352) || (from==2336));
            if ((from == 2352) || (from == 2448))
            {
               if (in_buff[15]==1)
                  memcpy(out_buff,&in_buff[0x10],2048); //0x10 -> mode1
               else
                  memcpy(out_buff,&in_buff[0x18],2048); //0x18 -> mode2 (all forms ?)
            }
            else
               memcpy(out_buff,&in_buff[0x8],2048);	//hmm only possible on mode2.Skip the mode2 header
         }
         break;
      case 2352:
         //if (from >= 2352)
         {
            memcpy(out_buff,&in_buff[0],2352);
         }
         break;
      default :
         printf("Sector conversion from %d to %d not supported \n", from , to);
         break;
   }

   return true;
}

Disc* OpenDisc(const wchar* fn)
{
	Disc* rv;
	
	for (int i=0;drivers[i] && !(rv=drivers[i](fn));i++) ;

	return rv;
}

bool InitDrive_(wchar* fn)
{
	TermDrive();

	//try all drivers
	disc = OpenDisc(fn);

	if (disc!=0)
	{
		printf("gdrom: Opened image \"%s\"\n",fn);
		NullDriveDiscType=Busy;
#ifndef NOT_REICAST
		libCore_gdrom_disc_change();
#endif
//		Sleep(400); //busy for a bit // what, really ?
		return true;
	}
	else
	{
		printf("gdrom: Failed to open image \"%s\"\n",fn);
		NullDriveDiscType=NoDisk; //no disc :)
	}
	return false;
}

#ifndef NOT_REICAST
bool InitDrive(u32 fileflags)
{
	if (settings.imgread.LoadDefaultImage)
	{
		printf("Loading default image \"%s\"\n",settings.imgread.DefaultImage);
		if (!InitDrive_(settings.imgread.DefaultImage))
		{
			msgboxf("Default image \"%s\" failed to load",MBX_ICONERROR);
			return false;
		}
      return true;
	}

	wchar fn[512];
	strcpy(fn,settings.imgread.LastImage);
   int gfrv = 0;
   if (settings.System == DC_PLATFORM_DREAMCAST)
      gfrv=GetFile(fn,0,fileflags);

   switch (gfrv)
   {
      case 0:
         NullDriveDiscType=NoDisk;
         gd_setdisc();
         sns_asc=0x29;
         sns_ascq=0x00;
         sns_key=0x6;
         return true;
      case -1:
         return false;
   }

	strcpy(settings.imgread.LastImage,fn);
	SaveSettings();

	if (!InitDrive_(fn))
	{
		//msgboxf("Selected image failed to load",MBX_ICONERROR);
			NullDriveDiscType=NoDisk;
			gd_setdisc();
			sns_asc=0x29;
			sns_ascq=0x00;
			sns_key=0x6;
		return true;
	}
   return true;
}

bool DiscSwap(u32 fileflags)
{
	if (settings.imgread.LoadDefaultImage)
	{
		printf("Loading default image \"%s\"\n",settings.imgread.DefaultImage);
		if (!InitDrive_(settings.imgread.DefaultImage))
		{
			msgboxf("Default image \"%s\" failed to load",MBX_ICONERROR);
			return false;
		}
      return true;
	}

	wchar fn[512];
	strcpy(fn,settings.imgread.LastImage);
   int gfrv = 0;
   if (settings.System == DC_PLATFORM_DREAMCAST)
      gfrv=GetFile(fn,0,fileflags);
	if (gfrv == 0)
	{
		NullDriveDiscType=Open;
		gd_setdisc();
		sns_asc=0x28;
		sns_ascq=0x00;
		sns_key=0x6;
		return true;
	}
	else if (gfrv == -1)
	{
		sns_asc=0x28;
		sns_ascq=0x00;
		sns_key=0x6;
		return false;
	}

	strcpy(settings.imgread.LastImage,fn);
	SaveSettings();

	if (!InitDrive_(fn))
	{
		//msgboxf("Selected image failed to load",MBX_ICONERROR);
		NullDriveDiscType=Open;
		gd_setdisc();
		sns_asc=0x28;
		sns_ascq=0x00;
		sns_key=0x6;
		return true;
	}
	else
	{
		sns_asc=0x28;
		sns_ascq=0x00;
		sns_key=0x6;
		return true;
	}
}
#endif

void TermDrive()
{
	if (disc!=0)
		delete disc;

	disc=0;
}


//
//convert our nice toc struct to dc's native one :)

static u32 CreateTrackInfo(u32 ctrl,u32 addr,u32 fad)
{
	u8 p[4];
#ifdef MSB_FIRST
	p[1]=(ctrl<<4)|(addr<<0);
	p[0]=fad>>16;
	p[3]=fad>>8;
	p[2]=fad>>0;
#else
	p[0]=(ctrl<<4)|(addr<<0);
	p[1]=fad>>16;
	p[2]=fad>>8;
	p[3]=fad>>0;
#endif

	return *(u32*)p;
}

static u32 CreateTrackInfo_se(u32 ctrl,u32 addr,u32 tracknum)
{
	u8 p[4];
#ifdef MSB_FIRST
	p[1]=(ctrl<<4)|(addr<<0);
	p[0]=tracknum;
	p[3]=0;
	p[2]=0;
#else
	p[0]=(ctrl<<4)|(addr<<0);
	p[1]=tracknum;
	p[2]=0;
	p[3]=0;
#endif
	return *(u32*)p;
}


void GetDriveSector(u8 * buff,u32 StartSector,u32 SectorCount,u32 secsz)
{
   //printf("GD: read %08X, %d\n",StartSector,SectorCount);
   if (!disc)
      return;

   disc->ReadSectors(StartSector,SectorCount,buff,secsz);

   if (disc->type == GdRom && StartSector==45150 && SectorCount==7)
   {
      PatchRegion_0(buff,secsz);
      PatchRegion_6(buff+2048*6,secsz);
   }
}
void GetDriveToc(u32* to,DiskArea area)
{
	if (!disc)
		return;
	memset(to,0xFFFFFFFF,102*4);

	//can't get toc on the second area on discs that don't have it
	verify(area != DoubleDensity || disc->type == GdRom);

	//normal CDs: 1 .. tc
	//GDROM: area0 is 1 .. 2, area1 is 3 ... tc

	u32 first_track=1;
	u32 last_track=disc->tracks.size();
	if (area==DoubleDensity)
		first_track=3;
	else if (disc->type==GdRom)
		last_track=2;

	//Generate the TOC info

	//-1 for 1..99 0 ..98
	to[99]  = CreateTrackInfo_se(disc->tracks[first_track-1].CTRL,disc->tracks[first_track-1].ADDR,first_track); 
	to[100] = CreateTrackInfo_se(disc->tracks[last_track-1].CTRL,disc->tracks[last_track-1].ADDR,last_track); 
	
	if (disc->type==GdRom)
	{
		//use smaller LEADOUT
		if (area==SingleDensity)
			to[101] = CreateTrackInfo(disc->LeadOut.CTRL,disc->LeadOut.ADDR,13085);
	}
	else
		to[101] = CreateTrackInfo(disc->LeadOut.CTRL, disc->LeadOut.ADDR, disc->LeadOut.StartFAD);

	for (u32 i=first_track-1;i<last_track;i++)
		to[i] = CreateTrackInfo(disc->tracks[i].CTRL,disc->tracks[i].ADDR,disc->tracks[i].StartFAD); 
}

void GetDriveSessionInfo(u8* to,u8 session)
{
	if (!disc)
		return;
	to[0]=2;//status, will get overwritten anyway
	to[1]=0;//0's
	
	if (session==0)
	{
		to[2]=disc->sessions.size();//count of sessions
		to[3]=disc->EndFAD>>16;//fad is sessions end
		to[4]=disc->EndFAD>>8;
		to[5]=disc->EndFAD>>0;
	}
	else
	{
		to[2]=disc->sessions[session-1].FirstTrack;//start track of this session
		to[3]=disc->sessions[session-1].StartFAD>>16;//fad is session start
		to[4]=disc->sessions[session-1].StartFAD>>8;
		to[5]=disc->sessions[session-1].StartFAD>>0;
	}
}

void printtoc(TocInfo* toc,SessionInfo* ses)
{
	printf("Sessions %d\n",ses->SessionCount);
	for (u32 i=0;i<ses->SessionCount;i++)
	{
		printf("Session %d: FAD %d,First Track %d\n",i+1,ses->SessionFAD[i],ses->SessionStart[i]);
		for (u32 t=toc->FistTrack-1;t<=toc->LastTrack;t++)
		{
			if (toc->tracks[t].Session==i+1)
				printf("\tTrack %d : FAD %d CTRL %d ADR %d\n",
                  t,toc->tracks[t].FAD,toc->tracks[t].Control,toc->tracks[t].Addr);
		}
	}
	printf("Session END: FAD END %d\n",ses->SessionsEndFAD);
}

DiscType GuessDiscType(bool m1, bool m2, bool da)
{
	if ((m1==true) && (da==false) && (m2==false))
		return  CdRom;
	else if (m2)
		return  CdRom_XA;
	else if (da && m1) 
		return CdRom_Extra;
   return CdRom;
}
