/*
* Copyright (c) 2022 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 "gfx_utils/diagram/vertexprimitive/geometry_bezier_arc.h"

namespace OHOS {
#if defined(GRAPHIC_ENABLE_BEZIER_ARC_FLAG) && GRAPHIC_ENABLE_BEZIER_ARC_FLAG
const uint16_t BEZIER_ARC_SETUP = 2;

const uint16_t BEZIER_ARC_VERTICES_SIZE_STEP = 6;

const uint16_t BEZIER_ARC_POINTS = 4;
/* Limit Value of Bezier Arc */
const float BEZIER_ARC_ANGLE_EPSILON = 0.01f;

const float BEZIER_ARC_DELTAX = 4.0f;

const float BEZIER_ARC_EQUAL_DIVISION = 3.0f;

const float BEZIER_ARC_RADIICHECK = 10.0f;

void ArcToBezier(float cx, float cy, float rx, float ry,
                 float startAngle, float sweepAngle,
                 float* curve)
{
    float y0 = Sin((sweepAngle / FLOATNUM) * RADIAN_TO_ANGLE);
    float x0 = Cos((sweepAngle / FLOATNUM) * RADIAN_TO_ANGLE);
    float tx = (1.0f - x0) * BEZIER_ARC_DELTAX / BEZIER_ARC_EQUAL_DIVISION;
    if (y0 == 0) {
        y0 = y0 + VERTEX_DIST_EPSILON;
    }
    float ty = y0 - tx * x0 / y0;
    float px[BEZIER_ARC_POINTS];
    float py[BEZIER_ARC_POINTS];
    px[0] = x0;
    py[0] = -y0;
    px[1] = x0 + tx;
    py[1] = -ty;
    px[2] = x0 + tx;
    py[2] = ty;
    px[3] = x0;
    py[3] = y0;

    float cosVal = Cos((startAngle + sweepAngle / FLOATNUM) * RADIAN_TO_ANGLE);
    float sinVal = Sin((startAngle + sweepAngle / FLOATNUM) * RADIAN_TO_ANGLE);

    for (uint16_t i = 0; i < BEZIER_ARC_POINTS; i++) {
        curve[i * BEZIER_ARC_SETUP] = cx + rx * (px[i] * cosVal - py[i] * sinVal);
        curve[i * BEZIER_ARC_SETUP + 1] = cy + ry * (px[i] * sinVal + py[i] * cosVal);
    }
}

void BezierArc::Init(float centerX, float centerY,
                     float rx, float ry,
                     float startAngle,
                     float sweepAngle)
{
    startAngle = Fmod(startAngle, FLOATNUM * PI);
    if (sweepAngle <= -FLOATNUM * PI) {
        sweepAngle = -FLOATNUM * PI;
    }
    if (sweepAngle >= FLOATNUM * PI) {
        sweepAngle = FLOATNUM * PI;
    }
    if (MATH_ABS(sweepAngle) < 1e-10) {
        numberVertices_ = BEZIER_ARC_POINTS;
        currentCommand_ = PATH_CMD_LINE_TO;
        arrayVertices_[0] = centerX + rx * Cos(startAngle * RADIAN_TO_ANGLE);
        arrayVertices_[1] = centerY + ry * Sin(startAngle * RADIAN_TO_ANGLE);
        arrayVertices_[2] = centerX + rx * Cos((startAngle + sweepAngle) * RADIAN_TO_ANGLE);
        arrayVertices_[3] = centerY + ry * Sin((startAngle + sweepAngle) * RADIAN_TO_ANGLE);
        return;
    }

    float prevSweep;
    float totalSweep = 0.0f;
    float localSweep = 0.0f;
    numberVertices_ = BEZIER_ARC_SETUP;
    currentCommand_ = PATH_CMD_CURVE4;
    bool done = false;
    do {
        if (sweepAngle < 0.0f) {
            prevSweep = totalSweep;
            totalSweep -= PI * HALFNUM;
            localSweep = -PI * HALFNUM;
            if (totalSweep <= sweepAngle + BEZIER_ARC_ANGLE_EPSILON) {
                localSweep = sweepAngle - prevSweep;
                done = true;
            }
        } else {
            prevSweep = totalSweep;
            totalSweep += PI * HALFNUM;
            localSweep = PI * HALFNUM;
            if (totalSweep >= sweepAngle - BEZIER_ARC_ANGLE_EPSILON) {
                localSweep = sweepAngle - prevSweep;
                done = true;
            }
        }

        ArcToBezier(centerX, centerY, rx, ry, startAngle, localSweep, arrayVertices_
                    + numberVertices_ - BEZIER_ARC_SETUP);

        startAngle += localSweep;
        numberVertices_ += BEZIER_ARC_VERTICES_SIZE_STEP;
    } while (numberVertices_ < BEZIER_ARC_VERTEX_NUM && !done);
}

