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

#include "Font.h"
#include "SkData.h"
#include "SkFont.h"
#include "SkFontMetrics.h"
#include "SkFontMgr.h"
#include "SkRect.h"
#include "SkRefCnt.h"
#include "SkScalar.h"
#include "SkStream.h"
#include "SkTypeface.h"
#include "GraphicsJNI.h"
#include <nativehelper/ScopedUtfChars.h>
#include "Utils.h"
#include "FontUtils.h"

#include <hwui/MinikinSkia.h>
#include <hwui/Paint.h>
#include <hwui/Typeface.h>
#include <minikin/FontFamily.h>
#include <minikin/FontFileParser.h>
#include <minikin/LocaleList.h>
#include <minikin/SystemFonts.h>
#include <ui/FatVector.h>
#include <utils/TypefaceUtils.h>

#include <memory>

namespace android {

struct NativeFontBuilder {
    std::vector<minikin::FontVariation> axes;
};

static inline NativeFontBuilder* toBuilder(jlong ptr) {
    return reinterpret_cast<NativeFontBuilder*>(ptr);
}

static void releaseFont(jlong font) {
    delete reinterpret_cast<FontWrapper*>(font);
}

static void release_global_ref(const void* /*data*/, void* context) {
    JNIEnv* env = GraphicsJNI::getJNIEnv();
    bool needToAttach = (env == nullptr);
    if (needToAttach) {
        env = GraphicsJNI::attachJNIEnv("release_font_data");
        if (env == nullptr) {
            ALOGE("failed to attach to thread to release global ref.");
            return;
        }
    }

    jobject obj = reinterpret_cast<jobject>(context);
    env->DeleteGlobalRef(obj);
}

// Regular JNI
static jlong Font_Builder_initBuilder(JNIEnv*, jobject) {
    return reinterpret_cast<jlong>(new NativeFontBuilder());
}

// Critical Native
static void Font_Builder_addAxis(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr, jint tag, jfloat value) {
    toBuilder(builderPtr)->axes.emplace_back(static_cast<minikin::AxisTag>(tag), value);
}

// Regular JNI
static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jobject buffer,
                                jstring filePath, jstring langTags, jint weight, jboolean italic,
                                jint ttcIndex) {
    NPE_CHECK_RETURN_ZERO(env, buffer);
    std::unique_ptr<NativeFontBuilder> builder(toBuilder(builderPtr));
    const void* fontPtr = env->GetDirectBufferAddress(buffer);
    if (fontPtr == nullptr) {
        jniThrowException(env, "java/lang/IllegalArgumentException", "Not a direct buffer");
        return 0;
    }
    jlong fontSize = env->GetDirectBufferCapacity(buffer);
    if (fontSize <= 0) {
        jniThrowException(env, "java/lang/IllegalArgumentException",
                          "buffer size must not be zero or negative");
        return 0;
    }
    ScopedUtfChars fontPath(env, filePath);
    ScopedUtfChars langTagStr(env, langTags);
    jobject fontRef = MakeGlobalRefOrDie(env, buffer);
    sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize,
            release_global_ref, reinterpret_cast<void*>(fontRef)));
    std::shared_ptr<minikin::MinikinFont> minikinFont = fonts::createMinikinFontSkia(
        std::move(data), std::string_view(fontPath.c_str(), fontPath.size()),
        fontPtr, fontSize, ttcIndex, builder->axes);
    if (minikinFont == nullptr) {
        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
                             "Failed to create internal object. maybe invalid font data. filePath %s",
                             fontPath.c_str());
        return 0;
    }
    uint32_t localeListId = minikin::registerLocaleList(langTagStr.c_str());
    std::shared_ptr<minikin::Font> font =
            minikin::Font::Builder(minikinFont)
                    .setWeight(weight)
                    .setSlant(static_cast<minikin::FontStyle::Slant>(italic))
                    .setLocaleListId(localeListId)
                    .build();
    return reinterpret_cast<jlong>(new FontWrapper(std::move(font)));
}

