/**
 * Created by G-Canvas Open Source Team.
 * Copyright (c) 2017, Alibaba, Inc. All rights reserved.
 *
 * This source code is licensed under the Apache Licence 2.0.
 * For the full copyright and license information, please view
 * the LICENSE file in the root directory of this source tree.
 */
#include "CanvasRenderingContext2D.h"
#include <iostream>
#include "CanvasGradient.h"
#include "Image.h"
#include <GL/gl.h>
#include "Canvas.h"
#include "TextMetrics.h"
#include <time.h>

//测量耗时的调试开关
// #define DUMP_RUNNING_TIME 1

#ifdef DUMP_RUNNING_TIME
#define RECORD_TIME_BEGIN  \
    clock_t start, finish; \
    start = clock();

#define RECORD_TIME_END \
    finish = clock();   \
    printf("cost time %f ms\n", (double)(finish - start) * 1000.0f / CLOCKS_PER_SEC);
#else
#define RECORD_TIME_BEGIN
#define RECORD_TIME_END
#endif

#define DEFINE_VOID_METHOD(methodName)              \
     void                                                 \
    Context2D::methodName(const Napi::CallbackInfo &info) \
    {                                                     \
        RECORD_TIME_BEGIN                                 \
        mRenderContext->makeCurrent();                    \
        // printf("the function :  " #methodName " is  called \n");                            \
        \ 


#define DEFINE_RETURN_VALUE_METHOD(methodName)            \
     Napi::Value                                          \
    Context2D::methodName(const Napi::CallbackInfo &info) \
    {                                                     \
        RECORD_TIME_BEGIN                                 \
        mRenderContext->makeCurrent();                    \
        // printf("the function : " #methodName " is  called \n");                         \
            \  


#define DEFINE_SETTER_METHOD(methodName)                                            \
     void                                                                           \
    Context2D::methodName(const Napi::CallbackInfo &info, const Napi::Value &value) \
    {                                                                               \
          RECORD_TIME_BEGIN                                                         \
            NodeBinding::checkArgs(info, 1);                                        \
        mRenderContext->makeCurrent();                                              \
        if (info[0].As<Napi::Value>().IsUndefined())                                \
        {                                                                           \
            return;                                                                 \
        }                                                                           \
        // printf("the function : " #methodName " is  called \n");                     \
                \ 

#define DEFINE_GETTER_METHOD(methodName)                  \
     Napi::Value                                          \
    Context2D::methodName(const Napi::CallbackInfo &info) \
    {                                                     \
        RECORD_TIME_BEGIN                                 \
        mRenderContext->makeCurrent();                    \
        // printf("the function : " #methodName " is  called \n");                 \
                    \ 

namespace NodeBinding
{
    Napi::FunctionReference Context2D::constructor;
    Context2D::Context2D(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Context2D>(info)
    {
    }

    void Context2D::Init(Napi::Env env)
    {
        Napi::HandleScope scope(env);
        Napi::Function func = DefineClass(env, "CanvasRenderingContext2D", {
            InstanceMethod("fillRect", &Context2D::fillRect),
            InstanceMethod("arc", &Context2D::arc),
            InstanceMethod("arcTo", &Context2D::arcTo),
            InstanceMethod("beginPath", &Context2D::beginPath),
            InstanceMethod("bezierCurveTo", &Context2D::bezierCurveTo),
            InstanceMethod("clearRect", &Context2D::clearRect),
            InstanceMethod("clip", &Context2D::clip),
            InstanceMethod("closePath", &Context2D::closePath),
            InstanceMethod("createImageData", &Context2D::createImageData),
            InstanceMethod("createLinearGradient", &Context2D::createLinearGradient),
            InstanceMethod("createPattern", &Context2D::createPattern),
            InstanceMethod("createRadialGradient", &Context2D::createRadialGradient),
            InstanceMethod("drawImage", &Context2D::drawImage),
            InstanceMethod("fill", &Context2D::fill),
            InstanceMethod("fillText", &Context2D::fillText),
            InstanceMethod("getImageData", &Context2D::getImageData),
            InstanceMethod("getLineDash", &Context2D::getLineDash),
            InstanceMethod("lineTo", &Context2D::lineTo),
            InstanceMethod("measureText", &Context2D::measureText),
            InstanceMethod("moveTo", &Context2D::moveTo),
            InstanceMethod("putImageData", &Context2D::putImageData),
            InstanceMethod("quadraticCurveTo", &Context2D::quadraticCurveTo),
            InstanceMethod("rect", &Context2D::rect),
            InstanceMethod("resetTransform", &Context2D::resetTransform),
            InstanceMethod("restore", &Context2D::restore),
            InstanceMethod("rotate", &Context2D::rotate),
            InstanceMethod("save", &Context2D::save),
            InstanceMethod("scale", &Context2D::scale),
            InstanceMethod("setLineDash", &Context2D::setLineDash),
            InstanceMethod("setTransform", &Context2D::setTransform),
            InstanceMethod("stroke", &Context2D::stroke),
            InstanceMethod("strokeRect", &Context2D::strokeRect),
            InstanceMethod("strokeText", &Context2D::strokeText),
            InstanceMethod("transform", &Context2D::transform),
            InstanceMethod("translate", &Context2D::translate),

            InstanceAccessor("fillStyle", &Context2D::getfillStyle, &Context2D::setfillStyle),
            InstanceAccessor("font", &Context2D::getfont, &Context2D::setfont),
            InstanceAccessor("globalAlpha", &Context2D::getglobalAlpha, &Context2D::setglobalAlpha),
            InstanceAccessor("globalCompositeOperation", &Context2D::getglobalCompositeOperation, &Context2D::setglobalCompositeOperation),
            InstanceAccessor("lineCap", &Context2D::getlineCap, &Context2D::setlineCap),
            InstanceAccessor("lineDashOffset", &Context2D::getlineDashOffset, &Context2D::setlineDashOffset),
            InstanceAccessor("lineJoin", &Context2D::getlineJoin, &Context2D::setlineJoin),
            InstanceAccessor("lineWidth", &Context2D::getlineWidth, &Context2D::setlineWidth),
            InstanceAccessor("miterLimit", &Context2D::getmiterLimit, &Context2D::setmiterLimit),
            InstanceAccessor("shadowBlur", &Context2D::getshadowBlur, &Context2D::setshadowBlur),
            InstanceAccessor("shadowColor", &Context2D::getshadowColor, &Context2D::setshadowColor),
            InstanceAccessor("shadowOffsetX", &Context2D::getshadowOffsetX, &Context2D::setshadowOffsetX),
            InstanceAccessor("shadowOffsetY", &Context2D::getshadowOffsetY, &Context2D::setshadowOffsetY),
            InstanceAccessor("strokeStyle", &Context2D::getstrokeStyle, &Context2D::setstrokeStyle),
            InstanceAccessor("textAlign", &Context2D::gettextAlign, &Context2D::settextAlign),
            InstanceAccessor("textBaseline", &Context2D::gettextBaseline, &Context2D::settextBaseline),
            InstanceAccessor("canvas", &Context2D::getCanvas, nullptr),
        });
        constructor = Napi::Persistent(func);
        constructor.SuppressDestruct();
        return;
    }

    Napi::Object Context2D::NewInstance(Napi::Env env)
    {
        Napi::Object obj = constructor.New({});
        obj.Set("name", Napi::String::New(env, "context2d"));
        return obj;
    }
    DEFINE_VOID_METHOD(fillRect)
    Napi::Env env = info.Env();
    NodeBinding::checkArgs(info, 4);

    float x = info[0].As<Napi::Number>().FloatValue();
    float y = info[1].As<Napi::Number>().FloatValue();
    float width = info[2].As<Napi::Number>().FloatValue();
    float height = info[3].As<Napi::Number>().FloatValue();

    if (mRenderContext)
    {
        mRenderContext->getCtx2d()->FillRect(x, y, width, height);
    }
    RECORD_TIME_END
    return;
} // namespace NodeBinding

DEFINE_SETTER_METHOD(setfillStyle)
if (mRenderContext)
{
    if (value.IsString())
    {
        std::string arg = value.As<Napi::String>().Utf8Value();
        mRenderContext->getCtx2d()->SetFillStyle(arg.c_str());
    }
    else if (value.IsObject())
    {
        Napi::Object object = value.As<Napi::Object>();
        Napi::Value name = object.Get("name");
        if (!name.IsString())
        {
            throwError(info, "wrong argument for fillstyle");
            return;
        }
        std::string namePropetry = name.As<Napi::String>().Utf8Value();
        if (namePropetry == "linearGradient")
        {
            Gradient *gradient = Napi::ObjectWrap<Gradient>::Unwrap(object);
            float startArr[] = {gradient->mLinearGradientInfo->startX, gradient->mLinearGradientInfo->startY};
            float endArr[] = {gradient->mLinearGradientInfo->endX, gradient->mLinearGradientInfo->endY};
            const std::vector<ColorStop> colorStop = gradient->getColorStops();
            float offsetArray[colorStop.size()];
            std::string colorArray[colorStop.size()];
            for (int i = 0; i < colorStop.size(); i++)
            {
                offsetArray[i] = colorStop[i].offset;
                colorArray[i] = colorStop[i].color;
            }
            mRenderContext->getCtx2d()->SetFillStyleLinearGradient(startArr, endArr, gradient->getCount(), offsetArray, colorArray);
        }
        else if (namePropetry == "radialGradient")
        {
            Gradient *gradient = Napi::ObjectWrap<Gradient>::Unwrap(object);
            float startArr[] = {gradient->mRadialGradientInfo->startX, gradient->mRadialGradientInfo->startY, gradient->mRadialGradientInfo->startR};
            float endArr[] = {gradient->mRadialGradientInfo->endX, gradient->mRadialGradientInfo->endY, gradient->mRadialGradientInfo->endR};
            const std::vector<ColorStop> colorStop = gradient->getColorStops();
            float offsetArray[colorStop.size()];
            std::string colorArray[colorStop.size()];
            for (int i = 0; i < colorStop.size(); i++)
            {
                offsetArray[i] = colorStop[i].offset;
                colorArray[i] = colorStop[i].color;
            }
            mRenderContext->getCtx2d()->SetFillStyleRadialGradient(startArr, endArr, gradient->getCount(), offsetArray, colorArray);
        }
        else if (namePropetry == "pattern")
        {
            Pattern *pattern = Napi::ObjectWrap<Pattern>::Unwrap(object);
            int textureId = mRenderContext->getCtx2d()->BindImage(
                &pattern->content->getPixels()[0], GL_RGBA,
                pattern->content->getWidth(),
                pattern->content->getHeight());
            mRenderContext->getCtx2d()->SetFillStylePattern(
                textureId, pattern->content->getWidth(),
                pattern->content->getHeight(),
                pattern->getRepetition().c_str(), false);
            mRenderContext->recordTextures(textureId);
        }
        else
        {
            throwError(info, "fill style argment vaild");
        }
    }
}
RECORD_TIME_END
return;
}

DEFINE_RETURN_VALUE_METHOD(getfillStyle)
Napi::Env env = info.Env();
if (mRenderContext)
{
    return Napi::String::New(env, gcanvas::ColorToString(mRenderContext->getCtx2d()->FillStyle()));
}
RECORD_TIME_END
return Napi::String::New(env, "");
}

DEFINE_VOID_METHOD(clearRect)
Napi::Env env = info.Env();
NodeBinding::checkArgs(info, 4);
float x = info[0].As<Napi::Number>().FloatValue();
float y = info[1].As<Napi::Number>().FloatValue();
float width = info[2].As<Napi::Number>().FloatValue();
float height = info[3].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->ClearRect(x, y, width, height);
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(arc)
Napi::Env env = info.Env();
if (info.Length() < 5)
{
    throwError(info, "wrong argument number");
    return;
}
float x = info[0].As<Napi::Number>().FloatValue();
float y = info[1].As<Napi::Number>().FloatValue();
float r = info[2].As<Napi::Number>().FloatValue();
float startAngle = info[3].As<Napi::Number>().FloatValue();
float endAngle = info[4].As<Napi::Number>().FloatValue();

bool clockwise = true;
if (info.Length() == 6)
{
    clockwise = info[5].As<Napi::Boolean>().ToBoolean();
}
if (mRenderContext)
{
    mRenderContext->getCtx2d()->Arc(x, y, r, startAngle, endAngle, clockwise);
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(arcTo)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 5);
float x1 = info[0].As<Napi::Number>().FloatValue();
float y1 = info[1].As<Napi::Number>().FloatValue();
float x2 = info[2].As<Napi::Number>().FloatValue();
float y2 = info[3].As<Napi::Number>().FloatValue();
float r = info[4].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->ArcTo(x1, y1, x2, y2, r);
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(beginPath)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 0);
if (mRenderContext)
{
    mRenderContext->getCtx2d()->BeginPath();
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(bezierCurveTo)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 6);
float cp1x = info[0].As<Napi::Number>().FloatValue();
float cp1y = info[1].As<Napi::Number>().FloatValue();
float cp2x = info[2].As<Napi::Number>().FloatValue();
float cp2y = info[3].As<Napi::Number>().FloatValue();
float x = info[4].As<Napi::Number>().FloatValue();
float y = info[5].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->BezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(clip)
Napi::Env env = info.Env();

GFillRule rule = FILL_RULE_NONZERO;
if (info.Length() == 1)
{
    std::string value = info[0].As<Napi::String>().Utf8Value();
    if (value == "nonzero")
    {
        rule = FILL_RULE_NONZERO;
    }
    else if (value == "evenodd")
    {
        rule = FILL_RULE_EVENODD;
    }
    else
    {
        throwError(info, "fill rule value invaild");
    }
}

if (mRenderContext)
{
    mRenderContext->getCtx2d()->Clip(rule);
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(closePath)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 0);
if (mRenderContext)
{
    mRenderContext->getCtx2d()->ClosePath();
}
RECORD_TIME_END
}

DEFINE_GETTER_METHOD(createImageData)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 2);
return ImageData::NewInstance(info.Env(), info[0], info[1]);
}

