#include <common/purepal.hpp>
#include <common/bminfo.hpp>
#include <common/openfile.hpp>
#include <common/filemap.hpp>
#include <common/pview.hpp>
#include <bsptree/rgbtree.hpp>

PurePalette::PurePalette(const String &strPathFileName)
: mhPalette(0), mhOldPalette(0), mIsInUse(FALSE)
{
  readPalette(strPathFileName);
}

PurePalette::PurePalette(const PurePalette &somePurePalette)
: mhPalette(0), mhOldPalette(0), mIsInUse(FALSE)
{
  paletteEntries(somePurePalette.paletteEntries());
  ::GetPaletteEntries(somePurePalette.getPalette(),0,paletteEntries(),(PALETTEENTRY FAR *)&mPaletteData[0]);
	createPalette();
}

PurePalette::PurePalette(HPALETTE hPalette)
: mhPalette(0), mhOldPalette(0), mIsInUse(FALSE)
{
  DWORD palEntries;

	palEntries=::GetPaletteEntries(hPalette,0,0,0);
	paletteEntries(palEntries);
  ::GetPaletteEntries(hPalette,0,paletteEntries(),(PALETTEENTRY FAR *)&mPaletteData[0]);
	createPalette();
}

PurePalette::PurePalette(const BitmapInfo &someBitmapInfo)
: mhPalette(0), mhOldPalette(0), mIsInUse(FALSE)
{
  *this=someBitmapInfo;
}

PurePalette &PurePalette::operator=(const PurePalette &somePurePalette)
{
  destroyPalette();
	paletteEntries(somePurePalette.paletteEntries());
	::GetPaletteEntries(somePurePalette.getPalette(),0,paletteEntries(),(PALETTEENTRY FAR*)&mPaletteData[0]);
	createPalette();
	return *this;
}

PurePalette &PurePalette::operator=(const BitmapInfo &someBitmapInfo)
{
  destroyPalette();
	for(int entryIndex=0;entryIndex<someBitmapInfo.colorImportant()&&entryIndex<paletteEntries();entryIndex++)
	{
	  mPaletteData[entryIndex].red(((BitmapInfo&)someBitmapInfo)[entryIndex].red());
		mPaletteData[entryIndex].green(((BitmapInfo&)someBitmapInfo)[entryIndex].green());
		mPaletteData[entryIndex].blue(((BitmapInfo&)someBitmapInfo)[entryIndex].blue());
	}
	createPalette();
	return *this;
}

PurePalette &PurePalette::operator=(HPALETTE hPalette)
{
	DWORD entries;

	destroyPalette();
	entries=::GetPaletteEntries(hPalette,0,0,0);
	paletteEntries(entries);
	::GetPaletteEntries(hPalette,0,paletteEntries(),(PALETTEENTRY FAR*)&mPaletteData[0]);
	createPalette();
  return *this;
}

PurePalette &PurePalette::operator=(const String &strPathFileName)
{
  destroyPalette();
	readPalette(strPathFileName);
  return *this;
}

PurePalette &PurePalette::operator=(const Array<PaletteEntry> &paletteData)
{
  destroyPalette();
	mPaletteData=paletteData;
	createPalette();
  return *this;
}

WORD PurePalette::operator==(const PurePalette &somePurePalette)const
{
  if(paletteEntries()!=somePurePalette.paletteEntries())return FALSE;
	for(short paletteIndex=0;paletteIndex<paletteEntries();paletteIndex++)
		if(!(((PurePalette&)*this).mPaletteData[paletteIndex]==((PurePalette&)somePurePalette).mPaletteData[paletteIndex]))return FALSE;
	return TRUE;
}

void PurePalette::destroyPalette(void)
{
	if(mhPalette){::DeleteObject(mhPalette);mhPalette=0;}
	mhOldPalette=0;
  mIsInUse=0;
  return;
}

