// Copyright 2014 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
 
// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "../include/fpdfppo.h"
#include "../include/fsdk_define.h"

class CPDF_PageOrganizer
{
public:
	CPDF_PageOrganizer();
	~CPDF_PageOrganizer();
	
public:
	FX_BOOL				PDFDocInit(CPDF_Document *pDestPDFDoc, CPDF_Document *pSrcPDFDoc);
	FX_BOOL				ExportPage(CPDF_Document *pSrcPDFDoc, CFX_WordArray* nPageNum, CPDF_Document *pDestPDFDoc, int nIndex);
	CPDF_Object*		PageDictGetInheritableTag(CPDF_Dictionary *pDict, CFX_ByteString nSrctag);
	FX_BOOL				UpdateReference(CPDF_Object *pObj, CPDF_Document *pDoc, CFX_MapPtrToPtr* pMapPtrToPtr);
	int					GetNewObjId(CPDF_Document *pDoc, CFX_MapPtrToPtr* pMapPtrToPtr, CPDF_Reference *pRef);
	
};


CPDF_PageOrganizer::CPDF_PageOrganizer()
{

}

CPDF_PageOrganizer::~CPDF_PageOrganizer()
{

}

FX_BOOL CPDF_PageOrganizer::PDFDocInit(CPDF_Document *pDestPDFDoc, CPDF_Document *pSrcPDFDoc)
{
	if(!pDestPDFDoc || !pSrcPDFDoc)
		return false;
	
	CPDF_Dictionary* pNewRoot = pDestPDFDoc->GetRoot();
	if(!pNewRoot)	return FALSE;
	
	//Set the document information////////////////////////////////////////////
	
	CPDF_Dictionary* DInfoDict = pDestPDFDoc->GetInfo();
	
	if(!DInfoDict)
		return FALSE;
	
	CFX_ByteString producerstr;
	
#ifdef FOXIT_CHROME_BUILD
	producerstr.Format("Google");
#else
	 producerstr.Format("Foxit PDF SDK %s - Foxit Corporation", "2.0");
#endif
	DInfoDict->SetAt("Producer", new CPDF_String(producerstr));

	//Set type////////////////////////////////////////////////////////////////
	CFX_ByteString cbRootType = pNewRoot->GetString("Type","");
	if( cbRootType.Equal("") )
	{
		pNewRoot->SetAt("Type", new CPDF_Name("Catalog"));
	}
	
	CPDF_Dictionary* pNewPages = (CPDF_Dictionary*)(pNewRoot->GetElement("Pages")? pNewRoot->GetElement("Pages")->GetDirect() : NULL);
	if(!pNewPages)
	{
		pNewPages = new CPDF_Dictionary;
		FX_DWORD NewPagesON = pDestPDFDoc->AddIndirectObject(pNewPages);
		pNewRoot->SetAt("Pages", new CPDF_Reference(pDestPDFDoc, NewPagesON));
	}
	
	CFX_ByteString cbPageType = pNewPages->GetString("Type","");
	if(cbPageType.Equal(""))
	{
		pNewPages->SetAt("Type", new CPDF_Name("Pages"));
	}

	CPDF_Array* pKeysArray = pNewPages->GetArray("Kids");
	if(pKeysArray == NULL)
	{
		CPDF_Array* pNewKids = new CPDF_Array;
		FX_DWORD Kidsobjnum = -1;
		Kidsobjnum = pDestPDFDoc->AddIndirectObject(pNewKids);//, Kidsobjnum, Kidsgennum);
		
		pNewPages->SetAt("Kids", new CPDF_Reference(pDestPDFDoc, Kidsobjnum));//, Kidsgennum));
		pNewPages->SetAt("Count", new CPDF_Number(0));		
	}

	return true;
}

