/*
 * Copyright (c) 2020 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef OHOS_ACELITE_JSI_H
#define OHOS_ACELITE_JSI_H

#include <cstdlib>

#include "jsi_types.h"
#include "memory_heap.h"

/**
 * Enable JS TypedArray type support.
 */
#ifndef JS_FWK_TYPEDARRAY
#define JS_FWK_TYPEDARRAY 1
#endif

/**
 * Disable JS Symbol value support.
 */
#ifndef JS_FWK_SYMBOL
#define JS_FWK_SYMBOL 0
#endif

namespace OHOS {
namespace ACELite {
/**
 * @brief End flag used in ReleaseValueList(JSIValue value, ...).
 */
static const JSIValue ARGS_END = (JSIValue)(uintptr_t)-1;

/**
 * @brief Function pointer type used to create function callback.
 *
 * @param [in] thisVal: the this value provided for the function call
 * @param [in] args: the function arguments, array of JavaScript values
 * @param [in] argsNum: the number of arguments
 */
typedef JSIValue (*JSIFunctionHandler)(const JSIValue thisVal, const JSIValue *args, uint8_t argsNum);

/**
 * @brief Description of JerryScript heap memory status.
 */
struct JSHeapStatus : public MemoryHeap {
    size_t totalBytes;     // heap total size
    size_t allocBytes;     // currently allocated bytes
    size_t peakAllocBytes; // peak allocated bytes

    JSHeapStatus(const JSHeapStatus &) = delete;
    JSHeapStatus &operator=(const JSHeapStatus &) = delete;
    JSHeapStatus(JSHeapStatus &&) = delete;
    JSHeapStatus &operator=(JSHeapStatus &&) = delete;
    JSHeapStatus() : totalBytes(0), allocBytes(0), peakAllocBytes(0) {}
};

/**
 * @brief Struct definition for JS property descriptor
 */
struct JSPropertyDescriptor : public MemoryHeap {
    JSIFunctionHandler setter; // access function for setting value
    JSIFunctionHandler getter; // access function for getting value

    JSPropertyDescriptor() : setter(nullptr), getter(nullptr) {}
};

/**
 * @brief JavaScriptInterface for adaptation to javascript engines.
 */
class JSI final : public MemoryHeap {
public:
    /**
     * @brief Get the global javascript object.
     *
     * @return the global object acquired
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue GetGlobalObject();

    /**
     * @brief Create a javascript object.
     *
     * @return the object created
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue CreateObject();

    /**
     * @brief Set property to given javascript object.
     *
     * @param [in] object: host object to set
     * @param [in] key: key of the property to set
     * @param [in] value: value of the property to set
     */
    static void SetProperty(JSIValue object, JSIValue key, JSIValue value);

    /**
     * @brief Set property to javascript object with given name.
     *
     * @param [in] object: host object to set
     * @param [in] propName: name of the property to set
     * @param [in] value: value of the property to set
     */
    static void SetNamedProperty(JSIValue object, const char * const propName, JSIValue value);

    /**
     * @brief Set number property to javascript object with given name.
     *
     * @param [in] object: host object to set
     * @param [in] propName: name of the number property to set
     * @param [in] value: number value of the property to set
     */
    static void SetNumberProperty(JSIValue object, const char * const propName, double value);

    /**
     * @brief Set boolean property to javascript object with given name.
     *
     * @param [in] object: host object to set
     * @param [in] propName: name of the boolean property to set
     * @param [in] value: boolean value of the property to set
     */
    static void SetBooleanProperty(JSIValue object, const char * const propName, bool value);

    /**
     * @brief Set string property to javascript object with given name.
     *
     * @param [in] object: host object to set
     * @param [in] propName: name of the string property to set
     * @param [in] value: string value of the property to set
     */
    static void SetStringProperty(JSIValue object, const char * const propName, const char *value);

    /**
     * @brief Set string property to javascript object with given name.
     *
     * @param [in] object: host object to set
     * @param [in] propName: name of the string property to set
     * @param [in] value: string value of the property to set
     * @param [in] size: string size of the property to set
     */
    static void
        SetStringPropertyWithBufferSize(JSIValue object, const char *const propName, const char *value, size_t size);

