// Copyright 2017 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 "fxjs/xfa/cjx_tree.h"

#include <memory>
#include <vector>

#include "fxjs/js_resources.h"
#include "fxjs/xfa/cfxjse_engine.h"
#include "fxjs/xfa/cfxjse_value.h"
#include "third_party/base/numerics/safe_conversions.h"
#include "v8/include/cppgc/allocation.h"
#include "xfa/fxfa/parser/cxfa_arraynodelist.h"
#include "xfa/fxfa/parser/cxfa_attachnodelist.h"
#include "xfa/fxfa/parser/cxfa_document.h"
#include "xfa/fxfa/parser/cxfa_node.h"
#include "xfa/fxfa/parser/cxfa_object.h"

const CJX_MethodSpec CJX_Tree::MethodSpecs[] = {
    {"resolveNode", resolveNode_static},
    {"resolveNodes", resolveNodes_static}};

CJX_Tree::CJX_Tree(CXFA_Object* obj) : CJX_Object(obj) {
  DefineMethods(MethodSpecs);
}

CJX_Tree::~CJX_Tree() = default;

bool CJX_Tree::DynamicTypeIs(TypeTag eType) const {
  return eType == static_type__ || ParentType__::DynamicTypeIs(eType);
}

CJS_Result CJX_Tree::resolveNode(
    CFX_V8* runtime,
    const std::vector<v8::Local<v8::Value>>& params) {
  if (params.size() != 1)
    return CJS_Result::Failure(JSMessage::kParamError);

  WideString expression = runtime->ToWideString(params[0]);
  CFXJSE_Engine* pScriptContext = GetDocument()->GetScriptContext();
  CXFA_Object* refNode = GetXFAObject();
  if (refNode->GetElementType() == XFA_Element::Xfa)
    refNode = pScriptContext->GetThisObject();

  uint32_t dwFlag = XFA_RESOLVENODE_Children | XFA_RESOLVENODE_Attributes |
                    XFA_RESOLVENODE_Properties | XFA_RESOLVENODE_Parent |
                    XFA_RESOLVENODE_Siblings;
  Optional<CFXJSE_Engine::ResolveResult> maybeResult =
      pScriptContext->ResolveObjects(ToNode(refNode), expression.AsStringView(),
                                     dwFlag);
  if (!maybeResult.has_value())
    return CJS_Result::Success(runtime->NewNull());

  if (maybeResult.value().type == CFXJSE_Engine::ResolveResult::Type::kNodes) {
    return CJS_Result::Success(
        GetDocument()->GetScriptContext()->GetOrCreateJSBindingFromMap(
            maybeResult.value().objects.front().Get()));
  }

  if (!maybeResult.value().script_attribute.callback ||
      maybeResult.value().script_attribute.eValueType !=
          XFA_ScriptType::Object) {
    return CJS_Result::Success(runtime->NewNull());
  }

  auto pValue = std::make_unique<CFXJSE_Value>();
  CJX_Object* jsObject = maybeResult.value().objects.front()->JSObject();
  (*maybeResult.value().script_attribute.callback)(
      runtime->GetIsolate(), jsObject, pValue.get(), false,
      maybeResult.value().script_attribute.attribute);
  return CJS_Result::Success(
      pValue->DirectGetValue().Get(runtime->GetIsolate()));
}

CJS_Result CJX_Tree::resolveNodes(
    CFX_V8* runtime,
    const std::vector<v8::Local<v8::Value>>& params) {
  if (params.size() != 1)
    return CJS_Result::Failure(JSMessage::kParamError);

  CXFA_Object* refNode = GetXFAObject();
  if (refNode->GetElementType() == XFA_Element::Xfa)
    refNode = GetDocument()->GetScriptContext()->GetThisObject();

  CFXJSE_Engine* pScriptContext = GetDocument()->GetScriptContext();
  auto pValue = std::make_unique<CFXJSE_Value>();
  ResolveNodeList(pScriptContext->GetIsolate(), pValue.get(),
                  runtime->ToWideString(params[0]),
                  XFA_RESOLVENODE_Children | XFA_RESOLVENODE_Attributes |
                      XFA_RESOLVENODE_Properties | XFA_RESOLVENODE_Parent |
                      XFA_RESOLVENODE_Siblings,
                  ToNode(refNode));
  return CJS_Result::Success(
      pValue->DirectGetValue().Get(runtime->GetIsolate()));
}

void CJX_Tree::all(v8::Isolate* pIsolate,
                   CFXJSE_Value* pValue,
                   bool bSetting,
                   XFA_Attribute eAttribute) {
  if (bSetting) {
    ThrowInvalidPropertyException();
    return;
  }

  uint32_t dwFlag = XFA_RESOLVENODE_Siblings | XFA_RESOLVENODE_ALL;
  WideString wsExpression = GetAttributeByEnum(XFA_Attribute::Name) + L"[*]";
  ResolveNodeList(pIsolate, pValue, wsExpression, dwFlag, nullptr);
}

