/*
 * Copyright (c) 2020-2021 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 "layout/flex_layout.h"

namespace OHOS {
void FlexLayout::LayoutChildren(bool needInvalidate)
{
    if (childrenHead_ == nullptr) {
        return;
    }

    if ((direction_ == LAYOUT_HOR) || (direction_ == LAYOUT_HOR_R)) {
        LayoutHorizontal();
    } else {
        LayoutVertical();
    }
    if (needInvalidate) {
        Invalidate();
    }
}

void FlexLayout::GetStartPos(const int16_t& length,
                             int16_t& pos,
                             int16_t& interval,
                             int16_t count,
                             uint16_t* validLengths,
                             uint16_t* childsNum)
{
    if (!validLengths || !childsNum) {
        return;
    }
    pos = 0;
    interval = 0;

    if (majorAlign_ == ALIGN_START) {
        pos = 0;
    } else if (majorAlign_ == ALIGN_END) {
        pos = length - validLengths[count];
        /* if total length of children is too long or only one child, layout them centerly no matter what key word set.
         */
    } else if ((majorAlign_ == ALIGN_CENTER) || (validLengths[count] >= length) || (childsNum[count] == 1)) {
        pos = (length - validLengths[count]) / 2; // 2: half
    } else if (majorAlign_ == ALIGN_AROUND) {
        if (childsNum[count] == 0) {
            return;
        }
        interval = (length - validLengths[count]) / childsNum[count];
        pos = interval / 2; // 2: half
    } else if (majorAlign_ == ALIGN_EVENLY) {
        interval = (length - validLengths[count]) / (childsNum[count] + 1);
        pos = interval;
    } else {
        interval = (length - validLengths[count]) / (childsNum[count] - 1);
        pos = 0;
    }
}

void FlexLayout::GetNoWrapStartPos(const int16_t& length, int16_t& majorPos, int16_t& interval)
{
    uint16_t childrenNum = 0;
    uint16_t totalValidLength = 0;

    CalValidLength(totalValidLength, childrenNum);
    GetStartPos(length, majorPos, interval, 0, &totalValidLength, &childrenNum);
}

void FlexLayout::GetRowStartPos(int16_t& pos,
                                int16_t& interval,
                                int16_t count,
                                uint16_t* rowsWidth,
                                uint16_t* rowsChildNum)
{
    GetStartPos(GetWidth(), pos, interval, count, rowsWidth, rowsChildNum);
}

void FlexLayout::GetColumnStartPos(int16_t& pos,
                                   int16_t& interval,
                                   int16_t count,
                                   uint16_t* columnsHeight,
                                   uint16_t* columnsChildNum)
{
    GetStartPos(GetHeight(), pos, interval, count, columnsHeight, columnsChildNum);
}

void FlexLayout::CalValidLength(uint16_t& totalValidLength, uint16_t& allChildNum)
{
    UIView* child = childrenHead_;
    int16_t left;
    int16_t right;
    int16_t top;
    int16_t bottom;

    /* calculate valid length of all children views */
    while (child != nullptr) {
        if (child->IsVisible()) {
            child->ReMeasure();
            if ((direction_ == LAYOUT_HOR) || (direction_ == LAYOUT_HOR_R)) {
                left = child->GetStyle(STYLE_MARGIN_LEFT);
                right = child->GetStyle(STYLE_MARGIN_RIGHT);
                totalValidLength += (child->GetRelativeRect().GetWidth() + left + right);
            } else {
                top = child->GetStyle(STYLE_MARGIN_TOP);
                bottom = child->GetStyle(STYLE_MARGIN_BOTTOM);
                totalValidLength += (child->GetRelativeRect().GetHeight() + top + bottom);
            }
            allChildNum++;
        }
        child = child->GetNextSibling();
    }
}

void FlexLayout::CalRowCount()
{
    UIView* child = childrenHead_;
    int16_t pos = 0;
    int16_t left;
    int16_t right;

    rowCount_ = 1;
    while (child != nullptr) {
        if (child->IsVisible()) {
            child->ReMeasure();
            left = child->GetStyle(STYLE_MARGIN_LEFT);
            right = child->GetStyle(STYLE_MARGIN_RIGHT);
            pos += left;
            if ((pos + child->GetRelativeRect().GetWidth() + right) > GetWidth()) {
                pos = left;
                rowCount_++;
            }
            pos += child->GetRelativeRect().GetWidth() + right;
        }
        child = child->GetNextSibling();
    }
}

