// 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 = new CPDF_Array;
  pMediaBoxArray->Add(new CPDF_Number(left));
  pMediaBoxArray->Add(new CPDF_Number(bottom));
  pMediaBoxArray->Add(new CPDF_Number(FX_FLOAT(right)));
  pMediaBoxArray->Add(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 = new CPDF_Array;
  pCropBoxArray->Add(new CPDF_Number(left));
  pCropBoxArray->Add(new CPDF_Number(bottom));
  pCropBoxArray->Add(new CPDF_Number(FX_FLOAT(right)));
  pCropBoxArray->Add(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 = new CPDF_Dictionary;
  CPDF_Stream* pStream = 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 = 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 = new CPDF_Reference(pDoc, pStream->GetObjNum());
        pContentArray->InsertAt(0, pRef);
        pContentArray->AddReference(pDoc, pEndStream);

      } else if (pDirectObj->GetType() == PDFOBJ_STREAM) {
        pContentArray = 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 = 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 = new CPDF_Dictionary;
  CPDF_Stream* pStream = 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 = 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 = new CPDF_Reference(pDoc, pStream->GetObjNum());
        pContentArray->InsertAt(0, pRef);

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