DEFINE_GETTER_METHOD(createLinearGradient)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 4);
return Gradient::NewInstance(env, info);
}

DEFINE_GETTER_METHOD(createPattern)
Napi::Env env = info.Env();
NodeBinding::checkArgs(info, 2);

return Pattern::NewInstance(info, info[0], info[1]);
}

DEFINE_GETTER_METHOD(createRadialGradient)
Napi::Env env = info.Env();
NodeBinding::checkArgs(info, 6);
return Gradient::NewInstance(env, info);
}

DEFINE_VOID_METHOD(drawImage)
Napi::Env env = info.Env();
if (info.Length() < 3 || (info.Length() != 3 && info.Length() != 5 && info.Length() != 9))
{
    Napi::TypeError::New(info.Env(), "wrong argument number")
        .ThrowAsJavaScriptException();
    return;
}

if (!mRenderContext)
{
    throwError(info, "rendercontext invaild");
    return;
}

float srcX = 0, srcY = 0, srcWidth = 0, srcHeight = 0;
float desX = 0, desY = 0, desWidth = mRenderContext->getWdith(), desHeight = mRenderContext->getHeight();

int textureId = 0;
int textureWidth = 0, textureHeight = 0;

Napi::Object object = info[0].As<Napi::Object>();
Napi::Value name = object.Get("name");
//todo fixme refactor
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
if (name.IsString())
{
    std::string namePropetry = name.As<Napi::String>().Utf8Value();
    if (namePropetry == "canvas")
    {
        Canvas *canvas = Napi::ObjectWrap<Canvas>::Unwrap(info[0].As<Napi::Object>());
        textureWidth = canvas->getWidth();
        textureHeight = canvas->getHeight();
        srcWidth = textureWidth;
        srcHeight = textureHeight;

        size_t size = srcWidth * srcHeight * 4;
        uint8_t *pixels = new uint8_t[size];
        if (!pixels)
        {
            printf("DrawImage with canvas, memory allocate failed!\n");
            return;
        }

        //fixme later
        canvas->mRenderContext->BindFBO();
        canvas->mRenderContext->getCtx2d()->GetImageData(0, 0, textureWidth, textureHeight, pixels);
        textureId = canvas->mRenderContext->getCtx2d()->BindImage(pixels, GL_RGBA, textureWidth, textureHeight);
        printf("drawImage with canvas, textureId=%d, textureWidth=%d, textureHeight=%d\n", textureId, textureWidth, textureHeight);

        delete[] pixels;
        pixels = nullptr;
    }
    else if (namePropetry == "image")
    {
        Image *image = Napi::ObjectWrap<Image>::Unwrap(info[0].As<Napi::Object>());
        srcWidth = image->getWidth();
        srcHeight = image->getHeight();
        textureWidth = srcWidth;
        textureHeight = srcHeight;
        if (image->getTextureId() == -1)
        {
            int id = mRenderContext->getTextureIdByUrl(image->getSrc());
            if (id == -1)
            {
                id = mRenderContext->getCtx2d()->BindImage(&image->getPixels()[0], GL_RGBA, srcWidth, srcHeight);
                //缓存下url和纹理id的关系,避免重复bind
                mRenderContext->recordImageTexture(image->getSrc(), id);
            }
            else
            {
                // printf("the cached image id is %d \n",id);
            }
            image->setTextureId(id);
        }
        textureId = image->getTextureId();
    }
}

