/*
 * 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 "ge_log.h"
#include "ge_border_light_shader.h"
#include "ge_shader_diagnostics.h"
#include "ge_visual_effect_impl.h"

namespace OHOS {
namespace Rosen {

constexpr size_t NUM_0 = 0;
constexpr size_t NUM_1 = 1;
constexpr size_t NUM_2 = 2;
constexpr size_t NUM_3 = 3;
constexpr size_t NUM_4 = 4;
constexpr float MIN_LIGHT_POSITION = -10.0f;
constexpr float MAX_LIGHT_POSITION = 10.0f;
constexpr float MAX_LIGHT_WIDTH = 30.0f;

GEBorderLightShader::GEBorderLightShader() {}

GEBorderLightShader::GEBorderLightShader(Drawing::GEBorderLightShaderParams& param)
{
    borderLightParams_ = param;
}

void GEBorderLightShader::MakeDrawingShader(const Drawing::Rect& rect, float progress)
{
    drShader_ = MakeBorderLightShader(rect);
}

float GEBorderLightShader::ClampValue(float x, float minValue, float maxValue)
{
    return std::min(std::max(x, minValue), maxValue);
}

std::shared_ptr<Drawing::RuntimeShaderBuilder> GEBorderLightShader::GetBorderLightBuilder()
{
    thread_local std::shared_ptr<Drawing::RuntimeEffect> borderLightShaderEffect_ = nullptr;

    if (borderLightShaderEffect_ == nullptr) {
        static constexpr char prog[] = R"(
            uniform vec2 iResolution;
            uniform vec3 lightPosition;
            uniform half4 lightColor;
            uniform half lightIntensity;
            uniform float lightWidth;
            uniform half3 borderLightRotationAngle;
            uniform float cornerRadius;

            half sdRoundedBox(half2 p, half2 b, half r)
            {
                half2 q = abs(p) - b + r;
                return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
            }

            half2 sdRoundedBoxGradient(half2 p, half2 b, half r)
            {
                half2 signs = half2(p.x >= 0.0 ? 1.0 : -1.0, p.y >= 0.0 ? 1.0 : -1.0);
                half2 q = abs(p) - b + r;
                half2 nor = (q.y > q.x) ? half2(0.0, 1.0) : half2(1.0, 0.0);
                nor = (q.x > 0.0 && q.y > 0.0) ? normalize(q) : nor;
                return signs * nor;
            }

            half4 shinningEffect(in half3 fragPos, in half3 normal, in half3 lightPos, in half3 viewPos,
                in half4 specularColor, in half shinning)
            {
                half3 lightDir = normalize(lightPos - fragPos);
                half3 viewDir = normalize(viewPos - fragPos);
                half3 halfwayDir = normalize(lightDir + viewDir);
                
                // Blinn-Phong
                half4 specularC = specularColor * pow(max(dot(normal, halfwayDir), 0.), shinning);

                return specularC;
            }

            half4 createBoundNormal(half2 pos) // pos in [-w, w] x [-1, 1]
            {
                half2 halfWH = half2(iResolution.x, iResolution.y) / iResolution.y;
                half r = min(cornerRadius / iResolution.y * 2.0, min(halfWH.x, halfWH.y));
                half dist = sdRoundedBox(pos, halfWH, r);
                half thickness = min(lightWidth, cornerRadius) / iResolution.y;
                if (dist >= 0.0 || dist <= -thickness) {
                    return half4(0.0, 0.0, 1.0, -1.0);
                }

                half R = thickness * 9.0;  // 9.0 is adjustable parameters
                half sinTheta = abs(dist) / R;

                half normalZ = sqrt(1.0 - sinTheta * sinTheta);
                half2 normalXY = sdRoundedBoxGradient(pos, halfWH, r) * sinTheta;

                return half4(normalXY, normalZ, dist / thickness + 1.0);
            }

            half4 RoundedBoxShinning(half2 uv, half4 specularColor, half shinning, half3 lightPos,
                half3 viewPos, mat3 rotM)
            {
                half3 fragPos = half3(uv, 0.0);
                half4 normal = createBoundNormal(uv);
                if (normal.w < 0.0) {
                    return half4(0.0);
                }
                half3 fragNormal = rotM * normal.xyz;

                half4 shinningColor = shinningEffect(fragPos, fragNormal, lightPos, viewPos, specularColor, shinning);
                shinningColor *= smoothstep(0.0, 0.1, normal.w); // edge smooth
                return shinningColor;
            }

            mat3 GetRotationMatrix(half3 rotAngle)
            {
                rotAngle *= 3.1415926 / 180.0;
                mat3 Rx = mat3 (1.0, 0.0, 0.0,
                                0.0, cos(rotAngle.x), -sin(rotAngle.x),
                                0.0, sin(rotAngle.x), cos(rotAngle.x));
                mat3 Ry = mat3 (cos(rotAngle.y), 0.0, sin(rotAngle.y),
                                0.0, 1.0, 0.0,
                                -sin(rotAngle.y), 0.0, cos(rotAngle.y));
                mat3 Rz = mat3 (cos(rotAngle.z), -sin(rotAngle.z), 0.0,
                                sin(rotAngle.z), cos(rotAngle.z), 0.0,
                                0.0, 0.0, 1.0);
                return Rz * Ry * Rx;
            }

            half4 main(vec2 fragCoord)
            {
                vec2 uv = fragCoord / iResolution.xy;
                uv = uv + uv - 1.0;
                float screenRatio = iResolution.x / iResolution.y;
                uv.x *= screenRatio;

                half4 shinningColor = half4(0.0);
                vec3 lightPos = vec3(lightPosition.x * screenRatio, lightPosition.y, lightPosition.z);

                if (dot(borderLightRotationAngle, borderLightRotationAngle) < 0.01) {
                    float r = min(cornerRadius * 2.0, min(iResolution.x, iResolution.y));
                    vec2 q = abs(fragCoord + fragCoord - iResolution.xy + vec2(1.0)) - iResolution.xy + r;
                    float dist = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
                    float normalizedDist = dist / max(lightWidth, 0.0001) + 1.0;
                    const float thres = 0.2;
                    shinningColor = (normalizedDist > -thres)
                        ? lightColor * pow(max(normalize(lightPos - vec3(uv, 0.0)).z, 0.0), 16.0) : half4(0.0);
                    shinningColor *= smoothstep(-thres, thres, normalizedDist); // edge smooth
                } else {
                    mat3 rotM = GetRotationMatrix(borderLightRotationAngle);
                    half4 specularColor = lightColor;
                    half shinning = 16.0;
                    half3 viewPos = lightPos;
                    shinningColor = RoundedBoxShinning(uv, specularColor, shinning, lightPos, viewPos, rotM);
                }

                return half4(shinningColor.xyz * lightColor.w, shinningColor.w) * clamp(lightIntensity, 0.0, 1.0);
            }
        )";
        borderLightShaderEffect_ = GECreateRuntimeEffectForShader(prog);
    }

    if (borderLightShaderEffect_ == nullptr) {
        GE_LOGE("GEBorderLightShader::GetBorderLightBuilder borderLightShaderEffect_ is nullptr.");
        return nullptr;
    }
    return std::make_shared<Drawing::RuntimeShaderBuilder>(borderLightShaderEffect_);
}

void GEBorderLightShader::ClampLightValue()
{
    for (size_t idx = 0; idx < NUM_3; idx ++) {
        borderLightParams_.position[idx] =
            ClampValue(borderLightParams_.position[idx], MIN_LIGHT_POSITION, MAX_LIGHT_POSITION);
    }
    for (size_t idx = 0; idx < NUM_4; idx ++) {
        borderLightParams_.color[idx] = ClampValue(borderLightParams_.color[idx], 0.0f, 1.0f);
    }
    borderLightParams_.intensity = ClampValue(borderLightParams_.intensity, 0.0f, 1.0f);
    borderLightParams_.width = ClampValue(borderLightParams_.width, 0.0f, MAX_LIGHT_WIDTH);
}

std::shared_ptr<Drawing::ShaderEffect> GEBorderLightShader::MakeBorderLightShader(const Drawing::Rect& rect)
{
    auto width = rect.GetWidth();
    auto height = rect.GetHeight();
    float lightColor[NUM_4] = {borderLightParams_.color[NUM_0], borderLightParams_.color[NUM_1],
        borderLightParams_.color[NUM_2], borderLightParams_.color[NUM_3]};
    builder_ = GetBorderLightBuilder();
    if (builder_ == nullptr) {
        GE_LOGE("GEBorderLightShader::MakeBorderLightShader builder_ is nullptr.");
        return nullptr;
    }
    builder_->SetUniform("iResolution", width, height);
    builder_->SetUniform("lightPosition", borderLightParams_.position[NUM_0],
        borderLightParams_.position[NUM_1], borderLightParams_.position[NUM_2]);
    builder_->SetUniform("lightColor", lightColor, NUM_4);
    builder_->SetUniform("lightIntensity", borderLightParams_.intensity);
    builder_->SetUniform("lightWidth", borderLightParams_.width);
    builder_->SetUniform("borderLightRotationAngle", borderLightParams_.rotationAngle[NUM_0],
        borderLightParams_.rotationAngle[NUM_1], borderLightParams_.rotationAngle[NUM_2]);
    builder_->SetUniform("cornerRadius", borderLightParams_.cornerRadius);
    auto borderLightShader = builder_->MakeShader(nullptr, false);

    if (borderLightShader == nullptr) {
        GE_LOGE("GEBorderLightShader::MakeBorderLightShader borderLightShader is nullptr.");
        return nullptr;
    }
    return borderLightShader;
}

} // namespace Rosen
} // namespace OHOS