BOOL PurePalette::readPalette(const String &strPathFileName)
{
  FileHandle palFile(strPathFileName);
	FileMap palMap(palFile);
	PureViewOfFile palView(palMap);
	PaletteEntry paletteEntry;
	String strLine;
	int palEntries;
	char *pLine;
	char strTokens[]={' ','\0'};

	if(!palFile.isOkay())return FALSE;
	palView.getLine(strLine);
	if(!(strLine==String("JASC-PAL")))return FALSE;
	palView.getLine(strLine);
	if(!(strLine==String("0100")))return FALSE;
	palView.getLine(strLine);
	palEntries=::atoi(strLine);
	paletteEntries(palEntries);
	for(int palEntry=0;palEntry<palEntries;palEntry++)
	{
	  palView.getLine(strLine);
		pLine=(char*)strLine;
		pLine=::strtok(pLine,strTokens);
		if(!pLine)continue;
    paletteEntry.red(::atoi(pLine));
		pLine=::strtok(0,strTokens);
		if(!pLine)continue;
		paletteEntry.green(::atoi(pLine));
		pLine=::strtok(0,strTokens);
		if(!pLine)continue;
		paletteEntry.blue(::atoi(pLine));
		paletteEntry.flags(PaletteEntry::NoCollapse);
		mPaletteData[palEntry]=paletteEntry;
	}
	return createPalette();
}

BOOL PurePalette::writePalette(const String &strPathFileName)
{
  FileHandle palFile;
	String strLine;

  palFile.open(strPathFileName,FileHandle::Write,FileHandle::ShareRead,FileHandle::Overwrite);
	if(!palFile.isOkay())return FALSE;
	if(!palFile.writeLine("JASC-PAL"))return FALSE;
	if(!palFile.writeLine("0100"))return FALSE;
	::sprintf(strLine,"%d",paletteEntries());
	if(!palFile.writeLine(strLine))return FALSE;
	for(int palIndex=0;palIndex<paletteEntries();palIndex++)
	{
	  PaletteEntry &paletteEntry=mPaletteData[palIndex];
	  ::sprintf(strLine,"%d %d %d",paletteEntry.red(),paletteEntry.green(),paletteEntry.blue());
		if(!palFile.writeLine(strLine))return FALSE;
	}
	return TRUE;
}

void PurePalette::cyclePalette(void)
{
  PaletteEntry paletteEntryOne(mPaletteData[0]);
	::memcpy(&mPaletteData[0],&mPaletteData[1],sizeof(mPaletteData)-sizeof(PaletteEntry));
	mPaletteData[paletteEntries()-1]=paletteEntryOne;
	setPaletteColors();
}

WORD PurePalette::paletteEntry(PaletteIndex paletteIndex,RGBColor &someRGBColor)const
{
	if(paletteIndex>=paletteEntries())return FALSE;
  someRGBColor=(RGBColor)((PurePalette&)*this).mPaletteData[paletteIndex];
	return TRUE;
}

RGBColor PurePalette::paletteEntry(PaletteIndex paletteIndex)const
{
	RGBColor tempColor(0,0,0);

	paletteEntry(paletteIndex,tempColor);
	return tempColor;
}

WORD PurePalette::createPalette()
{
  PaletteEntry FAR *lpPaletteEntry;
	LOGPALETTE FAR *lpLogPalette;
	HGLOBAL hGlobalPalette;

	if(mhPalette){::DeleteObject(mhPalette);mhPalette=0;}
	hGlobalPalette=::GlobalAlloc(GMEM_ZEROINIT|GMEM_MOVEABLE,sizeof(LOGPALETTE)+(paletteEntries()*sizeof(PALETTEENTRY)));
	lpLogPalette=(LOGPALETTE FAR *)::GlobalLock(hGlobalPalette);
	lpLogPalette->palNumEntries=paletteEntries();
	lpLogPalette->palVersion=0x300;
	for(short paletteIndex=0;paletteIndex<paletteEntries();paletteIndex++)
  {
		lpPaletteEntry=(PaletteEntry FAR *)&lpLogPalette->palPalEntry[paletteIndex];
    *lpPaletteEntry=mPaletteData[paletteIndex];
  }
	mhPalette=::CreatePalette((LOGPALETTE FAR *)lpLogPalette);
	::GlobalUnlock(hGlobalPalette);
	::GlobalFree(hGlobalPalette);
	return (mhPalette?TRUE:FALSE);
}

