/*
 * Copyright (c) 2020-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 "draw/draw_label.h"
#include "common/typed_text.h"
#include "draw/draw_utils.h"
#include "engines/gfx/gfx_engine_manager.h"
#include "font/ui_font.h"
#include "font/ui_font_header.h"
#include "gfx_utils/graphic_log.h"

namespace OHOS {
uint16_t DrawLabel::DrawTextOneLine(BufferInfo& gfxDstBuffer, const LabelLineInfo& labelLine, uint16_t& letterIndex)
{
    if (labelLine.text == nullptr) {
        return 0;
    }
    UIFont* fontEngine = UIFont::GetInstance();
    if (labelLine.direct == TEXT_DIRECT_RTL) {
        labelLine.pos.x -= labelLine.offset.x;
    } else {
        labelLine.pos.x += labelLine.offset.x;
    }

    uint32_t i = 0;
    uint16_t retOffsetY = 0; // ret value elipse offsetY
    uint16_t offsetPosY = 0;
    uint8_t maxLetterSize = GetLineMaxLetterSize(labelLine.text, labelLine.lineLength, labelLine.fontId,
                                                 labelLine.fontSize, letterIndex, labelLine.spannableString);
    GlyphNode glyphNode;
    while (i < labelLine.lineLength) {
        uint32_t letter = TypedText::GetUTF8Next(labelLine.text, i, i);
        uint16_t fontId = labelLine.fontId;
        uint8_t fontSize = labelLine.fontSize;
#if defined(ENABLE_TEXT_STYLE) && ENABLE_TEXT_STYLE
        TextStyle textStyle = TEXT_STYLE_NORMAL;
        if (labelLine.textStyles) {
            textStyle = labelLine.textStyles[letterIndex];
        }
#endif
        bool haveLineBackgroundColor = false;
        ColorType lineBackgroundColor;
        bool havebackgroundColor = false;
        ColorType backgroundColor;
        ColorType foregroundColor = labelLine.style.textColor_;

        if (labelLine.spannableString != nullptr && labelLine.spannableString->GetSpannable(letterIndex)) {
            labelLine.spannableString->GetFontId(letterIndex, fontId);
            labelLine.spannableString->GetFontSize(letterIndex, fontSize);
            havebackgroundColor = labelLine.spannableString->GetBackgroundColor(letterIndex, backgroundColor);
            labelLine.spannableString->GetForegroundColor(letterIndex, foregroundColor);
#if defined(ENABLE_TEXT_STYLE) && ENABLE_TEXT_STYLE
            labelLine.spannableString->GetTextStyle(letterIndex, textStyle);
#endif
            haveLineBackgroundColor =
                labelLine.spannableString->GetLineBackgroundColor(letterIndex, lineBackgroundColor);
        }
        LabelLetterInfo letterInfo{labelLine.pos,
                                   labelLine.mask,
                                   foregroundColor,
                                   labelLine.opaScale,
                                   0,
                                   0,
                                   letter,
                                   labelLine.direct,
                                   fontId,
                                   0,
                                   fontSize,
#if defined(ENABLE_TEXT_STYLE) && ENABLE_TEXT_STYLE
                                   textStyle,
#endif
                                   labelLine.baseLine,
                                   labelLine.style.letterSpace_,
                                   labelLine.style.lineSpace_,
                                   havebackgroundColor,
                                   backgroundColor,
                                   haveLineBackgroundColor,
                                   lineBackgroundColor
                                   };
#if defined(ENABLE_TEXT_STYLE) && ENABLE_TEXT_STYLE
        glyphNode.textStyle = letterInfo.textStyle;
#endif
        glyphNode.advance = 0;
        uint8_t* fontMap = fontEngine->GetBitmap(letterInfo.letter, glyphNode, letterInfo.fontId, letterInfo.fontSize,
                                                 letterInfo.shapingId);
        if (fontMap != nullptr) {
            uint8_t weight = fontEngine->GetFontWeight(glyphNode.fontId);
            // 16: rgb565->16 rgba8888->32 font with rgba
            if (weight >= 16) {
                DrawUtils::GetInstance()->DrawColorLetter(gfxDstBuffer, letterInfo, fontMap,
                                                          glyphNode, labelLine.lineHeight);
            } else {
                letterInfo.offsetY = labelLine.ellipsisOssetY == 0 ? offsetPosY : labelLine.ellipsisOssetY;
                retOffsetY = offsetPosY;
                DrawUtils::GetInstance()->DrawNormalLetter(gfxDstBuffer, letterInfo, fontMap, glyphNode, maxLetterSize);
            }
        }
        if (labelLine.direct == TEXT_DIRECT_RTL) {
            labelLine.pos.x -= (glyphNode.advance + labelLine.style.letterSpace_);
        } else {
            labelLine.pos.x += (glyphNode.advance + labelLine.style.letterSpace_);
        }
        letterIndex++;
    }
    return retOffsetY;
}

uint8_t DrawLabel::GetLineMaxLetterSize(const char* text, uint16_t lineLength, uint16_t fontId, uint8_t fontSize,
                                        uint16_t letterIndex, SpannableString* spannableString)
{
    if (spannableString == nullptr) {
        return fontSize;
    }
    uint32_t i = 0;
    uint8_t maxLetterSize = fontSize;
    while (i < lineLength) {
        uint32_t unicode = TypedText::GetUTF8Next(text, i, i);
        if (TypedText::IsColourWord(unicode, fontId, fontSize)) {
            letterIndex++;
            continue;
        }
        if (spannableString != nullptr && spannableString->GetSpannable(letterIndex)) {
            uint8_t tempSize = fontSize;
            spannableString->GetFontSize(letterIndex, tempSize);
            if (tempSize > maxLetterSize) {
                maxLetterSize = tempSize;
            }
        }
        letterIndex++;
    }
    return maxLetterSize;
}

void DrawLabel::DrawArcText(BufferInfo& gfxDstBuffer,
                            const Rect& mask,
                            const char* text,
                            const Point& arcCenter,
                            uint16_t fontId,
                            uint8_t fontSize,
                            const ArcTextInfo arcTextInfo,
                            const float changeAngle,
                            TextOrientation orientation,
                            const Style& style,
                            OpacityType opaScale,
                            bool compatibilityMode)
{
    if ((text == nullptr) || (arcTextInfo.lineStart == arcTextInfo.lineEnd) || (arcTextInfo.radius == 0)) {
        GRAPHIC_LOGE("DrawLabel::DrawArcText invalid parameter\n");
        return;
    }
    OpacityType opa = DrawUtils::GetMixOpacity(opaScale, style.textOpa_);
    if (opa == OPA_TRANSPARENT) {
        return;
    }
    uint16_t letterWidth;
    UIFont* fontEngine = UIFont::GetInstance();

    uint16_t letterHeight = fontEngine->GetHeight(fontId, fontSize);
    uint32_t i = arcTextInfo.lineStart;
    bool orientationFlag = (orientation == TextOrientation::INSIDE);
    bool directFlag = (arcTextInfo.direct == TEXT_DIRECT_LTR);
    bool xorFlag = !directFlag;
    if (compatibilityMode) {
        xorFlag = !((orientationFlag && directFlag) || (!orientationFlag && !directFlag));
    }
    float angle = directFlag ? (arcTextInfo.startAngle + changeAngle) : (arcTextInfo.startAngle - changeAngle);

    float posX;
    float posY;
    float rotateAngle;
    while (i < arcTextInfo.lineEnd) {
        uint32_t tmp = i;
        uint32_t letter = TypedText::GetUTF8Next(text, tmp, i);
        if (letter == 0) {
            continue;
        }
        if ((letter == '\r') || (letter == '\n')) {
            break;
        }
        letterWidth = fontEngine->GetWidth(letter, fontId, fontSize, 0);
        if (!DrawLabel::CalculateAngle(letterWidth, letterHeight, style.letterSpace_,
                                       arcTextInfo, xorFlag, tmp, orientation,
                                       posX, posY, rotateAngle, angle,
                                       arcCenter, compatibilityMode)) {
            continue;
        }

        ArcLetterInfo letterInfo;
        letterInfo.InitData(fontId, fontSize, letter, { MATH_ROUND(posX), MATH_ROUND(posY) },
            static_cast<int16_t>(rotateAngle), style.textColor_, opaScale, arcTextInfo.startAngle,
            arcTextInfo.endAngle, angle, arcTextInfo.radius, compatibilityMode,
            directFlag, orientationFlag, arcTextInfo.hasAnimator);

        DrawLetterWithRotate(gfxDstBuffer, mask, letterInfo, posX, posY);
    }
}

bool DrawLabel::CalculateAngle(uint16_t letterWidth,
                               uint16_t letterHeight,
                               int16_t letterSpace,
                               const ArcTextInfo arcTextInfo,
                               bool xorFlag,
                               uint32_t index,
                               TextOrientation orientation,
                               float& posX,
                               float& posY,
                               float& rotateAngle,
                               float& angle,
                               const Point& arcCenter,
                               bool compatibilityMode)
{
    const int DIVIDER_BY_TWO = 2;
    if (compatibilityMode) {
        if ((index == arcTextInfo.lineStart) && xorFlag) {
            angle += TypedText::GetAngleForArcLen(static_cast<float>(letterWidth), letterHeight, arcTextInfo.radius,
                                                  arcTextInfo.direct, orientation);
        }
        uint16_t arcLen = letterWidth + letterSpace;
        if (arcLen == 0) {
            return false;
        }
        float incrementAngle = TypedText::GetAngleForArcLen(static_cast<float>(arcLen), letterHeight,
                                                            arcTextInfo.radius, arcTextInfo.direct, orientation);

        rotateAngle = (orientation == TextOrientation::INSIDE) ? angle : (angle - SEMICIRCLE_IN_DEGREE);

        // 2: half
        float fineTuningAngle = incrementAngle * (static_cast<float>(letterWidth) / (DIVIDER_BY_TWO * arcLen));
        rotateAngle += (xorFlag ? -fineTuningAngle : fineTuningAngle);
        TypedText::GetArcLetterPos(arcCenter, arcTextInfo.radius, angle, posX, posY);
        angle += incrementAngle;
    } else {
        float incrementAngle = TypedText::GetAngleForArcLen(letterWidth, letterSpace, arcTextInfo.radius);
        if (incrementAngle >= -EPSINON && incrementAngle <= EPSINON) {
            return false;
        }

        float fineTuningAngle = incrementAngle / DIVIDER_BY_TWO;
        rotateAngle = xorFlag ? (angle - SEMICIRCLE_IN_DEGREE - fineTuningAngle) : (angle + fineTuningAngle);
        TypedText::GetArcLetterPos(arcCenter, arcTextInfo.radius, angle, posX, posY);
        angle = xorFlag ? (angle - incrementAngle) : (angle + incrementAngle);
    }

    return true;
}

void DrawLabel::DrawLetterWithRotate(BufferInfo& gfxDstBuffer,
                                     const Rect& mask,
                                     const ArcLetterInfo& letterInfo,
                                     float posX,
                                     float posY)
{
    UIFont* fontEngine = UIFont::GetInstance();
    FontHeader head;
    GlyphNode node;
#if defined(ENABLE_TEXT_STYLE) && ENABLE_TEXT_STYLE
    node.textStyle = TEXT_STYLE_NORMAL;
#endif
    if (fontEngine->GetFontHeader(head, letterInfo.fontId, letterInfo.fontSize) != 0) {
        return;
    }

    const uint8_t* fontMap = fontEngine->GetBitmap(letterInfo.letter, node,
        letterInfo.fontId, letterInfo.fontSize, 0);
    if (fontMap == nullptr) {
        return;
    }
    uint8_t fontWeight = fontEngine->GetFontWeight(letterInfo.fontId);
    ColorMode colorMode = fontEngine->GetColorType(letterInfo.fontId);

    int16_t offset = letterInfo.compatibilityMode ? head.ascender : 0;
    Rect rectLetter;
    rectLetter.Resize(node.cols, node.rows);
    TransformMap transMap(rectLetter);
    // Avoiding errors caused by rounding calculations
    transMap.Translate(Vector2<float>(posX + node.left, posY + offset - node.top));
    transMap.Rotate(letterInfo.rotateAngle, Vector2<float>(posX, posY));

    TransformDataInfo letterTranDataInfo = {ImageHeader{colorMode, 0, 0, 0, node.cols, node.rows}, fontMap, fontWeight,
                                            BlurLevel::LEVEL0, TransformAlgorithm::BILINEAR};

    uint8_t* buffer = nullptr;
    if (letterInfo.hasAnimator) {
        bool inRange = DrawLabel::CalculatedTransformDataInfo(&buffer, letterTranDataInfo, letterInfo);
        if (inRange == false) {
            if (buffer != nullptr) {
                UIFree(buffer);
                buffer = nullptr;
            }
            return;
        }
    }

    BaseGfxEngine::GetInstance()->DrawTransform(gfxDstBuffer, mask, Point { 0, 0 }, letterInfo.color,
        letterInfo.opaScale, transMap, letterTranDataInfo);
    if (buffer != nullptr) {
        UIFree(buffer);
        buffer = nullptr;
    }
}

bool DrawLabel::CalculatedClipAngle(const ArcLetterInfo& letterInfo, float& angle)
{
    if (letterInfo.directFlag) {
        if ((letterInfo.compatibilityMode && letterInfo.orientationFlag) || !letterInfo.compatibilityMode) {
            if (letterInfo.currentAngle > letterInfo.endAngle) {
                angle = letterInfo.currentAngle - letterInfo.endAngle;
            } else if (letterInfo.currentAngle > letterInfo.startAngle) {
                angle = letterInfo.currentAngle - letterInfo.startAngle;
            } else {
                return false;
            }
        } else {
            if (letterInfo.currentAngle > letterInfo.endAngle) {
                angle = letterInfo.currentAngle - letterInfo.endAngle;
            } else if (letterInfo.currentAngle > letterInfo.startAngle) {
                angle = letterInfo.currentAngle - letterInfo.startAngle;
            } else {
                return false;
            }
        }
    } else {
        if (letterInfo.compatibilityMode && letterInfo.orientationFlag) {
            if (letterInfo.currentAngle < letterInfo.endAngle) {
                angle = letterInfo.endAngle - letterInfo.currentAngle;
            } else if (letterInfo.currentAngle < letterInfo.startAngle) {
                angle = letterInfo.startAngle - letterInfo.currentAngle;
            } else {
                return false;
            }
        } else if ((letterInfo.compatibilityMode && !letterInfo.orientationFlag) || !letterInfo.compatibilityMode) {
            if (letterInfo.currentAngle < letterInfo.endAngle) {
                angle = letterInfo.endAngle - letterInfo.currentAngle;
            } else if (letterInfo.currentAngle < letterInfo.startAngle) {
                angle = letterInfo.startAngle - letterInfo.currentAngle;
            } else {
                return false;
            }
        }
    }

    return true;
}

void DrawLabel::OnCalculatedClockwise(const ArcLetterInfo& letterInfo, const uint16_t sizePerPx,
                                      const uint16_t cols, const int16_t offsetX, uint16_t& begin,
                                      uint16_t& copyCols, TextInRange& range)
{
    if (!letterInfo.directFlag) {
        return;
    }
    if ((letterInfo.compatibilityMode && letterInfo.orientationFlag) || !letterInfo.compatibilityMode) {
        if (letterInfo.currentAngle > letterInfo.endAngle) {
            if (offsetX >= cols) {
                range = TextInRange::OUT_RANGE;
            }
            copyCols = cols - offsetX;
        } else if (letterInfo.currentAngle > letterInfo.startAngle) {
            if (offsetX >= cols) {
                range = TextInRange::IN_RANGE;
            }
            copyCols = offsetX;
            begin = (cols - offsetX) * sizePerPx;
        }
    } else {
        if (letterInfo.currentAngle > letterInfo.endAngle) {
            if (offsetX >= cols) {
                range = TextInRange::OUT_RANGE;
            }
            copyCols = cols - offsetX;
            begin = offsetX * sizePerPx;
        } else if (letterInfo.currentAngle > letterInfo.startAngle) {
            if (offsetX >= cols) {
                range = TextInRange::IN_RANGE;
            }
            copyCols = offsetX;
        }
    }
}

void DrawLabel::OnCalculatedAnticlockwise(const ArcLetterInfo& letterInfo, const uint16_t sizePerPx,
                                          const uint16_t cols, const int16_t offsetX, uint16_t& begin,
                                          uint16_t& copyCols, TextInRange& range)
{
    if (letterInfo.directFlag) {
        return;
    }
    if (letterInfo.compatibilityMode && letterInfo.orientationFlag) {
        if (letterInfo.currentAngle < letterInfo.endAngle) {
            if (offsetX >= cols) {
                range = TextInRange::OUT_RANGE;
            }
            copyCols = cols - offsetX;
            begin = offsetX * sizePerPx;
        } else if (letterInfo.currentAngle < letterInfo.startAngle) {
            if (offsetX >= cols) {
                range = TextInRange::IN_RANGE;
            }
            copyCols = offsetX;
        }
    } else if ((letterInfo.compatibilityMode && !letterInfo.orientationFlag) || !letterInfo.compatibilityMode) {
        if (letterInfo.currentAngle < letterInfo.endAngle) {
            if (offsetX >= cols) {
                range = TextInRange::OUT_RANGE;
            }
            copyCols = cols - offsetX;
        } else if (letterInfo.currentAngle < letterInfo.startAngle) {
            if (offsetX >= cols) {
                range = TextInRange::IN_RANGE;
            }
            copyCols = offsetX;
            begin = (cols - offsetX) * sizePerPx;
        }
    }
}

void DrawLabel::CalculatedBeginAndCopySize(const ArcLetterInfo& letterInfo, const uint16_t sizePerPx,
                                           const uint16_t cols, const int16_t offsetX, uint16_t& begin,
                                           uint16_t& copyCols, TextInRange& range)
{
    if (letterInfo.directFlag) {
        OnCalculatedClockwise(letterInfo, sizePerPx, cols, offsetX, begin, copyCols, range);
    } else {
        OnCalculatedAnticlockwise(letterInfo, sizePerPx, cols, offsetX, begin, copyCols, range);
    }
}

bool DrawLabel::CalculatedTransformDataInfo(uint8_t** buffer, TransformDataInfo& letterTranDataInfo,
    const ArcLetterInfo& letterInfo)
{
    float angle = 0.0f;
    if (DrawLabel::CalculatedClipAngle(letterInfo, angle) == false) {
        return false;
    }
    if (angle >= -EPSINON && angle <= EPSINON) {
        return true;
    }

    int16_t offsetX = static_cast<uint16_t>(angle * letterInfo.radius * UI_PI / SEMICIRCLE_IN_DEGREE);
    uint16_t copyCols = 0;
    uint16_t begin = 0;
    uint16_t sizePerPx = letterTranDataInfo.pxSize / 8; // 8 bit
    TextInRange range = TextInRange::NEED_CLIP;
    uint16_t cols = letterTranDataInfo.header.width;
    uint16_t rows = letterTranDataInfo.header.height;
    DrawLabel::CalculatedBeginAndCopySize(letterInfo, sizePerPx, cols, offsetX, begin, copyCols, range);
    if (range == TextInRange::IN_RANGE) {
        return true;
    } else if (range == TextInRange::OUT_RANGE) {
        return false;
    }

    const uint8_t* fontMap = letterTranDataInfo.data;

    uint32_t size = cols * rows * sizePerPx;
    *buffer = static_cast<uint8_t*>(UIMalloc(size));
    if (*buffer == nullptr) {
        return false;
    }

    if (memset_s(*buffer, size, 0, size) != EOK) {
        UIFree(*buffer);
        return false;
    }

    for (uint16_t i = 0; i < rows; i++) {
        uint16_t beginSize = i * cols * sizePerPx + begin;
        uint16_t copySize = copyCols * sizePerPx;
        if (memcpy_s(*buffer + beginSize, copySize, fontMap + beginSize, copySize) != EOK) {
            UIFree(*buffer);
            return false;
        }
    }
    letterTranDataInfo.data = *buffer;
    return true;
}

void DrawLabel::GetLineBackgroundColor(uint16_t letterIndex, List<LineBackgroundColor>* linebackgroundColor,
                                       bool& havelinebackground, ColorType& linebgColor)
{
    if (linebackgroundColor->Size() > 0) {
        ListNode<LineBackgroundColor>* lbColor = linebackgroundColor->Begin();
        for (; lbColor != linebackgroundColor->End(); lbColor = lbColor->next_) {
            uint32_t start = lbColor->data_.start;
            uint32_t end = lbColor->data_.end;
            if (letterIndex >= start && letterIndex <= end) {
                havelinebackground = true;
                linebgColor = lbColor->data_.linebackgroundColor ;
            }
        }
    }
};

void DrawLabel::GetBackgroundColor(uint16_t letterIndex, List<BackgroundColor>* backgroundColor,
                                   bool& havebackground, ColorType& bgColor)
{
    if (backgroundColor->Size() > 0) {
        ListNode<BackgroundColor>* bColor = backgroundColor->Begin();
        for (; bColor != backgroundColor->End(); bColor = bColor->next_) {
            uint16_t start = bColor->data_.start;
            uint16_t end = bColor->data_.end;
            if (letterIndex >= start && letterIndex <= end) {
                havebackground = true;
                bgColor = bColor->data_.backgroundColor ;
            }
        }
    }
};

void DrawLabel::GetForegroundColor(uint16_t letterIndex, List<ForegroundColor>* foregroundColor, ColorType& fgColor)
{
    if (foregroundColor->Size() > 0) {
        ListNode<ForegroundColor>* fColor = foregroundColor->Begin();
        for (; fColor != foregroundColor->End(); fColor = fColor->next_) {
            uint32_t start = fColor->data_.start;
            uint32_t end = fColor->data_.end;
            if (letterIndex >= start && letterIndex <= end) {
                fgColor = fColor->data_.fontColor;
            }
        }
    }
};

void DrawLabel::DrawLineBackgroundColor(BufferInfo& gfxDstBuffer, uint16_t letterIndex, const LabelLineInfo& labelLine)
{
    uint32_t i = 0;
    while (i < labelLine.lineLength) {
        TypedText::GetUTF8Next(labelLine.text, i, i);
        bool havelinebackground = false;
        ColorType linebackgroundColor;
        if (labelLine.spannableString != nullptr &&
            labelLine.spannableString->GetSpannable(letterIndex)) {
            havelinebackground =
                labelLine.spannableString->GetLineBackgroundColor(
                    letterIndex, linebackgroundColor);
        }
        if (havelinebackground) {
            Style style;
            style.bgColor_ = linebackgroundColor;
            Rect linebackground(labelLine.mask.GetLeft(), labelLine.pos.y,
                                labelLine.mask.GetRight(),
                                labelLine.pos.y + labelLine.lineHeight);
            BaseGfxEngine::GetInstance()->DrawRect(gfxDstBuffer, labelLine.mask,
                                                   linebackground, style,
                                                   linebackgroundColor.alpha);
        }
        letterIndex++;
    }
};
} // namespace OHOS