void FlexLayout::GetRowMaxHeight(uint16_t size, uint16_t* maxRosHegiht)
{
    UIView* child = childrenHead_;
    int16_t pos = 0;
    int16_t left;
    int16_t right;
    int16_t top;
    int16_t bottom;
    uint16_t i = 0;
    uint16_t height = 0;

    if ((maxRosHegiht == nullptr) || (size > rowCount_)) {
        return;
    }

    while (child != nullptr) {
        if (child->IsVisible()) {
            left = child->GetStyle(STYLE_MARGIN_LEFT);
            right = child->GetStyle(STYLE_MARGIN_RIGHT);
            top = child->GetStyle(STYLE_MARGIN_TOP);
            bottom = child->GetStyle(STYLE_MARGIN_BOTTOM);
            pos += left;
            if ((pos + child->GetRelativeRect().GetWidth() + right) > GetWidth()) {
                pos = left;
                maxRosHegiht[i] = height;
                height = 0;
                i++;
            }
            height = MATH_MAX(height, child->GetRelativeRect().GetHeight() + top + bottom);
            maxRosHegiht[i] = height;
            pos += child->GetRelativeRect().GetWidth() + right;
        }
        child = child->GetNextSibling();
    }
}

void FlexLayout::GetRowsWidth(uint16_t rowNum, uint16_t* rowsWidth, uint16_t* rowsChildNum)
{
    UIView* child = childrenHead_;
    int16_t pos = 0;
    int16_t left;
    int16_t right;
    uint16_t rowChildNum = 0;
    uint16_t rowCount = 0;
    uint16_t width = 0;

    if ((rowsWidth == nullptr) || (rowsChildNum == nullptr) || (rowNum > rowCount_)) {
        return;
    }

    while (child != nullptr) {
        if (child->IsVisible()) {
            left = child->GetStyle(STYLE_MARGIN_LEFT);
            right = child->GetStyle(STYLE_MARGIN_RIGHT);
            pos += left;
            if ((pos + child->GetRelativeRect().GetWidth() + right) > GetWidth()) {
                pos = left;
                rowsWidth[rowCount] = width;
                width = 0;
                rowsChildNum[rowCount] = rowChildNum;
                rowChildNum = 0;
                rowCount++;
            }
            width += child->GetRelativeRect().GetWidth() + right + left;
            rowsWidth[rowCount] = width;
            rowChildNum++;
            rowsChildNum[rowCount] = rowChildNum;
            pos += child->GetRelativeRect().GetWidth() + right;
        }
        child = child->GetNextSibling();
    }
}

void FlexLayout::GetCrossAxisPosY(int16_t& posY, uint16_t& count, uint16_t* rowsMaxHeight, UIView* child)
{
    if ((rowsMaxHeight == nullptr) || (child == nullptr)) {
        return;
    }

    uint16_t i = 0;
    uint16_t offset = 0;
    int16_t top = child->GetStyle(STYLE_MARGIN_TOP);
    int16_t bottom = child->GetStyle(STYLE_MARGIN_BOTTOM);

    if (secondaryAlign_ == ALIGN_START) {
        for (i = 0; i < count; i++) {
            offset += rowsMaxHeight[i];
        }
        posY = top + offset;
    } else if (secondaryAlign_ == ALIGN_END) {
        for (i = rowCount_ - 1; i > count; i--) {
            offset += rowsMaxHeight[i];
        }
        posY = GetHeight() - child->GetRelativeRect().GetHeight() - bottom - offset;
    } else {
        for (i = 0; i < rowCount_; i++) {
            offset += rowsMaxHeight[i];
        }
        offset = (rowsMaxHeight[0] - offset) / 2; // 2: half
        for (i = 1; i <= count; i++) {
            offset += (rowsMaxHeight[i - 1] + rowsMaxHeight[i]) / 2; // 2: half
        }
        posY = (GetHeight() - child->GetRelativeRect().GetHeight() - top - bottom) / 2 + top + offset; // 2: half
    }
}

