/*
 * Copyright (c) 2024 the ThorVG project. All rights reserved.

 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "../../lv_conf_internal.h"
#if LV_USE_THORVG_INTERNAL

#include "tvgMath.h"
#include "tvgLottieModel.h"
#include "tvgLottieExpressions.h"

#ifdef THORVG_LOTTIE_EXPRESSIONS_SUPPORT

/************************************************************************/
/* Internal Class Implementation                                        */
/************************************************************************/

struct ExpContent
{
    LottieObject* obj;
    float frameNo;
};


//reserved expressions specifiers
static const char* EXP_NAME = "name";
static const char* EXP_CONTENT = "content";
static const char* EXP_WIDTH = "width";
static const char* EXP_HEIGHT = "height";
static const char* EXP_CYCLE = "cycle";
static const char* EXP_PINGPONG = "pingpong";
static const char* EXP_OFFSET = "offset";
static const char* EXP_CONTINUE = "continue";
static const char* EXP_TIME = "time";
static const char* EXP_VALUE = "value";
static const char* EXP_INDEX = "index";
static const char* EXP_EFFECT= "effect";

static LottieExpressions* exps = nullptr;   //singleton instance engine


static void contentFree(void *native_p, struct jerry_object_native_info_t *info_p)
{
    free(native_p);
}

static jerry_object_native_info_t freeCb {contentFree, 0, 0};
static uint32_t engineRefCnt = 0;  //Expressions Engine reference count


static char* _name(jerry_value_t args)
{
    auto arg0 = jerry_value_to_string(args);
    auto len = jerry_string_length(arg0);
    auto name = (jerry_char_t*)malloc(len * sizeof(jerry_char_t) + 1);
    jerry_string_to_buffer(arg0, JERRY_ENCODING_UTF8, name, len);
    name[len] = '\0';
    jerry_value_free(arg0);
    return (char*) name;
}


static jerry_value_t _toComp(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    TVGERR("LOTTIE", "toComp is not supported in expressions!");

    return jerry_undefined();
}


static void _buildTransform(jerry_value_t context, LottieTransform* transform)
{
    if (!transform) return;

    auto obj = jerry_object();
    jerry_object_set_sz(context, "transform", obj);

    auto anchorPoint = jerry_object();
    jerry_object_set_native_ptr(anchorPoint, nullptr, &transform->anchor);
    jerry_object_set_sz(obj, "anchorPoint", anchorPoint);
    jerry_value_free(anchorPoint);

    auto position = jerry_object();
    jerry_object_set_native_ptr(position, nullptr, &transform->position);
    jerry_object_set_sz(obj, "position", position);
    jerry_value_free(position);

    auto scale = jerry_object();
    jerry_object_set_native_ptr(scale, nullptr, &transform->scale);
    jerry_object_set_sz(obj, "scale", scale);
    jerry_value_free(scale);

    auto rotation = jerry_object();
    jerry_object_set_native_ptr(rotation, nullptr, &transform->rotation);
    jerry_object_set_sz(obj, "rotation", rotation);
    jerry_value_free(rotation);

    auto opacity = jerry_object();
    jerry_object_set_native_ptr(opacity, nullptr, &transform->opacity);
    jerry_object_set_sz(obj, "opacity", opacity);
    jerry_value_free(opacity);

    jerry_value_free(obj);
}


static void _buildLayer(jerry_value_t context, LottieLayer* layer, LottieComposition* comp)
{
    auto width = jerry_number(layer->w);
    jerry_object_set_sz(context, EXP_WIDTH, width);
    jerry_value_free(width);

    auto height = jerry_number(layer->h);
    jerry_object_set_sz(context, EXP_HEIGHT, height);
    jerry_value_free(height);

    auto index = jerry_number(layer->id);
    jerry_object_set_sz(context, EXP_INDEX, index);
    jerry_value_free(index);

    auto parent = jerry_object();
    jerry_object_set_native_ptr(parent, nullptr, layer->parent);
    jerry_object_set_sz(context, "parent", parent);
    jerry_value_free(parent);

    auto hasParent = jerry_boolean(layer->parent ? true : false);
    jerry_object_set_sz(context, "hasParent", hasParent);
    jerry_value_free(hasParent);

    auto inPoint = jerry_number(layer->inFrame);
    jerry_object_set_sz(context, "inPoint", inPoint);
    jerry_value_free(inPoint);

    auto outPoint = jerry_number(layer->outFrame);
    jerry_object_set_sz(context, "outPoint", outPoint);
    jerry_value_free(outPoint);

    auto startTime = jerry_number(comp->timeAtFrame(layer->startFrame));
    jerry_object_set_sz(context, "startTime", startTime);
    jerry_value_free(startTime);

    auto hasVideo = jerry_boolean(false);
    jerry_object_set_sz(context, "hasVideo", hasVideo);
    jerry_value_free(hasVideo);

    auto hasAudio = jerry_boolean(false);
    jerry_object_set_sz(context, "hasAudio", hasAudio);
    jerry_value_free(hasAudio);

    //active, #current in the animation range?

    auto enabled = jerry_boolean(!layer->hidden);
    jerry_object_set_sz(context, "enabled", enabled);
    jerry_value_free(enabled);

    auto audioActive = jerry_boolean(false);
    jerry_object_set_sz(context, "audioActive", audioActive);
    jerry_value_free(audioActive);

    //sampleImage(point, radius = [.5, .5], postEffect=true, t=time)

    _buildTransform(context, layer->transform);

    //audioLevels, #the value of the Audio Levels property of the layer in decibels

    auto timeRemap = jerry_object();
    jerry_object_set_native_ptr(timeRemap, nullptr, &layer->timeRemap);
    jerry_object_set_sz(context, "timeRemap", timeRemap);
    jerry_value_free(timeRemap);

    //marker.key(index)
    //marker.key(name)
    //marker.nearestKey(t)
    //marker.numKeys

    auto name = jerry_string_sz(layer->name);
    jerry_object_set_sz(context, EXP_NAME, name);
    jerry_value_free(name);

    auto toComp = jerry_function_external(_toComp);
    jerry_object_set_sz(context, "toComp", toComp);
    jerry_object_set_native_ptr(toComp, nullptr, comp);
    jerry_value_free(toComp);
}