void PurePalette::usePalette(const PureDevice &somePureDevice,short usage)
{
	if(usage)
	{
		mhOldPalette=::SelectPalette(somePureDevice,mhPalette,FALSE);
		::RealizePalette(somePureDevice);
		mIsInUse=TRUE;
	}
	else
	{
		::SelectPalette(somePureDevice,mhOldPalette,FALSE);
		::RealizePalette(somePureDevice);
		mIsInUse=FALSE;
	}
}

WORD PurePalette::getPaletteColor(short paletteIndex,RGBColor &someRGBColor)const
{
	if(paletteIndex>=paletteEntries())return FALSE;
	someRGBColor=(RGBColor)((PurePalette&)*this).mPaletteData[paletteIndex];
  return TRUE;
}

void PurePalette::getPaletteColors(Array<RGBColor> &someRGBColors)const
{
	int paletteColors;

  someRGBColors.size(paletteEntries());
  paletteColors=someRGBColors.size();
	for(short paletteIndex=0;paletteIndex<paletteColors;paletteIndex++)
	  someRGBColors[paletteIndex]=(RGBColor)((PurePalette&)*this).mPaletteData[paletteIndex];
}

void PurePalette::setPaletteColors(Array<RGBColor> &someRGBColors,PaletteEntry::PaletteFlags paletteFlag)
{
	size_t paletteColors((size_t)someRGBColors.size());

	for(short paletteIndex=0;paletteIndex<paletteColors;paletteIndex++)
		setPaletteColor(paletteIndex,someRGBColors[paletteIndex],paletteFlag);
}

WORD PurePalette::setPaletteColor(short paletteIndex,RGBColor &someRGBColor,PaletteEntry::PaletteFlags paletteFlag)
{
	if(paletteIndex>=paletteEntries())return FALSE;
  mPaletteData[paletteIndex]=someRGBColor;
	mPaletteData[paletteIndex].flags(paletteFlag);
	::SetPaletteEntries(mhPalette,paletteIndex,1,(PALETTEENTRY FAR*)&mPaletteData[paletteIndex]);
	return TRUE;
}

void PurePalette::clearPalette(void)
{
	short paletteColors(paletteEntries());
	RGBColor nullColor;

	for(short paletteIndex=0;paletteIndex<paletteColors;paletteColors++)setPaletteColor(paletteIndex,nullColor);
}

WORD PurePalette::systemPalette(bool useStandardColors)
{
	PureDevice screenDevice;
  WORD systemPaletteEntries;

	destroyPalette();
  screenDevice.screenDevice();
	systemPaletteEntries=::GetSystemPaletteEntries(screenDevice,0,MaxColors,0);
	if(!systemPaletteEntries||useStandardColors)	 // we run into this on CE, so create a palette based upon X86 windows system palette
	{
	  systemPaletteEntries=256;
		mPaletteData.size(systemPaletteEntries);
		mPaletteData[0]=RGBColor(0,0,0);
		mPaletteData[1]=RGBColor(128,0,0);
		mPaletteData[2]=RGBColor(0,128,0);
		mPaletteData[3]=RGBColor(128,128,0);
		mPaletteData[4]=RGBColor(0,0,128);
		mPaletteData[5]=RGBColor(128,0,128);
		mPaletteData[6]=RGBColor(0,128,128);
		mPaletteData[7]=RGBColor(192,192,192);
		mPaletteData[8]=RGBColor(192,220,192);
		mPaletteData[9]=RGBColor(166,202,240);
		mPaletteData[246]=RGBColor(255,251,240);
		mPaletteData[247]=RGBColor(160,160,164);
		mPaletteData[248]=RGBColor(128,128,128);
		mPaletteData[249]=RGBColor(255,0,0);
		mPaletteData[250]=RGBColor(0,255,0);
		mPaletteData[251]=RGBColor(255,255,0);
		mPaletteData[252]=RGBColor(0,0,255);
		mPaletteData[253]=RGBColor(255,0,255);
		mPaletteData[254]=RGBColor(0,255,255);
		mPaletteData[255]=RGBColor(255,255,255);
  }
	else
	{
	  paletteEntries(systemPaletteEntries);
	  systemPaletteEntries=::GetSystemPaletteEntries(screenDevice,0,paletteEntries(),(PALETTEENTRY FAR*)&mPaletteData[0]);
  } 
	createPalette();
  return systemPaletteEntries;
}