void FlexLayout::LayoutHorizontal()
{
    UIView* child = childrenHead_;
    int16_t interval = 0;
    int16_t posX = 0;
    int16_t posY = 0;
    uint16_t count = 0;
    uint16_t widthsBuf[MAX_COUNT_DEFAULT] = {0};
    uint16_t maxHeightsBuf[MAX_COUNT_DEFAULT] = {0};
    uint16_t childsNumBuf[MAX_COUNT_DEFAULT] = {0};
    uint16_t* rowsWidth = widthsBuf;
    uint16_t* rowsMaxHeight = maxHeightsBuf;
    uint16_t* rowsChildNum = childsNumBuf;
    bool allocFlag = false;

    if (wrap_ == WRAP) {
        CalRowCount();
        if (rowCount_ > MAX_COUNT_DEFAULT) {
            rowsWidth = new uint16_t[rowCount_]();
            rowsMaxHeight = new uint16_t[rowCount_]();
            rowsChildNum = new uint16_t[rowCount_]();
            allocFlag = true;
        }
        GetRowMaxHeight(rowCount_, rowsMaxHeight);
        GetRowsWidth(rowCount_, rowsWidth, rowsChildNum);
        GetRowStartPos(posX, interval, count, rowsWidth, rowsChildNum);
    } else {
        GetNoWrapStartPos(GetWidth(), posX, interval);
    }

    while (child != nullptr) {
        if (child->IsVisible()) {
            int16_t left = child->GetStyle(STYLE_MARGIN_LEFT);
            int16_t right = child->GetStyle(STYLE_MARGIN_RIGHT);
            posX += left;
            if (((posX + child->GetRelativeRect().GetWidth() + right) > GetWidth()) && (wrap_ == WRAP)) {
                GetRowStartPos(posX, interval, ++count, rowsWidth, rowsChildNum);
                posX += left;
            }

            GetCrossAxisPosY(posY, count, rowsMaxHeight, child);
            if (direction_ == LAYOUT_HOR_R) {
                child->SetPosition(GetWidth() - posX - child->GetRelativeRect().GetWidth() - right,
                                   posY - child->GetStyle(STYLE_MARGIN_TOP));
            } else {
                child->SetPosition(posX - left, posY - child->GetStyle(STYLE_MARGIN_TOP));
            }
            posX += child->GetRelativeRect().GetWidth() + right + interval;
            child->LayoutChildren();
        }
        child = child->GetNextSibling();
    }

    if (allocFlag) {
        delete[] rowsWidth;
        delete[] rowsMaxHeight;
        delete[] rowsChildNum;
    }
}

void FlexLayout::CalColumnCount()
{
    UIView* child = childrenHead_;
    int16_t pos = 0;
    int16_t top;
    int16_t bottom;

    columnCount_ = 1;
    while (child != nullptr) {
        if (child->IsVisible()) {
            child->ReMeasure();
            top = child->GetStyle(STYLE_MARGIN_TOP);
            bottom = child->GetStyle(STYLE_MARGIN_BOTTOM);
            pos += top;
            if ((pos + child->GetRelativeRect().GetHeight() + bottom) > GetHeight()) {
                pos = top;
                columnCount_++;
            }
            pos += child->GetRelativeRect().GetHeight() + bottom;
        }
        child = child->GetNextSibling();
    }
}

void FlexLayout::GetColumnMaxWidth(uint16_t size, uint16_t* maxColumnsWidth)
{
    UIView* child = childrenHead_;
    int16_t pos = 0;
    int16_t left;
    int16_t right;
    int16_t bottom;
    uint16_t i = 0;
    uint16_t width = 0;

    if ((maxColumnsWidth == nullptr) || (size > columnCount_)) {
        return;
    }

    while (child != nullptr) {
        if (child->IsVisible()) {
            left = child->GetStyle(STYLE_MARGIN_LEFT);
            right = child->GetStyle(STYLE_MARGIN_RIGHT);
            bottom = child->GetStyle(STYLE_MARGIN_BOTTOM);
            pos += left;
            if ((pos + child->GetRelativeRect().GetHeight() + bottom) > GetHeight()) {
                pos = left;
                maxColumnsWidth[i] = width;
                width = 0;
                i++;
            }
            width = MATH_MAX(width, child->GetRelativeRect().GetWidth() + left + right);
            maxColumnsWidth[i] = width;
            pos += child->GetRelativeRect().GetHeight() + bottom;
        }
        child = child->GetNextSibling();
    }
}

void FlexLayout::GetColumnsHeight(uint16_t columnNum, uint16_t* columnsHeight, uint16_t* columnsChildNum)
{
    UIView* child = childrenHead_;
    int16_t pos = 0;
    int16_t top;
    int16_t bottom;
    uint16_t columnChildNum = 0;
    uint16_t columnCount = 0;
    uint16_t height = 0;

    if ((columnsHeight == nullptr) || (columnsChildNum == nullptr) || (columnNum > columnCount_)) {
        return;
    }

    while (child != nullptr) {
        if (child->IsVisible()) {
            top = child->GetStyle(STYLE_MARGIN_TOP);
            bottom = child->GetStyle(STYLE_MARGIN_BOTTOM);
            pos += top;
            if ((pos + child->GetRelativeRect().GetHeight() + bottom) > GetHeight()) {
                pos = top;
                columnsHeight[columnCount] = height;
                height = 0;
                columnsChildNum[columnCount] = columnChildNum;
                columnChildNum = 0;
                columnCount++;
            }
            height += child->GetRelativeRect().GetHeight() + top + bottom;
            columnsHeight[columnCount] = height;
            columnChildNum++;
            columnsChildNum[columnCount] = columnChildNum;
            pos += child->GetRelativeRect().GetHeight() + bottom;
        }
        child = child->GetNextSibling();
    }
}