FX_BOOL CPDF_PageOrganizer::ExportPage(CPDF_Document *pSrcPDFDoc, CFX_WordArray* nPageNum, 
												CPDF_Document *pDestPDFDoc,int nIndex)
{
	int curpage =nIndex;

	CFX_MapPtrToPtr* pMapPtrToPtr = new CFX_MapPtrToPtr;
	pMapPtrToPtr->InitHashTable(1001);

	for(int i=0; i<nPageNum->GetSize(); i++)
	{
		
		CPDF_Dictionary* pCurPageDict = pDestPDFDoc->CreateNewPage(curpage);
		CPDF_Dictionary* pSrcPageDict = pSrcPDFDoc->GetPage(nPageNum->GetAt(i)-1);
		if(!pSrcPageDict || !pCurPageDict)
		{
			delete pMapPtrToPtr;
			return FALSE;
		}
		
		// Clone the page dictionary///////////
		FX_POSITION	SrcPos = pSrcPageDict->GetStartPos();
		while (SrcPos)
		{
			CFX_ByteString cbSrcKeyStr;
			CPDF_Object* pObj = pSrcPageDict->GetNextElement(SrcPos, cbSrcKeyStr);
			if(cbSrcKeyStr.Compare(("Type")) && cbSrcKeyStr.Compare(("Parent")))
			{
				if(pCurPageDict->KeyExist(cbSrcKeyStr))
					pCurPageDict->RemoveAt(cbSrcKeyStr);
				pCurPageDict->SetAt(cbSrcKeyStr, pObj->Clone());
			}
		}
		
		//inheritable item///////////////////////
		CPDF_Object* pInheritable = NULL;
		//1	MediaBox  //required
		if(!pCurPageDict->KeyExist("MediaBox"))
		{
			
			pInheritable = PageDictGetInheritableTag(pSrcPageDict, "MediaBox");
			if(!pInheritable) 
			{
				//Search the "CropBox" from source page dictionary, if not exists,we take the letter size.
				pInheritable = PageDictGetInheritableTag(pSrcPageDict, "CropBox");
				if(pInheritable)
					pCurPageDict->SetAt("MediaBox", pInheritable->Clone());
				else
				{
					//Make the default size to be letter size (8.5'x11')
					CPDF_Array* pArray = new CPDF_Array;
					pArray->AddNumber(0);
					pArray->AddNumber(0);
					pArray->AddNumber(612);
					pArray->AddNumber(792);
					pCurPageDict->SetAt("MediaBox", pArray);
				}
			}
			else
				pCurPageDict->SetAt("MediaBox", pInheritable->Clone());
		}
		//2 Resources //required
		if(!pCurPageDict->KeyExist("Resources"))
		{
			pInheritable = PageDictGetInheritableTag(pSrcPageDict, "Resources");
			if(!pInheritable) 
			{
				delete pMapPtrToPtr;
				return FALSE;
			}
			pCurPageDict->SetAt("Resources", pInheritable->Clone());
		}
		//3 CropBox  //Optional
		if(!pCurPageDict->KeyExist("CropBox"))
		{
			pInheritable = PageDictGetInheritableTag(pSrcPageDict, "CropBox");
			if(pInheritable) 
				pCurPageDict->SetAt("CropBox", pInheritable->Clone());
		}
		//4 Rotate  //Optional
		if(!pCurPageDict->KeyExist("Rotate"))
		{
			pInheritable = PageDictGetInheritableTag(pSrcPageDict, "Rotate");
			if(pInheritable) 
				pCurPageDict->SetAt("Rotate", pInheritable->Clone());
		}

		/////////////////////////////////////////////
		//Update the reference
		FX_DWORD dwOldPageObj = pSrcPageDict->GetObjNum();
		FX_DWORD dwNewPageObj = pCurPageDict->GetObjNum();
		
		pMapPtrToPtr->SetAt((FX_LPVOID)(FX_UINTPTR)dwOldPageObj, (FX_LPVOID)(FX_UINTPTR)dwNewPageObj);

		this->UpdateReference(pCurPageDict, pDestPDFDoc, pMapPtrToPtr);
		curpage++;
	}

	delete pMapPtrToPtr;
	return TRUE;
}

CPDF_Object* CPDF_PageOrganizer::PageDictGetInheritableTag(CPDF_Dictionary *pDict, CFX_ByteString nSrctag)
{
	if(!pDict || !pDict->KeyExist("Type") || nSrctag.IsEmpty())	
		return NULL;

	CPDF_Object* pType = pDict->GetElement("Type")->GetDirect();
	if(!pType || pType->GetType() != PDFOBJ_NAME)	return NULL;

	if(pType->GetString().Compare("Page"))	return NULL;

	if(!pDict->KeyExist("Parent"))	return NULL;
	CPDF_Object* pParent = pDict->GetElement("Parent")->GetDirect();
	if(!pParent || pParent->GetType() != PDFOBJ_DICTIONARY)	return NULL;
	
	CPDF_Dictionary* pp = (CPDF_Dictionary*)pParent;
	
	if(pDict->KeyExist((const char*)nSrctag))	
		return pDict->GetElement((const char*)nSrctag);
	while (pp)
	{
		if(pp->KeyExist((const char*)nSrctag))	
			return pp->GetElement((const char*)nSrctag);
		else if (pp->KeyExist("Parent"))
		{
			pp = (CPDF_Dictionary*)pp->GetElement("Parent")->GetDirect();
			if (pp->GetType() == PDFOBJ_NULL) break;
		}
		else break;
	}
	
	return NULL;
}