    /**
     * @brief Create javascript function with given native function.
     *
     * @param [in] handler: native function pointer
     * @return javascript function object
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue CreateFunction(JSIFunctionHandler handler);

    /**
     * @brief Create javascript string object with character string.
     *
     * @param [in] str: string source
     * @return the string object created
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue CreateString(const char * const str);

    /**
     * @brief Create javascript string object with character string.
     *
     * @param [in] str: string source
     * @param [in] size: string length
     * @return the string object created
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue CreateStringWithBufferSize(const char * const str, size_t size);

    /**
     * @brief Create an undefined object.
     *
     * @return the undefined object created
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue CreateUndefined();

    /**
     * @brief Check if the specified value is a function object value, the value must be created by jerry interface.
     *
     * @param [in] value: value to check
     * @return true: if the given value is a function
     *         false: otherwise
     */
    static bool ValueIsFunction(JSIValue value);

    /**
     * @brief Check if the specified value is undefined.
     *
     * @param [in] value: value to check
     * @return true: if the given value is undefined
     *         false: otherwise
     */
    static bool ValueIsUndefined(JSIValue value);

    /**
     * @brief Check if the specified value is a number.
     *
     * @param [in] value: value to check
     * @return true: if the given value is a number
     *         false: otherwise
     */
    static bool ValueIsNumber(JSIValue value);

    /**
     * @brief Check if the specified value is a string value.
     *
     * @param [in] value: value to check
     * @return true: if the given value is a string value
     *         false: otherwise
     */
    static bool ValueIsString(JSIValue value);

    /**
     * @brief Check if the specified value is a boolean value.
     *
     * @param [in] value: value to check
     * @return true: if the given value is a boolean value
     *         false: otherwise
     */
    static bool ValueIsBoolean(JSIValue value);

    /**
     * @brief Check if the specified value is a null value.
     *
     * @param [in] value: value to check
     * @return true: if the given value is a null value
     *         false: otherwise
     */
    static bool ValueIsNull(JSIValue value);

    /**
     * @brief Check if the specified value is an object value.
     *
     * @param [in] value: value to check
     * @return true: if the given value is an object value
     *         false: otherwise
     */
    static bool ValueIsObject(JSIValue value);

    /**
     * @brief Check if the specified value is error value.
     *
     * @param [in] value: value to check
     * @return true: if the given value is error value
     *         false: otherwise
     */
    static bool ValueIsError(JSIValue value);

    /**
     * @brief Get the same value as json.stringify().
     *
     * @param [in] value: value which can be json stringfied
     * @return the json string created
     * value returned should be released with ReleaseString(char *&str) when it won't be used any more
     */
    static char *JsonStringify(JSIValue value);

    /**
     * @brief Get the same value as json.parse().
     *
     * @param [in] str: character string which is in json pattern
     * @return JSI value created
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue JsonParse(const char * const str);

    /**
     * @brief Get value of a property from the specified object with the given key.
     *
     * @param [in] object: object value
     * @param [in] key: property name
     * @return acquired value of the property
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue GetProperty(JSIValue object, JSIValue key);

    /**
     * @brief Get value of a property from the specified object with the given character name.
     *
     * @param [in] object: object value
     * @param [in] propName: property name in character string
     * @return acquired value of the property
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue GetNamedProperty(JSIValue object, const char * const propName);

    /**
     * @brief Get number value from the specified object with the given property name.
     *
     * @param [in] object: object value
     * @param [in] propName: the number property name
     * @return number value acquired
     * 0.0 will be returned if the argument passed is not a number object
     */
    static double GetNumberProperty(JSIValue object, const char * const propName);

    /**
     * @brief Get boolean value from the specified object with the given property name.
     *
     * @param [in] object: object value
     * @param [in] propName: the boolean property name
     * @return true: if the property value is logical true
     *         false: otherwise
     */
    static bool GetBooleanProperty(JSIValue object, const char * const propName);

    /**
     * @brief Get string value from the specified object with the given property name.
     *
     * @param [in] object: object value
     * @param [in] propName: the string property name
     * @return the string value acquired
     * value returned should be released with ReleaseString(char *&str) when it won't be used any more
     */
    static char *GetStringProperty(JSIValue object, const char * const propName);

    /**
     * @brief Get string value from the specified object with the given property name.
     *
     * @param [in] object: object value
     * @param [in] propName: the string property name
     * @param [out] size: the string property size
     * @return the string value acquired
     * value returned should be released with ReleaseString(char *&str) when it won't be used any more
     */
    static char *GetStringPropertyWithBufferSize(JSIValue object, const char * const propName, size_t &size);

    /**
     * @brief: Release specified API value.
     *
     * @param: value JSI value to release
     */
    static void ReleaseValue(JSIValue value);