WORD PurePalette::identityPalette(void)
{
  PurePalette paletteData;
	RGBTree paletteTree;
	RGBTree systemTree;
  RGBColor systemColor;
	PureDevice pureDevice;
	WORD staticColors;
  WORD useableIndex(0);
	RGBColor rgbColor;

	pureDevice.screenDevice();
	paletteData.systemPalette();
	if(SYSPAL_NOSTATIC==::GetSystemPaletteUse(pureDevice))
	{
	  Array<RGBColor> paletteColors;
		getPaletteColors(paletteColors);
		setPaletteColors(paletteColors,PaletteEntry::NoCollapse);
		setPaletteColor(PurePalette::MaxColors-1,RGBColor(255,255,255));
		setPaletteColor(0,RGBColor(0,0,0));
		createPalette();
		return TRUE;
	}
	if(SystemStaticColors!=(staticColors=::GetDeviceCaps(pureDevice,NUMCOLORS)))return FALSE;
	staticColors/=2;
	for(int paletteIndex=0;paletteIndex<staticColors;paletteIndex++)
		systemTree.insertItem(paletteData[paletteIndex]);
	for(paletteIndex=MaxColors-staticColors;paletteIndex<MaxColors;paletteIndex++)
    systemTree.insertItem(paletteData[paletteIndex]);
	for(int index=0;index<staticColors;index++)paletteData[index].flags(PaletteEntry::NullFlag);
	while(index<MaxColors-staticColors&&useableIndex<MaxColors)
	{
		getPaletteColor(useableIndex,rgbColor);
		if(!systemTree.searchItem(rgbColor))
		{
			if(!paletteTree.searchItem(rgbColor))
			{
				paletteTree.insertItem(rgbColor);
				paletteData[index]=rgbColor;
				paletteData[index].flags(PaletteEntry::NullFlag);
				index++;
			}
		}
		useableIndex++;
	}
	for(;index<MaxColors-staticColors;index++)paletteData[index].flags(PaletteEntry::NullFlag);  // NoCollapse 
	for(index=MaxColors-staticColors;index<MaxColors;index++)paletteData[index].flags(PaletteEntry::NullFlag);
  mPaletteData=paletteData.mPaletteData;
	createPalette();
  return TRUE;
}

WORD PurePalette::isIdentityPalette(void)const
{
	PurePalette paletteData;
	PureDevice pureDevice;
	WORD staticColors;

	paletteData.systemPalette();
	if(paletteEntries()!=paletteData.paletteEntries())return FALSE;
	pureDevice.screenDevice();
	if(SYSPAL_NOSTATIC==::GetSystemPaletteUse(pureDevice))
	{
		if(!(RGBColor(255,255,255)==((PurePalette&)*this).mPaletteData[PurePalette::MaxColors-1]))return FALSE;
		if(!(RGBColor(0,0,0)==((PurePalette&)*this).mPaletteData[0]))return FALSE;
    return TRUE;
	}
	if(SystemStaticColors!=(staticColors=::GetDeviceCaps(pureDevice,NUMCOLORS)))return FALSE;
	staticColors/=2;
	for(int paletteIndex=0;paletteIndex<staticColors;paletteIndex++)
		if(!(RGBColor(((PurePalette&)*this).mPaletteData[paletteIndex])==RGBColor(paletteData[paletteIndex])))return FALSE;
	for(paletteIndex=paletteEntries()-staticColors;paletteIndex<paletteEntries();paletteIndex++)
		if(!(RGBColor(((PurePalette&)*this).mPaletteData[paletteIndex])==RGBColor(paletteData[paletteIndex])))return FALSE;
	return TRUE;
}
