/**
 * Copyright (c) 2021 - 2025 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 ES2PANDA_COMPILER_CORE_CODEGEN_H
#define ES2PANDA_COMPILER_CORE_CODEGEN_H

#include <cstdint>
#include "compiler/base/literals.h"
#include "compiler/core/ASTCompiler.h"
#include "compiler/core/regAllocator.h"
#include "compiler/core/regScope.h"
namespace ark::es2panda::compiler {
class CatchTable;
class DynamicContext;

enum class Constant {
    JS_NAN,
    JS_HOLE,
    JS_INFINITY,
    JS_UNDEFINED,
    JS_NULL,
    JS_TRUE,
    JS_FALSE,
    JS_SYMBOL,
    JS_GLOBAL,
};

class DebugInfo {
public:
    explicit DebugInfo(SArenaAllocator *allocator) : variableDebugInfo_(allocator->Adapter()) {};
    DEFAULT_COPY_SEMANTIC(DebugInfo);
    DEFAULT_MOVE_SEMANTIC(DebugInfo);
    ~DebugInfo() = default;

    SArenaVector<const varbinder::Scope *> &VariableDebugInfo()
    {
        return variableDebugInfo_;
    }

    const SArenaVector<const varbinder::Scope *> &VariableDebugInfo() const
    {
        return variableDebugInfo_;
    }

    const ir::Statement *FirstStatement() const
    {
        return firstStmt_;
    }

private:
    friend class CodeGen;

    SArenaVector<const varbinder::Scope *> variableDebugInfo_;
    const ir::Statement *firstStmt_ {};
};

class CodeGen {
public:
    using TypeMap = SArenaUnorderedMap<VReg, const checker::Type *>;

    explicit CodeGen(SArenaAllocator *allocator, RegSpiller *spiller, public_lib::Context *context,
                     std::tuple<varbinder::FunctionScope *, ProgramElement *, AstCompiler *> toCompile) noexcept
        : astCompiler_(std::get<AstCompiler *>(toCompile)),
          allocator_(allocator),
          context_(context),
          debugInfo_(allocator_),
          topScope_(std::get<varbinder::FunctionScope *>(toCompile)),
          scope_(topScope_),
          rootNode_(scope_->Node()),
          insns_(allocator_->Adapter()),
          catchList_(allocator_->Adapter()),
          typeMap_(allocator_->Adapter()),
          programElement_(std::get<ProgramElement *>(toCompile)),
          scopeInsnRanges_(allocator->Adapter()),
          sa_(this),
          ra_(this, spiller),
          rra_(this, spiller)
    {
        astCompiler_->SetCodeGen(this);
    }
    virtual ~CodeGen() = default;
    NO_COPY_SEMANTIC(CodeGen);
    NO_MOVE_SEMANTIC(CodeGen);
    virtual IRNode *AllocSpillMov(const ir::AstNode *node, VReg vd, VReg vs, OperandType type) = 0;

    [[nodiscard]] virtual IRNode *AllocMov(const ir::AstNode *node, VReg vd, VReg vs) = 0;
    [[nodiscard]] virtual IRNode *AllocMov(const ir::AstNode *node, OutVReg vd, VReg vs) = 0;

    [[nodiscard]] SArenaAllocator *Allocator() const noexcept;
    [[nodiscard]] const SArenaVector<CatchTable *> &CatchList() const noexcept;
    [[nodiscard]] const varbinder::FunctionScope *TopScope() const noexcept;
    [[nodiscard]] const varbinder::Scope *Scope() const noexcept;
    [[nodiscard]] const ir::AstNode *RootNode() const noexcept;

    [[nodiscard]] SArenaList<IRNode *> &Insns() noexcept;
    [[nodiscard]] const SArenaList<IRNode *> &Insns() const noexcept;

    [[nodiscard]] VReg AllocReg();
    [[nodiscard]] VReg AllocRegWithType(const checker::Type *type);
    [[nodiscard]] VReg NextReg() const noexcept;

    [[nodiscard]] std::uint32_t TotalRegsNum() const noexcept;
    [[nodiscard]] std::size_t LabelCount() const noexcept;
    [[nodiscard]] const DebugInfo &Debuginfo() const noexcept;
    [[nodiscard]] constexpr std::uint32_t IcSize() const noexcept
    {
        return 0U;
    }

    [[nodiscard]] bool IsDebug() const noexcept;
    [[nodiscard]] std::uint32_t ParamCount() const noexcept;
    [[nodiscard]] std::uint32_t FormalParametersCount() const noexcept;
    [[nodiscard]] std::uint32_t InternalParamCount() const noexcept;
    [[nodiscard]] const util::StringView &InternalName() const noexcept;
    [[nodiscard]] const util::StringView &FunctionName() const noexcept;
    [[nodiscard]] varbinder::VarBinder *VarBinder() const noexcept;

    [[nodiscard]] Label *AllocLabel();
    [[nodiscard]] std::uint32_t AddLiteralBuffer(LiteralBuffer &&buf);

    void LoadAccumulatorString(const ir::AstNode *node, const util::StringView &str);

    void SetLabel(const ir::AstNode *node, Label *label);
    void Branch(const ir::AstNode *node, class Label *label);
    [[nodiscard]] bool CheckControlFlowChange() const;
    Label *ControlFlowChangeBreak(const ir::Identifier *label = nullptr);
    [[nodiscard]] Label *ControlFlowChangeContinue(const ir::Identifier *label);

    uint32_t TryDepth() const;
    [[nodiscard]] CatchTable *CreateCatchTable(util::StringView exceptionType = "");
    [[nodiscard]] CatchTable *CreateCatchTable(LabelPair tryLabelPair, util::StringView exceptionType = "");
    void SortCatchTables();

    void SetFirstStmt(const ir::Statement *stmt) noexcept;

    [[noreturn]] static void Unimplemented();

    void SetVRegType(VReg vreg, const checker::Type *type);

    [[nodiscard]] virtual const checker::Type *GetVRegType(VReg vreg) const;

    [[nodiscard]] public_lib::Context *Context() const noexcept;

    [[nodiscard]] virtual checker::Type const *TypeForVar(varbinder::Variable const *var) const noexcept;

    [[nodiscard]] uint32_t GetSpillDebugOffset() const noexcept;

    compiler::AstCompiler *GetAstCompiler() const;

    const SArenaList<IRNode *> &GetInsns() const noexcept
    {
        return insns_;
    }

    void SetInsns(const SArenaList<IRNode *> &insns)
    {
        insns_ = insns;
    }

    void AddSpillRegsToUsedRegs(VReg::Index spillRegs)
    {
        ES2PANDA_ASSERT(totalRegs_ >= spillRegs);
        totalRegs_ -= spillRegs;
        ES2PANDA_ASSERT(usedRegs_ >= spillRegs);
        usedRegs_ -= spillRegs;
        spillDebugOffset_ += spillRegs;
    }

    uint32_t GetRegsNum() const
    {
        uint32_t min = std::min(totalRegs_, usedRegs_);
        ES2PANDA_ASSERT(min <= VReg::REG_START);
        return static_cast<uint32_t>(VReg::REG_START - min);
    }

    void FinalizeRegAllocation()
    {
        Ra().AdjustInsRegWhenHasSpill();
    }

    auto &ScopeInsnRange(varbinder::Scope const *scope)
    {
        return scopeInsnRanges_.insert({scope, {}}).first->second;
    }

    auto const &ScopeInsnRange(varbinder::Scope const *scope) const
    {
        auto it = scopeInsnRanges_.find(scope);
        ES2PANDA_ASSERT(it->first);
        return it->second;
    }

protected:
    [[nodiscard]] SimpleAllocator &Sa() noexcept;
    [[nodiscard]] const SimpleAllocator &Sa() const noexcept;
    [[nodiscard]] RegAllocator &Ra() noexcept;
    [[nodiscard]] const RegAllocator &Ra() const noexcept;
    [[nodiscard]] RangeRegAllocator &Rra() noexcept;
    [[nodiscard]] const RangeRegAllocator &Rra() const noexcept;
    [[nodiscard]] ProgramElement *ProgElement() const noexcept;
    [[nodiscard]] TypeMap &GetTypeMap() noexcept;
    [[nodiscard]] const TypeMap &GetTypeMap() const noexcept;

private:
    AstCompiler *astCompiler_;
    SArenaAllocator *allocator_ {};
    public_lib::Context *context_ {};
    DebugInfo debugInfo_;
    varbinder::FunctionScope *topScope_ {};
    varbinder::Scope *scope_ {};
    const ir::AstNode *rootNode_ {};
    // NOTE(mbolshov): Refector to ArenaDeque
    SArenaList<IRNode *> insns_;
    SArenaVector<CatchTable *> catchList_;
    TypeMap typeMap_;
    ProgramElement *programElement_ {};
    DynamicContext *dynamicContext_ {};
    SArenaUnorderedMap<varbinder::Scope const *, std::pair<const compiler::IRNode *, const compiler::IRNode *>>
        scopeInsnRanges_;

    SimpleAllocator sa_;
    RegAllocator ra_;
    RangeRegAllocator rra_;
    std::size_t labelId_ {0};
    std::uint32_t literalBufferIdx_ {0};

    std::uint32_t usedRegs_ {VReg::REG_START};
    std::uint32_t totalRegs_ {VReg::REG_START};
    std::uint32_t spillDebugOffset_ {0};
    friend class ScopeContext;
    friend class RegScope;
    friend class LocalRegScope;
    friend class LoopRegScope;
    friend class ParamRegScope;
    friend class FunctionRegScope;
    friend class DynamicContext;
};
}  // namespace ark::es2panda::compiler

#endif