FX_BOOL CPDF_PageOrganizer::UpdateReference(CPDF_Object *pObj, CPDF_Document *pDoc, 
										 CFX_MapPtrToPtr* pMapPtrToPtr)
{
	switch (pObj->GetType())
	{
	case PDFOBJ_REFERENCE:
		{
			CPDF_Reference* pReference = (CPDF_Reference*)pObj;
			int newobjnum = GetNewObjId(pDoc, pMapPtrToPtr, pReference);
			if (newobjnum == 0) return FALSE;
			pReference->SetRef(pDoc, newobjnum);//, 0);
			break;
		}
	case PDFOBJ_DICTIONARY:
		{
			CPDF_Dictionary* pDict = (CPDF_Dictionary*)pObj;
			
			FX_POSITION pos = pDict->GetStartPos();
			while(pos)
			{
				CFX_ByteString key("");
				CPDF_Object* pNextObj = pDict->GetNextElement(pos, key);
				if (!FXSYS_strcmp(key, "Parent") || !FXSYS_strcmp(key, "Prev") || !FXSYS_strcmp(key, "First"))
					continue;
				if(pNextObj)
				{
					if(!UpdateReference(pNextObj, pDoc, pMapPtrToPtr))
						pDict->RemoveAt(key);
				}
				else
					return FALSE;
			}
			break;
		}
	case	PDFOBJ_ARRAY:
		{
			CPDF_Array* pArray = (CPDF_Array*)pObj;
			FX_DWORD count = pArray->GetCount();
			for(FX_DWORD i = 0; i < count; i ++)
			{
				CPDF_Object* pNextObj = pArray->GetElement(i);
				if(pNextObj)
				{
					if(!UpdateReference(pNextObj, pDoc, pMapPtrToPtr))
						return FALSE;
				}
				else
					return FALSE;
			}
			break;
		}
	case	PDFOBJ_STREAM:
		{
			CPDF_Stream* pStream = (CPDF_Stream*)pObj;
			CPDF_Dictionary* pDict = pStream->GetDict();
			if(pDict)
			{
				if(!UpdateReference(pDict, pDoc, pMapPtrToPtr))
					return FALSE;
			}
			else
				return FALSE;
			break;
		}
	default:	break;
	}

	return TRUE;
}

int	CPDF_PageOrganizer::GetNewObjId(CPDF_Document *pDoc, CFX_MapPtrToPtr* pMapPtrToPtr,
									CPDF_Reference *pRef)
{
	size_t dwObjnum = 0;
	if(!pRef)
		return 0;
	dwObjnum = pRef->GetRefObjNum();
	
	size_t dwNewObjNum = 0;
	
	pMapPtrToPtr->Lookup((FX_LPVOID)dwObjnum, (FX_LPVOID&)dwNewObjNum);
	if(dwNewObjNum)
	{
		return (int)dwNewObjNum;
	}
	else
	{
		CPDF_Object* pDirect = pRef->GetDirect();
		if(!pDirect)
		{
			return 0;
		}

		CPDF_Object* pClone = pDirect->Clone();
		if(!pClone)
		{
			return 0;
		}
		
		if(pClone->GetType() == PDFOBJ_DICTIONARY)
		{
			CPDF_Dictionary* pDictClone = (CPDF_Dictionary*)pClone;
			if(pDictClone->KeyExist("Type"))
			{
				CFX_ByteString strType = pDictClone->GetString("Type");
				if(!FXSYS_stricmp(strType, "Pages"))
				{
					pDictClone->Release();
					return 4;
				}
				else if(!FXSYS_stricmp(strType, "Page"))
				{
					pDictClone->Release();
					return  0;
				}
			}
		}
		dwNewObjNum = pDoc->AddIndirectObject(pClone);//, onum, gnum);
		pMapPtrToPtr->SetAt((FX_LPVOID)dwObjnum, (FX_LPVOID)dwNewObjNum);
		
		if(!UpdateReference(pClone, pDoc, pMapPtrToPtr))
		{
			pClone->Release();
			return 0;
		}
		return (int)dwNewObjNum;
	}
	return 0;
}

