// 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 "../../public/fpdf_transformpage.h"
#include "../include/fsdk_define.h"
#include "../include/fpdfxfa/fpdfxfa_doc.h"
#include "../include/fpdfxfa/fpdfxfa_page.h"

DLLEXPORT void STDCALL FPDFPage_SetMediaBox(FPDF_PAGE page, float left, float bottom, float right, float top)
{
	if(!page)
		return;
	CPDF_Page* pPage = ((CPDFXFA_Page*)page)->GetPDFPage();
	if (!pPage) return;
	CPDF_Dictionary* pPageDict = pPage->m_pFormDict;
	CPDF_Array* pMediaBoxArray = FX_NEW CPDF_Array;
	pMediaBoxArray->Add(FX_NEW CPDF_Number(left));
	pMediaBoxArray->Add(FX_NEW CPDF_Number(bottom));
	pMediaBoxArray->Add(FX_NEW CPDF_Number(FX_FLOAT(right)));
	pMediaBoxArray->Add(FX_NEW CPDF_Number(FX_FLOAT(top)));

	pPageDict->SetAt("MediaBox", pMediaBoxArray);
}


DLLEXPORT void STDCALL FPDFPage_SetCropBox(FPDF_PAGE page, float left, float bottom, float right, float top)
{
	if(!page)
		return;
	CPDF_Page* pPage = ((CPDFXFA_Page*)page)->GetPDFPage();
	if (!pPage) return;
	CPDF_Dictionary* pPageDict = pPage->m_pFormDict;
	CPDF_Array* pCropBoxArray = FX_NEW CPDF_Array;
	pCropBoxArray->Add(FX_NEW CPDF_Number(left));
	pCropBoxArray->Add(FX_NEW CPDF_Number(bottom));
	pCropBoxArray->Add(FX_NEW CPDF_Number(FX_FLOAT(right)));
	pCropBoxArray->Add(FX_NEW CPDF_Number(FX_FLOAT(top)));


	pPageDict->SetAt("CropBox", pCropBoxArray);
}


DLLEXPORT FPDF_BOOL STDCALL FPDFPage_GetMediaBox(FPDF_PAGE page, float* left, float* bottom, float* right, float* top)
{
	if(!page)
		return FALSE;
	CPDF_Page* pPage = ((CPDFXFA_Page*)page)->GetPDFPage();
	if (!pPage) return FALSE;
	CPDF_Dictionary* pPageDict = pPage->m_pFormDict;
	CPDF_Array* pArray = pPageDict->GetArray("MediaBox");
	if(pArray)
	{
		*left = pArray->GetFloat(0);
		*bottom = pArray->GetFloat(1);
		*right = pArray->GetFloat(2);
		*top = pArray->GetFloat(3);
		return TRUE;
	}
	return FALSE;
}

DLLEXPORT FPDF_BOOL STDCALL FPDFPage_GetCropBox(FPDF_PAGE page, float* left, float* bottom, float* right, float* top)
{
	if(!page)
		return FALSE;
	CPDF_Page* pPage = ((CPDFXFA_Page*)page)->GetPDFPage();
	if (!pPage) return FALSE;
	CPDF_Dictionary* pPageDict = pPage->m_pFormDict;
	CPDF_Array* pArray = pPageDict->GetArray("CropBox");
	if(pArray)
	{
		*left = pArray->GetFloat(0);
		*bottom = pArray->GetFloat(1);
		*right = pArray->GetFloat(2);
		*top = pArray->GetFloat(3);
		return TRUE;
	}
	return FALSE;
}

