/*
 * Copyright 2021 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkPaint.h"
#include "include/core/SkRRect.h"
#include "include/core/SkShader.h"
#include "modules/jetski/src/Utils.h"

#include <jni.h>

namespace {

jint Canvas_GetWidth(JNIEnv* env, jobject, jlong native_instance) {
    const auto* canvas = reinterpret_cast<const SkCanvas*>(native_instance);
    return canvas ? canvas->imageInfo().width() : 0;
}

jint Canvas_GetHeight(JNIEnv* env, jobject, jlong native_instance) {
    const auto* canvas = reinterpret_cast<const SkCanvas*>(native_instance);
    return canvas ? canvas->imageInfo().height() : 0;
}

jint Canvas_Save(JNIEnv* env, jobject, jlong native_instance) {
    if (auto* canvas = reinterpret_cast<SkCanvas*>(native_instance)) {
        return canvas->save();
    }
    return 0;
}

void Canvas_Restore(JNIEnv* env, jobject, jlong native_instance) {
    if (auto* canvas = reinterpret_cast<SkCanvas*>(native_instance)) {
        canvas->restore();
    }
}

void Canvas_RestoreToCount(JNIEnv* env, jobject, jlong native_instance, jint count) {
    if (auto* canvas = reinterpret_cast<SkCanvas*>(native_instance)) {
        canvas->restoreToCount(count);
    }
}

jint Canvas_SaveLayer(JNIEnv* env, jobject, jlong native_instance, jlong native_paint) {
    auto* paint = reinterpret_cast<SkPaint* >(native_paint);
    if (auto* canvas = reinterpret_cast<SkCanvas*>(native_instance)) {
        return canvas->saveLayer(nullptr, paint);
    }
    return 0;
}

jlong Canvas_LocalToDevice(JNIEnv* env, jobject, jlong native_instance) {
    if (auto* canvas = reinterpret_cast<SkCanvas*>(native_instance)) {
        SkM44* m = new SkM44(canvas->getLocalToDevice());
        return reinterpret_cast<jlong>(m);
    }
    return 0;
}

void Canvas_Concat(JNIEnv* env, jobject, jlong native_instance, jlong native_matrix) {
    auto* canvas = reinterpret_cast<SkCanvas*>(native_instance);
    auto* matrix = reinterpret_cast<SkM44*   >(native_matrix);

    if (canvas && matrix) {
        canvas->concat(*matrix);
    }
}

void Canvas_Concat16f(JNIEnv* env, jobject, jlong native_instance, jfloatArray jmatrix) {
    SkASSERT(env->GetArrayLength(jmatrix) == 16);

    if (auto* canvas = reinterpret_cast<SkCanvas*>(native_instance)) {
        auto* m = env->GetFloatArrayElements(jmatrix, nullptr);
        canvas->concat(SkM44::RowMajor(m));
        env->ReleaseFloatArrayElements(jmatrix, m, 0);
    }
}

void Canvas_Translate(JNIEnv* env, jobject, jlong native_instance,
                      jfloat tx, jfloat ty, jfloat tz) {
    if (auto* canvas = reinterpret_cast<SkCanvas*>(native_instance)) {
        canvas->concat(SkM44::Translate(tx, ty, tz));
    }
}

void Canvas_Scale(JNIEnv* env, jobject, jlong native_instance, jfloat sx, jfloat sy, jfloat sz) {
    if (auto* canvas = reinterpret_cast<SkCanvas*>(native_instance)) {
        canvas->concat(SkM44::Scale(sx, sy, sz));
    }
}

void Canvas_ClipPath(JNIEnv* env, jobject, jlong native_instance, jlong native_path,
                                           jint native_clipOp, jboolean doAA) {
    if (auto* canvas = reinterpret_cast<SkCanvas*>(native_instance)) {
        if (auto* path = reinterpret_cast<SkPath*>(native_path)) {
            canvas->clipPath(*path, static_cast<SkClipOp>(native_clipOp), doAA);
        }
    }
}

void Canvas_ClipRect(JNIEnv* env, jobject, jlong native_instance, jfloat l, jfloat t, jfloat r, jfloat b,
                                           jint native_clipOp, jboolean doAA) {
    if (auto* canvas = reinterpret_cast<SkCanvas*>(native_instance)) {
        canvas->clipRect(SkRect::MakeLTRB(l, t, r, b), static_cast<SkClipOp>(native_clipOp), doAA);
    }
}

void Canvas_ClipRRect(JNIEnv* env, jobject, jlong native_instance, jfloat l, jfloat t, jfloat r, jfloat b,
                                                                   jfloat xRad, jfloat yRad,
                                                                   jint native_clipOp, jboolean doAA) {
    if (auto* canvas = reinterpret_cast<SkCanvas*>(native_instance)) {
        canvas->clipRRect(SkRRect::MakeRectXY(SkRect::MakeLTRB(l, t, r, b), xRad, yRad),
                          static_cast<SkClipOp>(native_clipOp), doAA);
    }
}

void Canvas_ClipShader(JNIEnv* env, jobject, jlong native_instance, jlong native_shader, jint native_clipOp) {
    if (auto* canvas = reinterpret_cast<SkCanvas*>(native_instance)) {
        if (auto* shader = reinterpret_cast<SkShader*>(native_shader)) {
            canvas->clipShader(sk_ref_sp(shader), static_cast<SkClipOp>(native_clipOp));
        }
    }
}

void Canvas_DrawColor(JNIEnv* env, jobject, jlong native_instance,
                      float r, float g, float b, float a) {
    if (auto* canvas = reinterpret_cast<SkCanvas*>(native_instance)) {
        canvas->drawColor(SkColor4f{r, g, b, a});
    }
}

void Canvas_DrawRect(JNIEnv* env, jobject, jlong native_instance,
                     jfloat left, jfloat top, jfloat right, jfloat bottom,
                     jlong native_paint) {
    auto* canvas = reinterpret_cast<SkCanvas*>(native_instance);
    auto* paint  = reinterpret_cast<SkPaint* >(native_paint);
    if (canvas && paint) {
        canvas->drawRect(SkRect::MakeLTRB(left, top, right, bottom), *paint);
    }
}

void Canvas_DrawImage(JNIEnv* env, jobject, jlong native_instance, jlong native_image,
                      jfloat x, jfloat y,
                      jint sampling_desc, jfloat sampling_b, jfloat sampling_c) {
    auto* canvas = reinterpret_cast<SkCanvas*>(native_instance);
    auto*  image = reinterpret_cast<SkImage *>(native_image);

    if (canvas && image) {
        canvas->drawImage(image, x, y,
            jetski::utils::SamplingOptions(sampling_desc, sampling_b, sampling_c));
    }
}

void Canvas_DrawPath(JNIEnv* env, jobject, jlong native_instance, jlong native_path,
                     jlong native_paint) {
    auto* canvas = reinterpret_cast<SkCanvas*>(native_instance);
    auto* path  = reinterpret_cast<SkPath* >(native_path);
    auto* paint  = reinterpret_cast<SkPaint* >(native_paint);
    if (canvas && paint && path) {
        canvas->drawPath(*path, *paint);
    }
}

// jPos: a composite array in the form of [x1, y1, x2, y2, ... ,xn, yn]
// callers of this function check should throw IllegalArgumentException in Java
void Canvas_DrawGlyphs(JNIEnv* env, jobject, jlong native_instance, jcharArray jglyphs,
                                             jfloatArray jPos, jfloat xOrigin, jfloat yOrigin,
                                             jlong native_font, jlong native_paint) {
    auto* canvas = reinterpret_cast<SkCanvas*>(native_instance);
    auto* font = reinterpret_cast<SkFont*>(native_font);
    auto* paint  = reinterpret_cast<SkPaint* >(native_paint);
    if (canvas && font && paint) {
        int count = env->GetArrayLength(jglyphs);
        auto* compositePositions = env->GetFloatArrayElements(jPos, nullptr);
        auto* positions = reinterpret_cast<SkPoint*>(compositePositions);
        auto* glyphs = env->GetCharArrayElements(jglyphs, nullptr);
        canvas->drawGlyphs(count, glyphs, positions, {xOrigin, yOrigin}, *font, *paint);

        env->ReleaseCharArrayElements(jglyphs, glyphs, 0);
        env->ReleaseFloatArrayElements(jPos, compositePositions, 0);
    }
}

}  // namespace

int register_jetski_Canvas(JNIEnv* env) {
    static const JNINativeMethod methods[] = {
        {"nGetWidth"        , "(J)I"           , reinterpret_cast<void*>(Canvas_GetWidth)      },
        {"nGetHeight"       , "(J)I"           , reinterpret_cast<void*>(Canvas_GetHeight)     },
        {"nSave"            , "(J)I"           , reinterpret_cast<void*>(Canvas_Save)          },
        {"nSaveLayer"       , "(JJ)I"          , reinterpret_cast<void*>(Canvas_SaveLayer)     },
        {"nRestore"         , "(J)V"           , reinterpret_cast<void*>(Canvas_Restore)       },
        {"nRestoreToCount"  , "(JI)V"          , reinterpret_cast<void*>(Canvas_RestoreToCount)},
        {"nGetLocalToDevice", "(J)J"           , reinterpret_cast<void*>(Canvas_LocalToDevice) },
        {"nConcat"          , "(JJ)V"          , reinterpret_cast<void*>(Canvas_Concat)        },
        {"nConcat16f"       , "(J[F)V"         , reinterpret_cast<void*>(Canvas_Concat16f)     },
        {"nTranslate"       , "(JFFF)V"        , reinterpret_cast<void*>(Canvas_Translate)     },
        {"nScale"           , "(JFFF)V"        , reinterpret_cast<void*>(Canvas_Scale)         },
        {"nClipPath"        , "(JJIZ)V"        , reinterpret_cast<void*>(Canvas_ClipPath)      },
        {"nClipRect"        , "(JFFFFIZ)V"     , reinterpret_cast<void*>(Canvas_ClipRect)      },
        {"nClipRRect"       , "(JFFFFFFIZ)V"   , reinterpret_cast<void*>(Canvas_ClipRRect)     },
        {"nClipShader"      , "(JJI)V"         , reinterpret_cast<void*>(Canvas_ClipShader)    },
        {"nDrawColor"       , "(JFFFF)V"       , reinterpret_cast<void*>(Canvas_DrawColor)     },
        {"nDrawRect"        , "(JFFFFJ)V"      , reinterpret_cast<void*>(Canvas_DrawRect)      },
        {"nDrawImage"       , "(JJFFIFF)V"     , reinterpret_cast<void*>(Canvas_DrawImage)     },
        {"nDrawPath"        , "(JJJ)V"         , reinterpret_cast<void*>(Canvas_DrawPath)      },
        {"nDrawGlyphs"      , "(J[C[FFFJJ)V", reinterpret_cast<void*>(Canvas_DrawGlyphs)    },

    };

    const auto clazz = env->FindClass("org/skia/jetski/Canvas");
    return clazz
        ? env->RegisterNatives(clazz, methods, std::size(methods))
        : JNI_ERR;
}