static jerry_value_t _value(float frameNo, LottieExpression* exp)
{
    switch (exp->type) {
        case LottieProperty::Type::Point: {
            auto value = jerry_object();
            auto pos = (*static_cast<LottiePoint*>(exp->property))(frameNo);
            auto val1 = jerry_number(pos.x);
            auto val2 = jerry_number(pos.y);
            jerry_object_set_index(value, 0, val1);
            jerry_object_set_index(value, 1, val2);
            jerry_value_free(val1);
            jerry_value_free(val2);
            return value;
        }
        case LottieProperty::Type::Float: {
            return jerry_number((*static_cast<LottieFloat*>(exp->property))(frameNo));
        }
        case LottieProperty::Type::Opacity: {
            return jerry_number((*static_cast<LottieOpacity*>(exp->property))(frameNo));
        }
        case LottieProperty::Type::PathSet: {
            auto value = jerry_object();
            jerry_object_set_native_ptr(value, nullptr, exp->property);
            return value;
        }
        case LottieProperty::Type::Position: {
            auto value = jerry_object();
            auto pos = (*static_cast<LottiePosition*>(exp->property))(frameNo);
            auto val1 = jerry_number(pos.x);
            auto val2 = jerry_number(pos.y);
            jerry_object_set_index(value, 0, val1);
            jerry_object_set_index(value, 1, val2);
            jerry_value_free(val1);
            jerry_value_free(val2);
            return value;
        }
        default: {
            TVGERR("LOTTIE", "Non supported type for value? = %d", (int) exp->type);
        }
    }
    return jerry_undefined();
}


static jerry_value_t _addsub(const jerry_value_t args[], float addsub)
{
    //1d
    if (jerry_value_is_number(args[0])) return jerry_number(jerry_value_as_number(args[0]) + addsub * jerry_value_as_number(args[1]));

    //2d
    auto val1 = jerry_object_get_index(args[0], 0);
    auto val2 = jerry_object_get_index(args[0], 1);
    auto val3 = jerry_object_get_index(args[1], 0);
    auto val4 = jerry_object_get_index(args[1], 1);
    auto x = jerry_value_as_number(val1) + addsub * jerry_value_as_number(val3);
    auto y = jerry_value_as_number(val2) + addsub * jerry_value_as_number(val4);

    jerry_value_free(val1);
    jerry_value_free(val2);
    jerry_value_free(val3);
    jerry_value_free(val4);

    auto obj = jerry_object();
    val1 = jerry_number(x);
    val2 = jerry_number(y);
    jerry_object_set_index(obj, 0, val1);
    jerry_object_set_index(obj, 1, val2);
    jerry_value_free(val1);
    jerry_value_free(val2);

    return obj;
}


static jerry_value_t _muldiv(const jerry_value_t arg1, float arg2)
{
    //1d
    if (jerry_value_is_number(arg1)) return jerry_number(jerry_value_as_number(arg1) * arg2);

    //2d
    auto val1 = jerry_object_get_index(arg1, 0);
    auto val2 = jerry_object_get_index(arg1, 1);
    auto x = jerry_value_as_number(val1) * arg2;
    auto y = jerry_value_as_number(val2) * arg2;

    jerry_value_free(val1);
    jerry_value_free(val2);

    auto obj = jerry_object();
    val1 = jerry_number(x);
    val2 = jerry_number(y);
    jerry_object_set_index(obj, 0, val1);
    jerry_object_set_index(obj, 1, val2);
    jerry_value_free(val1);
    jerry_value_free(val2);

    return obj;
}


static jerry_value_t _add(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    return _addsub(args, 1.0f);
}


static jerry_value_t _sub(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    return _addsub(args, -1.0f);
}


static jerry_value_t _mul(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    return _muldiv(args[0], jerry_value_as_number(args[1]));
}


static jerry_value_t _div(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    return _muldiv(args[0], 1.0f / jerry_value_as_number(args[1]));
}