DLLEXPORT FPDF_BOOL STDCALL FPDFPage_TransFormWithClip(FPDF_PAGE page, FS_MATRIX* matrix, FS_RECTF* clipRect)
{
	if(!page)
		return FALSE;

	CPDF_Page* pPage = ((CPDFXFA_Page*)page)->GetPDFPage();
	if (!pPage)
		return FALSE;

	CFX_ByteTextBuf textBuf;
	textBuf<<"q ";
	CFX_FloatRect rect(clipRect->left, clipRect->bottom, clipRect->right, clipRect->top);
	rect.Normalize();
	CFX_ByteString bsClipping;
	bsClipping.Format("%f %f %f %f re W* n ", rect.left, rect.bottom, rect.Width(), rect.Height());
	textBuf<<bsClipping;

	CFX_ByteString bsMatix;
	bsMatix.Format("%f %f %f %f %f %f cm ", matrix->a, matrix->b,matrix->c,matrix->d,matrix->e,matrix->f);
	textBuf<<bsMatix;


	CPDF_Dictionary* pPageDic = pPage->m_pFormDict;
	CPDF_Object* pContentObj = pPageDic ? pPageDic->GetElement("Contents") : NULL;
	if(!pContentObj)
		pContentObj = pPageDic ? pPageDic->GetArray("Contents") : NULL;
	if(!pContentObj)
		return FALSE;

	CPDF_Dictionary* pDic = FX_NEW CPDF_Dictionary;
	CPDF_Stream* pStream = FX_NEW CPDF_Stream(NULL,0, pDic);
	pStream->SetData(textBuf.GetBuffer(), textBuf.GetSize(), FALSE, FALSE);
	CPDF_Document* pDoc = pPage->m_pDocument;
	if(!pDoc)
		return FALSE;
	pDoc->AddIndirectObject(pStream);

	pDic = new CPDF_Dictionary;
	CPDF_Stream* pEndStream = new CPDF_Stream(NULL,0, pDic);
	pEndStream->SetData((const uint8_t*)" Q", 2, FALSE, FALSE);
	pDoc->AddIndirectObject(pEndStream);

	CPDF_Array* pContentArray = NULL;
	if (pContentObj && pContentObj->GetType() == PDFOBJ_ARRAY)
	{
		pContentArray = (CPDF_Array*)pContentObj;
		CPDF_Reference* pRef = FX_NEW CPDF_Reference(pDoc, pStream->GetObjNum());
		pContentArray->InsertAt(0, pRef);
		pContentArray->AddReference(pDoc,pEndStream);

	}
	else if(pContentObj && pContentObj->GetType() == PDFOBJ_REFERENCE)
	{
		CPDF_Reference* pReference = (CPDF_Reference*)pContentObj;
		CPDF_Object* pDirectObj = pReference->GetDirect();
		if(pDirectObj != NULL)
		{
			if(pDirectObj->GetType() == PDFOBJ_ARRAY)
			{
				pContentArray = (CPDF_Array*)pDirectObj;
				CPDF_Reference* pRef = FX_NEW CPDF_Reference(pDoc, pStream->GetObjNum());
				pContentArray->InsertAt(0, pRef);
				pContentArray->AddReference(pDoc,pEndStream);

			}
			else if(pDirectObj->GetType() == PDFOBJ_STREAM)
			{
				pContentArray = FX_NEW CPDF_Array();
				pContentArray->AddReference(pDoc,pStream->GetObjNum());
				pContentArray->AddReference(pDoc,pDirectObj->GetObjNum());
				pContentArray->AddReference(pDoc, pEndStream);
				pPageDic->SetAtReference("Contents", pDoc, pDoc->AddIndirectObject(pContentArray));
			}
		}
	}

	//Need to transform the patterns as well.
	CPDF_Dictionary* pRes = pPageDic->GetDict(FX_BSTRC("Resources"));
	if(pRes)
	{
		CPDF_Dictionary* pPattenDict = pRes->GetDict(FX_BSTRC("Pattern"));
		if(pPattenDict)
		{
			FX_POSITION pos = pPattenDict->GetStartPos();
			while(pos)
			{
				CPDF_Dictionary* pDict = NULL;
				CFX_ByteString key;
				CPDF_Object* pObj = pPattenDict->GetNextElement(pos, key);
				if(pObj->GetType() == PDFOBJ_REFERENCE)
					pObj = pObj->GetDirect();
				if(pObj->GetType() == PDFOBJ_DICTIONARY)
				{
					pDict = (CPDF_Dictionary*)pObj;
				}
				else if(pObj->GetType() == PDFOBJ_STREAM)
				{
					pDict = ((CPDF_Stream*)pObj)->GetDict();
				}
				else
					continue;

				CFX_AffineMatrix m = pDict->GetMatrix(FX_BSTRC("Matrix"));
				CFX_AffineMatrix t = *(CFX_AffineMatrix*)matrix;
				m.Concat(t);
				pDict->SetAtMatrix(FX_BSTRC("Matrix"), m);
			}
		}
	}

	return TRUE;
}

DLLEXPORT void STDCALL FPDFPageObj_TransformClipPath(FPDF_PAGEOBJECT page_object,double a, double b, double c, double d, double e, double f)
{
	CPDF_PageObject* pPageObj = (CPDF_PageObject*)page_object;
	if(pPageObj == NULL)
		return;
	CFX_AffineMatrix matrix((FX_FLOAT)a,(FX_FLOAT)b,(FX_FLOAT)c,(FX_FLOAT)d,(FX_FLOAT)e,(FX_FLOAT)f);

	//Special treatment to shading object, because the ClipPath for shading object is already transformed.
	if(pPageObj->m_Type != PDFPAGE_SHADING)
		pPageObj->TransformClipPath(matrix);
	pPageObj->TransformGeneralState(matrix);
}


DLLEXPORT FPDF_CLIPPATH STDCALL FPDF_CreateClipPath(float left, float bottom, float right, float top)
{
	CPDF_ClipPath* pNewClipPath =  FX_NEW CPDF_ClipPath();
	pNewClipPath->GetModify();
	CPDF_Path Path;
	Path.GetModify();
	Path.AppendRect(left, bottom, right, top);
	pNewClipPath->AppendPath(Path, FXFILL_ALTERNATE, FALSE);
	return pNewClipPath;
}