// Fast Native
static jlong Font_Builder_clone(JNIEnv* env, jobject clazz, jlong fontPtr, jlong builderPtr,
                                jint weight, jboolean italic, jint ttcIndex) {
    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
    std::unique_ptr<NativeFontBuilder> builder(toBuilder(builderPtr));

    // Reconstruct SkTypeface with different arguments from existing SkTypeface.
    FatVector<SkFontArguments::VariationPosition::Coordinate, 2> skVariation;
    for (const auto& axis : builder->axes) {
        skVariation.push_back({axis.axisTag, axis.value});
    }
    SkFontArguments args;
    args.setCollectionIndex(ttcIndex);
    args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});

    sk_sp<SkTypeface> newTypeface = minikinSkia->GetSkTypeface()->makeClone(args);

    std::shared_ptr<minikin::MinikinFont> newMinikinFont = std::make_shared<MinikinFontSkia>(
            std::move(newTypeface), minikinSkia->GetSourceId(), minikinSkia->GetFontData(),
            minikinSkia->GetFontSize(), minikinSkia->getFilePath(), minikinSkia->GetFontIndex(),
            builder->axes);
    std::shared_ptr<minikin::Font> newFont = minikin::Font::Builder(newMinikinFont)
              .setWeight(weight)
              .setSlant(static_cast<minikin::FontStyle::Slant>(italic))
              .build();
    return reinterpret_cast<jlong>(new FontWrapper(std::move(newFont)));
}

///////////////////////////////////////////////////////////////////////////////
// Font JNI functions

// Fast Native
static jfloat Font_getGlyphBounds(JNIEnv* env, jobject, jlong fontHandle, jint glyphId,
                                  jlong paintHandle, jobject rect) {
    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
    Paint* paint = reinterpret_cast<Paint*>(paintHandle);

    SkFont* skFont = &paint->getSkFont();
    // We don't use populateSkFont since it is designed to be used for layout result with addressing
    // auto fake-bolding.
    skFont->setTypeface(minikinSkia->RefSkTypeface());

    uint16_t glyph16 = glyphId;
    SkRect skBounds;
    SkScalar skWidth;
    skFont->getWidthsBounds(&glyph16, 1, &skWidth, &skBounds, nullptr);
    GraphicsJNI::rect_to_jrectf(skBounds, env, rect);
    return SkScalarToFloat(skWidth);
}

// Fast Native
static jfloat Font_getFontMetrics(JNIEnv* env, jobject, jlong fontHandle, jlong paintHandle,
                                  jobject metricsObj) {
    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
    Paint* paint = reinterpret_cast<Paint*>(paintHandle);

    SkFont* skFont = &paint->getSkFont();
    // We don't use populateSkFont since it is designed to be used for layout result with addressing
    // auto fake-bolding.
    skFont->setTypeface(minikinSkia->RefSkTypeface());

    SkFontMetrics metrics;
    SkScalar spacing = skFont->getMetrics(&metrics);
    GraphicsJNI::set_metrics(env, metricsObj, metrics);
    return spacing;
}

// Critical Native
static jlong Font_getMinikinFontPtr(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
    return reinterpret_cast<jlong>(font->font.get());
}

// Critical Native
static jlong Font_cloneFont(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
    std::shared_ptr<minikin::Font> ref = font->font;
    return reinterpret_cast<jlong>(new FontWrapper(std::move(ref)));
}

// Fast Native
static jobject Font_newByteBuffer(JNIEnv* env, jobject, jlong fontPtr) {
    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
    const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
    return env->NewDirectByteBuffer(const_cast<void*>(minikinFont->GetFontData()),
                                    minikinFont->GetFontSize());
}

// Critical Native
static jlong Font_getBufferAddress(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
    return reinterpret_cast<jlong>(font->font->baseTypeface()->GetFontData());
}

// Critical Native
static jlong Font_getReleaseNativeFontFunc(CRITICAL_JNI_PARAMS) {
    return reinterpret_cast<jlong>(releaseFont);
}

// Fast Native
static jstring Font_getFontPath(JNIEnv* env, jobject, jlong fontPtr) {
    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
    minikin::BufferReader reader = font->font->typefaceMetadataReader();
    if (reader.current() != nullptr) {
        std::string path = std::string(reader.readString());
        if (path.empty()) {
            return nullptr;
        }
        return env->NewStringUTF(path.c_str());
    } else {
        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
        const std::string& path = minikinFont->GetFontPath();
        if (path.empty()) {
            return nullptr;
        }
        return env->NewStringUTF(path.c_str());
    }
}

// Fast Native
static jstring Font_getLocaleList(JNIEnv* env, jobject, jlong fontPtr) {
    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
    uint32_t localeListId = font->font->getLocaleListId();
    if (localeListId == 0) {
        return nullptr;
    }
    std::string langTags = minikin::getLocaleString(localeListId);
    if (langTags.empty()) {
        return nullptr;
    }
    return env->NewStringUTF(langTags.c_str());
}