static jerry_value_t _interp(float t, const jerry_value_t args[], int argsCnt)
{
    auto tMin = 0.0f;
    auto tMax = 1.0f;
    int idx = 0;

    if (argsCnt > 3) {
        tMin = jerry_value_as_number(args[1]);
        tMax = jerry_value_as_number(args[2]);
        idx += 2;
    }

    //2d
    if (jerry_value_is_object(args[idx + 1]) && jerry_value_is_object(args[idx + 2])) {
        auto val1 = jerry_object_get_index(args[0], 0);
        auto val2 = jerry_object_get_index(args[0], 1);
        auto val3 = jerry_object_get_index(args[1], 0);
        auto val4 = jerry_object_get_index(args[1], 1);

        Point pt1 = {(float)jerry_value_as_number(val1),  (float)jerry_value_as_number(val2)};
        Point pt2 = {(float)jerry_value_as_number(val3),  (float)jerry_value_as_number(val4)};
        Point ret;
        if (t <= tMin) ret = pt1;
        else if (t >= tMax) ret = pt2;
        else ret = mathLerp(pt1, pt2, t);

        jerry_value_free(val1);
        jerry_value_free(val2);
        jerry_value_free(val3);
        jerry_value_free(val4);

        auto obj = jerry_object();
        val1 = jerry_number(ret.x);
        val2 = jerry_number(ret.y);
        jerry_object_set_index(obj, 0, val1);
        jerry_object_set_index(obj, 1, val2);
        jerry_value_free(val1);
        jerry_value_free(val2);

        return obj;
    }

    //1d
    auto val1 = (float) jerry_value_as_number(args[idx + 1]);
    if (t <= tMin) jerry_number(val1);
    auto val2 = (float) jerry_value_as_number(args[idx + 2]);
    if (t >= tMax) jerry_number(val2);
    return jerry_number(mathLerp(val1, val2, t));
}


static jerry_value_t _linear(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto t = (float) jerry_value_as_number(args[0]);
    return _interp(t, args, jerry_value_as_uint32(argsCnt));
}


static jerry_value_t _ease(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto t = (float) jerry_value_as_number(args[0]);
    t = (t < 0.5) ? (4 * t * t * t) : (1.0f - pow(-2.0f * t + 2.0f, 3) * 0.5f);
    return _interp(t, args, jerry_value_as_uint32(argsCnt));
}



static jerry_value_t _easeIn(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto t = (float) jerry_value_as_number(args[0]);
    t = t * t * t;
    return _interp(t, args, jerry_value_as_uint32(argsCnt));
}


static jerry_value_t _easeOut(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto t = (float) jerry_value_as_number(args[0]);
    t = 1.0f - pow(1.0f - t, 3);
    return _interp(t, args, jerry_value_as_uint32(argsCnt));
}


static jerry_value_t _clamp(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto num = jerry_value_as_number(args[0]);
    auto limit1 = jerry_value_as_number(args[1]);
    auto limit2 = jerry_value_as_number(args[2]);

    //clamping
    if (num < limit1) num = limit1;
    if (num > limit2) num = limit2;

    return jerry_number(num);
}


static jerry_value_t _dot(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto val1 = jerry_object_get_index(args[0], 0);
    auto val2 = jerry_object_get_index(args[0], 1);
    auto val3 = jerry_object_get_index(args[1], 0);
    auto val4 = jerry_object_get_index(args[1], 1);

    auto x = jerry_value_as_number(val1) * jerry_value_as_number(val3);
    auto y = jerry_value_as_number(val2) * jerry_value_as_number(val4);

    jerry_value_free(val1);
    jerry_value_free(val2);
    jerry_value_free(val3);
    jerry_value_free(val4);

    return jerry_number(x + y);
}


static jerry_value_t _cross(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto val1 = jerry_object_get_index(args[0], 0);
    auto val2 = jerry_object_get_index(args[0], 1);
    auto val3 = jerry_object_get_index(args[1], 0);
    auto val4 = jerry_object_get_index(args[1], 1);

    auto x = jerry_value_as_number(val1) * jerry_value_as_number(val4);
    auto y = jerry_value_as_number(val2) * jerry_value_as_number(val3);

    jerry_value_free(val1);
    jerry_value_free(val2);
    jerry_value_free(val3);
    jerry_value_free(val4);

    return jerry_number(x - y);
}


static jerry_value_t _normalize(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto val1 = jerry_object_get_index(args[0], 0);
    auto val2 = jerry_object_get_index(args[0], 1);
    auto x = jerry_value_as_number(val1);
    auto y = jerry_value_as_number(val2);

    jerry_value_free(val1);
    jerry_value_free(val2);

    auto length = sqrtf(x * x + y * y);

    x /= length;
    y /= length;

    auto obj = jerry_object();
    val1 = jerry_number(x);
    val2 = jerry_number(y);
    jerry_object_set_index(obj, 0, val1);
    jerry_object_set_index(obj, 0, val2);
    jerry_value_free(val1);
    jerry_value_free(val2);

    return obj;
}


static jerry_value_t _length(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto val1 = jerry_object_get_index(args[0], 0);
    auto val2 = jerry_object_get_index(args[0], 1);
    auto x = jerry_value_as_number(val1);
    auto y = jerry_value_as_number(val2);

    jerry_value_free(val1);
    jerry_value_free(val2);

    return jerry_number(sqrtf(x * x + y * y));
}


static jerry_value_t _random(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto val = (float)(rand() % 10000001);
    return jerry_number(val * 0.0000001f);
}


static jerry_value_t _deg2rad(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    return jerry_number(mathDeg2Rad((float)jerry_value_as_number(args[0])));
}


static jerry_value_t _rad2deg(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    return jerry_number(mathRad2Deg((float)jerry_value_as_number(args[0])));
}


