/*
 * 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_COMPILER_ASSEMBLER_AARCH64_H
#define ECMASCRIPT_COMPILER_ASSEMBLER_AARCH64_H

#include <map>

#include "ecmascript/compiler/assembler/assembler.h"
#include "ecmascript/compiler/assembler/aarch64/register_aarch64.h"

namespace panda::ecmascript::aarch64 {

class Immediate {
public:
    Immediate(int64_t value) : value_(value) {}
    ~Immediate() = default;

    int64_t Value() const
    {
        return value_;
    }
private:
    int64_t value_;
};

class LogicalImmediate {
public:
    static LogicalImmediate Create(uint64_t imm, int width);
    int Value() const
    {
        ASSERT(IsValid());
        return imm_;
    }

    bool IsValid() const
    {
        return imm_ != InvalidLogicalImmediate;
    }

    bool Is64bit() const
    {
        return imm_ & BITWISE_OP_N_MASK;
    }
private:
    explicit LogicalImmediate(int value)
        : imm_(value)
    {
    }
    static const int InvalidLogicalImmediate = -1;
    int imm_;
};

class Operand {
public:
    Operand(Immediate imm)
        : reg_(invalidReg), extend_(Extend::NO_EXTEND), shift_(Shift::NO_SHIFT),
          shiftAmount_(0), immediate_(imm)
    {
    }
    Operand(Register reg, Shift shift = Shift::LSL, uint8_t shift_amount = 0)
        : reg_(reg), extend_(Extend::NO_EXTEND), shift_(shift), shiftAmount_(shift_amount), immediate_(0)
    {
    }
    Operand(Register reg, Extend extend, uint8_t shiftAmount = 0)
        : reg_(reg), extend_(extend), shift_(Shift::NO_SHIFT), shiftAmount_(shiftAmount), immediate_(0)
    {
    }
    ~Operand() = default;

    inline bool IsImmediate() const
    {
        return !reg_.IsValid();
    }

    inline bool IsShifted() const
    {
        return reg_.IsValid() && shift_ != Shift::NO_SHIFT;
    }

    inline bool IsExtended() const
    {
        return reg_.IsValid() && extend_ != Extend::NO_EXTEND;
    }

    inline Register Reg() const
    {
        return reg_;
    }

    inline Shift GetShiftOption() const
    {
        return shift_;
    }

    inline Extend GetExtendOption() const
    {
        return extend_;
    }

    inline uint8_t GetShiftAmount() const
    {
        return shiftAmount_;
    }

    inline int64_t ImmediateValue() const
    {
        return immediate_.Value();
    }

    inline Immediate GetImmediate() const
    {
        return immediate_;
    }
private:
    Register reg_;
    Extend  extend_;
    Shift  shift_;
    uint8_t  shiftAmount_;
    Immediate immediate_;
};

class MemoryOperand {
public:
    MemoryOperand(Register base, Register offset, Extend extend, uint8_t  shiftAmount = 0)
        : base_(base), offsetReg_(offset), offsetImm_(0), addrmod_(AddrMode::OFFSET),
          extend_(extend), shift_(Shift::NO_SHIFT), shiftAmount_(shiftAmount)
    {
    }
    MemoryOperand(Register base, Register offset, Shift shift = Shift::NO_SHIFT, uint8_t  shiftAmount = 0)
        : base_(base), offsetReg_(offset), offsetImm_(0), addrmod_(AddrMode::OFFSET),
          extend_(Extend::NO_EXTEND), shift_(shift), shiftAmount_(shiftAmount)
    {
    }
    MemoryOperand(Register base, int64_t offset, AddrMode addrmod = AddrMode::OFFSET)
        : base_(base), offsetReg_(invalidReg), offsetImm_(offset), addrmod_(addrmod),
          extend_(Extend::NO_EXTEND), shift_(Shift::NO_SHIFT), shiftAmount_(0)
    {
    }
    ~MemoryOperand() = default;

    Register GetRegBase() const
    {
        return base_;
    }

    bool IsImmediateOffset() const
    {
        return !offsetReg_.IsValid();
    }

    Immediate GetImmediate() const
    {
        return offsetImm_;
    }

    AddrMode GetAddrMode() const
    {
        return addrmod_;
    }

    Extend GetExtendOption() const
    {
        return extend_;
    }

    Shift GetShiftOption() const
    {
        return shift_;
    }

    uint8_t GetShiftAmount() const
    {
        return shiftAmount_;
    }

    Register GetRegisterOffset() const
    {
        return offsetReg_;
    }
private:
    Register base_;
    Register offsetReg_;
    Immediate offsetImm_;
    AddrMode addrmod_;
    Extend extend_;
    Shift shift_;
    uint8_t shiftAmount_;
};

class AssemblerAarch64 : public Assembler {
public:
    explicit AssemblerAarch64(Chunk *chunk)
        : Assembler(chunk)
    {
    }
    void Ldp(const Register &rt, const Register &rt2, const MemoryOperand &operand);
    void Stp(const Register &rt, const Register &rt2, const MemoryOperand &operand);
    void Ldp(const VRegister &vt, const VRegister &vt2, const MemoryOperand &operand);
    void Stp(const VRegister &vt, const VRegister &vt2, const MemoryOperand &operand);
    void Ldr(const Register &rt, const MemoryOperand &operand);
    void Ldr(const VRegister &vt, const MemoryOperand &operand);  // Load SIMD&FP from memory
    void Ldrh(const Register &rt, const MemoryOperand &operand);
    void Ldrb(const Register &rt, const MemoryOperand &operand);
    void Str(const Register &rt, const MemoryOperand &operand);
    void Ldur(const Register &rt, const MemoryOperand &operand);
    void Stur(const Register &rt, const MemoryOperand &operand);
    void Mov(const Register &rd, const Immediate &imm);
    void Mov(const Register &rd, const Register &rm);
    void Movz(const Register &rd, uint64_t imm, int shift);
    void Movk(const Register &rd, uint64_t imm, int shift);
    void Movn(const Register &rd, uint64_t imm, int shift);
    // Floating-point/Vector register operations
    void Mov(const VRegister &vd, const VRegister &vn);
    void Fmov(const VRegister &vd, const Register &rn);  // FMOV Dd, Xn - move from GP to V register
    void Orr(const Register &rd, const Register &rn, const LogicalImmediate &imm);
    void Orr(const Register &rd, const Register &rn, const Operand &operand);
    void And(const Register &rd, const Register &rn, const Operand &operand);
    void Ands(const Register &rd, const Register &rn, const Operand &operand);
    void And(const Register &rd, const Register &rn, const LogicalImmediate &imm);
    void Ands(const Register &rd, const Register &rn, const LogicalImmediate &imm);
    void Lsr(const Register &rd, const Register &rn, unsigned shift);
    void Lsl(const Register &rd, const Register &rn, const Register &rm);
    void Lsr(const Register &rd, const Register &rn, const Register &rm);
    void Ubfm(const Register &rd, const Register &rn, unsigned immr, unsigned imms);
    void Bfm(const Register &rd, const Register &rn, unsigned immr, unsigned imms);

    void Adr(const Register &rd, Label *label);
    void Adr(const Register &rd, int32_t imm);
    void Add(const Register &rd, const Register &rn, const Operand &operand);
    void Adds(const Register &rd, const Register &rn, const Operand &operand);
    void Sub(const Register &rd, const Register &rn, const Operand &operand);
    void Subs(const Register &rd, const Register &rn, const Operand &operand);
    void Cmp(const Register &rd, const Operand &operand);
    void CMov(const Register &rd, const Register &rn, const Operand &operand, Condition cond);
    void B(Label *label);
    void B(int32_t imm);
    void B(Condition cond, Label *label);
    void B(Condition cond, int32_t imm);
    void Br(const Register &rn);
    void Blr(const Register &rn);
    void Bl(Label *label);
    void Bl(int32_t imm);
    void Cbz(const Register &rt, int32_t imm);
    void Cbz(const Register &rt, Label *label);
    void Cbnz(const Register &rt, int32_t imm);
    void Cbnz(const Register &rt, Label *label);
    void Tbz(const Register &rt, int32_t bitPos, Label *label);
    void Tbz(const Register &rt, int32_t bitPos, int32_t imm);
    void Tbnz(const Register &rt, int32_t bitPos, Label *label);
    void Tbnz(const Register &rt, int32_t bitPos, int32_t imm);
    void Tst(const Register &rn, const Operand &operand);
    void Tst(const Register &rn, const LogicalImmediate &imm);
    void Ret();
    void Ret(const Register &rn);
    void Brk(const Immediate &imm);
    void Bind(Label *target);
private:
    // common reg field defines
    inline uint32_t Rd(uint32_t id)
    {
        return (id << COMMON_REG_Rd_LOWBITS) & COMMON_REG_Rd_MASK;
    }

    inline uint32_t Rn(uint32_t id)
    {
        return (id << COMMON_REG_Rn_LOWBITS) & COMMON_REG_Rn_MASK;
    }

    inline uint32_t Rm(uint32_t id)
    {
        return (id << COMMON_REG_Rm_LOWBITS) & COMMON_REG_Rm_MASK;
    }

    inline uint32_t Rt(uint32_t id)
    {
        return (id << COMMON_REG_Rt_LOWBITS) & COMMON_REG_Rt_MASK;
    }

    inline uint32_t Rt2(uint32_t id)
    {
        return (id << COMMON_REG_Rt2_LOWBITS) & COMMON_REG_Rt2_MASK;
    }

    inline uint32_t Sf(uint32_t id)
    {
        return (id << COMMON_REG_Sf_LOWBITS) & COMMON_REG_Sf_MASK;
    }

    inline uint32_t LoadAndStorePairImm(uint32_t imm)
    {
        return (((imm) << LDP_STP_Imm7_LOWBITS) & LDP_STP_Imm7_MASK);
    }

    inline uint32_t LoadAndStoreImm(uint32_t imm, bool isSigned)
    {
        if (isSigned) {
            return (imm << LDR_STR_Imm9_LOWBITS) & LDR_STR_Imm9_MASK;
        } else {
            return (imm << LDR_STR_Imm12_LOWBITS) & LDR_STR_Imm12_MASK;
        }
    }

    inline uint32_t BranchImm19(uint32_t imm)
    {
        return (imm << BRANCH_Imm19_LOWBITS) & BRANCH_Imm19_MASK;
    }

    bool IsAddSubImm(uint64_t imm);
    void AddSubImm(AddSubOpCode op, const Register &rd, const Register &rn, bool setFlags, uint64_t imm);
    void AddSubReg(AddSubOpCode op, const Register &rd, const Register &rn, bool setFlags, const Operand &operand);
    void MovWide(uint32_t op, const Register &rd, uint64_t imm, int shift);
    void BitWiseOpImm(BitwiseOpCode op, const Register &rd, const Register &rn, uint64_t imm);
    void BitWiseOpShift(BitwiseOpCode op, const Register &rd, const Register &rn, const Operand &operand);
    bool TrySequenceOfOnes(const Register &rd, uint64_t imm);
    bool TryReplicateHWords(const Register &rd, uint64_t imm);
    void EmitMovInstruct(const Register &rd, uint64_t imm,
                         unsigned int allOneHWords, unsigned int allZeroHWords);
    int32_t GetLinkOffsetFromBranchInst(int32_t pos);
    int32_t LinkAndGetInstOffsetToLabel(Label *label);
    int32_t ImmBranch(uint32_t branchCode);
    void SetRealOffsetToBranchInst(uint32_t linkPos, int32_t disp);
    void Ldr(const Register &rt, const MemoryOperand &operand, Scale scale);
    uint64_t GetImmOfLdr(const MemoryOperand &operand, Scale scale, bool isRegX);
    uint64_t GetOpcodeOfLdr(const MemoryOperand &operand, Scale scale);
    uint32_t GetShiftOfLdr(const MemoryOperand &operand, Scale scale, bool isRegX);
};
}  // namespace panda::ecmascript::aarch64
#endif