// 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 "../../include/javascript/JavaScript.h"
#include "../../include/javascript/JS_Define.h"
#include "../../include/javascript/JS_Object.h"
#include "../../include/javascript/JS_Value.h"
#include "../../include/javascript/Document.h"

/* ---------------------------- CJS_Value ---------------------------- */

CJS_Value::CJS_Value(v8::Isolate* isolate)
    : m_eType(VT_unknown), m_isolate(isolate) {}
CJS_Value::CJS_Value(v8::Isolate* isolate,
                     v8::Local<v8::Value> pValue,
                     FXJSVALUETYPE t)
    : m_pValue(pValue), m_eType(t), m_isolate(isolate) {}

CJS_Value::CJS_Value(v8::Isolate* isolate, const int& iValue)
    : m_isolate(isolate) {
  operator=(iValue);
}

CJS_Value::CJS_Value(v8::Isolate* isolate, const bool& bValue)
    : m_isolate(isolate) {
  operator=(bValue);
}

CJS_Value::CJS_Value(v8::Isolate* isolate, const float& fValue)
    : m_isolate(isolate) {
  operator=(fValue);
}

CJS_Value::CJS_Value(v8::Isolate* isolate, const double& dValue)
    : m_isolate(isolate) {
  operator=(dValue);
}

CJS_Value::CJS_Value(v8::Isolate* isolate, v8::Local<v8::Object> pJsObj)
    : m_isolate(isolate) {
  operator=(pJsObj);
}

CJS_Value::CJS_Value(v8::Isolate* isolate, CJS_Object* pJsObj)
    : m_isolate(isolate) {
  operator=(pJsObj);
}

CJS_Value::CJS_Value(v8::Isolate* isolate, CJS_Document* pJsDoc)
    : m_isolate(isolate) {
  m_eType = VT_object;
  if (pJsDoc)
    m_pValue = (v8::Local<v8::Object>)*pJsDoc;
}

CJS_Value::CJS_Value(v8::Isolate* isolate, const FX_WCHAR* pWstr)
    : m_isolate(isolate) {
  operator=(pWstr);
}

CJS_Value::CJS_Value(v8::Isolate* isolate, const FX_CHAR* pStr)
    : m_isolate(isolate) {
  operator=(pStr);
}

CJS_Value::CJS_Value(v8::Isolate* isolate, CJS_Array& array)
    : m_isolate(isolate) {
  operator=(array);
}

CJS_Value::~CJS_Value() {}

void CJS_Value::Attach(v8::Local<v8::Value> pValue, FXJSVALUETYPE t) {
  m_pValue = pValue;
  m_eType = t;
}

void CJS_Value::Attach(CJS_Value* pValue) {
  if (pValue)
    Attach(pValue->ToV8Value(), pValue->GetType());
}

void CJS_Value::Detach() {
  m_pValue = v8::Local<v8::Value>();
  m_eType = VT_unknown;
}

/* ----------------------------------------------------------------------------------------
 */

int CJS_Value::ToInt() const {
  return JS_ToInt32(m_isolate, m_pValue);
}

bool CJS_Value::ToBool() const {
  return JS_ToBoolean(m_isolate, m_pValue);
}

double CJS_Value::ToDouble() const {
  return JS_ToNumber(m_isolate, m_pValue);
}

float CJS_Value::ToFloat() const {
  return (float)ToDouble();
}

CJS_Object* CJS_Value::ToCJSObject() const {
  v8::Local<v8::Object> pObj = JS_ToObject(m_isolate, m_pValue);
  return (CJS_Object*)JS_GetPrivate(m_isolate, pObj);
}

v8::Local<v8::Object> CJS_Value::ToV8Object() const {
  return JS_ToObject(m_isolate, m_pValue);
}

CFX_WideString CJS_Value::ToCFXWideString() const {
  return JS_ToString(m_isolate, m_pValue);
}

CFX_ByteString CJS_Value::ToCFXByteString() const {
  return CFX_ByteString::FromUnicode(ToCFXWideString());
}

v8::Local<v8::Value> CJS_Value::ToV8Value() const {
  return m_pValue;
}

v8::Local<v8::Array> CJS_Value::ToV8Array() const {
  if (IsArrayObject())
    return v8::Local<v8::Array>::Cast(JS_ToObject(m_isolate, m_pValue));
  return v8::Local<v8::Array>();
}

/* ----------------------------------------------------------------------------------------
 */

void CJS_Value::operator=(int iValue) {
  m_pValue = JS_NewNumber(m_isolate, iValue);

  m_eType = VT_number;
}