static jerry_value_t _effect(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    TVGERR("LOTTIE", "effect is not supported in expressions!");

    return jerry_undefined();
}


static jerry_value_t _fromCompToSurface(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    TVGERR("LOTTIE", "fromCompToSurface is not supported in expressions!");

    return jerry_undefined();
}


static jerry_value_t _content(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto name = _name(args[0]);
    auto data = static_cast<ExpContent*>(jerry_object_get_native_ptr(info->function, &freeCb));
    auto group = static_cast<LottieGroup*>(data->obj);
    auto target = group->content((char*)name);
    free(name);
    if (!target) return jerry_undefined();

    //find the a path property(sh) in the group layer?
    switch (target->type) {
        case LottieObject::Group: {
            auto group = static_cast<LottieGroup*>(target);
            auto obj = jerry_function_external(_content);

            //attach a transform
            for (auto c = group->children.begin(); c < group->children.end(); ++c) {
                if ((*c)->type == LottieObject::Type::Transform) {
                    _buildTransform(obj, static_cast<LottieTransform*>(*c));
                    break;
                }
            }
            auto data2 = (ExpContent*)malloc(sizeof(ExpContent));
            data2->obj = group;
            data2->frameNo = data->frameNo;
            jerry_object_set_native_ptr(obj, &freeCb, data2);
            jerry_object_set_sz(obj, EXP_CONTENT, obj);
            return obj;
        }
        case LottieObject::Path: {
            jerry_value_t obj = jerry_object();
            jerry_object_set_native_ptr(obj, nullptr, &static_cast<LottiePath*>(target)->pathset);
            jerry_object_set_sz(obj, "path", obj);
            return obj;
        }
        case LottieObject::Trimpath: {
            auto trimpath = static_cast<LottieTrimpath*>(target);
            jerry_value_t obj = jerry_object();
            auto start = jerry_number(trimpath->start(data->frameNo));
            jerry_object_set_sz(obj, "start", start);
            jerry_value_free(start);
            auto end = jerry_number(trimpath->end(data->frameNo));
            jerry_object_set_sz(obj, "end", end);
            jerry_value_free(end);
            auto offset = jerry_number(trimpath->offset(data->frameNo));
            jerry_object_set_sz(obj, "offset", end);
            jerry_value_free(offset);
            return obj;
        }
        default: break;
    }
    return jerry_undefined();
}


static jerry_value_t _layer(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto comp = static_cast<LottieComposition*>(jerry_object_get_native_ptr(info->function, nullptr));
    LottieLayer* layer;

    //layer index
    if (jerry_value_is_number(args[0])) {
        auto idx = (uint16_t)jerry_value_as_int32(args[0]);
        layer = comp->layer(idx);
        jerry_value_free(idx);
    //layer name
    } else {
        auto name = _name(args[0]);
        layer = comp->layer((char*)name);
        free(name);
    }

    if (!layer) return jerry_undefined();

    auto obj = jerry_object();
    jerry_object_set_native_ptr(obj, nullptr, layer);
    _buildLayer(obj, layer, comp);

    return obj;
}


static jerry_value_t _nearestKey(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto exp = static_cast<LottieExpression*>(jerry_object_get_native_ptr(info->function, nullptr));
    auto time = jerry_value_as_number(args[0]);
    auto frameNo = exp->comp->frameAtTime(time);
    auto index = jerry_number(exp->property->nearest(frameNo));

    auto obj = jerry_object();
    jerry_object_set_sz(obj, EXP_INDEX, index);
    jerry_value_free(index);

    return obj;
}


static jerry_value_t _valueAtTime(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto exp = static_cast<LottieExpression*>(jerry_object_get_native_ptr(info->function, nullptr));
    auto time = jerry_value_as_number(args[0]);
    auto frameNo = exp->comp->frameAtTime(time);
    return _value(frameNo, exp);
}


static jerry_value_t _velocityAtTime(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto exp = static_cast<LottieExpression*>(jerry_object_get_native_ptr(info->function, nullptr));
    auto time = jerry_value_as_number(args[0]);
    auto frameNo = exp->comp->frameAtTime(time);
    auto key = exp->property->nearest(frameNo);
    auto pframe = exp->property->frameNo(key - 1);
    auto cframe = exp->property->frameNo(key);
    auto elapsed = (cframe - pframe) / (exp->comp->frameRate);

    Point cur, prv;

    //compute the velocity
    switch (exp->type) {
        case LottieProperty::Type::Point: {
            prv = (*static_cast<LottiePoint*>(exp->property))(pframe);
            cur = (*static_cast<LottiePoint*>(exp->property))(cframe);
            break;
        }
        case LottieProperty::Type::Position: {
            prv = (*static_cast<LottiePosition*>(exp->property))(pframe);
            cur = (*static_cast<LottiePosition*>(exp->property))(cframe);
            break;
        }
        default: {
            TVGERR("LOTTIE", "Non supported type for velocityAtTime?");
            return jerry_undefined();
        }
    }

    float velocity[] = {(cur.x - prv.x) / elapsed, (cur.y - prv.y) / elapsed};

    auto obj = jerry_object();
    auto val1 = jerry_number(velocity[0]);
    auto val2 = jerry_number(velocity[1]);
    jerry_object_set_index(obj, 0, val1);
    jerry_object_set_index(obj, 1, val2);
    jerry_value_free(val1);
    jerry_value_free(val2);

    return obj;
}


