/*
 * 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.
 */

#ifndef ECMASCRIPT_MEM_LINEAR_SPACE_H
#define ECMASCRIPT_MEM_LINEAR_SPACE_H

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

namespace panda::ecmascript {
class LinearSpace : public MonoSpace, public SweepableSpace {
public:
    LinearSpace(Heap *heap, MemSpaceType type, size_t initialCapacity, size_t maximumCapacity);
    NO_COPY_SEMANTIC(LinearSpace);
    NO_MOVE_SEMANTIC(LinearSpace);
    uintptr_t Allocate(size_t size, bool isPromoted = false);
    bool Expand(bool isPromoted);
    void Stop();
    void ResetAllocator();
    void IterateOverObjects(const std::function<void(TaggedObject *object)> &objectVisitor) const;
    void PrepareSweeping(LinearSpace *fromSpace);
    void Sweep(LinearSpace *fromSpace);

    const uintptr_t *GetAllocationTopAddress()
    {
        return allocator_.GetTopAddress();
    }
    const uintptr_t *GetAllocationEndAddress()
    {
        return allocator_.GetEndAddress();
    }
    size_t GetOvershootSize() const
    {
        return overShootSize_;
    }
    void InvokeAllocationInspector(Address object, size_t size, size_t alignedSize);

    void RecordCurrentRegionAsHalfFresh()
    {
        Region *region = GetCurrentAllocatorRegion();
        if (region != nullptr) {
            ASSERT(!region->IsFreshRegion() && !region->IsHalfFreshRegion());
            region->SetRegionTypeFlag(RegionTypeFlag::HALF_FRESH);
            freshObjectWaterLine_ = allocator_.GetTop();
            ASSERT(region->InRange(freshObjectWaterLine_));
        }
    }

    bool IsFreshObjectInHalfFreshRegion(TaggedObject *object)
    {
        uintptr_t addr = ToUintPtr(object);
        ASSERT(Region::ObjectAddressToRange(object)->IsHalfFreshRegion());
        ASSERT(Region::ObjectAddressToRange(object)->InRange(addr));
        return addr >= freshObjectWaterLine_;
    }

    Region *GetCurrentAllocatorRegion() const
    {
        return currentAllocatorRegion_;
    }

    void PrepareSweeping() override
    {
        LOG_GC(FATAL) << "can not call this method";
    }

    void Sweep() override
    {
        LOG_GC(FATAL) << "can not call this method";
    }

    void AsyncSweep(bool isMain, bool releaseMemory = false) override;

    bool TryFillSweptRegion() override;
    
    bool FinishFillSweptRegion() override;

private:
    struct FreeMemory {
        size_t length;
        uintptr_t start;

        FreeMemory(size_t memoryLength, uintptr_t memoryStart) : length(memoryLength), start(memoryStart)
        {
        }
    };
    enum class SweepingState {
        NO_SWEEP,
        SWEEPING,
        SWEPT,
    };

    Region *GetSweepingRegionSafe();
    void AddSweptRegionSafe(Region *region);
    Region *GetSweptRegionSafe();
    void SweepRegion(Region *region, std::vector<FreeMemory> &freeList);
    void FreeLiveRange(Region *region, uintptr_t freeStart, uintptr_t freeEnd, std::vector<FreeMemory> &freeList);
    void BuildFreeList();

    void DiscardCurrentAllocator(bool isPromoted);
    bool TryGetUsableFreeList(size_t size);
    bool TryUseFreeList(size_t size);

    Mutex mutex_ {};
    std::atomic<SweepingState> sweeping_ {SweepingState::NO_SWEEP};
    std::atomic<size_t> numPendingSweepingRegions_ {0};
    std::vector<Region *> pendingSweepingRegions_ {};
    std::vector<Region *> sweptRegions_ {};
    std::vector<FreeMemory> sweptFreeList_ {};
    std::vector<FreeMemory> freeList_ {};
    std::vector<Region *> freeListRegions_ {};

protected:
    Heap *localHeap_;
    JSThread *thread_ {nullptr};
    BumpPointerAllocator allocator_;
    size_t overShootSize_ {0};
    size_t overShootSizeForConcurrentMark_ {0};
    size_t allocateAfterLastGC_ {0};
    size_t survivalObjectSize_ {0};
    uintptr_t waterLine_ {0};
    // This value is set in ConcurrentMark::InitializeMarking before post GC task, so do not need atomic store/load.
    uintptr_t freshObjectWaterLine_ {0};
    size_t currentFreeListLength_ {0};
    Region *currentAllocatorRegion_ {nullptr};
};

class SemiSpace : public LinearSpace {
public:
    SemiSpace(Heap *heap, size_t initialCapacity, size_t maximumCapacity);
    ~SemiSpace() override = default;
    NO_COPY_SEMANTIC(SemiSpace);
    NO_MOVE_SEMANTIC(SemiSpace);

    void Initialize() override;
    void Restart(size_t overShootSize = 0);
    size_t CalculateNewOverShootSize();
    bool CommittedSizeIsLarge();

    uintptr_t AllocateSync(size_t size);

    void SetOverShootSize(size_t size);
    void AddOverShootSize(size_t size);
    bool AdjustCapacity(size_t allocatedSizeSinceGC, JSThread *thread);
    void SetWaterLine(bool cmsGC);

    uintptr_t GetWaterLine() const
    {
        return waterLine_;
    }
    uintptr_t GetTop() const
    {
        return allocator_.GetTop();
    }
    size_t GetHeapObjectSize() const;
    size_t GetSurvivalObjectSize() const;
    size_t GetAllocatedSizeSinceGC(uintptr_t top = 0) const;

    bool SwapRegion(Region *region, SemiSpace *fromSpace);

private:
    static constexpr int GROWING_FACTOR = 2;
    Mutex lock_;
    size_t minimumCapacity_;
};

class SnapshotSpace : public LinearSpace {
public:
    SnapshotSpace(Heap *heap, size_t initialCapacity, size_t maximumCapacity);
    ~SnapshotSpace() override = default;
    NO_COPY_SEMANTIC(SnapshotSpace);
    NO_MOVE_SEMANTIC(SnapshotSpace);

    size_t GetHeapObjectSize() const
    {
        return liveObjectSize_;
    }

    void IncreaseLiveObjectSize(size_t size)
    {
        liveObjectSize_ += size;
    }

private:
    size_t liveObjectSize_ {0};
};

class ReadOnlySpace : public LinearSpace {
public:
    ReadOnlySpace(Heap *heap, size_t initialCapacity, size_t maximumCapacity,
        MemSpaceType type = MemSpaceType::READ_ONLY_SPACE);
    ~ReadOnlySpace() override = default;
    void SetReadOnly()
    {
        auto cb = [](Region *region) {
            region->SetReadOnlyAndMarked();
        };
        EnumerateRegions(cb);
    }

    void ClearReadOnly()
    {
        auto cb = [](Region *region) {
            region->ClearReadOnly();
        };
        EnumerateRegions(cb);
    }

    NO_COPY_SEMANTIC(ReadOnlySpace);
    NO_MOVE_SEMANTIC(ReadOnlySpace);
};
}  // namespace panda::ecmascript
#endif  // ECMASCRIPT_MEM_LINEAR_SPACE_H