// Copyright 2016 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 "core/fpdfdoc/cpdf_occontext.h"

#include "core/fpdfapi/page/cpdf_pageobject.h"
#include "core/fpdfapi/parser/cpdf_array.h"
#include "core/fpdfapi/parser/cpdf_document.h"

namespace {

int32_t FindGroup(const CPDF_Array* pArray, const CPDF_Dictionary* pGroupDict) {
  if (!pArray || !pGroupDict)
    return -1;

  for (size_t i = 0; i < pArray->GetCount(); i++) {
    if (pArray->GetDictAt(i) == pGroupDict)
      return i;
  }
  return -1;
}

bool HasIntent(const CPDF_Dictionary* pDict,
               const ByteStringView& csElement,
               const ByteStringView& csDef) {
  CPDF_Object* pIntent = pDict->GetDirectObjectFor("Intent");
  if (!pIntent)
    return csElement == csDef;

  ByteString bsIntent;
  if (CPDF_Array* pArray = pIntent->AsArray()) {
    for (size_t i = 0; i < pArray->GetCount(); i++) {
      bsIntent = pArray->GetStringAt(i);
      if (bsIntent == "All" || bsIntent == csElement)
        return true;
    }
    return false;
  }
  bsIntent = pIntent->GetString();
  return bsIntent == "All" || bsIntent == csElement;
}

CPDF_Dictionary* GetConfig(CPDF_Document* pDoc,
                           const CPDF_Dictionary* pOCGDict) {
  ASSERT(pOCGDict);
  CPDF_Dictionary* pOCProperties = pDoc->GetRoot()->GetDictFor("OCProperties");
  if (!pOCProperties)
    return nullptr;

  CPDF_Array* pOCGs = pOCProperties->GetArrayFor("OCGs");
  if (!pOCGs)
    return nullptr;

  if (FindGroup(pOCGs, pOCGDict) < 0)
    return nullptr;

  CPDF_Dictionary* pConfig = pOCProperties->GetDictFor("D");
  CPDF_Array* pConfigs = pOCProperties->GetArrayFor("Configs");
  if (!pConfigs)
    return pConfig;

  for (size_t i = 0; i < pConfigs->GetCount(); i++) {
    CPDF_Dictionary* pFind = pConfigs->GetDictAt(i);
    if (pFind && HasIntent(pFind, "View", "View"))
      return pFind;
  }
  return pConfig;
}

ByteString GetUsageTypeString(CPDF_OCContext::UsageType eType) {
  ByteString csState;
  switch (eType) {
    case CPDF_OCContext::Design:
      csState = "Design";
      break;
    case CPDF_OCContext::Print:
      csState = "Print";
      break;
    case CPDF_OCContext::Export:
      csState = "Export";
      break;
    default:
      csState = "View";
      break;
  }
  return csState;
}

}  // namespace

CPDF_OCContext::CPDF_OCContext(CPDF_Document* pDoc, UsageType eUsageType)
    : m_pDocument(pDoc), m_eUsageType(eUsageType) {
  ASSERT(pDoc);
}

CPDF_OCContext::~CPDF_OCContext() {}

bool CPDF_OCContext::LoadOCGStateFromConfig(
    const ByteString& csConfig,
    const CPDF_Dictionary* pOCGDict) const {
  CPDF_Dictionary* pConfig = GetConfig(m_pDocument.Get(), pOCGDict);
  if (!pConfig)
    return true;

  bool bState = pConfig->GetStringFor("BaseState", "ON") != "OFF";
  CPDF_Array* pArray = pConfig->GetArrayFor("ON");
  if (pArray) {
    if (FindGroup(pArray, pOCGDict) >= 0)
      bState = true;
  }
  pArray = pConfig->GetArrayFor("OFF");
  if (pArray) {
    if (FindGroup(pArray, pOCGDict) >= 0)
      bState = false;
  }
  pArray = pConfig->GetArrayFor("AS");
  if (!pArray)
    return bState;

  ByteString csFind = csConfig + "State";
  for (size_t i = 0; i < pArray->GetCount(); i++) {
    CPDF_Dictionary* pUsage = pArray->GetDictAt(i);
    if (!pUsage)
      continue;

    if (pUsage->GetStringFor("Event", "View") != csConfig)
      continue;

    CPDF_Array* pOCGs = pUsage->GetArrayFor("OCGs");
    if (!pOCGs)
      continue;

    if (FindGroup(pOCGs, pOCGDict) < 0)
      continue;

    CPDF_Dictionary* pState = pUsage->GetDictFor(csConfig);
    if (!pState)
      continue;

    bState = pState->GetStringFor(csFind) != "OFF";
  }
  return bState;
}