void CJS_Value::operator=(bool bValue) {
  m_pValue = JS_NewBoolean(m_isolate, bValue);

  m_eType = VT_boolean;
}

void CJS_Value::operator=(double dValue) {
  m_pValue = JS_NewNumber(m_isolate, dValue);

  m_eType = VT_number;
}

void CJS_Value::operator=(float fValue) {
  m_pValue = JS_NewNumber(m_isolate, fValue);
  m_eType = VT_number;
}

void CJS_Value::operator=(v8::Local<v8::Object> pObj) {
  m_pValue = JS_NewObject(m_isolate, pObj);

  m_eType = VT_fxobject;
}

void CJS_Value::operator=(CJS_Object* pObj) {
  if (pObj)
    operator=((v8::Local<v8::Object>)*pObj);
}

void CJS_Value::operator=(CJS_Document* pJsDoc) {
  m_eType = VT_object;
  if (pJsDoc) {
    m_pValue = static_cast<v8::Local<v8::Object>>(*pJsDoc);
  }
}

void CJS_Value::operator=(const FX_WCHAR* pWstr) {
  m_pValue = JS_NewString(m_isolate, (wchar_t*)pWstr);

  m_eType = VT_string;
}

void CJS_Value::SetNull() {
  m_pValue = JS_NewNull();

  m_eType = VT_null;
}

void CJS_Value::operator=(const FX_CHAR* pStr) {
  operator=(CFX_WideString::FromLocal(pStr).c_str());
}

void CJS_Value::operator=(CJS_Array& array) {
  m_pValue = JS_NewObject2(m_isolate, (v8::Local<v8::Array>)array);

  m_eType = VT_object;
}

void CJS_Value::operator=(CJS_Date& date) {
  m_pValue = JS_NewDate(m_isolate, (double)date);

  m_eType = VT_date;
}

void CJS_Value::operator=(CJS_Value value) {
  m_pValue = value.ToV8Value();

  m_eType = value.m_eType;
  m_isolate = value.m_isolate;
}

/* ----------------------------------------------------------------------------------------
 */

FXJSVALUETYPE CJS_Value::GetType() const {
  if (m_pValue.IsEmpty())
    return VT_unknown;
  if (m_pValue->IsString())
    return VT_string;
  if (m_pValue->IsNumber())
    return VT_number;
  if (m_pValue->IsBoolean())
    return VT_boolean;
  if (m_pValue->IsDate())
    return VT_date;
  if (m_pValue->IsObject())
    return VT_object;
  if (m_pValue->IsNull())
    return VT_null;
  if (m_pValue->IsUndefined())
    return VT_undefined;
  return VT_unknown;
}

FX_BOOL CJS_Value::IsArrayObject() const {
  if (m_pValue.IsEmpty())
    return FALSE;
  return m_pValue->IsArray();
}

FX_BOOL CJS_Value::IsDateObject() const {
  if (m_pValue.IsEmpty())
    return FALSE;
  return m_pValue->IsDate();
}

// CJS_Value::operator CJS_Array()
FX_BOOL CJS_Value::ConvertToArray(CJS_Array& array) const {
  if (IsArrayObject()) {
    array.Attach(JS_ToArray(m_isolate, m_pValue));
    return TRUE;
  }

  return FALSE;
}

FX_BOOL CJS_Value::ConvertToDate(CJS_Date& date) const {
  // 	if (GetType() == VT_date)
  // 	{
  // 		date = (double)(*this);
  // 		return TRUE;
  // 	}

  if (IsDateObject()) {
    date.Attach(m_pValue);
    return TRUE;
  }

  return FALSE;
}

/* ---------------------------- CJS_PropValue ---------------------------- */

CJS_PropValue::CJS_PropValue(const CJS_Value& value)
    : CJS_Value(value), m_bIsSetting(0) {}

CJS_PropValue::CJS_PropValue(v8::Isolate* isolate)
    : CJS_Value(isolate), m_bIsSetting(0) {}

CJS_PropValue::~CJS_PropValue() {}

FX_BOOL CJS_PropValue::IsSetting() {
  return m_bIsSetting;
}

FX_BOOL CJS_PropValue::IsGetting() {
  return !m_bIsSetting;
}

void CJS_PropValue::operator<<(int iValue) {
  ASSERT(!m_bIsSetting);
  CJS_Value::operator=(iValue);
}

void CJS_PropValue::operator>>(int& iValue) const {
  ASSERT(m_bIsSetting);
  iValue = CJS_Value::ToInt();
}

void CJS_PropValue::operator<<(bool bValue) {
  ASSERT(!m_bIsSetting);
  CJS_Value::operator=(bValue);
}