    static void ReleaseValueList() {}

    /**
     * @brief: Release API value list.
     *
     * @param: JSI value list to release
     */
    template<class T, class... Args> static void ReleaseValueList(T head, Args... rest)
    {
        ReleaseValue(head);
        ReleaseValueList(rest...);
    }

    /**
     * @brief: Release string value.
     *
     * @param: str: pointer to the buffer to be released
     */
    static void ReleaseString(char *&str);

    /**
     * @brief Call javascript function specified by a function value.
     *
     * @param [in] funcObj: the function object to call
     * @param [in] thisVal: object for 'this' binding
     * @param [in] argv:  function's call arguments
     * @param [in] argc: number of arguments
     */
    static void CallFunction(JSIValue funcObj, JSIValue thisVal, const JSIValue *argv, uint8_t argc);

    /**
     * @brief Create javascript number value.
     *
     * @param [in] value: source number
     * @return the number value created
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue CreateNumber(double value);

    /**
     * @brief Create a JSIValue representing a not-a-number value.
     *
     * @return a JSIValue representing the not-a-number value
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue CreateNumberNaN();

    /**
     * @brief Create javascript error object.
     *
     * @param [in] type: error type
     * @param [in] errorMsg: value of 'message' property of constructed error object
     * @return value of the constructed error object
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue CreateError(JsiErrorType type, const char * const errorMsg);

    /**
     * @brief Get the type of the error object.
     *
     * @param [in] errorValue: error value to get
     * @return the type of the error object
     */
    static JsiErrorType GetErrorType(JSIValue errorValue);

    /**
     * @brief Create javascript boolean value.
     *
     * @param [in] value: bool value
     * @return the boolean value created
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue CreateBoolean(bool value);

    /**
     * @brief Create javascript null object.
     *
     * @return the null object created
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue CreateNull();

#if (JS_FWK_SYMBOL == 1)
    /**
     * @brief Create a javascript symbol.
     *
     * @param [in] description: source value
     * @return the symbol object created
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue CreateSymbol(JSIValue description);

    /**
     * @brief Check if the specified value is a symbol value.
     *
     * @param [in] value: value to check
     * @return true: if the given value is a symbol value
     *         false: otherwise
     */
    static bool ValueIsSymbol(JSIValue value);
#endif // JS_FWK_SYMBOL

    /**
     * @brief Create a javascript array.
     *
     * @param [in] length: array length
     * @return the array object created
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue CreateArray(uint32_t length);

    /**
     * @brief Set indexed value in the specified javascript object.
     *
     * @param [in] object: object to set
     * @param [in] index: index number
     * @param [in] value: value to set
     * @return true: if the operation succeed
     *         false: otherwise
     */
    static bool SetPropertyByIndex(JSIValue object, uint32_t index, JSIValue value);

    /**
     * @brief Get the string value of the given JSIValue object.
     *
     * @param [in] value: source value
     * @return the string value created
     * value returned should be released with ReleaseString(char *&str) when it won't be used any more
     */
    static char *ValueToString(JSIValue value);

    /**
     * @brief Get the string value of the given JSIValue object.
     *
     * @param [in] value: source value
     * @param [out] size: source size
     * @return the string value created
     * value returned should be released with ReleaseString(char *&str) when it won't be used any more
     */
    static char *ValueToStringWithBufferSize(JSIValue value, size_t &size);

    /**
     * @brief Get the number value of the given JSIValue object.
     *
     * @param [in] value: source value
     * @return double value acquired
     * 0.0 will be returned if the argument passed is not a number object
     */
    static double ValueToNumber(JSIValue value);

    /**
     * @brief Get the boolean value of the given JSIValue object.
     *
     * @param [in] value: source value
     * @return true: if the given value is logical true
     *         false: otherwise
     */
    static bool ValueToBoolean(JSIValue value);

    /**
     * @brief Get the string value of the given any JSIValue.
     *
     * @param [in] jsi value: source value
     * @return the string value created
     * value returned should be released with ReleaseString(char *&str) when it won't be used any more
     */
    static char *JSIValueToString(JSIValue value);

    /**
     * @brief Check if the specified value is an array object.
     *
     * @param [in] value: array value
     * @return array length acquired
     * 0 will be returned if the argument passed is not an array object
     */
    static uint32_t GetArrayLength(JSIValue value);