bool CPDF_OCContext::LoadOCGState(const CPDF_Dictionary* pOCGDict) const {
  if (!HasIntent(pOCGDict, "View", "View"))
    return true;

  ByteString csState = GetUsageTypeString(m_eUsageType);
  CPDF_Dictionary* pUsage = pOCGDict->GetDictFor("Usage");
  if (pUsage) {
    CPDF_Dictionary* pState = pUsage->GetDictFor(csState);
    if (pState) {
      ByteString csFind = csState + "State";
      if (pState->KeyExist(csFind))
        return pState->GetStringFor(csFind) != "OFF";
    }
    if (csState != "View") {
      pState = pUsage->GetDictFor("View");
      if (pState && pState->KeyExist("ViewState"))
        return pState->GetStringFor("ViewState") != "OFF";
    }
  }
  return LoadOCGStateFromConfig(csState, pOCGDict);
}

bool CPDF_OCContext::GetOCGVisible(const CPDF_Dictionary* pOCGDict) {
  if (!pOCGDict)
    return false;

  const auto it = m_OCGStates.find(pOCGDict);
  if (it != m_OCGStates.end())
    return it->second;

  bool bState = LoadOCGState(pOCGDict);
  m_OCGStates[pOCGDict] = bState;
  return bState;
}

bool CPDF_OCContext::CheckObjectVisible(const CPDF_PageObject* pObj) {
  for (size_t i = 0; i < pObj->m_ContentMark.CountItems(); ++i) {
    const CPDF_ContentMarkItem& item = pObj->m_ContentMark.GetItem(i);
    if (item.GetName() == "OC" &&
        item.GetParamType() == CPDF_ContentMarkItem::PropertiesDict &&
        !CheckOCGVisible(item.GetParam())) {
      return false;
    }
  }
  return true;
}

bool CPDF_OCContext::GetOCGVE(CPDF_Array* pExpression, int nLevel) {
  if (nLevel > 32 || !pExpression)
    return false;

  ByteString csOperator = pExpression->GetStringAt(0);
  if (csOperator == "Not") {
    CPDF_Object* pOCGObj = pExpression->GetDirectObjectAt(1);
    if (!pOCGObj)
      return false;
    if (CPDF_Dictionary* pDict = pOCGObj->AsDictionary())
      return !GetOCGVisible(pDict);
    if (CPDF_Array* pArray = pOCGObj->AsArray())
      return !GetOCGVE(pArray, nLevel + 1);
    return false;
  }

  if (csOperator != "Or" && csOperator != "And")
    return false;

  bool bValue = false;
  for (size_t i = 1; i < pExpression->GetCount(); i++) {
    CPDF_Object* pOCGObj = pExpression->GetDirectObjectAt(1);
    if (!pOCGObj)
      continue;

    bool bItem = false;
    if (CPDF_Dictionary* pDict = pOCGObj->AsDictionary())
      bItem = GetOCGVisible(pDict);
    else if (CPDF_Array* pArray = pOCGObj->AsArray())
      bItem = GetOCGVE(pArray, nLevel + 1);

    if (i == 1) {
      bValue = bItem;
    } else {
      if (csOperator == "Or") {
        bValue = bValue || bItem;
      } else {
        bValue = bValue && bItem;
      }
    }
  }
  return bValue;
}

bool CPDF_OCContext::LoadOCMDState(const CPDF_Dictionary* pOCMDDict) {
  CPDF_Array* pVE = pOCMDDict->GetArrayFor("VE");
  if (pVE)
    return GetOCGVE(pVE, 0);

  ByteString csP = pOCMDDict->GetStringFor("P", "AnyOn");
  CPDF_Object* pOCGObj = pOCMDDict->GetDirectObjectFor("OCGs");
  if (!pOCGObj)
    return true;

  if (const CPDF_Dictionary* pDict = pOCGObj->AsDictionary())
    return GetOCGVisible(pDict);

  CPDF_Array* pArray = pOCGObj->AsArray();
  if (!pArray)
    return true;

  bool bState = (csP == "AllOn" || csP == "AllOff");
  // At least one entry of OCGs needs to be a valid dictionary for it to be
  // considered present. See "OCGs" in table 4.49 in the PDF 1.7 spec.
  bool bValidEntrySeen = false;
  for (size_t i = 0; i < pArray->GetCount(); i++) {
    bool bItem = true;
    CPDF_Dictionary* pItemDict = pArray->GetDictAt(i);
    if (!pItemDict)
      continue;

    bValidEntrySeen = true;
    bItem = GetOCGVisible(pItemDict);

    if ((csP == "AnyOn" && bItem) || (csP == "AnyOff" && !bItem))
      return true;
    if ((csP == "AllOn" && !bItem) || (csP == "AllOff" && bItem))
      return false;
  }

  return !bValidEntrySeen || bState;
}

bool CPDF_OCContext::CheckOCGVisible(const CPDF_Dictionary* pOCGDict) {
  if (!pOCGDict)
    return true;

  ByteString csType = pOCGDict->GetStringFor("Type", "OCG");
  if (csType == "OCG")
    return GetOCGVisible(pOCGDict);
  return LoadOCMDState(pOCGDict);
}