DLLEXPORT void STDCALL FPDF_DestroyClipPath(FPDF_CLIPPATH clipPath)
{
    delete (CPDF_ClipPath*)clipPath;
}

void OutputPath(CFX_ByteTextBuf& buf, CPDF_Path path)
{
	const CFX_PathData* pPathData = path;
	if (pPathData == NULL) return;

	FX_PATHPOINT* pPoints = pPathData->GetPoints();

	if (path.IsRect()) {
		buf << (pPoints[0].m_PointX) << " " << (pPoints[0].m_PointY) << " "
			<< (pPoints[2].m_PointX - pPoints[0].m_PointX) << " "
			<< (pPoints[2].m_PointY - pPoints[0].m_PointY) << " re\n";
		return;
	}

	CFX_ByteString temp;
	for (int i = 0; i < pPathData->GetPointCount(); i ++) {
		buf << (pPoints[i].m_PointX) << " " << (pPoints[i].m_PointY);
		int point_type = pPoints[i].m_Flag & FXPT_TYPE;
		if (point_type == FXPT_MOVETO)
			buf << " m\n";
		else if (point_type == FXPT_BEZIERTO) {
			buf << " " << (pPoints[i+1].m_PointX) << " " << (pPoints[i+1].m_PointY) << " " <<
				(pPoints[i+2].m_PointX) << " " << (pPoints[i+2].m_PointY);
			if (pPoints[i+2].m_Flag & FXPT_CLOSEFIGURE)
				buf << " c h\n";
			else
				buf << " c\n";
			i += 2;
		} else if (point_type == FXPT_LINETO) {
			if (pPoints[i].m_Flag & FXPT_CLOSEFIGURE)
				buf << " l h\n";
			else
				buf << " l\n";
		}
	}
}

DLLEXPORT void STDCALL FPDFPage_InsertClipPath(FPDF_PAGE page,FPDF_CLIPPATH clipPath)
{
	if(!page)
		return;
	CPDF_Page* pPage = ((CPDFXFA_Page*)page)->GetPDFPage();
	if (!pPage) return;
	CPDF_Dictionary* pPageDic = pPage->m_pFormDict;
	CPDF_Object* pContentObj = pPageDic ? pPageDic->GetElement("Contents") : NULL;
	if(!pContentObj)
		pContentObj = pPageDic ? pPageDic->GetArray("Contents") : NULL;
	if(!pContentObj)
		return;

	CFX_ByteTextBuf strClip;
	CPDF_ClipPath* pClipPath = (CPDF_ClipPath*)clipPath;
	FX_DWORD i;
	for (i = 0; i < pClipPath->GetPathCount(); i ++) {
		CPDF_Path path = pClipPath->GetPath(i);
		int iClipType = pClipPath->GetClipType(i);
		if (path.GetPointCount() == 0) {
			// Empty clipping (totally clipped out)
			strClip << "0 0 m W n ";
		} else {
			OutputPath(strClip, path);
			if (iClipType == FXFILL_WINDING)
				strClip << "W n\n";
			else
				strClip << "W* n\n";
		}
	}
	CPDF_Dictionary* pDic = FX_NEW CPDF_Dictionary;
	CPDF_Stream* pStream = FX_NEW CPDF_Stream(NULL,0, pDic);
	pStream->SetData(strClip.GetBuffer(), strClip.GetSize(), FALSE, FALSE);
	CPDF_Document* pDoc = pPage->m_pDocument;
	if(!pDoc)
		return;
	pDoc->AddIndirectObject(pStream);

	CPDF_Array* pContentArray = NULL;
	if (pContentObj && pContentObj->GetType() == PDFOBJ_ARRAY)
	{
		pContentArray = (CPDF_Array*)pContentObj;
		CPDF_Reference* pRef = FX_NEW CPDF_Reference(pDoc, pStream->GetObjNum());
		pContentArray->InsertAt(0, pRef);

	}
	else if(pContentObj && pContentObj->GetType() == PDFOBJ_REFERENCE)
	{
		CPDF_Reference* pReference = (CPDF_Reference*)pContentObj;
		CPDF_Object* pDirectObj = pReference->GetDirect();
		if(pDirectObj != NULL)
		{
			if(pDirectObj->GetType() == PDFOBJ_ARRAY)
			{
				pContentArray = (CPDF_Array*)pDirectObj;
				CPDF_Reference* pRef = FX_NEW CPDF_Reference(pDoc, pStream->GetObjNum());
				pContentArray->InsertAt(0, pRef);

			}
			else if(pDirectObj->GetType() == PDFOBJ_STREAM)
			{
				pContentArray = FX_NEW CPDF_Array();
				pContentArray->AddReference(pDoc,pStream->GetObjNum());
				pContentArray->AddReference(pDoc,pDirectObj->GetObjNum());
				pPageDic->SetAtReference("Contents", pDoc, pDoc->AddIndirectObject(pContentArray));
			}
		}
	}
}