if (info.Length() == 3)
{
    desX = info[1].As<Napi::Number>().FloatValue();
    desY = info[2].As<Napi::Number>().FloatValue();
    desWidth = textureWidth;
    desHeight = textureHeight;
}
else if (info.Length() == 5)
{
    desX = info[1].As<Napi::Number>().FloatValue();
    desY = info[2].As<Napi::Number>().FloatValue();
    desWidth = info[3].As<Napi::Number>().FloatValue();
    desHeight = info[4].As<Napi::Number>().FloatValue();
}
else if (info.Length() == 9)
{
    srcX = info[1].As<Napi::Number>().FloatValue();
    srcY = info[2].As<Napi::Number>().FloatValue();
    srcWidth = info[3].As<Napi::Number>().FloatValue();
    srcHeight = info[4].As<Napi::Number>().FloatValue();

    desX = info[5].As<Napi::Number>().FloatValue();
    desY = info[6].As<Napi::Number>().FloatValue();
    desWidth = info[7].As<Napi::Number>().FloatValue();
    desHeight = info[8].As<Napi::Number>().FloatValue();
}
if (mRenderContext)
{
    mRenderContext->getCtx2d()->DrawImage(textureId,
                                        textureWidth,
                                        textureHeight, // image width & height
                                        srcX,          // srcX
                                        srcY,          // srcY
                                        srcWidth,      // srcWidth
                                        srcHeight,     //srcHeight
                                        desX,          //desStartX
                                        desY,          //desStartY
                                        desWidth,      //desWidth
                                        desHeight);    //desHeight
    mRenderContext->recordTextures(textureId);
}
}