    /**
     * @brief Get indexed value from the javascript array object.
     *
     * @param [in] object: source array object
     * @param [in] index: index number
     * @return value acquired from the specified index of the array
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue GetPropertyByIndex(JSIValue object, uint32_t index);

    /**
     * @brief Get keys of the specified object value.
     *
     * @param [in] object: object value
     * @return array object of the given object keys
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue GetObjectKeys(JSIValue object);

    /**
     * @brief Get heap memory status.
     *
     * @param [out] heapStatus: struct for heap memory status acquired
     * @return true: if the operation succeed
     *         false: otherwise
     */
    static bool GetJSHeapStatus(JSHeapStatus &heapStatus);

    /**
     * @brief Check if the specified value is an array object.
     *
     * @param [in] value: value to check
     * @return true: if the given value is an array object
     *         false: otherwise
     */
    static bool ValueIsArray(JSIValue value);

    /**
     * @brief Acquire the specified JSI value to create a reference
     *
     * @param [in] value: JSI value to acquire
     * @return acquired value that may be used outside of the engine
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue AcquireValue(JSIValue value);

    /**
     * @brief Set API for given exports object
     *
     * @param [in] exports: object to set
     * @param [in] name: API name
     * @param [in] handler: pointer to the native implementation of this JS API
     */
    static void SetModuleAPI(JSIValue exports, const char * const name, JSIFunctionHandler handler);

    /**
     * @brief Set JS page destroy callback on given module object
     *
     * @param [in] object: JS module object
     * @param [in] callback: native destroy callback
     * Note: this function is deprecated, use the other one alternatively
     */
    static void SetOnDestroy(JSIValue object, NativeCallback callback);

    /**
     * @brief set JS page destroy callback on given module object
     *
     * @param [in] object: JS module object
     * @param [in] callback: native destroy callback
     */
    static void SetOnDestroy(JSIValue object, JsiCallback callback);

    /**
     * @brief set JS ability terminate callback on given module object
     *
     * @param [in] object: JS module object
     * @param [in] callback: native terminate callback
     * Note: this function is deprecated, use the other one alternatively
     */
    static void SetOnTerminate(JSIValue object, NativeCallback callback);

    /**
     * @brief set JS ability terminate callback on given module object
     *
     * @param [in] object: JS module object
     * @param [in] callback: native terminate callback
     */
    static void SetOnTerminate(JSIValue object, JsiCallback callback);

#if (JS_FWK_TYPEDARRAY == 1)
    /**
     * @brief Get the properties of the given javascript TypedArray object.
     *
     * @param [in] typedArray: TypedArray object
     * @param [out] type: type of the TypedArray, one of the TypedArrayType enum value
     * @param [out] length: the element number of the TypedArray
     * 0 if the typedArray parameter is not a TypedArray object
     * @param [out] arrayBuffer: the ArrayBuffer object used by the TypedArray object
     * value should be released by caller with ReleaseValue when it won't be used any more
     * @param [out] byteOffset: the start offset of the ArrayBuffer for the TypedArray
     * @return pointer to the Array Buffer's data area
     * must ensure that the output pointer is used correctly, that is there is no out of bounds reads or writes
     * the lifetime of the underlying data buffer is managed by the ArrayBuffer value, thus, do not release the
     * pointer returned
     */
    static uint8_t *GetTypedArrayInfo(JSIValue typedArray,
                                      TypedArrayType &type,
                                      size_t &length,
                                      JSIValue &arrayBuffer,
                                      size_t &byteOffset);

    /**
     * @brief Create a TypedArray object using an already existing ArrayBuffer object.
     *
     * @param [in] type: type of the TypedArray, one of the TypedArrayType enum value
     * @param [in] length: the element number of the TypedArray
     * @param [in] arrayBuffer: the ArrayBuffer object to use for the new TypedArray
     * @param [in] byteOffset: the start offset of the ArrayBuffer for the TypedArray
     * @return the TypedArray object created
     * value returned should be released by caller with ReleaseValue when it won't be used any more.
     */
    static JSIValue CreateTypedArray(TypedArrayType type, size_t length, JSIValue arrayBuffer, size_t byteOffset);

    /**
     * @brief Get the properties of the given ArrayBuffer object.
     *
     * @param [in] arrayBuffer: ArrayBuffer object
     * @param [out] byteLength size of the ArrayBuffer in bytes
     * 0 if the arrayBuffer parameter is not an ArrayBuffer object
     * @return pointer to the Array Buffer's data area
     * must ensure that the output pointer is used correctly, that is there is no out of bounds reads or writes
     * the lifetime of the underlying data buffer is managed by the ArrayBuffer value, thus, do not release the
     * pointer returned
     */
    static uint8_t *GetArrayBufferInfo(JSIValue arrayBuffer, size_t &byteLength);

