// TextureDoc.cpp : implementation file
//

#include "stdafx.h"
#include "Multitron.h"
#include "TextureDoc.h"
#include "MultitronDoc.h"
#include "MainFrm.h"

#include <GameOs\TooLOS.hpp>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define FLAG_MEGATEXTURE 1
#define FLAG_TEXTUREMODIFIED 2

/////////////////////////////////////////////////////////////////////////////
// CTextureDoc

IMPLEMENT_DYNCREATE(CTextureDoc, CDocument)

CTextureDoc::CTextureDoc()
{
}

BOOL CTextureDoc::OnNewDocument()
{
	if (!CDocument::OnNewDocument())
		return FALSE;
	return TRUE;
}

CTextureDoc::~CTextureDoc()
{
	POSITION pos = m_listImages.GetHeadPosition();
	while (pos)
	{
		Image *pImage = (Image *)m_listImages.GetNext(pos);
		if (pImage)
			delete pImage;
	}
	m_listImages.RemoveAll();
}


BEGIN_MESSAGE_MAP(CTextureDoc, CDocument)
	//{{AFX_MSG_MAP(CTextureDoc)
	ON_COMMAND(ID_FILE_SAVE, OnFileSave)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTextureDoc diagnostics

#ifdef _DEBUG
void CTextureDoc::AssertValid() const
{
	CDocument::AssertValid();
}