DEFINE_VOID_METHOD(fill)
Napi::Env env = info.Env();

GFillRule rule = FILL_RULE_NONZERO;
if (info.Length() == 1)
{
    std::string value = info[0].As<Napi::String>().Utf8Value();
    if (value == "nonzero")
    {
        rule = FILL_RULE_NONZERO;
    }
    else if (value == "evenodd")
    {
        rule = FILL_RULE_EVENODD;
    }
    else
    {
        throwError(info, "fill rule value invaild");
    }
}
if (mRenderContext)
{
    mRenderContext->getCtx2d()->Fill(rule);
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(fillText)
Napi::Env env = info.Env();

if (info.Length() < 3)
{
    Napi::TypeError::New(info.Env(), "wrong argument number")
        .ThrowAsJavaScriptException();
}
if (mRenderContext)
{
    std::string content = info[0].As<Napi::String>().Utf8Value();
    float x = info[1].As<Napi::Number>().FloatValue();
    float y = info[2].As<Napi::Number>().FloatValue();
    if (info.Length() == 4)
    {
        float maxWidth = info[3].As<Napi::Number>().FloatValue();
        mRenderContext->getCtx2d()->DrawText(content.c_str(), x, y, maxWidth);
    }
    else
    {
        mRenderContext->getCtx2d()->DrawText(content.c_str(), x, y);
    }
}
RECORD_TIME_END
}

DEFINE_RETURN_VALUE_METHOD(getImageData)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 4);
if (mRenderContext)
{
    int x = info[0].As<Napi::Number>().Int32Value();
    int y = info[1].As<Napi::Number>().Int32Value();

    int width = info[2].As<Napi::Number>().Int32Value();
    int height = info[3].As<Napi::Number>().Int32Value();
    //offscreen condition: y need convert
    y = mRenderContext->getHeight() - (y + height);

    Napi::Object imageDataObj = ImageData::NewInstance(env, info[2], info[3]);
    ImageData *ptr = Napi::ObjectWrap<ImageData>::Unwrap(imageDataObj);
    mRenderContext->getCtx2d()->GetImageData(x, y, width, height, &ptr->getPixels()[0]);

    //flipY
    gcanvas::FlipPixel(&ptr->getPixels()[0], width, height);

    return imageDataObj;
}
RECORD_TIME_END
}

DEFINE_RETURN_VALUE_METHOD(getLineDash)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 0);
Napi::Array ret = Napi::Array::New(env);
if (mRenderContext)
{
    std::vector<float> dash = mRenderContext->getCtx2d()->LineDash();
    for (int i = 0; i < dash.size(); i++)
    {
        ret.Set(i, Napi::Number::New(env, dash[i]));
    }
}
RECORD_TIME_END
return ret;
}

DEFINE_VOID_METHOD(lineTo)
Napi::Env env = info.Env();
NodeBinding::checkArgs(info, 2);
float x = info[0].As<Napi::Number>().FloatValue();
float y = info[1].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->LineTo(x, y);
}
RECORD_TIME_END
}

