// 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 "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"
#include "xfa/fxfa/parser/xfa_resolvenode_rs.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;
  XFA_RESOLVENODE_RS resolveNodeRS;
  if (!pScriptContext->ResolveObjects(ToNode(refNode),
                                      expression.AsStringView(), &resolveNodeRS,
                                      dwFlag, nullptr)) {
    return CJS_Result::Success(runtime->NewNull());
  }

  if (resolveNodeRS.dwFlags == XFA_ResolveNode_RSType_Nodes) {
    CXFA_Object* pObject = resolveNodeRS.objects.front().Get();
    CFXJSE_Value* value =
        GetDocument()->GetScriptContext()->GetOrCreateJSBindingFromMap(pObject);

    return CJS_Result::Success(
        value->DirectGetValue().Get(runtime->GetIsolate()));
  }

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

  auto pValue = std::make_unique<CFXJSE_Value>(pScriptContext->GetIsolate());
  CJX_Object* jsObject = resolveNodeRS.objects.front()->JSObject();
  (*resolveNodeRS.script_attribute.callback)(
      jsObject, pValue.get(), false, resolveNodeRS.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>(pScriptContext->GetIsolate());
  ResolveNodeList(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(CFXJSE_Value* pValue,
                   bool bSetting,
                   XFA_Attribute eAttribute) {
  if (bSetting) {
    ThrowInvalidPropertyException();
    return;
  }

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

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

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

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

  CFXJSE_Engine* pScriptContext = GetDocument()->GetScriptContext();
  CXFA_AttachNodeList* pNodeList =
      new CXFA_AttachNodeList(GetDocument(), GetXFANode());
  pValue->SetHostObject(pNodeList, pScriptContext->GetJseNormalClass());
}

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

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

  pValue->Assign(
      GetDocument()->GetScriptContext()->GetOrCreateJSBindingFromMap(pParent));
}

void CJX_Tree::index(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(pdfium::base::checked_cast<int32_t>(iIndex));
}

void CJX_Tree::classIndex(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(pdfium::base::checked_cast<int32_t>(iIndex));
}

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

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

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

  XFA_RESOLVENODE_RS resolveNodeRS;
  CFXJSE_Engine* pScriptContext = GetDocument()->GetScriptContext();
  pScriptContext->ResolveObjects(refNode, wsExpression.AsStringView(),
                                 &resolveNodeRS, dwFlag, nullptr);
  CXFA_ArrayNodeList* pNodeList = new CXFA_ArrayNodeList(GetDocument());
  if (resolveNodeRS.dwFlags == XFA_ResolveNode_RSType_Nodes) {
    for (auto& pObject : resolveNodeRS.objects) {
      if (pObject->IsNode())
        pNodeList->Append(pObject->AsNode());
    }
  } else {
    if (resolveNodeRS.script_attribute.callback &&
        resolveNodeRS.script_attribute.eValueType == XFA_ScriptType::Object) {
      for (auto& pObject : resolveNodeRS.objects) {
        auto innerValue =
            std::make_unique<CFXJSE_Value>(pScriptContext->GetIsolate());
        CJX_Object* jsObject = pObject->JSObject();
        (*resolveNodeRS.script_attribute.callback)(
            jsObject, innerValue.get(), false,
            resolveNodeRS.script_attribute.attribute);
        CXFA_Object* obj = CFXJSE_Engine::ToObject(innerValue.get());
        if (obj->IsNode())
          pNodeList->Append(obj->AsNode());
      }
    }
  }
  pValue->SetHostObject(pNodeList, pScriptContext->GetJseNormalClass());
}