// Critical Native
static jint Font_getPackedStyle(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
    uint32_t weight = font->font->style().weight();
    uint32_t isItalic = font->font->style().slant() == minikin::FontStyle::Slant::ITALIC ? 1 : 0;
    return (isItalic << 16) | weight;
}

// Critical Native
static jint Font_getIndex(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
    minikin::BufferReader reader = font->font->typefaceMetadataReader();
    if (reader.current() != nullptr) {
        reader.skipString();  // fontPath
        return reader.read<int>();
    } else {
        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
        return minikinFont->GetFontIndex();
    }
}

// Critical Native
static jint Font_getAxisCount(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
    minikin::BufferReader reader = font->font->typefaceMetadataReader();
    if (reader.current() != nullptr) {
        reader.skipString();  // fontPath
        reader.skip<int>();   // fontIndex
        return reader.readArray<minikin::FontVariation>().second;
    } else {
        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
        return minikinFont->GetAxes().size();
    }
}

// Critical Native
static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr, jint index) {
    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
    minikin::BufferReader reader = font->font->typefaceMetadataReader();
    minikin::FontVariation var;
    if (reader.current() != nullptr) {
        reader.skipString();  // fontPath
        reader.skip<int>();   // fontIndex
        var = reader.readArray<minikin::FontVariation>().first[index];
    } else {
        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
        var = minikinFont->GetAxes().at(index);
    }
    uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
    return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
}

// Critical Native
static jint Font_getSourceId(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
    return font->font->baseTypeface()->GetSourceId();
}

static jlongArray Font_getAvailableFontSet(JNIEnv* env, jobject) {
    std::vector<jlong> refArray;
    minikin::SystemFonts::getFontSet(
            [&refArray](const std::vector<std::shared_ptr<minikin::Font>>& fontSet) {
                refArray.reserve(fontSet.size());
                for (const auto& font : fontSet) {
                    std::shared_ptr<minikin::Font> fontRef = font;
                    refArray.push_back(
                            reinterpret_cast<jlong>(new FontWrapper(std::move(fontRef))));
                }
            });
    jlongArray r = env->NewLongArray(refArray.size());
    env->SetLongArrayRegion(r, 0, refArray.size(), refArray.data());
    return r;
}

// Fast Native
static jlong FontFileUtil_getFontRevision(JNIEnv* env, jobject, jobject buffer, jint index) {
    NPE_CHECK_RETURN_ZERO(env, buffer);
    const void* fontPtr = env->GetDirectBufferAddress(buffer);
    if (fontPtr == nullptr) {
        jniThrowException(env, "java/lang/IllegalArgumentException", "Not a direct buffer");
        return 0;
    }
    jlong fontSize = env->GetDirectBufferCapacity(buffer);
    if (fontSize <= 0) {
        jniThrowException(env, "java/lang/IllegalArgumentException",
                          "buffer size must not be zero or negative");
        return 0;
    }
    minikin::FontFileParser parser(fontPtr, fontSize, index);
    std::optional<uint32_t> revision = parser.getFontRevision();
    if (!revision.has_value()) {
        return -1L;
    }
    return revision.value();
}

static jstring FontFileUtil_getFontPostScriptName(JNIEnv* env, jobject, jobject buffer,
                                                  jint index) {
    NPE_CHECK_RETURN_ZERO(env, buffer);
    const void* fontPtr = env->GetDirectBufferAddress(buffer);
    if (fontPtr == nullptr) {
        jniThrowException(env, "java/lang/IllegalArgumentException", "Not a direct buffer");
        return nullptr;
    }
    jlong fontSize = env->GetDirectBufferCapacity(buffer);
    if (fontSize <= 0) {
        jniThrowException(env, "java/lang/IllegalArgumentException",
                          "buffer size must not be zero or negative");
        return nullptr;
    }
    minikin::FontFileParser parser(fontPtr, fontSize, index);
    std::optional<std::string> psName = parser.getPostScriptName();
    if (!psName.has_value()) {
        return nullptr;  // null
    }
    return env->NewStringUTF(psName->c_str());
}

static jint FontFileUtil_isPostScriptType1Font(JNIEnv* env, jobject, jobject buffer, jint index) {
    NPE_CHECK_RETURN_ZERO(env, buffer);
    const void* fontPtr = env->GetDirectBufferAddress(buffer);
    if (fontPtr == nullptr) {
        jniThrowException(env, "java/lang/IllegalArgumentException", "Not a direct buffer");
        return -1;
    }
    jlong fontSize = env->GetDirectBufferCapacity(buffer);
    if (fontSize <= 0) {
        jniThrowException(env, "java/lang/IllegalArgumentException",
                          "buffer size must not be zero or negative");
        return -1;
    }
    minikin::FontFileParser parser(fontPtr, fontSize, index);
    std::optional<bool> isType1 = parser.isPostScriptType1Font();
    if (!isType1.has_value()) {
        return -1;  // not an OpenType font. HarfBuzz failed to parse it.
    }
    return isType1.value();
}