static jerry_value_t _speedAtTime(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto exp = static_cast<LottieExpression*>(jerry_object_get_native_ptr(info->function, nullptr));
    auto time = jerry_value_as_number(args[0]);
    auto frameNo = exp->comp->frameAtTime(time);
    auto key = exp->property->nearest(frameNo);
    auto pframe = exp->property->frameNo(key - 1);
    auto cframe = exp->property->frameNo(key);
    auto elapsed = (cframe - pframe) / (exp->comp->frameRate);

    Point cur, prv;

    //compute the velocity
    switch (exp->type) {
        case LottieProperty::Type::Point: {
            prv = (*static_cast<LottiePoint*>(exp->property))(pframe);
            cur = (*static_cast<LottiePoint*>(exp->property))(cframe);
            break;
        }
        case LottieProperty::Type::Position: {
            prv = (*static_cast<LottiePosition*>(exp->property))(pframe);
            cur = (*static_cast<LottiePosition*>(exp->property))(cframe);
            break;
        }
        default: {
            TVGERR("LOTTIE", "Non supported type for speedAtTime?");
            return jerry_undefined();
        }
    }

    auto speed = sqrtf(pow(cur.x - prv.x, 2) + pow(cur.y - prv.y, 2)) / elapsed;
    auto obj = jerry_number(speed);
    return obj;
}


static bool _loopOutCommon(LottieExpression* exp, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    exp->loop.mode = LottieExpression::LoopMode::OutCycle;

    if (argsCnt > 0) {
        auto name = _name(args[0]);
        if (!strcmp(name, EXP_CYCLE)) exp->loop.mode = LottieExpression::LoopMode::OutCycle;
        else if (!strcmp(name, EXP_PINGPONG)) exp->loop.mode = LottieExpression::LoopMode::OutPingPong;
        else if (!strcmp(name, EXP_OFFSET)) exp->loop.mode = LottieExpression::LoopMode::OutOffset;
        else if (!strcmp(name, EXP_CONTINUE)) exp->loop.mode = LottieExpression::LoopMode::OutContinue;
        free(name);
    }

    if (exp->loop.mode != LottieExpression::LoopMode::OutCycle) {
        TVGERR("hermet", "Not supported loopOut type = %d", exp->loop.mode);
        return false;
    }

    return true;
}


static jerry_value_t _loopOut(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto exp = static_cast<LottieExpression*>(jerry_object_get_native_ptr(info->function, nullptr));

    if (!_loopOutCommon(exp, args, argsCnt)) return jerry_undefined();

    if (argsCnt > 1) exp->loop.key = jerry_value_as_int32(args[1]);

    auto obj = jerry_object();
    jerry_object_set_native_ptr(obj, nullptr, exp->property);
    return obj;
}


static jerry_value_t _loopOutDuration(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto exp = static_cast<LottieExpression*>(jerry_object_get_native_ptr(info->function, nullptr));

    if (!_loopOutCommon(exp, args, argsCnt)) return jerry_undefined();

    if (argsCnt > 1) {
        exp->loop.in = exp->comp->frameAtTime((float)jerry_value_as_int32(args[1]));
    }

    auto obj = jerry_object();
    jerry_object_set_native_ptr(obj, nullptr, exp->property);
    return obj;
}


static bool _loopInCommon(LottieExpression* exp, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    exp->loop.mode = LottieExpression::LoopMode::InCycle;

    if (argsCnt > 0) {
        auto name = _name(args[0]);
        if (!strcmp(name, EXP_CYCLE)) exp->loop.mode = LottieExpression::LoopMode::InCycle;
        else if (!strcmp(name, EXP_PINGPONG)) exp->loop.mode = LottieExpression::LoopMode::InPingPong;
        else if (!strcmp(name, EXP_OFFSET)) exp->loop.mode = LottieExpression::LoopMode::InOffset;
        else if (!strcmp(name, EXP_CONTINUE)) exp->loop.mode = LottieExpression::LoopMode::InContinue;
        free(name);
    }

    if (exp->loop.mode != LottieExpression::LoopMode::InCycle) {
        TVGERR("hermet", "Not supported loopOut type = %d", exp->loop.mode);
        return false;
    }

    return true;
}

static jerry_value_t _loopIn(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto exp = static_cast<LottieExpression*>(jerry_object_get_native_ptr(info->function, nullptr));

    if (!_loopInCommon(exp, args, argsCnt)) return jerry_undefined();

    if (argsCnt > 1) {
        exp->loop.in = exp->comp->frameAtTime((float)jerry_value_as_int32(args[1]));
    }

    auto obj = jerry_object();
    jerry_object_set_native_ptr(obj, nullptr, exp->property);
    return obj;
}


static jerry_value_t _loopInDuration(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto exp = static_cast<LottieExpression*>(jerry_object_get_native_ptr(info->function, nullptr));

    if (argsCnt > 1) {
        exp->loop.in = exp->comp->frameAtTime((float)jerry_value_as_int32(args[1]));
    }

    if (!_loopInCommon(exp, args, argsCnt)) return jerry_undefined();

    auto obj = jerry_object();
    jerry_object_set_native_ptr(obj, nullptr, exp->property);
    return obj;
}