void CJX_Tree::classAll(v8::Isolate* pIsolate,
                        CFXJSE_Value* pValue,
                        bool bSetting,
                        XFA_Attribute eAttribute) {
  if (bSetting) {
    ThrowInvalidPropertyException();
    return;
  }

  WideString wsExpression =
      L"#" + WideString::FromASCII(GetXFAObject()->GetClassName()) + L"[*]";
  ResolveNodeList(pIsolate, pValue, wsExpression,
                  XFA_RESOLVENODE_Siblings | XFA_RESOLVENODE_ALL, nullptr);
}

void CJX_Tree::nodes(v8::Isolate* pIsolate,
                     CFXJSE_Value* pValue,
                     bool bSetting,
                     XFA_Attribute eAttribute) {
  if (bSetting) {
    WideString wsMessage = L"Unable to set ";
    FXJSE_ThrowMessage(wsMessage.ToUTF8().AsStringView());
    return;
  }

  CXFA_Document* pDoc = GetDocument();
  auto* pNodeList = cppgc::MakeGarbageCollected<CXFA_AttachNodeList>(
      pDoc->GetHeap()->GetAllocationHandle(), pDoc, GetXFANode());
  pDoc->GetNodeOwner()->PersistList(pNodeList);

  CFXJSE_Engine* pEngine = pDoc->GetScriptContext();
  pValue->SetHostObject(pIsolate, pNodeList->JSObject(),
                        pEngine->GetJseNormalClass());
}

void CJX_Tree::parent(v8::Isolate* pIsolate,
                      CFXJSE_Value* pValue,
                      bool bSetting,
                      XFA_Attribute eAttribute) {
  if (bSetting) {
    ThrowInvalidPropertyException();
    return;
  }

  CXFA_Node* pParent = GetXFANode()->GetParent();
  if (!pParent) {
    pValue->SetNull(pIsolate);
    return;
  }

  pValue->ForceSetValue(
      pIsolate,
      GetDocument()->GetScriptContext()->GetOrCreateJSBindingFromMap(pParent));
}

void CJX_Tree::index(v8::Isolate* pIsolate,
                     CFXJSE_Value* pValue,
                     bool bSetting,
                     XFA_Attribute eAttribute) {
  if (bSetting) {
    ThrowInvalidPropertyException();
    return;
  }

  CXFA_Node* pNode = GetXFANode();
  size_t iIndex = pNode ? pNode->GetIndexByName() : 0;
  pValue->SetInteger(pIsolate, pdfium::base::checked_cast<int32_t>(iIndex));
}

void CJX_Tree::classIndex(v8::Isolate* pIsolate,
                          CFXJSE_Value* pValue,
                          bool bSetting,
                          XFA_Attribute eAttribute) {
  if (bSetting) {
    ThrowInvalidPropertyException();
    return;
  }

  CXFA_Node* pNode = GetXFANode();
  size_t iIndex = pNode ? pNode->GetIndexByClassName() : 0;
  pValue->SetInteger(pIsolate, pdfium::base::checked_cast<int32_t>(iIndex));
}

void CJX_Tree::somExpression(v8::Isolate* pIsolate,
                             CFXJSE_Value* pValue,
                             bool bSetting,
                             XFA_Attribute eAttribute) {
  if (bSetting) {
    ThrowInvalidPropertyException();
    return;
  }

  WideString wsSOMExpression = GetXFAObject()->GetSOMExpression();
  pValue->SetString(pIsolate, wsSOMExpression.ToUTF8().AsStringView());
}

void CJX_Tree::ResolveNodeList(v8::Isolate* pIsolate,
                               CFXJSE_Value* pValue,
                               WideString wsExpression,
                               uint32_t dwFlag,
                               CXFA_Node* refNode) {
  if (!refNode)
    refNode = GetXFANode();

  CXFA_Document* pDoc = GetDocument();
  CFXJSE_Engine* pScriptContext = pDoc->GetScriptContext();
  Optional<CFXJSE_Engine::ResolveResult> maybeResult =
      pScriptContext->ResolveObjects(refNode, wsExpression.AsStringView(),
                                     dwFlag);

  auto* pNodeList = cppgc::MakeGarbageCollected<CXFA_ArrayNodeList>(
      pDoc->GetHeap()->GetAllocationHandle(), pDoc);
  pDoc->GetNodeOwner()->PersistList(pNodeList);

  if (maybeResult.has_value()) {
    if (maybeResult.value().type ==
        CFXJSE_Engine::ResolveResult::Type::kNodes) {
      for (auto& pObject : maybeResult.value().objects) {
        if (pObject->IsNode())
          pNodeList->Append(pObject->AsNode());
      }
    } else {
      if (maybeResult.value().script_attribute.callback &&
          maybeResult.value().script_attribute.eValueType ==
              XFA_ScriptType::Object) {
        for (auto& pObject : maybeResult.value().objects) {
          auto innerValue = std::make_unique<CFXJSE_Value>();
          CJX_Object* jsObject = pObject->JSObject();
          (*maybeResult.value().script_attribute.callback)(
              pIsolate, jsObject, innerValue.get(), false,
              maybeResult.value().script_attribute.attribute);
          CXFA_Object* obj = CFXJSE_Engine::ToObject(
              pScriptContext->GetIsolate(), innerValue.get());
          if (obj->IsNode())
            pNodeList->Append(obj->AsNode());
        }
      }
    }
  }
  pValue->SetHostObject(pIsolate, pNodeList->JSObject(),
                        pScriptContext->GetJseNormalClass());
}