void CJS_PropValue::operator>>(bool& bValue) const {
  ASSERT(m_bIsSetting);
  bValue = CJS_Value::ToBool();
}

void CJS_PropValue::operator<<(double dValue) {
  ASSERT(!m_bIsSetting);
  CJS_Value::operator=(dValue);
}

void CJS_PropValue::operator>>(double& dValue) const {
  ASSERT(m_bIsSetting);
  dValue = CJS_Value::ToDouble();
}

void CJS_PropValue::operator<<(CJS_Object* pObj) {
  ASSERT(!m_bIsSetting);
  CJS_Value::operator=(pObj);
}

void CJS_PropValue::operator>>(CJS_Object*& ppObj) const {
  ASSERT(m_bIsSetting);
  ppObj = CJS_Value::ToCJSObject();
}

void CJS_PropValue::operator<<(CJS_Document* pJsDoc) {
  ASSERT(!m_bIsSetting);
  CJS_Value::operator=(pJsDoc);
}

void CJS_PropValue::operator>>(CJS_Document*& ppJsDoc) const {
  ASSERT(m_bIsSetting);
  ppJsDoc = static_cast<CJS_Document*>(CJS_Value::ToCJSObject());
}

void CJS_PropValue::operator<<(v8::Local<v8::Object> pObj) {
  ASSERT(!m_bIsSetting);
  CJS_Value::operator=(pObj);
}

void CJS_PropValue::operator>>(v8::Local<v8::Object>& ppObj) const {
  ASSERT(m_bIsSetting);
  ppObj = CJS_Value::ToV8Object();
}

void CJS_PropValue::StartSetting() {
  m_bIsSetting = 1;
}

void CJS_PropValue::StartGetting() {
  m_bIsSetting = 0;
}
void CJS_PropValue::operator<<(CFX_ByteString string) {
  ASSERT(!m_bIsSetting);
  CJS_Value::operator=(string.c_str());
}

void CJS_PropValue::operator>>(CFX_ByteString& string) const {
  ASSERT(m_bIsSetting);
  string = CJS_Value::ToCFXByteString();
}

void CJS_PropValue::operator<<(const FX_WCHAR* c_string) {
  ASSERT(!m_bIsSetting);
  CJS_Value::operator=(c_string);
}

void CJS_PropValue::operator>>(CFX_WideString& wide_string) const {
  ASSERT(m_bIsSetting);
  wide_string = CJS_Value::ToCFXWideString();
}

void CJS_PropValue::operator<<(CFX_WideString wide_string) {
  ASSERT(!m_bIsSetting);
  CJS_Value::operator=(wide_string.c_str());
}

void CJS_PropValue::operator>>(CJS_Array& array) const {
  ASSERT(m_bIsSetting);
  ConvertToArray(array);
}

void CJS_PropValue::operator<<(CJS_Array& array) {
  ASSERT(!m_bIsSetting);
  CJS_Value::operator=(array);
}

void CJS_PropValue::operator>>(CJS_Date& date) const {
  ASSERT(m_bIsSetting);
  ConvertToDate(date);
}

void CJS_PropValue::operator<<(CJS_Date& date) {
  ASSERT(!m_bIsSetting);
  CJS_Value::operator=(date);
}

CJS_PropValue::operator v8::Local<v8::Value>() const {
  return m_pValue;
}

/* ======================================== CJS_Array
 * ========================================= */
CJS_Array::CJS_Array(v8::Isolate* isolate) : m_isolate(isolate) {}

CJS_Array::~CJS_Array() {}

void CJS_Array::Attach(v8::Local<v8::Array> pArray) {
  m_pArray = pArray;
}

FX_BOOL CJS_Array::IsAttached() {
  return FALSE;
}

void CJS_Array::GetElement(unsigned index, CJS_Value& value) {
  if (m_pArray.IsEmpty())
    return;
  v8::Local<v8::Value> p = JS_GetArrayElement(m_isolate, m_pArray, index);
  value.Attach(p, VT_object);
}

void CJS_Array::SetElement(unsigned index, CJS_Value value) {
  if (m_pArray.IsEmpty())
    m_pArray = JS_NewArray(m_isolate);

  JS_PutArrayElement(m_isolate, m_pArray, index, value.ToV8Value(),
                     value.GetType());
}

int CJS_Array::GetLength() {
  if (m_pArray.IsEmpty())
    return 0;
  return JS_GetArrayLength(m_pArray);
}

CJS_Array::operator v8::Local<v8::Array>() {
  if (m_pArray.IsEmpty())
    m_pArray = JS_NewArray(m_isolate);

  return m_pArray;
}

/* ======================================== CJS_Date
 * ========================================= */