void FlexLayout::GetCrossAxisPosX(int16_t& posX, uint16_t& count, uint16_t* columnsMaxWidth, UIView* child)
{
    if ((columnsMaxWidth == nullptr) || (child == nullptr)) {
        return;
    }

    uint16_t i = 0;
    uint16_t offset = 0;
    int16_t left = child->GetStyle(STYLE_MARGIN_LEFT);
    int16_t right = child->GetStyle(STYLE_MARGIN_RIGHT);

    if (secondaryAlign_ == ALIGN_START) {
        for (i = 0; i < count; i++) {
            offset += columnsMaxWidth[i];
        }
        posX = left + offset;
    } else if (secondaryAlign_ == ALIGN_END) {
        for (i = columnCount_ - 1; i > count; i--) {
            offset += columnsMaxWidth[i];
        }
        posX = GetWidth() - child->GetRelativeRect().GetWidth() - right - offset;
    } else {
        for (i = 0; i < columnCount_; i++) {
            offset += columnsMaxWidth[i];
        }
        offset = (columnsMaxWidth[0] - offset) / 2; // 2: half
        for (i = 1; i <= count; i++) {
            offset += (columnsMaxWidth[i - 1] + columnsMaxWidth[i]) / 2; // 2: half
        }
        posX = (GetWidth() - child->GetRelativeRect().GetWidth() - left - right) / 2 + left + offset; // 2: half
    }
}

void FlexLayout::LayoutVertical()
{
    UIView* child = childrenHead_;
    int16_t interval = 0;
    int16_t posX = 0;
    int16_t posY = 0;
    uint16_t count = 0;
    uint16_t heightsBuf[MAX_COUNT_DEFAULT] = {0};
    uint16_t maxWidthsBuf[MAX_COUNT_DEFAULT] = {0};
    uint16_t childsNumBuf[MAX_COUNT_DEFAULT] = {0};
    uint16_t* columnsHeight = heightsBuf;
    uint16_t* columnsMaxWidth = maxWidthsBuf;
    uint16_t* columnsChildNum = childsNumBuf;
    bool allocFlag = false;

    if (wrap_ == WRAP) {
        CalColumnCount();
        if (columnCount_ > MAX_COUNT_DEFAULT) {
            columnsHeight = new uint16_t[columnCount_]();
            columnsMaxWidth = new uint16_t[columnCount_]();
            columnsChildNum = new uint16_t[columnCount_]();
            allocFlag = true;
        }
        GetColumnMaxWidth(columnCount_, columnsMaxWidth);
        GetColumnsHeight(columnCount_, columnsHeight, columnsChildNum);
        GetColumnStartPos(posY, interval, count, columnsHeight, columnsChildNum);
    } else {
        GetNoWrapStartPos(GetHeight(), posY, interval);
    }

    while (child != nullptr) {
        if (child->IsVisible()) {
            int16_t top = child->GetStyle(STYLE_MARGIN_TOP);
            int16_t bottom = child->GetStyle(STYLE_MARGIN_BOTTOM);
            posY += top;
            if (((posY + child->GetRelativeRect().GetHeight() + bottom) > GetHeight()) && (wrap_ == WRAP)) {
                GetColumnStartPos(posY, interval, ++count, columnsHeight, columnsChildNum);
                posY += top;
            }

            GetCrossAxisPosX(posX, count, columnsMaxWidth, child);
            if (direction_ == LAYOUT_VER_R) {
                child->SetPosition(posX - child->GetStyle(STYLE_MARGIN_LEFT),
                                   GetHeight() - posY - child->GetRelativeRect().GetHeight() - bottom);
            } else {
                child->SetPosition(posX - child->GetStyle(STYLE_MARGIN_LEFT), posY - top);
            }
            posY += child->GetRelativeRect().GetHeight() + bottom + interval;
            child->LayoutChildren();
        }
        child = child->GetNextSibling();
    }

    if (allocFlag) {
        delete[] columnsHeight;
        delete[] columnsMaxWidth;
        delete[] columnsChildNum;
    }
}
} // namespace OHOS