DEFINE_RETURN_VALUE_METHOD(measureText)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 1);
std::string text = info[0].As<Napi::String>().Utf8Value();
if (mRenderContext)
{
    float width = mRenderContext->getCtx2d()->MeasureTextWidth(text.c_str());
    return TextMetrics::NewInstance(env, Napi::Number::New(env, width));
}
else
{
    return env.Undefined();
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(moveTo)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 2);
float x = info[0].As<Napi::Number>().FloatValue();
float y = info[1].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->MoveTo(x, y);
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(putImageData)
Napi::Env env = info.Env();
if (info.Length() < 3)
{
    throwError(info, "wrong argument number");
    return;
}
if (!info[0].IsObject())
{
    throwError(info, "imgData must be object");
    return;
}
ImageData *imgData = Napi::ObjectWrap<ImageData>::Unwrap(info[0].As<Napi::Object>());
if (mRenderContext)
{
    int x = info[1].As<Napi::Number>().Int32Value();
    int y = info[2].As<Napi::Number>().Int32Value();
    int dirtyX = 0;
    int dirtyY = 0;
    int dirtyWidth = imgData->getWidth();
    int dirtyHeight = imgData->getHeight();

    if (info.Length() == 7)
    {
        dirtyX = info[3].As<Napi::Number>().Int32Value();
        dirtyY = info[4].As<Napi::Number>().Int32Value();
        dirtyWidth = info[5].As<Napi::Number>().Int32Value();
        dirtyHeight = info[6].As<Napi::Number>().Int32Value();
    }
    mRenderContext->getCtx2d()->PutImageData(
        &imgData->getPixels()[0], //content
        imgData->getWidth(),      //imageData width
        imgData->getHeight(),     //imageData height
        x,                        // draw start x
        y,                        //draw start y
        dirtyX,                   // dirtyX
        dirtyY,                   //// dirtyY
        dirtyWidth,               //dirtyWidth
        dirtyHeight               //dirtyHeight
    );
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(quadraticCurveTo)
Napi::Env env = info.Env();
NodeBinding::checkArgs(info, 4);
float cpx = info[0].As<Napi::Number>().FloatValue();
float cpy = info[1].As<Napi::Number>().FloatValue();
float x = info[2].As<Napi::Number>().FloatValue();
float y = info[3].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->QuadraticCurveTo(cpx, cpy, x, y);
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(rect)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 4);
float x = info[0].As<Napi::Number>().FloatValue();
float y = info[1].As<Napi::Number>().FloatValue();
float width = info[2].As<Napi::Number>().FloatValue();
float height = info[3].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->Rect(x, y, width, height);
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(resetTransform)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 0);
if (mRenderContext)
{
    mRenderContext->getCtx2d()->ResetTransform();
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(restore)
Napi::Env env = info.Env();
NodeBinding::checkArgs(info, 0);
if (mRenderContext)
{
    mRenderContext->getCtx2d()->Restore();
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(rotate)
Napi::Env env = info.Env();
float angle = info[0].As<Napi::Number>().FloatValue();
NodeBinding::checkArgs(info, 1);
if (mRenderContext)
{
    mRenderContext->getCtx2d()->Rotate(angle);
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(save)
Napi::Env env = info.Env();
NodeBinding::checkArgs(info, 0);
if (mRenderContext)
{
    mRenderContext->getCtx2d()->Save();
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(scale)
Napi::Env env = info.Env();
float x = info[0].As<Napi::Number>().FloatValue();
float y = info[1].As<Napi::Number>().FloatValue();
NodeBinding::checkArgs(info, 2);
if (mRenderContext)
{
    mRenderContext->getCtx2d()->Scale(x, y);
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(setLineDash)
Napi::Env env = info.Env();
NodeBinding::checkArgs(info, 1);
Napi::Array array = info[0].As<Napi::Array>();

std::vector<float> dash;
for (int i = 0; i < array.Length(); i++)
{
    dash.push_back(array.Get(i).As<Napi::Number>().FloatValue());
}
if (mRenderContext)
{
    mRenderContext->getCtx2d()->SetLineDash(std::move(dash));
}
RECORD_TIME_END
}

void Context2D::setCanvasRef(NodeBinding::Canvas *canvas)
{
    mCanvas = canvas;
}
DEFINE_VOID_METHOD(setTransform)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 6);

float scaleX = info[0].As<Napi::Number>().FloatValue();
float scaleY = info[1].As<Napi::Number>().FloatValue();
float rotateX = info[2].As<Napi::Number>().FloatValue();
float rototaY = info[3].As<Napi::Number>().FloatValue();
float translateX = info[4].As<Napi::Number>().FloatValue();
float translateY = info[5].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->SetTransform(scaleX, scaleY, rotateX, rototaY, translateX, translateY);
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(stroke)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 0);
if (mRenderContext)
{
    mRenderContext->getCtx2d()->Stroke();
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(strokeRect)
Napi::Env env = info.Env();
NodeBinding::checkArgs(info, 4);
float x = info[0].As<Napi::Number>().FloatValue();
float y = info[1].As<Napi::Number>().FloatValue();
float width = info[2].As<Napi::Number>().FloatValue();
float height = info[3].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->StrokeRect(x, y, width, height);
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(strokeText)
Napi::Env env = info.Env();
if (info.Length() < 3)
{
    Napi::TypeError::New(info.Env(), "wrong argument number")
        .ThrowAsJavaScriptException();
}
if (mRenderContext)
{
    std::string content = info[0].As<Napi::String>().Utf8Value();
    float x = info[1].As<Napi::Number>().FloatValue();
    float y = info[2].As<Napi::Number>().FloatValue();
    if (info.Length() == 4)
    {
        float maxWidth = info[3].As<Napi::Number>().FloatValue();
        mRenderContext->getCtx2d()->StrokeText(content.c_str(), x, y, maxWidth);
    }
    else
    {
        mRenderContext->getCtx2d()->StrokeText(content.c_str(), x, y);
    }
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(transform)
Napi::Env env = info.Env();

NodeBinding::checkArgs(info, 6);
float scaleX = info[0].As<Napi::Number>().FloatValue();
float rotateX = info[1].As<Napi::Number>().FloatValue();
float rotateY = info[2].As<Napi::Number>().FloatValue();
float scaleY = info[3].As<Napi::Number>().FloatValue();
float translateX = info[4].As<Napi::Number>().FloatValue();
float translateY = info[5].As<Napi::Number>().FloatValue();
// printf("the Transfrom called  scaleX %f scaleY %f rotateX %f rototaY %f translateX %f  translateY %f \n",scaleX,scaleY,rotateX,rotateY,translateX,translateY);
if (mRenderContext)
{
    mRenderContext->getCtx2d()->Transfrom(scaleX, rotateX, rotateY, scaleY, translateX, translateY);
}
RECORD_TIME_END
}

DEFINE_VOID_METHOD(translate)
Napi::Env env = info.Env();
float tx = info[0].As<Napi::Number>().FloatValue();
float ty = info[1].As<Napi::Number>().FloatValue();
NodeBinding::checkArgs(info, 2);
if (mRenderContext)
{
    mRenderContext->getCtx2d()->Translate(tx, ty);
}
RECORD_TIME_END
}

DEFINE_SETTER_METHOD(setfont)
std::string font = value.As<Napi::String>().Utf8Value();
// printf("the set fon value is %s \n",font.c_str());
if (mRenderContext)
{
    mRenderContext->getCtx2d()->SetFont(font.c_str());
}
RECORD_TIME_END
}

DEFINE_SETTER_METHOD(setglobalAlpha)
float colorValue = info[0].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->SetGlobalAlpha(colorValue);
}
RECORD_TIME_END
}