    /**
     * @brief Create an ArrayBuffer object.
     *
     * @param [in] byteLength: size of the ArrayBuffer to create in bytes
     * @param [out] buffPtr: pointer to the Array Buffer's data area
     * must ensure that the output pointer is used correctly, that is there is no out of bounds reads or writes
     * the lifetime of the underlying data buffer is managed by the ArrayBuffer value, thus, do not release the pointer
     * @return the ArrayBuffer object created
     * result should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue CreateArrayBuffer(size_t byteLength, uint8_t *&buffPtr);

    /**
     * @brief Check if the specified value is an ArrayBuffer object.
     *
     * @param [in] value: value to check
     * @return true: if the given value is an ArrayBuffer object
     *         false: otherwise
     */
    static bool ValueIsArrayBuffer(JSIValue value);

    /**
     * @brief Check if the specified value is a TypedArray object.
     *
     * @param [in] value: value to check
     * @return true: if the given value is an TypedArray object
     *         false: otherwise
     */
    static bool ValueIsTypedArray(JSIValue value);
#endif // JS_FWK_TYPEDARRAY

    /**
     * @brief Define a property on the specified object with the given name.
     *
     * @param [in] object: object to define property on
     * @param [in] propName: property name
     * @param [in] descriptor: property descriptor
     * @return true: if success
     *         false: otherwise
     */
    static bool DefineProperty(JSIValue object, JSIValue propName, JSPropertyDescriptor descriptor);

    /**
     * @brief Define a property on the specified object with the given character name.
     *
     * @param [in] object: object to define property on
     * @param [in] propNameStr: property name in character string
     * @param [in] descriptor: property descriptor
     * @return true: if success
     *         false: otherwise
     */
    static bool DefineNamedProperty(JSIValue object, const char * const propNameStr, JSPropertyDescriptor descriptor);

    /**
     * @brief Define a property on the specified object with the given setter and getter.
     *
     * @param [in] object: object to define property on
     * @param [in] propNameStr: property name in character string
     * @param [in] setter: access function for setting value
     * @param [in] getter: access function for getting value
     * @return true: if success
     *         false: otherwise
     */
    static bool DefineNamedProperty(JSIValue object,
                                    const char * const propNameStr,
                                    JSIFunctionHandler setter,
                                    JSIFunctionHandler getter);

    /**
     * @brief Call fail and complete callbacks acquired from args.
     *
     * @param [in] thisVal: object for 'this' binding
     * @param [in] args: object to acquire function callbacks from
     * @param [in] errCode: error code for fail callback
     * @param [in] errDesc: error description for fail callback
     */
    static void FailCallback(const JSIValue thisVal, const JSIValue args, int32_t errCode, const char * const errDesc);

    /**
     * @brief Call success and complete callbacks acquired from args.
     *
     * @param [in] thisVal: object for 'this' binding
     * @param [in] args: object to acquire function callbacks from
     * @param [in] argv: arguments for success callback
     * @param [in] argc: number of arguments for success callback
     */
    static void SuccessCallback(const JSIValue thisVal, const JSIValue args, const JSIValue *argv, uint8_t argc);

    /**
     * @brief Create javascript error object with error code and error message.
     * This interface can be used to create one uniform error object, which can be throw to JS API caller,
     * The error object's structure is shown as following,
     *    {
     *       code: 100001, // the error code
     *       message: "error details" // the error message
     *       data: {} // more error info, this is optional
     *    }
     *
     * @param [in] errCode: the error code which can indicate
     * @param [in] errorMsg: value of 'message' property of constructed error object
     * @param [in] extraErrData: value of 'data' property of constructed error object, which is optional,
     *             if it's given by caller, it must be one object, and it should be released by caller itself
     * @return value of the constructed error object
     *
     * value returned should be released by caller with ReleaseValue when it won't be used any more
     */
    static JSIValue CreateErrorWithCode(uint32_t errCode,
                                        const char * const errMsg,
                                        const JSIValue extraErrData = 0);

private:
    // private constructor for singleton instance
    JSI() {}
    ~JSI() {}
    static void SetNamedPointer(JSIValue object, const char * const name, JsiCallback callback);
};
} // namespace ACELite
} // namespace OHOS

#endif // OHOS_ACELITE_JSI_H