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

#include "shape.h"

namespace OHOS::Ace {
namespace {
const char* ANI_SHAPE_NAME = "@ohos.arkui.shape.RectShape";
void ParseWidthAndHeight(ani_env* env, ani_object object, OHOS::Ace::RefPtr<OHOS::Ace::ShapeRect>& rectShape)
{
    std::optional<OHOS::Ace::CalcDimension> widthOpt;
    std::optional<OHOS::Ace::CalcDimension> heightOpt;
    ani_ref width_ref;
    ani_ref height_ref;
    if (env->Object_GetPropertyByName_Ref(object, "width", &width_ref) != ANI_OK) {
        return;
    }
    ParseStringNumberUndefinedObject(env, width_ref, OHOS::Ace::DimensionUnit::VP, widthOpt);
    if (widthOpt.has_value() && widthOpt->IsValid()) {
        rectShape->SetWidth(widthOpt.value());
    }
    if (env->Object_GetPropertyByName_Ref(object, "height", &height_ref) != ANI_OK) {
        return;
    }
    ParseStringNumberUndefinedObject(env, height_ref, OHOS::Ace::DimensionUnit::VP, heightOpt);
    if (heightOpt.has_value() && heightOpt->IsValid()) {
        rectShape->SetHeight(heightOpt.value());
    }
}
}

void ANICreateRectShapeWithParam(
    ani_env* env, [[maybe_unused]] ani_object object, [[maybe_unused]] ani_object aniOption)
{
    if (GetIsUndefinedObject(env, aniOption)) {
        return;
    }

    RectPeer* shapePeer = new RectPeer();
    auto rect = AceType::MakeRefPtr<ShapeRect>();
    ani_status status;

    if (IsInstanceOfCls(env, aniOption, "@ohos.arkui.shape.RectShapeOptions")) {
        ani_ref params_ref;
        if ((status = env->Object_GetPropertyByName_Ref(aniOption, "radius", &params_ref)) != ANI_OK) {
            LOGE("Get radius failed, status:%{public}d", status);
            delete shapePeer;
            return;
        }
        if (!GetIsUndefinedObject(env, params_ref) && GetIsArrayObject(env, params_ref)) {
            OHOS::Ace::CalcDimension radius;
            ParseArray(env, rect, static_cast<ani_object>(params_ref));
        } else {
            std::optional<OHOS::Ace::CalcDimension> radiusOpt;
            if (ParseStringNumberUndefinedObject(env, params_ref, OHOS::Ace::DimensionUnit::VP, radiusOpt) &&
                radiusOpt.has_value()) {
                rect->SetRadiusWidth(radiusOpt.value());
                rect->SetRadiusHeight(radiusOpt.value());
            }
        }
    } else if (IsInstanceOfCls(env, aniOption, "@ohos.arkui.shape.RoundRectShapeOptions")) {
        std::optional<OHOS::Ace::CalcDimension> radiusWidthOpt;
        if (ParseStringNumberUndefinedOption(
            env, aniOption, radiusWidthOpt, "radiusWidth", "@ohos.arkui.shape.RoundRectShapeOptions") &&
            radiusWidthOpt.has_value()) {
            rect->SetRadiusWidth(radiusWidthOpt.value());
        }
        std::optional<OHOS::Ace::CalcDimension> radiusHeightOpt;
        if (ParseStringNumberUndefinedOption(
            env, aniOption, radiusHeightOpt, "radiusHeight", "@ohos.arkui.shape.RoundRectShapeOptions") &&
            radiusHeightOpt.has_value()) {
            rect->SetRadiusHeight(radiusHeightOpt.value());
        }
    }
    ParseWidthAndHeight(env, aniOption, rect);
    shapePeer->rectShape = rect;
    if ((status = env->Object_SetPropertyByName_Long(
             object, "basicShapeResult", reinterpret_cast<ani_long>(shapePeer))) != ANI_OK) {
        LOGE("RectShape set addr failed, status:%{public}d", status);
        delete shapePeer;
        return;
    }
}

RectPeer* GetRectShape(ani_env* env, ani_object obj)
{
    ani_long rectAni;
    if (ANI_OK != env->Object_GetFieldByName_Long(obj, "basicShapeResult", &rectAni)) {
        return nullptr;
    }

    auto rect = reinterpret_cast<RectPeer*>(rectAni);
    return rect;
}

ani_object ANIRectShapeWidth(ani_env* env, [[maybe_unused]] ani_object object, [[maybe_unused]] ani_object aniOption)
{
    if (GetIsUndefinedObject(env, aniOption)) {
        return object;
    }
    ani_class cls;
    if (ANI_OK != env->FindClass(ANI_SHAPE_NAME, &cls)) {
        return object;
    }
    RectPeer* rectObj = GetRectShape(env, object);
    CHECK_NULL_RETURN(rectObj, object);
    CalcDimension width;
    if (!ParseLength(env, object, aniOption, width)) {
        return object;
    }
    if (rectObj->rectShape) {
        rectObj->rectShape->SetWidth(width);
    }
    return object;
}

ani_object ANIRectShapeHeight(ani_env* env, [[maybe_unused]] ani_object object, [[maybe_unused]] ani_object aniOption)
{
    if (GetIsUndefinedObject(env, aniOption)) {
        return object;
    }
    ani_class cls;
    if (ANI_OK != env->FindClass(ANI_SHAPE_NAME, &cls)) {
        return object;
    }
    RectPeer* rectObj = GetRectShape(env, object);
    CHECK_NULL_RETURN(rectObj, object);
    CalcDimension height;
    if (!ParseLength(env, object, aniOption, height)) {
        return object;
    }
    if (rectObj->rectShape) {
        rectObj->rectShape->SetHeight(height);
    }
    return object;
}

ani_object ANIRectShapeSize(ani_env* env, [[maybe_unused]] ani_object object, [[maybe_unused]] ani_object aniOption)
{
    if (GetIsUndefinedObject(env, aniOption)) {
        return object;
    }
    RectPeer* rectObj = GetRectShape(env, object);
    if (!rectObj) {
        return object;
    }
    CalcDimension width;
    ParseOption(env, aniOption, width, "width", "arkui.component.units.SizeOptions");
    if (rectObj->rectShape) {
        rectObj->rectShape->SetWidth(width);
    }
    CalcDimension height;
    ParseOption(env, aniOption, height, "height", "arkui.component.units.SizeOptions");
    if (rectObj->rectShape) {
        rectObj->rectShape->SetHeight(height);
    }
    return object;
}

ani_object ANIRectShapePosition(ani_env* env, [[maybe_unused]] ani_object object, [[maybe_unused]] ani_object aniOption)
{
    if (GetIsUndefinedObject(env, aniOption)) {
        return object;
    }
    RectPeer* rectObj = GetRectShape(env, object);
    if (!rectObj) {
        return object;
    }
    CalcDimension dx;
    ParseOption(env, aniOption, dx, "x", "arkui.component.units.Position");
    CalcDimension dy;
    ParseOption(env, aniOption, dy, "y", "arkui.component.units.Position");
    DimensionOffset position(dx, dy);
    if (rectObj->rectShape) {
        rectObj->rectShape->SetPosition(position);
    }
    return object;
}

ani_object ANIRectShapeOffset(ani_env* env, [[maybe_unused]] ani_object object, [[maybe_unused]] ani_object aniOption)
{
    if (GetIsUndefinedObject(env, aniOption)) {
        return object;
    }
    RectPeer* rectObj = GetRectShape(env, object);
    if (!rectObj) {
        return object;
    }
    CalcDimension dx;
    ParseOption(env, aniOption, dx, "x", "arkui.component.units.Position");
    CalcDimension dy;
    ParseOption(env, aniOption, dy, "y", "arkui.component.units.Position");
    DimensionOffset position(dx, dy);
    if (rectObj->rectShape) {
        rectObj->rectShape->SetOffset(position);
    }
    return object;
}

ani_object ANIRectShapeColor(ani_env* env, ani_object object, [[maybe_unused]] ani_object aniOption)
{
    if (GetIsUndefinedObject(env, aniOption)) {
        return object;
    }
    OHOS::Ace::Color resourceColor;
    if (!ParseAniColor(env, aniOption, resourceColor)) {
        return object;
    }
    RectPeer* rectObj = GetRectShape(env, object);
    if (!rectObj) {
        return object;
    }
    if (rectObj->rectShape) {
        rectObj->rectShape->SetColor(resourceColor);
    }
    return object;
}

ani_object ANIRectShapeRadiusWidth(ani_env* env, ani_object object, [[maybe_unused]] ani_object aniOption)
{
    if (GetIsUndefinedObject(env, aniOption)) {
        return object;
    }
    RectPeer* rectObj = GetRectShape(env, object);
    if (!rectObj) {
        return object;
    }
    std::optional<OHOS::Ace::CalcDimension> radiusVal;
    if (!ParseStringNumberUndefinedObject(
        env, static_cast<ani_ref>(aniOption), OHOS::Ace::DimensionUnit::VP, radiusVal)) {
        return object;
    }
    if (rectObj->rectShape) {
        rectObj->rectShape->SetRadiusWidth(radiusVal.value_or(OHOS::Ace::CalcDimension()));
    }
    return object;
}

ani_object ANIRectShapeRadiusHeight(ani_env* env, ani_object object, [[maybe_unused]] ani_object aniOption)
{
    if (GetIsUndefinedObject(env, aniOption)) {
        return object;
    }
    RectPeer* rectObj = GetRectShape(env, object);
    if (!rectObj) {
        return object;
    }
    std::optional<OHOS::Ace::CalcDimension> radiusVal;
    if (!ParseStringNumberUndefinedObject(
        env, static_cast<ani_ref>(aniOption), OHOS::Ace::DimensionUnit::VP, radiusVal)) {
        return object;
    }
    if (rectObj->rectShape) {
        rectObj->rectShape->SetRadiusHeight(radiusVal.value_or(OHOS::Ace::CalcDimension()));
    }
    return object;
}

ani_object ANIRectShapeRadius(ani_env* env, ani_object object, [[maybe_unused]] ani_object aniOption)
{
    if (GetIsUndefinedObject(env, aniOption)) {
        return object;
    }
    RectPeer* rectObj = GetRectShape(env, object);
    if (!rectObj) {
        return object;
    }
    if (!rectObj->rectShape) {
        return object;
    }
    if (GetIsArrayObject(env, static_cast<ani_ref>(aniOption))) {
        OHOS::Ace::CalcDimension radius;
        ParseArray(env, rectObj->rectShape, aniOption);
    } else {
        std::optional<OHOS::Ace::CalcDimension> radiusVal;
        ParseStringNumberUndefinedObject(env, static_cast<ani_ref>(aniOption), OHOS::Ace::DimensionUnit::VP, radiusVal);
        auto radius = radiusVal.value_or(OHOS::Ace::CalcDimension());
        rectObj->rectShape->SetRadiusWidth(radius);
        rectObj->rectShape->SetRadiusHeight(radius);
    }
    return object;
}

ani_object RectShape::ANIRectShapeFromPtr(ani_env* env, [[maybe_unused]] ani_object aniClass, ani_long ptr)
{
    return ANIShapeFromPtr<RectPeer>(env, ptr, ANI_SHAPE_NAME, "basicShapeResult");
}

ani_status RectShape::BindRectShape(ani_env* env)
{
    ani_class cls;
    if (ANI_OK != env->FindClass(ANI_SHAPE_NAME, &cls)) {
        return ANI_ERROR;
    }

    std::array methods = {
        ani_native_function { "<ctor>",
            "X{C{@ohos.arkui.shape.RectShapeOptions}C{@ohos.arkui.shape.RoundRectShapeOptions}}:",
            reinterpret_cast<void*>(ANICreateRectShapeWithParam) },
        ani_native_function { "width", nullptr, reinterpret_cast<void*>(ANIRectShapeWidth) },
        ani_native_function { "radiusWidth", nullptr, reinterpret_cast<void*>(ANIRectShapeRadiusWidth) },
        ani_native_function { "radiusHeight", nullptr, reinterpret_cast<void*>(ANIRectShapeRadiusHeight) },
        ani_native_function { "radius", nullptr, reinterpret_cast<void*>(ANIRectShapeRadius) },
        ani_native_function { "height", nullptr, reinterpret_cast<void*>(ANIRectShapeHeight) },
        ani_native_function { "size", nullptr, reinterpret_cast<void*>(ANIRectShapeSize) },
        ani_native_function { "position", nullptr, reinterpret_cast<void*>(ANIRectShapePosition) },
        ani_native_function { "offset", nullptr, reinterpret_cast<void*>(ANIRectShapeOffset) },
        ani_native_function { "fill", nullptr, reinterpret_cast<void*>(ANIRectShapeColor) },
    };
    ani_status tmp = env->Class_BindNativeMethods(cls, methods.data(), methods.size());
    if (ANI_OK != tmp) {
        LOGW("bind Rect methods error, status:%{public}d", tmp);
        return ANI_ERROR;
    }
    return ANI_OK;
}
} // namespace OHOS::Ace