/*
 * Copyright (c) Huawei Device Co., Ltd. 2026-2026. All rights reserved.
 * 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 <algorithm>
#include <iomanip>
#include <sstream>

#include "alloctable.h"
#include "utils.h"

namespace OHOS {
namespace ObjectEditor {
// LCOV_EXCL_START
const uint32_t AllocTable::Avail = 0xffffffff;
const uint32_t AllocTable::Eof = 0xfffffffe;
const uint32_t AllocTable::Bat = 0xfffffffd;
const uint32_t AllocTable::MetaBat = 0xfffffffc;

void AllocTable::Set(size_t index, uint32_t value)
{
    if (index >= Count()) {
        Resize(index + 1);
    }
    data_[index] = value;
}

void AllocTable::SetBlockSize(uint32_t size)
{
    blockSize_ = size;
    data_.clear();
    Resize(DEFAULT_BLOCK_SIZE);
}

void AllocTable::SetChain(const std::vector<uint32_t> &chain)
{
    if (chain.empty()) {
        OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "chain is empty");
        return;
    }
    const auto maxIt = std::max_element(chain.begin(), chain.end());
    if (*maxIt >= Count()) {
        Resize(*maxIt + 1);
    }

    size_t chainSize = chain.size() - 1;
    for (size_t i = 0; i < chainSize; i++) {
        Set(chain[i], chain[i + 1]);
    }
    Set(chain.back(), Eof);
}

bool AllocTable::Follow(uint32_t start, std::vector<uint32_t> &chain) const
{
    if (start == Eof || start == Avail || start == MetaBat || start == Bat) {
        OBJECT_EDITOR_LOGD(ObjectEditorDomain::DOCUMENT, "start: %{public}d is Eof, Avail, MetaBat, or Bat", start);
        return true;
    }
    if (start >= Count()) {
        OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "start: %{public}d is out of range, count: %{public}zu",
            start, Count());
        return false;
    }

    uint32_t p = start;
    size_t loopControl = 0;
    while (p < Count()) {
        if (loopControl >= Count()) {
            OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT,
                "loopControl: %{public}zu, count: %{public}zu, start: %{public}u",
                loopControl, Count(), start);
            return false;
        }
        chain.push_back(p);
        p = data_[p];
        ++loopControl;
    }

    if (p != Eof && p != Avail && p != Bat && p != MetaBat) {
        OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "p: %{public}d is not Eof, start: %{public}u",
            p, start);
        return false;
    }

    return true;
}

size_t AllocTable::Unused()
{
    // Start searching from the last found position
    for (size_t i = nextUnused_; i < data_.size(); i++) {
        if (data_[i] == Avail) {
            nextUnused_ = i + 1;  // Update next search position
            return i;
        }
    }

    // If not found, expand the table
    OBJECT_EDITOR_LOGD(ObjectEditorDomain::DOCUMENT, "No unused block found");
    nextUnused_ = 0;
    size_t block = data_.size();
    Resize(data_.size() + kGrowStep);
    nextUnused_ = block + 1;  // Update next search position
    return block;
}

bool AllocTable::Load(const Byte *buffer, size_t len)
{
    if ((len % FOUR_BYTE_SIZE != 0) || buffer == nullptr) [[unlikely]] {
        OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "invalid buffer or len: %{public}d",
            static_cast<int32_t>(len));
        return false;
    }
    Resize(len / FOUR_BYTE_SIZE);
    size_t count = Count();
    OBJECT_EDITOR_LOGD(ObjectEditorDomain::DOCUMENT, "count: %{public}d len: %{public}d",
        static_cast<int32_t>(count), static_cast<int32_t>(len));
    for (size_t i = 0; i < count; i++) {
        Set(i, ReadUint32(buffer + i * FOUR_BYTE_SIZE));
    }
    return true;
}

bool AllocTable::Save(Byte *buffer, size_t len)
{
    size_t count = Count();
    const size_t required = FOUR_BYTE_SIZE * count;
    if (len < required || buffer == nullptr) [[unlikely]] {
        OBJECT_EDITOR_LOGE(ObjectEditorDomain::DOCUMENT, "invalid buffer or len: %{public}d",
            static_cast<int32_t>(len));
        return false;
    }

    for (size_t i = 0; i < count; i++) {
        WriteUint32(buffer + i * FOUR_BYTE_SIZE, data_[i]);
    }
    return true;
}

void AllocTable::Debug() const
{
    constexpr int32_t MINI_WIDTH = 4;
    std::ostringstream oss;
    oss << "==================== FAT ALLOCATION TABLE ====================" << std::endl;
    const size_t totalBlocks = data_.size();
    size_t usedBlocks = 0;
    size_t freeBlocks = 0;
    for (size_t i = 0; i < totalBlocks; ++i) {
        const uint32_t val = static_cast<uint32_t>(data_[i] & 0xFFFFFFFFUL);
        if (val == Avail) {
            ++freeBlocks;
        } else {
            ++usedBlocks;
        }
    }

    oss << "Total Blocks:   " << totalBlocks << std::endl;
    oss << "Used Blocks:    " << usedBlocks << std::endl;
    oss << "Free Blocks:    " << freeBlocks << std::endl;
    oss << "Block Size:     " << BlockSize() << " bytes" << std::endl;

    for (size_t i = 0; i < totalBlocks; ++i) {
        const uint32_t val = static_cast<uint32_t>(data_[i] & 0xFFFFFFFFUL);
        if (val == Avail) {
            continue;
        }
        oss << "0x" << std::hex << std::setw(MINI_WIDTH) << std::setfill('0') << i << std::dec << ": ";

        if (val == Eof) {
            oss << "[EOF]";
        } else if (val == Bat) {
            oss << "[FAT]";
        } else if (val == MetaBat) {
            oss << "[MetaFAT]";
        } else {
            oss << "-> 0x" << std::hex << std::setw(MINI_WIDTH) << std::setfill('0') << static_cast<uint64_t>(val)
                << std::dec;
        }
        oss << std::endl;
    }

    oss << "============================================================" << std::endl;
    OBJECT_EDITOR_LOGD(ObjectEditorDomain::DOCUMENT, "AllocTable dump: %{public}s", oss.str().c_str());
}
// LCOV_EXCL_STOP
} // namespace ObjectEditor
} // namespace OHOS