///////////////////////////////////////////////////////////////////////////////

static const JNINativeMethod gFontBuilderMethods[] = {
        {"nInitBuilder", "()J", (void*)Font_Builder_initBuilder},
        {"nAddAxis", "(JIF)V", (void*)Font_Builder_addAxis},
        {"nBuild", "(JLjava/nio/ByteBuffer;Ljava/lang/String;Ljava/lang/String;IZI)J",
         (void*)Font_Builder_build},
        {"nClone", "(JJIZI)J", (void*)Font_Builder_clone},
};

static const JNINativeMethod gFontMethods[] = {
        {"nGetMinikinFontPtr", "(J)J", (void*)Font_getMinikinFontPtr},
        {"nCloneFont", "(J)J", (void*)Font_cloneFont},
        {"nNewByteBuffer", "(J)Ljava/nio/ByteBuffer;", (void*)Font_newByteBuffer},
        {"nGetBufferAddress", "(J)J", (void*)Font_getBufferAddress},
        {"nGetReleaseNativeFont", "()J", (void*)Font_getReleaseNativeFontFunc},
        {"nGetGlyphBounds", "(JIJLandroid/graphics/RectF;)F", (void*)Font_getGlyphBounds},
        {"nGetFontMetrics", "(JJLandroid/graphics/Paint$FontMetrics;)F",
         (void*)Font_getFontMetrics},
        {"nGetFontPath", "(J)Ljava/lang/String;", (void*)Font_getFontPath},
        {"nGetLocaleList", "(J)Ljava/lang/String;", (void*)Font_getLocaleList},
        {"nGetPackedStyle", "(J)I", (void*)Font_getPackedStyle},
        {"nGetIndex", "(J)I", (void*)Font_getIndex},
        {"nGetAxisCount", "(J)I", (void*)Font_getAxisCount},
        {"nGetAxisInfo", "(JI)J", (void*)Font_getAxisInfo},
        {"nGetSourceId", "(J)I", (void*)Font_getSourceId},

        // System font accessors
        {"nGetAvailableFontSet", "()[J", (void*)Font_getAvailableFontSet},
};

static const JNINativeMethod gFontFileUtilMethods[] = {
    { "nGetFontRevision", "(Ljava/nio/ByteBuffer;I)J", (void*) FontFileUtil_getFontRevision },
    { "nGetFontPostScriptName", "(Ljava/nio/ByteBuffer;I)Ljava/lang/String;",
        (void*) FontFileUtil_getFontPostScriptName },
    { "nIsPostScriptType1Font", "(Ljava/nio/ByteBuffer;I)I",
        (void*) FontFileUtil_isPostScriptType1Font },
};

int register_android_graphics_fonts_Font(JNIEnv* env) {
    return RegisterMethodsOrDie(env, "android/graphics/fonts/Font$Builder", gFontBuilderMethods,
            NELEM(gFontBuilderMethods)) +
            RegisterMethodsOrDie(env, "android/graphics/fonts/Font", gFontMethods,
            NELEM(gFontMethods)) +
            RegisterMethodsOrDie(env, "android/graphics/fonts/FontFileUtil", gFontFileUtilMethods,
            NELEM(gFontFileUtilMethods));
}

namespace fonts {

std::shared_ptr<minikin::MinikinFont> createMinikinFontSkia(
        sk_sp<SkData>&& data, std::string_view fontPath, const void *fontPtr, size_t fontSize,
        int ttcIndex, const std::vector<minikin::FontVariation>& axes) {
    FatVector<SkFontArguments::VariationPosition::Coordinate, 2> skVariation;
    for (const auto& axis : axes) {
        skVariation.push_back({axis.axisTag, axis.value});
    }

    std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));

    SkFontArguments args;
    args.setCollectionIndex(ttcIndex);
    args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});

    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
    sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), args));
    if (face == nullptr) {
        return nullptr;
    }
    return std::make_shared<MinikinFontSkia>(std::move(face), getNewSourceId(), fontPtr, fontSize,
                                             fontPath, ttcIndex, axes);
}

int getNewSourceId() {
    static std::atomic<int> sSourceId = {0};
    return sSourceId++;
}

}  // namespace fonts

}  // namespace android