CJS_Date::CJS_Date(v8::Isolate* isolate) : m_isolate(isolate) {}

CJS_Date::CJS_Date(v8::Isolate* isolate, double dMsec_time) {
  m_isolate = isolate;
  m_pDate = JS_NewDate(isolate, dMsec_time);
}

CJS_Date::CJS_Date(v8::Isolate* isolate,
                   int year,
                   int mon,
                   int day,
                   int hour,
                   int min,
                   int sec) {
  m_isolate = isolate;
  m_pDate = JS_NewDate(isolate, MakeDate(year, mon, day, hour, min, sec, 0));
}

double CJS_Date::MakeDate(int year,
                          int mon,
                          int day,
                          int hour,
                          int min,
                          int sec,
                          int ms) {
  return JS_MakeDate(JS_MakeDay(year, mon, day),
                     JS_MakeTime(hour, min, sec, ms));
}

CJS_Date::~CJS_Date() {}

FX_BOOL CJS_Date::IsValidDate() {
  if (m_pDate.IsEmpty())
    return FALSE;
  return !JS_PortIsNan(JS_ToNumber(m_isolate, m_pDate));
}

void CJS_Date::Attach(v8::Local<v8::Value> pDate) {
  m_pDate = pDate;
}

int CJS_Date::GetYear() {
  if (IsValidDate())
    return JS_GetYearFromTime(JS_LocalTime(JS_ToNumber(m_isolate, m_pDate)));

  return 0;
}

void CJS_Date::SetYear(int iYear) {
  double date = MakeDate(iYear, GetMonth(), GetDay(), GetHours(), GetMinutes(),
                         GetSeconds(), 0);
  JS_ValueCopy(m_pDate, JS_NewDate(m_isolate, date));
}

int CJS_Date::GetMonth() {
  if (IsValidDate())
    return JS_GetMonthFromTime(JS_LocalTime(JS_ToNumber(m_isolate, m_pDate)));

  return 0;
}

void CJS_Date::SetMonth(int iMonth) {
  double date = MakeDate(GetYear(), iMonth, GetDay(), GetHours(), GetMinutes(),
                         GetSeconds(), 0);
  JS_ValueCopy(m_pDate, JS_NewDate(m_isolate, date));
}

int CJS_Date::GetDay() {
  if (IsValidDate())
    return JS_GetDayFromTime(JS_LocalTime(JS_ToNumber(m_isolate, m_pDate)));

  return 0;
}

void CJS_Date::SetDay(int iDay) {
  double date = MakeDate(GetYear(), GetMonth(), iDay, GetHours(), GetMinutes(),
                         GetSeconds(), 0);
  JS_ValueCopy(m_pDate, JS_NewDate(m_isolate, date));
}

int CJS_Date::GetHours() {
  if (IsValidDate())
    return JS_GetHourFromTime(JS_LocalTime(JS_ToNumber(m_isolate, m_pDate)));

  return 0;
}

void CJS_Date::SetHours(int iHours) {
  double date = MakeDate(GetYear(), GetMonth(), GetDay(), iHours, GetMinutes(),
                         GetSeconds(), 0);
  JS_ValueCopy(m_pDate, JS_NewDate(m_isolate, date));
}

int CJS_Date::GetMinutes() {
  if (IsValidDate())
    return JS_GetMinFromTime(JS_LocalTime(JS_ToNumber(m_isolate, m_pDate)));

  return 0;
}

void CJS_Date::SetMinutes(int minutes) {
  double date = MakeDate(GetYear(), GetMonth(), GetDay(), GetHours(), minutes,
                         GetSeconds(), 0);
  JS_ValueCopy(m_pDate, JS_NewDate(m_isolate, date));
}

int CJS_Date::GetSeconds() {
  if (IsValidDate())
    return JS_GetSecFromTime(JS_LocalTime(JS_ToNumber(m_isolate, m_pDate)));

  return 0;
}

void CJS_Date::SetSeconds(int seconds) {
  double date = MakeDate(GetYear(), GetMonth(), GetDay(), GetHours(),
                         GetMinutes(), seconds, 0);
  JS_ValueCopy(m_pDate, JS_NewDate(m_isolate, date));
}

CJS_Date::operator v8::Local<v8::Value>() {
  return m_pDate;
}

CJS_Date::operator double() const {
  if (m_pDate.IsEmpty())
    return 0.0;
  return JS_ToNumber(m_isolate, m_pDate);
}

CFX_WideString CJS_Date::ToString() const {
  if (m_pDate.IsEmpty())
    return L"";
  return JS_ToString(m_isolate, m_pDate);
}