/*
 * Copyright (c) 2024 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.
 */

#ifndef ECMASCRIPT_SERIALIZER_SERIALIZATION_CHUNK_H
#define ECMASCRIPT_SERIALIZER_SERIALIZATION_CHUNK_H

#include "ecmascript/js_tagged_value-inl.h"
#include "ecmascript/mem/mem.h"

namespace panda::ecmascript {

class SerializationChunk {
public:
    explicit SerializationChunk() {}
    ~SerializationChunk()
    {
        if (begin_ != 0U) {
            free(ToVoidPtr(begin_));
        }
    }
    NO_COPY_SEMANTIC(SerializationChunk);
    NO_MOVE_SEMANTIC(SerializationChunk);

    void Emplace(JSTaggedType value)
    {
        if (top_ == end_) {
            Expand();
        }
        ASSERT(top_ < end_);
        *reinterpret_cast<JSTaggedType *>(top_) = value;
        top_ += JSTaggedValue::TaggedTypeSize();
    }

    void Set(size_t index, JSTaggedType value)
    {
        ASSERT(begin_ + index * JSTaggedValue::TaggedTypeSize() < top_);
        *reinterpret_cast<JSTaggedType *>(begin_ + index * JSTaggedValue::TaggedTypeSize()) = value;
    }

    JSTaggedType Get(size_t index) const
    {
        ASSERT(begin_ + index * JSTaggedValue::TaggedTypeSize() < top_);
        return *reinterpret_cast<JSTaggedType *>(begin_ + index * JSTaggedValue::TaggedTypeSize());
    }

    void Expand()
    {
        ASSERT(top_ == end_);
        size_t oldCapacity = end_ - begin_;
        size_t newCapacity = 0;
        if (oldCapacity == 0U) {
            newCapacity = INITIAL_CHUNK_CAPACITY;
        } else {
            newCapacity = (end_ - begin_) * CHUNK_CAPACITY_INCREASE_RATE;
        }
        void *newChunk = malloc(newCapacity);
        if (newChunk == nullptr) {
            LOG_ECMA_MEM(FATAL) << "malloc failed, current alloc size = " << newCapacity;
            UNREACHABLE();
        }

        if (begin_ != 0U) {
            if (memcpy_s(newChunk, newCapacity, ToVoidPtr(begin_), end_ - begin_) != EOK) {
                LOG_FULL(FATAL) << "memcpy_s fail";
            }
            free(ToVoidPtr(begin_));
        }
        begin_ = ToUintPtr(newChunk);
        top_ = begin_ + oldCapacity;
        end_ = begin_ + newCapacity;
    }

    bool Empty() const
    {
        return top_ == begin_;
    }

    size_t Size() const
    {
        return (top_ - begin_) / JSTaggedValue::TaggedTypeSize();
    }

    void Iterate(RootVisitor &v)
    {
        for (uintptr_t slot = begin_; slot < top_; slot += JSTaggedValue::TaggedTypeSize()) {
            v.VisitRoot(Root::ROOT_VM, ObjectSlot(slot));
        }
    }

private:
    static constexpr size_t INITIAL_CHUNK_CAPACITY = 1_KB;
    static constexpr int CHUNK_CAPACITY_INCREASE_RATE = 2;
    uintptr_t begin_ {0U};
    uintptr_t end_ {0U};
    uintptr_t top_ {0U};
};
}

#endif  // ECMASCRIPT_SERIALIZER_SERIALIZE_DATA_H