static jerry_value_t _key(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto exp = static_cast<LottieExpression*>(jerry_object_get_native_ptr(info->function, nullptr));
    auto key = jerry_value_as_int32(args[0]);
    auto frameNo = exp->property->frameNo(key);
    auto time = jerry_number(exp->comp->timeAtFrame(frameNo));
    auto value = _value(frameNo, exp);

    auto obj = jerry_object();
    jerry_object_set_sz(obj, EXP_TIME, time);
    jerry_object_set_sz(obj, EXP_INDEX, args[0]);
    jerry_object_set_sz(obj, EXP_VALUE, value);

    jerry_value_free(time);
    jerry_value_free(value);

    return obj;
}



static jerry_value_t _createPath(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    //TODO: arg1: points, arg2: inTangents, arg3: outTangents, arg4: isClosed
    auto arg1 = jerry_value_to_object(args[0]);
    auto pathset = jerry_object_get_native_ptr(arg1, nullptr);
    if (!pathset) {
        TVGERR("LOTTIE", "failed createPath()");
        return jerry_undefined();
    }

    jerry_value_free(arg1);

    auto obj = jerry_object();
    jerry_object_set_native_ptr(obj, nullptr, pathset);
    return obj;
}


static jerry_value_t _uniformPath(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto pathset = static_cast<LottiePathSet*>(jerry_object_get_native_ptr(info->function, nullptr));

    /* TODO: ThorVG prebuilds the path data for performance.
       It actually need to constructs the Array<Point> for points, inTangents, outTangents and then return here... */
    auto obj = jerry_object();
    jerry_object_set_native_ptr(obj, nullptr, pathset);
    return obj;
}


static jerry_value_t _isClosed(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    //TODO: Not used
    return jerry_boolean(true);
}


static void _buildPath(jerry_value_t context, LottieExpression* exp)
{
    //Trick for fast building path.
    auto points = jerry_function_external(_uniformPath);
    jerry_object_set_native_ptr(points, nullptr, exp->property);
    jerry_object_set_sz(context, "points", points);
    jerry_value_free(points);

    auto inTangents = jerry_function_external(_uniformPath);
    jerry_object_set_native_ptr(inTangents, nullptr, exp->property);
    jerry_object_set_sz(context, "inTangents", inTangents);
    jerry_value_free(inTangents);

    auto outTangents = jerry_function_external(_uniformPath);
    jerry_object_set_native_ptr(outTangents, nullptr, exp->property);
    jerry_object_set_sz(context, "outTangents", outTangents);
    jerry_value_free(outTangents);

    auto isClosed = jerry_function_external(_isClosed);
    jerry_object_set_native_ptr(isClosed, nullptr, exp->property);
    jerry_object_set_sz(context, "isClosed", isClosed);
    jerry_value_free(isClosed);

}


static void _buildProperty(float frameNo, jerry_value_t context, LottieExpression* exp)
{
    auto value = _value(frameNo, exp);
    jerry_object_set_sz(context, EXP_VALUE, value);
    jerry_value_free(value);

    auto valueAtTime = jerry_function_external(_valueAtTime);
    jerry_object_set_sz(context, "valueAtTime", valueAtTime);
    jerry_object_set_native_ptr(valueAtTime, nullptr, exp);
    jerry_value_free(valueAtTime);

    auto velocity = jerry_number(0.0f);
    jerry_object_set_sz(context, "velocity", velocity);
    jerry_value_free(velocity);

    auto velocityAtTime = jerry_function_external(_velocityAtTime);
    jerry_object_set_sz(context, "velocityAtTime", velocityAtTime);
    jerry_object_set_native_ptr(velocityAtTime, nullptr, exp);
    jerry_value_free(velocityAtTime);

    auto speed = jerry_number(0.0f);
    jerry_object_set_sz(context, "speed", speed);
    jerry_value_free(speed);

    auto speedAtTime = jerry_function_external(_speedAtTime);
    jerry_object_set_sz(context, "speedAtTime", speedAtTime);
    jerry_object_set_native_ptr(speedAtTime, nullptr, exp);
    jerry_value_free(speedAtTime);

    //wiggle(freq, amp, octaves=1, amp_mult=.5, t=time)
    //temporalWiggle(freq, amp, octaves=1, amp_mult=.5, t=time)
    //smooth(width=.2, samples=5, t=time)

    auto loopIn = jerry_function_external(_loopIn);
    jerry_object_set_sz(context, "loopIn", loopIn);
    jerry_object_set_native_ptr(loopIn, nullptr, exp);
    jerry_value_free(loopIn);

    auto loopOut = jerry_function_external(_loopOut);
    jerry_object_set_sz(context, "loopOut", loopOut);
    jerry_object_set_native_ptr(loopOut, nullptr, exp);
    jerry_value_free(loopOut);

    auto loopInDuration = jerry_function_external(_loopInDuration);
    jerry_object_set_sz(context, "loopInDuration", loopInDuration);
    jerry_object_set_native_ptr(loopInDuration, nullptr, exp);
    jerry_value_free(loopInDuration);

    auto loopOutDuration = jerry_function_external(_loopOutDuration);
    jerry_object_set_sz(context, "loopOutDuration", loopOutDuration);
    jerry_object_set_native_ptr(loopOutDuration, nullptr, exp);
    jerry_value_free(loopOutDuration);

    auto key = jerry_function_external(_key);
    jerry_object_set_sz(context, "key", key);
    jerry_object_set_native_ptr(key, nullptr, exp);
    jerry_value_free(key);

    //key(markerName)

    auto nearestKey = jerry_function_external(_nearestKey);
    jerry_object_set_native_ptr(nearestKey, nullptr, exp);
    jerry_object_set_sz(context, "nearestKey", nearestKey);
    jerry_value_free(nearestKey);

    auto numKeys = jerry_number(exp->property->frameCnt());
    jerry_object_set_sz(context, "numKeys", numKeys);
    jerry_value_free(numKeys);

    //propertyGroup(countUp = 1)
    //propertyIndex
    //name

    //content("name"), #look for the named property from a layer
    auto data = (ExpContent*)malloc(sizeof(ExpContent));
    data->obj = exp->layer;
    data->frameNo = frameNo;

    auto content = jerry_function_external(_content);
    jerry_object_set_sz(context, EXP_CONTENT, content);
    jerry_object_set_native_ptr(content, &freeCb, data);
    jerry_value_free(content);
}