//TODO
DEFINE_SETTER_METHOD(setglobalCompositeOperation)
std::string opValue = info[0].As<Napi::String>().Utf8Value();
if (mRenderContext)
{
    if (opValue == "source-over")
    {
        mRenderContext->getCtx2d()->DoSetGlobalCompositeOperation(COMPOSITE_OP_SOURCE_OVER);
    }
    else if (opValue == "source-out")
    {
        mRenderContext->getCtx2d()->DoSetGlobalCompositeOperation(COMPOSITE_OP_SOURCE_OUT);
    }
    else if (opValue == "source-atop")
    {
        mRenderContext->getCtx2d()->DoSetGlobalCompositeOperation(COMPOSITE_OP_SOURCE_ATOP);
    }
    else if (opValue == "destination-over")
    {
        mRenderContext->getCtx2d()->DoSetGlobalCompositeOperation(COMPOSITE_OP_DESTINATION_OVER);
    }
    else if (opValue == "destination-in")
    {
        mRenderContext->getCtx2d()->DoSetGlobalCompositeOperation(COMPOSITE_OP_DESTINATION_IN);
    }
    else if (opValue == "destination-out")
    {
        mRenderContext->getCtx2d()->DoSetGlobalCompositeOperation(COMPOSITE_OP_DESTINATION_OUT);
    }
    else if (opValue == "lighter")
    {
        mRenderContext->getCtx2d()->DoSetGlobalCompositeOperation(COMPOSITE_OP_LIGHTER);
    }
    else if (opValue == "xor")
    {
        mRenderContext->getCtx2d()->DoSetGlobalCompositeOperation(COMPOSITE_OP_XOR);
    }
    else
    {
        throwError(info, "compite value invaild or not support");
    }
}
RECORD_TIME_END
}

DEFINE_SETTER_METHOD(setlineCap)
std::string lineCap = info[0].As<Napi::String>().Utf8Value();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->SetLineCap(lineCap.c_str());
}
RECORD_TIME_END
}

DEFINE_SETTER_METHOD(setlineDashOffset)
if (mRenderContext)
{
    float offset = info[0].As<Napi::Number>().FloatValue();
    mRenderContext->getCtx2d()->SetLineDashOffset(offset);
}
RECORD_TIME_END
}

DEFINE_SETTER_METHOD(setlineJoin)
std::string lineJoin = info[0].As<Napi::String>().Utf8Value();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->SetLineJoin(lineJoin.c_str());
}
RECORD_TIME_END
}

DEFINE_SETTER_METHOD(setlineWidth)
float lineWidth = info[0].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->SetLineWidth(lineWidth);
}
RECORD_TIME_END
}

DEFINE_SETTER_METHOD(setmiterLimit)
float miterLimit = info[0].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->SetMiterLimit(miterLimit);
}
RECORD_TIME_END
}

DEFINE_SETTER_METHOD(setshadowBlur)
float shadowBlur = info[0].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->SetShadowBlur(shadowBlur);
}
RECORD_TIME_END
}

DEFINE_SETTER_METHOD(setshadowColor)
std::string color = info[0].As<Napi::String>().Utf8Value();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->SetShadowColor(color.c_str());
}
RECORD_TIME_END
}

DEFINE_SETTER_METHOD(setshadowOffsetX)
float offsetX = info[0].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->SetShadowOffsetX(offsetX);
}
RECORD_TIME_END
}

DEFINE_SETTER_METHOD(setshadowOffsetY)
float offsetY = info[0].As<Napi::Number>().FloatValue();
if (mRenderContext)
{
    mRenderContext->getCtx2d()->SetShadowOffsetY(offsetY);
}
RECORD_TIME_END
}

