// 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_ppo.h"
#include "../include/fsdk_define.h"
#include "../include/fpdfxfa/fpdfxfa_doc.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;
    producerstr.Format("PDFium");
    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((void*)(uintptr_t)dwOldPageObj, (void*)(uintptr_t)dwNewPageObj);

        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);
        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)
{
    if(!pRef)
        return 0;

    size_t dwObjnum = pRef->GetRefObjNum();
    size_t dwNewObjNum = 0;

    pMapPtrToPtr->Lookup((void*)dwObjnum, (void*&)dwNewObjNum);
    if(dwNewObjNum)
    {
        return (int)dwNewObjNum;
    }

    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);
    pMapPtrToPtr->SetAt((void*)dwObjnum, (void*)dwNewObjNum);
    if(!UpdateReference(pClone, pDoc, pMapPtrToPtr))
    {
        pClone->Release();
        return 0;
    }

    return (int)dwNewObjNum;
}

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 = atol(cbMidRange.Mid(nMid,nEnd));
                if (nStartPageNum < 0 ||nStartPageNum >nEndPageNum|| nEndPageNum > nCount)
                    return FALSE;

                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;
    CPDFXFA_Document* pSrcDoc = (CPDFXFA_Document*)src_doc;
    CPDF_Document* pSrcPDFDoc = pSrcDoc->GetPDFDoc();
    int nCount = pSrcPDFDoc->GetPageCount();
    if(pagerange)
    {
        if(ParserPageRangeString(pagerange,&pageArray,nCount) == FALSE)
            return FALSE;
    }
    else
    {
        for(int i=1; i<=nCount; i++)
        {
            pageArray.Add(i);
        }
    }

    CPDFXFA_Document* pDestDoc = (CPDFXFA_Document*)dest_doc;
    CPDF_Document* pDestPDFDoc = pDestDoc->GetPDFDoc();
    CPDF_PageOrganizer pageOrg;

    pageOrg.PDFDocInit(pDestPDFDoc,pSrcPDFDoc);

    if(pageOrg.ExportPage(pSrcPDFDoc,&pageArray,pDestPDFDoc,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;
    CPDFXFA_Document* pSrcDoc = (CPDFXFA_Document*)src_doc;
    CPDF_Document* pSrcPDFDoc = pSrcDoc->GetPDFDoc();
    CPDF_Dictionary* pSrcDict = pSrcPDFDoc->GetRoot();
    pSrcDict = pSrcDict->GetDict(FX_BSTRC("ViewerPreferences"));
    if(!pSrcDict)
        return FALSE;
    CPDFXFA_Document* pDstDoc = (CPDFXFA_Document*)dest_doc;
    CPDF_Document* pDstPDFDoc = pDstDoc->GetPDFDoc();
    CPDF_Dictionary* pDstDict = pDstPDFDoc->GetRoot();
    if(!pDstDict)
        return FALSE;
    pDstDict->SetAt(FX_BSTRC("ViewerPreferences"), pSrcDict->Clone(TRUE));
    return TRUE;
}