static jerry_value_t _comp(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
    auto comp = static_cast<LottieComposition*>(jerry_object_get_native_ptr(info->function, nullptr));
    LottieLayer* layer;

    auto arg0 = jerry_value_to_string(args[0]);
    auto len = jerry_string_length(arg0);
    auto name = (jerry_char_t*)alloca(len * sizeof(jerry_char_t) + 1);
    jerry_string_to_buffer(arg0, JERRY_ENCODING_UTF8, name, len);
    name[len] = '\0';

    jerry_value_free(arg0);

    layer = comp->asset((char*)name);

    if (!layer) return jerry_undefined();

    auto obj = jerry_object();
    jerry_object_set_native_ptr(obj, nullptr, layer);
    _buildLayer(obj, layer, comp);

    return obj;
}


static void _buildMath(jerry_value_t context)
{
    auto bm_mul = jerry_function_external(_mul);
    jerry_object_set_sz(context, "$bm_mul", bm_mul);
    jerry_value_free(bm_mul);

    auto bm_sum = jerry_function_external(_add);
    jerry_object_set_sz(context, "$bm_sum", bm_sum);
    jerry_value_free(bm_sum);

    auto bm_add = jerry_function_external(_add);
    jerry_object_set_sz(context, "$bm_add", bm_add);
    jerry_value_free(bm_add);

    auto bm_sub = jerry_function_external(_sub);
    jerry_object_set_sz(context, "$bm_sub", bm_sub);
    jerry_value_free(bm_sub);

    auto bm_div = jerry_function_external(_div);
    jerry_object_set_sz(context, "$bm_div", bm_div);
    jerry_value_free(bm_div);

    auto mul = jerry_function_external(_mul);
    jerry_object_set_sz(context, "mul", mul);
    jerry_value_free(mul);

    auto sum = jerry_function_external(_add);
    jerry_object_set_sz(context, "sum", sum);
    jerry_value_free(sum);

    auto add = jerry_function_external(_add);
    jerry_object_set_sz(context, "add", add);
    jerry_value_free(add);

    auto sub = jerry_function_external(_sub);
    jerry_object_set_sz(context, "sub", sub);
    jerry_value_free(sub);

    auto div = jerry_function_external(_div);
    jerry_object_set_sz(context, "div", div);
    jerry_value_free(div);

    auto clamp = jerry_function_external(_clamp);
    jerry_object_set_sz(context, "clamp", clamp);
    jerry_value_free(clamp);

    auto dot = jerry_function_external(_dot);
    jerry_object_set_sz(context, "dot", dot);
    jerry_value_free(dot);

    auto cross = jerry_function_external(_cross);
    jerry_object_set_sz(context, "cross", cross);
    jerry_value_free(cross);

    auto normalize = jerry_function_external(_normalize);
    jerry_object_set_sz(context, "normalize", normalize);
    jerry_value_free(normalize);

    auto length = jerry_function_external(_length);
    jerry_object_set_sz(context, "length", length);
    jerry_value_free(length);

    auto random = jerry_function_external(_random);
    jerry_object_set_sz(context, "random", random);
    jerry_value_free(random);

    auto deg2rad = jerry_function_external(_deg2rad);
    jerry_object_set_sz(context, "degreesToRadians", deg2rad);
    jerry_value_free(deg2rad);

    auto rad2deg = jerry_function_external(_rad2deg);
    jerry_object_set_sz(context, "radiansToDegrees", rad2deg);
    jerry_value_free(rad2deg);

    auto linear = jerry_function_external(_linear);
    jerry_object_set_sz(context, "linear", linear);
    jerry_value_free(linear);

    auto ease = jerry_function_external(_ease);
    jerry_object_set_sz(context, "ease", ease);
    jerry_value_free(ease);

    auto easeIn = jerry_function_external(_easeIn);
    jerry_object_set_sz(context, "easeIn", easeIn);
    jerry_value_free(easeIn);

    auto easeOut = jerry_function_external(_easeOut);
    jerry_object_set_sz(context, "easeOut", easeOut);
    jerry_value_free(easeOut);

    //lookAt
}