DEFINE_SETTER_METHOD(setstrokeStyle)
if (mRenderContext)
{
    if (value.IsString())
    {
        std::string arg = value.As<Napi::String>().Utf8Value();
        mRenderContext->getCtx2d()->SetStrokeStyle(arg.c_str());
    }
    else if (value.IsObject())
    {
        Napi::Object object = value.As<Napi::Object>();
        Napi::Value name = object.Get("name");
        if (!name.IsString())
        {
            throwError(info, "wrong argument for fillstyle");
            return;
        }
        std::string namePropetry = name.As<Napi::String>().Utf8Value();
        if (namePropetry == "linearGradient")
        {
            Gradient *gradient = Napi::ObjectWrap<Gradient>::Unwrap(object);
            float startArr[] = {gradient->mLinearGradientInfo->startX, gradient->mLinearGradientInfo->startY};
            float endArr[] = {gradient->mLinearGradientInfo->endX, gradient->mLinearGradientInfo->endY};
            const std::vector<ColorStop> colorStop = gradient->getColorStops();
            float offsetArray[colorStop.size()];
            std::string colorArray[colorStop.size()];
            for (int i = 0; i < colorStop.size(); i++)
            {
                offsetArray[i] = colorStop[i].offset;
                colorArray[i] = colorStop[i].color;
            }
            mRenderContext->getCtx2d()->SetFillStyleLinearGradient(startArr, endArr, gradient->getCount(), offsetArray, colorArray, true);
        }
        else if (namePropetry == "radialGradient")
        {
            Gradient *gradient = Napi::ObjectWrap<Gradient>::Unwrap(object);
            float startArr[] = {gradient->mRadialGradientInfo->startX, gradient->mRadialGradientInfo->startY, gradient->mRadialGradientInfo->startR};
            float endArr[] = {gradient->mRadialGradientInfo->endX, gradient->mRadialGradientInfo->endY, gradient->mRadialGradientInfo->endR};
            const std::vector<ColorStop> colorStop = gradient->getColorStops();
            float offsetArray[colorStop.size()];
            std::string colorArray[colorStop.size()];
            for (int i = 0; i < colorStop.size(); i++)
            {
                offsetArray[i] = colorStop[i].offset;
                colorArray[i] = colorStop[i].color;
            }
            mRenderContext->getCtx2d()->SetFillStyleRadialGradient(startArr, endArr, gradient->getCount(), offsetArray, colorArray, true);
        }
        else if (namePropetry == "pattern")
        {
            Pattern *pattern =
                Napi::ObjectWrap<Pattern>::Unwrap(object);
            int textureId = mRenderContext->getCtx2d()->BindImage(
                &pattern->content->getPixels()[0], GL_RGBA,
                pattern->content->getWidth(),
                pattern->content->getHeight());
            mRenderContext->getCtx2d()->SetFillStylePattern(
                textureId, pattern->content->getWidth(),
                pattern->content->getHeight(),
                pattern->getRepetition().c_str(), true);
            mRenderContext->recordTextures(textureId);
        }
        else
        {
            throwError(info, "stroke style argment vaild");
        }
    }
}
RECORD_TIME_END
return;
}

DEFINE_SETTER_METHOD(settextAlign)
std::string textAlign = value.As<Napi::String>().Utf8Value();
if (mRenderContext)
{
    if (textAlign == "start")
    {
        mRenderContext->getCtx2d()->SetTextAlign(TEXT_ALIGN_START);
    }
    else if (textAlign == "end")
    {
        mRenderContext->getCtx2d()->SetTextAlign(TEXT_ALIGN_END);
    }
    else if (textAlign == "left")
    {
        mRenderContext->getCtx2d()->SetTextAlign(TEXT_ALIGN_LEFT);
    }
    else if (textAlign == "center")
    {
        mRenderContext->getCtx2d()->SetTextAlign(TEXT_ALIGN_CENTER);
    }
    else if (textAlign == "right")
    {
        mRenderContext->getCtx2d()->SetTextAlign(TEXT_ALIGN_RIGHT);
    }
    else
    {
        throwError(info, "wrong text align value");
    }
}
RECORD_TIME_END
}

DEFINE_SETTER_METHOD(settextBaseline)
std::string baseline = value.As<Napi::String>().Utf8Value();
if (mRenderContext)
{
    if (baseline == "top")
    {
        mRenderContext->getCtx2d()->SetTextBaseline(TEXT_BASELINE_TOP);
    }
    else if (baseline == "bottom")
    {
        mRenderContext->getCtx2d()->SetTextBaseline(TEXT_BASELINE_BOTTOM);
    }
    else if (baseline == "middle")
    {
        mRenderContext->getCtx2d()->SetTextBaseline(TEXT_BASELINE_MIDDLE);
    }
    else if (baseline == "alphabetic")
    {
        mRenderContext->getCtx2d()->SetTextBaseline(TEXT_BASELINE_ALPHABETIC);
    }
    else if (baseline == "hanging")
    {
        mRenderContext->getCtx2d()->SetTextBaseline(TEXT_BASELINE_HANGING);
    }
    else
    {
        throwError(info, "wrong text align value");
    }
}
RECORD_TIME_END
}

DEFINE_GETTER_METHOD(getfont)
Napi::Env env = info.Env();
if (mRenderContext)
{
    std::string value = mRenderContext->getCtx2d()->mCurrentState->mFont->GetOriginFontName();
    return Napi::String::New(env, value);
}
RECORD_TIME_END
return Napi::String::New(env, "");
}

DEFINE_GETTER_METHOD(getglobalAlpha)
Napi::Env env = info.Env();
if (mRenderContext)
{
    float value = mRenderContext->getCtx2d()->GlobalAlpha();
    return Napi::Number::New(env, value);
}
RECORD_TIME_END
return Napi::Number::New(env, -1.0f);
}

DEFINE_GETTER_METHOD(getglobalCompositeOperation)
Napi::Env env = info.Env();
if (mRenderContext)
{
    GCompositeOperation value = mRenderContext->getCtx2d()->GlobalCompositeOperation();
    if (value == COMPOSITE_OP_SOURCE_OVER)
    {
        return Napi::String::New(env, "source-over");
    }
    else if (value == COMPOSITE_OP_SOURCE_OUT)
    {
        return Napi::String::New(env, "source-out");
    }
    else if (value == COMPOSITE_OP_SOURCE_ATOP)
    {
        return Napi::String::New(env, "source-atop");
    }
    else if (value == COMPOSITE_OP_DESTINATION_OVER)
    {
        return Napi::String::New(env, "destination-over");
    }
    else if (value == COMPOSITE_OP_DESTINATION_IN)
    {
        return Napi::String::New(env, "destination-in");
    }
    else if (value == COMPOSITE_OP_DESTINATION_OUT)
    {
        return Napi::String::New(env, "destination-out");
    }
    else if (value == COMPOSITE_OP_XOR)
    {
        return Napi::String::New(env, "xor");
    }
    else if (value == COMPOSITE_OP_LIGHTER)
    {
        return Napi::String::New(env, "lighter");
    }
}
RECORD_TIME_END
return Napi::String::New(env, "");
}