void BezierArcSvg::Init(float x0, float y0,
                        float rx, float ry,
                        float angle,
                        bool largeArcFlag,
                        bool sweepFlag,
                        float x2, float y2)
{
    if (ry < 0.0f) {
        ry = -ry;
    }
    if (rx < 0.0f) {
        rx = -rx;
    }
    isRadiusJoinPath_ = true;
    float delatY2 = (y0 - y2) / FLOATNUM;
    float delatX2 = (x0 - x2) / FLOATNUM;
    float sinA = Sin(angle * RADIAN_TO_ANGLE);
    float cosA = Cos(angle * RADIAN_TO_ANGLE);
    float y1 = -sinA * delatX2 + cosA * delatY2;
    float x1 = cosA * delatX2 + sinA * delatY2;
    float prx = rx * rx;
    float pry = ry * ry;
    float px1 = x1 * x1;
    float py1 = y1 * y1;
    float radiiCheck = px1 / prx + py1 / pry;
    if (radiiCheck > 1.0f) {
        ry = Sqrt(radiiCheck) * ry;
        rx = Sqrt(radiiCheck) * rx;
        pry = ry * ry;
        prx = rx * rx;
        if (radiiCheck > BEZIER_ARC_RADIICHECK) {
            isRadiusJoinPath_ = false;
        }
    }
    float sign = (largeArcFlag == sweepFlag) ? -1.0f : 1.0f;
    float sq = (prx * pry - prx * py1 - pry * px1) / (prx * py1 + pry * px1);
    float coef = sign * Sqrt((sq < 0) ? 0 : sq);
    if (ry == 0) {
        ry += VERTEX_DIST_EPSILON;
    }
    if (rx == 0) {
        rx += VERTEX_DIST_EPSILON;
    }
    float cx1 = coef * ((rx * y1) / ry);
    float cy1 = coef * -((ry * x1) / rx);
    float sx2 = (x0 + x2) / FLOATNUM;
    float sy2 = (y0 + y2) / FLOATNUM;
    float cx = sx2 + (cosA * cx1 - sinA * cy1);
    float cy = sy2 + (sinA * cx1 + cosA * cy1);
    float ux = (x1 - cx1) / rx;
    float uy = (y1 - cy1) / ry;
    float vx = (-x1 - cx1) / rx;
    float vy = (-y1 - cy1) / ry;
    float p = ux;
    float n = Sqrt(ux * ux + uy * uy);
    sign = (uy < 0) ? -1.0f : 1.0f;
    if (n == 0) {
        n += VERTEX_DIST_EPSILON;
    }
    float v = p / n;
    if (v > 1.0f) {
        v = 1.0f;
    }
    if (v < -1.0f) {
        v = -1.0f;
    }
    float startAngle = sign * Acos(v);
    n = Sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
    p = ux * vx + uy * vy;
    sign = (ux * vy - uy * vx < 0) ? -1.0f : 1.0f;
    if (n == 0) {
        n += VERTEX_DIST_EPSILON;
    }
    v = p / n;
    if (v < -1.0f) {
        v = -1.0f;
    }
    if (v > 1.0f) {
        v = 1.0f;
    }
    float sweepAngle = sign * Acos(v);
    if (!sweepFlag && sweepAngle > 0.0f) {
        sweepAngle -= PI * FLOATNUM;
    } else if (sweepFlag && sweepAngle < 0.0f) {
        sweepAngle += PI * FLOATNUM;
    }
    bezierArcModel_.Init(0.0f, 0.0f, rx, ry, startAngle, sweepAngle);
    TransAffine mtx = TransAffine::TransAffineRotation(angle);
    mtx *= TransAffine::TransAffineTranslation(cx, cy);
    uint32_t limit = bezierArcModel_.GetNumberVertices() - BEZIER_ARC_SETUP;
    for (uint32_t i = BEZIER_ARC_SETUP; i < limit; i += BEZIER_ARC_SETUP) {
        mtx.Transform(bezierArcModel_.GetVertices() + i, bezierArcModel_.GetVertices() + i + 1);
    }
    bezierArcModel_.GetVertices()[0] = x0;
    bezierArcModel_.GetVertices()[1] = y0;
    if (bezierArcModel_.GetNumberVertices() > BEZIER_ARC_SETUP) {
        bezierArcModel_.GetVertices()[bezierArcModel_.GetNumberVertices() - BEZIER_ARC_SETUP] = x2;
        bezierArcModel_.GetVertices()[bezierArcModel_.GetNumberVertices() - 1] = y2;
    }
}
#endif
} // namespace OHOS