void LottieExpressions::buildComp(LottieComposition* comp)
{
    jerry_object_set_native_ptr(this->comp, nullptr, comp);
    jerry_object_set_native_ptr(thisComp, nullptr, comp);
    jerry_object_set_native_ptr(layer, nullptr, comp);

    //marker
    //marker.key(index)
    //marker.key(name)
    //marker.nearestKey(t)
    //marker.numKeys

    auto numLayers = jerry_number(comp->root->children.count);
    jerry_object_set_sz(thisComp, "numLayers", numLayers);
    jerry_value_free(numLayers);

    //activeCamera

    auto width = jerry_number(comp->w);
    jerry_object_set_sz(thisComp, EXP_WIDTH, width);
    jerry_value_free(width);

    auto height = jerry_number(comp->h);
    jerry_object_set_sz(thisComp, EXP_HEIGHT, height);
    jerry_value_free(height);

    auto duration = jerry_number(comp->duration());
    jerry_object_set_sz(thisComp, "duration", duration);
    jerry_value_free(duration);

    //ntscDropFrame
    //displayStartTime

    auto frameDuration = jerry_number(1.0f / comp->frameRate);
    jerry_object_set_sz(thisComp, "frameDuration", frameDuration);
    jerry_value_free(frameDuration);

    //shutterAngle
    //shutterPhase
    //bgColor
    //pixelAspect

    auto name = jerry_string((jerry_char_t*)comp->name, strlen(comp->name), JERRY_ENCODING_UTF8);
    jerry_object_set_sz(thisComp, EXP_NAME, name);
    jerry_value_free(name);
}


jerry_value_t LottieExpressions::buildGlobal()
{
    global = jerry_current_realm();

    //comp(name)
    comp = jerry_function_external(_comp);
    jerry_object_set_sz(global, "comp", comp);

    //footage(name)

    thisComp = jerry_object();
    jerry_object_set_sz(global, "thisComp", thisComp);

    //layer(index) / layer(name) / layer(otherLayer, reIndex)
    layer = jerry_function_external(_layer);
    jerry_object_set_sz(thisComp, "layer", layer);

    thisLayer = jerry_object();
    jerry_object_set_sz(global, "thisLayer", thisLayer);

    thisProperty = jerry_object();
    jerry_object_set_sz(global, "thisProperty", thisProperty);

    auto effect = jerry_function_external(_effect);
    jerry_object_set_sz(global, EXP_EFFECT, effect);
    jerry_value_free(effect);

    auto fromCompToSurface = jerry_function_external(_fromCompToSurface);
    jerry_object_set_sz(global, "fromCompToSurface", fromCompToSurface);
    jerry_value_free(fromCompToSurface);

    auto createPath = jerry_function_external(_createPath);
    jerry_object_set_sz(global, "createPath", createPath);
    jerry_value_free(createPath);

    //posterizeTime(framesPerSecond)
    //value

    return global;
}


jerry_value_t LottieExpressions::evaluate(float frameNo, LottieExpression* exp)
{
    buildComp(exp->comp);

    //update global context values
    jerry_object_set_native_ptr(thisLayer, nullptr, exp->layer);
    _buildLayer(thisLayer, exp->layer, exp->comp);

    jerry_object_set_native_ptr(thisProperty, nullptr, exp->property);
    _buildProperty(frameNo, global, exp);

    if (exp->type == LottieProperty::Type::PathSet) _buildPath(thisProperty, exp);
    if (exp->object->type == LottieObject::Transform) _buildTransform(global, static_cast<LottieTransform*>(exp->object));

    //evaluate the code
    auto eval = jerry_eval((jerry_char_t *) exp->code, strlen(exp->code), JERRY_PARSE_NO_OPTS);

    if (jerry_value_is_exception(eval) || jerry_value_is_undefined(eval)) {
        exp->enabled = false;  // The feature is experimental, it will be forcefully turned off if it's incompatible.
        return jerry_undefined();
    }

    jerry_value_free(eval);

    return jerry_object_get_sz(global, "$bm_rt");
}


/************************************************************************/
/* External Class Implementation                                        */
/************************************************************************/

LottieExpressions::~LottieExpressions()
{
    jerry_value_free(thisProperty);
    jerry_value_free(thisLayer);
    jerry_value_free(layer);
    jerry_value_free(thisComp);
    jerry_value_free(comp);
    jerry_value_free(global);
    jerry_cleanup();
}


LottieExpressions::LottieExpressions()
{
    jerry_init(JERRY_INIT_EMPTY);
    _buildMath(buildGlobal());
}


void LottieExpressions::update(float curTime)
{
    //time, #current time in seconds
    auto time = jerry_number(curTime);
    jerry_object_set_sz(global, EXP_TIME, time);
    jerry_value_free(time);
}


//FIXME: Threads support
#include "tvgTaskScheduler.h"

LottieExpressions* LottieExpressions::instance()
{
    //FIXME: Threads support
    if (TaskScheduler::threads() > 1) {
        TVGLOG("LOTTIE", "Lottie Expressions are not supported with tvg threads");
        return nullptr;
    }

    if (!exps) exps = new LottieExpressions;
    ++engineRefCnt;
    return exps;
}


void LottieExpressions::retrieve(LottieExpressions* instance)
{
    if (--engineRefCnt == 0) {
        delete(instance);
        exps = nullptr;
    }
}


#endif //THORVG_LOTTIE_EXPRESSIONS_SUPPORT

#endif /* LV_USE_THORVG_INTERNAL */