FPDF_BOOL ParserPageRangeString(CFX_ByteString rangstring, CFX_WordArray* pageArray,int nCount)
{

	if(rangstring.GetLength() != 0)
	{
		rangstring.Remove(' ');
		int nLength = rangstring.GetLength();
		CFX_ByteString cbCompareString("0123456789-,");
		for(int i=0; i<nLength; i++)
		{
			if(cbCompareString.Find(rangstring[i]) == -1)
				return FALSE;
		}
		CFX_ByteString cbMidRange;
		int nStringFrom = 0;
		int nStringTo=0;
		while(nStringTo < nLength)
		{
			nStringTo = rangstring.Find(',',nStringFrom);
			if(nStringTo == -1)
			{
				nStringTo = nLength;
			}
			cbMidRange = rangstring.Mid(nStringFrom,nStringTo-nStringFrom);
			
			int nMid = cbMidRange.Find('-');
			if(nMid == -1)
			{
				long lPageNum = atol(cbMidRange);
				if(lPageNum <= 0 || lPageNum > nCount)
					return FALSE;
				pageArray->Add((FX_WORD)lPageNum);
			}
			else
			{
				int nStartPageNum = atol(cbMidRange.Mid(0,nMid));
				if (nStartPageNum ==0)
				{
					return FALSE;
				}


				nMid = nMid+1;
				int nEnd = cbMidRange.GetLength()-nMid;

				if(nEnd ==0)return FALSE;
				
				//				int nEndPageNum = (nEnd == 0)?nCount:atol(cbMidRange.Mid(nMid,nEnd));
				int nEndPageNum = atol(cbMidRange.Mid(nMid,nEnd));
				
				if(nStartPageNum < 0 ||nStartPageNum >nEndPageNum|| nEndPageNum > nCount)
				{
					return FALSE;
				}
				else
				{
					for(int nIndex=nStartPageNum; nIndex <= nEndPageNum; nIndex ++)
						pageArray->Add(nIndex);
				}
			}
			nStringFrom = nStringTo +1;
		}
	}
	return TRUE;
}

DLLEXPORT FPDF_BOOL STDCALL FPDF_ImportPages(FPDF_DOCUMENT dest_doc,FPDF_DOCUMENT src_doc, 
											 FPDF_BYTESTRING pagerange, int index)
{
	if(dest_doc == NULL || src_doc == NULL )
		return FALSE;
	CFX_WordArray pageArray;
	CPDF_Document* pSrcDoc = (CPDF_Document*)src_doc;
	int nCount = pSrcDoc->GetPageCount();
	if(pagerange)
	{
		if(ParserPageRangeString(pagerange,&pageArray,nCount) == FALSE)
			return FALSE;
	}
	else
	{
		for(int i=1; i<=nCount; i++)
		{
			pageArray.Add(i);
		}
	}
	
	CPDF_Document* pDestDoc = (CPDF_Document*)dest_doc;
	CPDF_PageOrganizer pageOrg;

	pageOrg.PDFDocInit(pDestDoc,pSrcDoc);

	if(pageOrg.ExportPage(pSrcDoc,&pageArray,pDestDoc,index))
		return TRUE;
	return FALSE;
}

DLLEXPORT FPDF_BOOL STDCALL FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc, FPDF_DOCUMENT src_doc)
{
	if(src_doc == NULL || dest_doc == NULL)
		return false;
	CPDF_Document* pSrcDoc = (CPDF_Document*)src_doc;
	CPDF_Dictionary* pSrcDict = pSrcDoc->GetRoot();
	pSrcDict = pSrcDict->GetDict(FX_BSTRC("ViewerPreferences"));;
	if(!pSrcDict)
		return FALSE;
	CPDF_Document* pDstDoc = (CPDF_Document*)dest_doc;
	CPDF_Dictionary* pDstDict = pDstDoc->GetRoot();
	if(!pDstDict)
		return FALSE;
	pDstDict->SetAt(FX_BSTRC("ViewerPreferences"), pSrcDict->Clone(TRUE));
	return TRUE;
}