void CTextureDoc::Dump(CDumpContext& dc) const
{
	CDocument::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CTextureDoc serialization

void CTextureDoc::Serialize(CArchive& ar)
{
	if (ar.IsStoring())
	{
		// TODO: add storing code here
	}
	else
	{
		// TODO: add loading code here
	}
}

/*******************************************************************************
/* function name: OnOpenDocument
/* description: 
/*******************************************************************************/
BOOL CTextureDoc::OnOpenDocument(LPCTSTR lpszPathName) 
{
	Stuff::Page *page = (Stuff::Page *)lpszPathName;
	Check_Object(page);

	return TRUE;
}

/*******************************************************************************
/* function name: SetPathName
/* description: Called when a document/hint page is opened
/*******************************************************************************/
void CTextureDoc::SetPathName(LPCTSTR lpszPathName, BOOL bAddToMRU) 
{
	if (lpszPathName == NULL) {
		ClearAllPages();
		return;
	}

	Stuff::Page *page = (Stuff::Page *)lpszPathName;
	Check_Object(page);
	POSITION pos = m_listPages.GetHeadPosition();
	while (pos)
	{
		Stuff::Page *oldpage = (Stuff::Page *)m_listPages.GetNext(pos);
		if (page->GetName() == oldpage->GetName())
			return;
	}

	m_listPages.AddTail((LPVOID)page);
	DWORD flags = 0;
	Image *pImage = NULL;
	const char *entry;
	if (page->GetEntry("PageSize",&entry))
	{
		flags |= FLAG_MEGATEXTURE;
		pImage = ArrangeTextures(page);
		if (pImage)
			flags |= FLAG_TEXTUREMODIFIED;
	}

	if (NULL == pImage)
	{
		pImage = LoadTexture(page);
	}
	m_listImages.AddTail((LPVOID)pImage);
	m_listFlags.AddTail((LPVOID)flags);

	CString name;
	if (GetCount() == 1) 
	{
		Stuff::Page *p = GetPage(0);
		name = p->GetName();
	}
	else 
		name = "Multiple Pages";

	SetDefaults(page, pImage);
	m_strPathName = name;
	this->SetTitle(name);
}

static CDWordArray arrVerts;

CCell::CCell()
{
	bSplit = false;
	for (int x=0;x<4;x++)
		m_arrChildren[x] = NULL;
	m_image = NULL;
	state = 0;
}

CCell::~CCell()
{
	for (int x=0;x<4;x++)
		if (m_arrChildren[x])
			delete m_arrChildren[x];
}

CPoint CCell::GetPoint1()
{
	DWORD dw = arrVerts[V1];
	CPoint pt(LOWORD(dw),HIWORD(dw));
	return pt;
}

int CCell::AddVert(int x,int y)
{
	DWORD dw = MAKELONG(x,y);
	return arrVerts.Add(dw);
}

int CCell::GetXDim()
{
	DWORD dw1 = arrVerts[V1];
	WORD x1 = LOWORD(dw1);

	DWORD dw2 = arrVerts[V2];
	WORD x2 = LOWORD(dw2);
	return (x2-x1);
}

int CCell::GetYDim()
{
	DWORD dw1 = arrVerts[V1];
	WORD y1 = HIWORD(dw1);

	DWORD dw2 = arrVerts[V2];
	WORD y2 = HIWORD(dw2);
	return (y2-y1);
}

int CCell::GetV1()
{
	return V1;
}

int CCell::GetV2()
{
	return V2;
}

int CCell::SetVerts(int a,int b)
{
	V1 = a;
	V2 = b;
	return 1;
}

int CCell::CreateCell(int xSize,int ySize)
{
	V1 = AddVert(0,0);
	V2 = AddVert(xSize,ySize);
	state = 0;	// Empty
	return 1;
}

CCell *CCell::FillCell(Image *image)
{
	if (2==state)
		return NULL;
	int xSize = image->GetWidth();
	int ySize = image->GetHeight();

	int xdim = GetXDim();
	int ydim = GetYDim();

	if ((xSize==xdim)&&(ySize==ydim)&&(0==state))
	{
		// use this cell, fill it up totally
		m_image = image;
		name = image->GetFileName();
		state = 2;
		return this;
	}
	else
	{
		if ((xSize<=xdim) && (ySize<=ydim))
		{
			SplitCell(xSize,ySize);
			BYTE flags = 0;
			for (int x=0;x<4;x++)
			{
				CCell *cell = m_arrChildren[x]->FillCell(image);
				flags |= (2==m_arrChildren[x]->state) << x;
				if (cell)
				{
					if (15==flags)
						state = 2;
					else 
						state = 1;
					return cell;
				}
			}

		}
		else
		{
			// Cannot fill this cell with something this big, unless I union with neighbors
			return NULL;
		}
	}
	return NULL;
}

int CCell::SplitCell(int xSize,int ySize)
{
	if (!bSplit)
	{
		CPoint pt1 = GetPoint1();
		int xdim = GetXDim();
		int ydim = GetYDim();

		int topleft = GetV1();
		int bottomright = GetV2();
		int middle = AddVert(pt1.x+xSize,pt1.y+ySize);
		int middletop = AddVert(pt1.x+xSize,pt1.y);
		int middleleft = AddVert(pt1.x,pt1.y+ySize);
		int middlebottom = AddVert(pt1.x+xSize,pt1.y+ydim);
		int middleright = AddVert(pt1.x+xdim,pt1.y+ySize);

		CCell *cell = m_arrChildren[0] = new CCell;
		cell->SetVerts(topleft,middle);

		cell = m_arrChildren[1] = new CCell;
		cell->SetVerts(middletop,middleright);

		cell = m_arrChildren[2] = new CCell;
		cell->SetVerts(middleleft,middlebottom);

		cell = m_arrChildren[3] = new CCell;
		cell->SetVerts(middle,bottomright);

		bSplit = true;
	}
	return 4;
}

/*******************************************************************************
/* function name: BuildMegaTexture
/* description: 
/*******************************************************************************/
int CTextureDoc::BuildMegaTexture(CCell *pCell,Image *pImage)
{
	if (pCell)
	{
		if (pCell->m_image)
		{
			CPoint pt = pCell->GetPoint1();
			pImage->Blt(*(pCell->m_image),pt.x,pt.y);
		}
		for (int x=0;x<4;x++)
			BuildMegaTexture(pCell->m_arrChildren[x],pImage);
	}
	return 1;
}

/*******************************************************************************
/* function name: ConstructPage
/* description: 
/*******************************************************************************/
int CTextureDoc::ConstructPage(CCell *pCell,Stuff::Page *page)
{
	if (NULL == pCell)
		return 0;
	if (pCell->m_image)
		// Acutally m_image is already deleted by now so I can't use it
	{
		char name[MAX_PATH];
		_splitpath(pCell->name,NULL,NULL,name,NULL);
		CString str;
		CPoint pt = pCell->GetPoint1();
		str.Format("%d %d",pt.x,pt.y);
		page->SetEntry(name,str);
	}
	int count = 1;
	for (int x=0;x<4;x++)
	{
		count += ConstructPage(pCell->m_arrChildren[x],page);
	}
	return count;
}

/*******************************************************************************
/* function name: ArrangeTextures
/* description: 
/*******************************************************************************/
Image *CTextureDoc::ArrangeTextures(Stuff::Page *mega_page)
{
	CMapStringToPtr mapImages;
	int xSize=0,ySize=0;
	Stuff::Page::NoteIterator *notes = mega_page->MakeNoteIterator();
	Stuff::Note *note;

	CCell *pCell = NULL;
	int minBpp = 0;

	while (NULL != (note=notes->ReadAndNext()))
	{
		const char *entry;
		note->GetEntry(&entry);
		if (stricmp(note->GetName(),"PageSize")==0)
		{
			sscanf(entry,"%d %d",&xSize,&ySize);
			pCell = new CCell;
			pCell->CreateCell(xSize,ySize);
		}
		else
		{
			CMultitronApp *pApp = (CMultitronApp *)AfxGetApp();
			CString value;
			CString name = note->GetName();
			name.MakeLower();
			if (!pApp->m_mapHintParams.Lookup(name,value))
			{
				//This is probably a valid texture name/location
				const char *entry;
				note->GetEntry(&entry);

				int xpos,ypos;
				if (2==sscanf(entry,"%d %d",&xpos,&ypos))
				{
					// This texture has already been created
					delete pCell;
					return NULL;
				}
				CString imagename = note->GetName();
				CString path = "content\\textures\\"+imagename;

				Image *image = new Image;

				CString fullname = path+".png";
				if (gos_DoesFileExist(fullname))
				{
					image->Load((char *)(LPCTSTR)fullname);
				}
				else
				{
					fullname = path+".tga";
					if (gos_DoesFileExist(fullname))
					{
						image->Load((char *)(LPCTSTR)fullname);
					}
				}

				int bpp = image->GetBpp();
				if (bpp>minBpp)
					minBpp = bpp;

				RGBMask mask;
				if(32 == bpp)
					mask.Set(0x00ff0000,0x0000ff00,0x000000ff,0xff000000);
				else
					mask.Set(0x00ff0000,0x0000ff00,0x000000ff);
				image->MaskTo(mask);

				Verify(pCell);
				CCell *cell = pCell->FillCell(image);
				if (NULL == cell)
				{
					CString str;
					str.Format("Could not fit texture %s",imagename);
					MessageBox(NULL,str,"Fill Cell",MB_OK);
				}
				mapImages[imagename] = image;
			}
		}
	}
	delete notes;

	Image *mega_image = new Image;
	mega_image->CreateSolid(xSize,ySize,ImgRGBColor(0xff,0xff,0xff,0xff),ITYPE_RGB,minBpp);

	BuildMegaTexture(pCell,mega_image);

	POSITION pos = mapImages.GetStartPosition();
	while (pos)
	{
		CString imagename;
		LPVOID lpvoid;
		mapImages.GetNextAssoc(pos,imagename,lpvoid);
		Image *pImage = (Image *)lpvoid;
		
		delete pImage;
	}
	mapImages.RemoveAll();

	ConstructPage(pCell,mega_page);

	delete pCell;

	if (24 == minBpp)
	{
		mega_page->SetEntry("Format","Solid");
	}

	RGBMask mask;
	if(32 == minBpp)
		mask.Set(0x00ff0000,0x0000ff00,0x000000ff,0xff000000);
	else
		mask.Set(0x00ff0000,0x0000ff00,0x000000ff);
	mega_image->MaskTo(mask);
	return mega_image;
}

/*******************************************************************************
/* function name: LoadTexture
/* description: Load texture into an Image object
/*******************************************************************************/
Image *CTextureDoc::LoadTexture(Stuff::Page *page)
{
	CString imagename = page->GetName();
	const char *entry;
	if (page->GetEntry("Alias",&entry))
	{
		imagename = entry;
	}

	Image *pImage = new Image;

	CString path = "content\\textures\\"+imagename;

	CString fullname = path+".png";
	if (gos_DoesFileExist(fullname))
	{
		pImage->Load((char *)(LPCTSTR)fullname);
	}
	else
	{
		fullname = path+".tga";
		if (gos_DoesFileExist(fullname))
		{
			pImage->Load((char *)(LPCTSTR)fullname);
		}
		else
		{
			delete pImage;
			return NULL;
		}
	}

	// Default Format entry to Solid if image doesn't have alpha channel
	if (!page->GetEntry("Format",&entry))
	{
		if (pImage->GetBpp()==24)
			page->SetEntry("Format","Solid");
	}

	RGBMask mask;
	if(pImage->GetBpp()==32)
		mask.Set(0x00ff0000,0x0000ff00,0x000000ff,0xff000000);
	else
		mask.Set(0x00ff0000,0x0000ff00,0x000000ff);
	
	pImage->MaskTo(mask);

	return pImage;
}

/*******************************************************************************
/* function name: OnCloseDocument
/* description: 
/*******************************************************************************/
void CTextureDoc::OnCloseDocument() 
{
	CMultitronApp *pApp = (CMultitronApp *)AfxGetApp();
	POSITION pos = pApp->m_pMainDoc->GetFirstDocPosition();
	while (pos)
	{
		CMultitronDoc *pDoc = (CMultitronDoc *)pApp->m_pMainDoc->GetNextDoc(pos);
		pDoc->CloseTexture(this);
	}

	ClearAllPages();
	CDocument::OnCloseDocument();
}

/*******************************************************************************
/* function name: ClearAllPages
/* description: Deleted all hosted hint pages and their image objects
/*******************************************************************************/
void CTextureDoc::ClearAllPages()
{	
	SaveModified();

	m_listPages.RemoveAll();
	m_listFlags.RemoveAll();

	POSITION pos = m_listImages.GetHeadPosition();
	while (pos)
	{
		Image *pImage = (Image *)m_listImages.GetNext(pos);
		if (pImage)
			delete pImage;
	}
	m_listImages.RemoveAll();

}

/*******************************************************************************
/* function name: GetCount
/* description: Return the number of hosted hint pages
/*******************************************************************************/
int CTextureDoc::GetCount()
{
	return m_listPages.GetCount();
}

/*******************************************************************************
/* function name: GetPage
/* description: Return the hint page at index i
/*******************************************************************************/
Stuff::Page *CTextureDoc::GetPage(int i)
{
	POSITION pos = m_listPages.FindIndex(i);
	if (pos)
		return (Stuff::Page *)m_listPages.GetAt(pos);
	return NULL;
}

/*******************************************************************************
/* function name: GetImage
/* description: Return the texture image of hint page at index i
/*******************************************************************************/
Image *CTextureDoc::GetImage(int i)
{
	POSITION pos = m_listImages.FindIndex(i);
	if (pos)
		return (Image *)m_listImages.GetAt(pos);
	return NULL;
}

/*******************************************************************************
/* function name: SetImage
/* description: Set image object for hint page at index i
/*******************************************************************************/
void CTextureDoc::SetImage(Image* pImage, int i)
{
	POSITION pos = m_listImages.FindIndex(i);
	if (pos) {
		Image* oldImage = (Image *)m_listImages.GetAt(pos);
		m_listImages.SetAt(pos, pImage);
		delete oldImage;
	}
}

/*******************************************************************************
/* function name: ReplacePage
/* description: Replace hint page at index i
/*******************************************************************************/
int CTextureDoc::ReplacePage(Stuff::Page *oldpage,Stuff::Page *newpage)
{
	POSITION pos = m_listPages.Find(oldpage);
	if (pos)
	{
		m_listPages.SetAt(pos,newpage);
		return 1;
	}
	return 0;
}

/*******************************************************************************
/* function name: SaveModified
/* description: Called when hint page is being closed without being saved
/*******************************************************************************/
BOOL CTextureDoc::SaveModified() 
{
	int count = 0;
	POSITION pos = m_listPages.GetHeadPosition();
	while (pos)
	{
		DWORD flags = 0;
		POSITION fPos = m_listFlags.FindIndex(count);
		if (fPos)
		{
			flags = (DWORD)m_listFlags.GetAt(fPos);
		}

		Stuff::Page *page = (Stuff::Page *)m_listPages.GetNext(pos);
		if (flags & FLAG_MEGATEXTURE)
		{
			if (flags & FLAG_TEXTUREMODIFIED)
			{
				// Save the image?
				CString str;
				str.Format("Megatexture: %s created.  Save Now?",page->GetName());
				CString str1;
				str1.Format("%s Image",page->GetName());
				if (IDYES==AfxGetMainWnd()->MessageBox(str,str1,MB_YESNO))
				{
					CString path = "content\\textures\\";
					path += page->GetName();
					path += ".png";
					POSITION iPos = m_listImages.FindIndex(count);
					if (iPos)
					{
						Image *pImage = (Image *)m_listImages.GetAt(iPos);
						if (pImage)
							pImage->SavePng((char *)(LPCTSTR)path);
					}
					flags &= ~FLAG_TEXTUREMODIFIED;
					m_listFlags.SetAt(fPos,(LPVOID)flags);
				}
			}
		}
		count++;
	}

	return CDocument::SaveModified();
}

/*******************************************************************************
/* function name: OnFileSave
/* description: 
/*******************************************************************************/
void CTextureDoc::OnFileSave() 
{
	SaveModified();
	CMultitronApp *pApp = (CMultitronApp *)AfxGetApp();
	pApp->OnFileSave();
}

/*******************************************************************************
/* function name: SetDefaults
/* description: Set default values for entries in the hint page
/*******************************************************************************/
int CTextureDoc::SetDefaults(Stuff::Page *page, Image* pImage)
{
	CMultitronApp *pApp = (CMultitronApp *)AfxGetApp();
	POSITION pos = pApp->m_mapHintParams.GetStartPosition();
	while (pos)
	{
		CString value,key;
		pApp->m_mapHintParams.GetNextAssoc(pos,key,value);

		value.MakeLower();
		bool bSet = true;
		const char *entry;
		if (page->GetEntry(key,&entry))
		{
			CString strEntry(entry);
			strEntry.MakeLower();
			// Check that the entry is valid
			if (value.Find(strEntry)>=0)
				bSet = false;
		}

		if (bSet)
		{
			// Set to default
			int i = value.Find('|');
			if (i>0)
			{
				value = value.Left(i);
			}
			page->SetEntry(key,value);
			
			if (stricmp(key, "format")==0 && 
				pImage && !pImage->HasAlpha())
				page->SetEntry("format", "solid");
			
			CString name = page->GetName();
			if (stricmp(key, "mipmap")==0 &&
				name.GetAt(name.GetLength()-1) <= '9' &&
				name.GetAt(name.GetLength()-1) >= '0')
				page->SetEntry("mipmap", "explicit");
		}
	}
	return 0;
}