DEFINE_GETTER_METHOD(getlineCap)
Napi::Env env = info.Env();
if (mRenderContext)
{
    GLineCap cap = mRenderContext->getCtx2d()->LineCap();
    if (cap == LINE_CAP_BUTT)
    {
        return Napi::String::New(env, "butt");
    }
    else if (cap == LINE_CAP_ROUND)
    {
        return Napi::String::New(env, "round");
    }
    else if (cap == LINE_CAP_SQUARE)
    {
        return Napi::String::New(env, "square");
    }
}
RECORD_TIME_END
return Napi::String::New(env, "");
}

DEFINE_GETTER_METHOD(getlineDashOffset)
Napi::Env env = info.Env();
if (mRenderContext)
{
    float value = mRenderContext->getCtx2d()->LineDashOffset();
    return Napi::Number::New(env, value);
}
RECORD_TIME_END
return Napi::Number::New(env, -1);
}

DEFINE_GETTER_METHOD(getlineJoin)
Napi::Env env = info.Env();
if (mRenderContext)
{
    GLineJoin value = mRenderContext->getCtx2d()->LineJoin();
    if (value == LINE_JOIN_BEVEL)
    {
        return Napi::String::New(env, "bevel");
    }
    else if (value == LINE_JOIN_ROUND)
    {
        return Napi::String::New(env, "round");
    }
    else if (value == LINE_JOIN_MITER)
    {
        return Napi::String::New(env, "miter");
    }
}
RECORD_TIME_END
return Napi::String::New(env, "");
}

DEFINE_GETTER_METHOD(getlineWidth)
Napi::Env env = info.Env();
if (mRenderContext)
{
    return Napi::Number::New(env, mRenderContext->getCtx2d()->LineWidth());
}
RECORD_TIME_END
return Napi::Number::New(env, -1);
}

DEFINE_GETTER_METHOD(getmiterLimit)
Napi::Env env = info.Env();
if (mRenderContext)
{
    float value = mRenderContext->getCtx2d()->MiterLimit();
    return Napi::Number::New(env, value);
}
RECORD_TIME_END
return Napi::Number::New(env, -1);
}

DEFINE_GETTER_METHOD(getshadowBlur)
Napi::Env env = info.Env();
if (mRenderContext)
{
    return Napi::Number::New(env, mRenderContext->getCtx2d()->mCurrentState->mShadowBlur);
}
RECORD_TIME_END
return Napi::String::New(env, "");
}

DEFINE_GETTER_METHOD(getshadowColor)
Napi::Env env = info.Env();
if (mRenderContext)
{
    return Napi::String::New(env, gcanvas::ColorToString(mRenderContext->getCtx2d()->mCurrentState->mShadowColor));
}
RECORD_TIME_END
return Napi::String::New(env, "");
}

DEFINE_GETTER_METHOD(getshadowOffsetX)
Napi::Env env = info.Env();
if (mRenderContext)
{
    return Napi::Number::New(env, mRenderContext->getCtx2d()->mCurrentState->mShadowOffsetX);
}
RECORD_TIME_END
return Napi::Number::New(env, -1);
}

DEFINE_GETTER_METHOD(getshadowOffsetY)
Napi::Env env = info.Env();
if (mRenderContext)
{
    return Napi::Number::New(env, mRenderContext->getCtx2d()->mCurrentState->mShadowOffsetY);
}
RECORD_TIME_END
return Napi::String::New(env, "");
}

DEFINE_GETTER_METHOD(getstrokeStyle)
Napi::Env env = info.Env();
if (mRenderContext)
{
    return Napi::String::New(env, gcanvas::ColorToString(mRenderContext->getCtx2d()->StrokeStyle()));
}
RECORD_TIME_END
return Napi::String::New(env, "");
}

DEFINE_GETTER_METHOD(gettextAlign)
Napi::Env env = info.Env();
if (mRenderContext)
{
    GTextAlign value = mRenderContext->getCtx2d()->TextAlign();
    if (value == TEXT_ALIGN_LEFT)
    {
        return Napi::String::New(env, "left");
    }
    else if (value == TEXT_ALIGN_RIGHT)
    {
        return Napi::String::New(env, "right");
    }
    else if (value == TEXT_ALIGN_CENTER)
    {
        return Napi::String::New(env, "center");
    }
    else if (value == TEXT_ALIGN_START)
    {
        return Napi::String::New(env, "start");
    }
    else if (value == TEXT_ALIGN_END)
    {
        return Napi::String::New(env, "end");
    }
}
RECORD_TIME_END
return Napi::String::New(env, "");
}

DEFINE_GETTER_METHOD(gettextBaseline)
Napi::Env env = info.Env();
if (mRenderContext)
{
    GTextBaseline value = mRenderContext->getCtx2d()->TextBaseline();
    if (value == TEXT_BASELINE_TOP)
    {
        return Napi::String::New(env, "top");
    }
    else if (value == TEXT_BASELINE_HANGING)
    {
        return Napi::String::New(env, "hanging");
    }
    else if (value == TEXT_BASELINE_ALPHABETIC)
    {
        return Napi::String::New(env, "alphabetic");
    }
    else if (value == TEXT_BASELINE_BOTTOM)
    {
        return Napi::String::New(env, "bottom");
    }
    else if (value == TEXT_BASELINE_IDEOGRAPHIC)
    {
        return Napi::String::New(env, "ideographic");
    }
}
RECORD_TIME_END
return Napi::String::New(env, "");
}

DEFINE_GETTER_METHOD(getCanvas)
RECORD_TIME_END
return mCanvas->mRef.Value();
}

Context2D::~Context2D()
{
    mRenderContext = nullptr;
}
} // namespace NodeBinding