/*
 * Copyright (c) 2023 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.
 */
#include "ecmascript/compiler/gate_accessor.h"
#include "ecmascript/compiler/share_opcodes.h"
#include "ecmascript/compiler/verifier.h"
#include "ecmascript/compiler/typed_bytecode_lowering.h"
#include "ecmascript/compiler/typed_hcr_lowering.h"
#include "ecmascript/pgo_profiler/types/pgo_profiler_type.h"
#include "ecmascript/elements.h"
#include "ecmascript/mem/concurrent_marker.h"
#include "ecmascript/mem/native_area_allocator.h"
#include "ecmascript/tests/test_helper.h"
#include "gtest/gtest.h"
#include <cstddef>

namespace panda::test {
class MetaDataEqualTests : public testing::Test {};

using ecmascript::kungfu::Circuit;
using ecmascript::kungfu::CircuitBuilder;
using ecmascript::kungfu::EcmaOpcode;
using ecmascript::kungfu::ElementsKind;
using ecmascript::kungfu::Environment;
using ecmascript::kungfu::GateAccessor;
using ecmascript::kungfu::GateMetaData;
using ecmascript::kungfu::GateType;
using ecmascript::kungfu::JSBytecodeMetaData;
using ecmascript::kungfu::MachineType;
using ecmascript::kungfu::PGOTypeRef;
using ecmascript::kungfu::PGOSampleType;
using ecmascript::kungfu::TypedBinOp;
using ecmascript::kungfu::TypedCallTargetCheckOp;

HWTEST_F_L0(MetaDataEqualTests, StringMetaDataEqualTest)
{
    // construct a circuit
    ecmascript::NativeAreaAllocator allocator;
    Circuit circuit(&allocator);
    ecmascript::Chunk chunk(&allocator);
    GateAccessor acc(&circuit);
    CircuitBuilder builder(&circuit);
    Environment env(0, &builder);
    builder.SetEnvironment(&env);

    auto stringGate1 = circuit.GetConstantStringGate(MachineType::ARCH, "test1", GateType::NJSValue());


    auto stringGate2 = circuit.GetConstantStringGate(MachineType::ARCH, "test2", GateType::NJSValue());

    EXPECT_FALSE(acc.MetaDataValueEqu(stringGate1, stringGate2));

    auto stringGate3 = circuit.GetConstantStringGate(MachineType::ARCH, "test1", GateType::NJSValue());

    EXPECT_TRUE(acc.MetaDataValueEqu(stringGate1, stringGate3));
}

HWTEST_F_L0(MetaDataEqualTests, ConstantMetaDataEqualTest)
{
    // construct a circuit
    ecmascript::NativeAreaAllocator allocator;
    Circuit circuit(&allocator);
    ecmascript::Chunk chunk(&allocator);
    GateAccessor acc(&circuit);
    CircuitBuilder builder(&circuit);
    Environment env(0, &builder);
    builder.SetEnvironment(&env);


    auto constantValue1 = circuit.GetConstantGate(MachineType::I64, 2, GateType::NJSValue());

    auto constantValue2 = circuit.GetConstantGate(MachineType::I64, 2, GateType::NJSValue());

    EXPECT_TRUE(acc.MetaDataValueEqu(constantValue1, constantValue2));

    auto constantValue3 = circuit.GetConstantGate(MachineType::I64, 2, GateType::NJSValue());

    auto constantValue4 = circuit.GetConstantGate(MachineType::I64, 3, GateType::NJSValue());

    EXPECT_FALSE(acc.MetaDataValueEqu(constantValue3, constantValue4));


    // MetaData is equal, but Gate is not equal
    auto constantValue5 = circuit.GetConstantGate(MachineType::I64, 2, GateType::NJSValue());

    auto constantValue6 = circuit.GetConstantGate(MachineType::I32, 2, GateType::NJSValue());

    EXPECT_TRUE(acc.MetaDataValueEqu(constantValue5, constantValue6));

    // MetaData is equal, but Gate is not equal
    auto ConstGateNJSValue2 = circuit.GetConstantGate(MachineType::I64, 2, GateType::NJSValue());

    auto ConstGateUndefined =
        circuit.GetConstantGate(MachineType::I64, JSTaggedValue::VALUE_UNDEFINED, GateType::UndefinedType());

    EXPECT_TRUE(acc.MetaDataValueEqu(ConstGateNJSValue2, ConstGateUndefined));
}

HWTEST_F_L0(MetaDataEqualTests, TypeErrorMetaDataEqualTest)
{
    // construct a circuit
    ecmascript::NativeAreaAllocator allocator;
    Circuit circuit(&allocator);
    ecmascript::Chunk chunk(&allocator);
    GateAccessor acc(&circuit);
    CircuitBuilder builder(&circuit);
    Environment env(0, &builder);
    builder.SetEnvironment(&env);

    auto constantValue1 = circuit.GetConstantGate(MachineType::I64, 2, GateType::NJSValue());
    auto stringGate1 = circuit.GetConstantStringGate(MachineType::ARCH, "test1", GateType::NJSValue());
    EXPECT_FALSE(acc.MetaDataValueEqu(constantValue1, stringGate1));
    EXPECT_FALSE(acc.MetaDataValueEqu(stringGate1, constantValue1));
}

HWTEST_F_L0(MetaDataEqualTests, HCRMetaDataEqualTest)
{
    // construct a circuit
    ecmascript::NativeAreaAllocator allocator;
    Circuit circuit(&allocator);
    ecmascript::Chunk chunk(&allocator);
    GateAccessor acc(&circuit);
    CircuitBuilder builder(&circuit);
    Environment env(0, &builder);
    builder.SetEnvironment(&env);

    // JSBytecodeMetaData
    auto meta = circuit.JSBytecode(0, 0, EcmaOpcode::JEQZ_IMM8, 0, 0, true, false);
    auto gate =
        circuit.NewGate(meta, MachineType::I64, {Circuit::NullGate(), Circuit::NullGate()}, GateType::AnyType());
    auto meta2 = circuit.JSBytecode(0, 0, EcmaOpcode::JEQZ_IMM8, 0, 0, true, false);
    auto gate2 =
        circuit.NewGate(meta2, MachineType::I64, {Circuit::NullGate(), Circuit::NullGate()}, GateType::AnyType());

    EXPECT_TRUE(acc.MetaDataValueEqu(gate, gate2));

    static_cast<JSBytecodeMetaData *>(const_cast<GateMetaData *>(meta))->SetElementsKind(ElementsKind::NUMBER);
    static_cast<JSBytecodeMetaData *>(const_cast<GateMetaData *>(meta2))->SetElementsKind(ElementsKind::NUMBER);
    EXPECT_TRUE(acc.MetaDataValueEqu(gate, gate2));

    PGOSampleType type = PGOSampleType::CreateProfileType(0, 0);
    PGOSampleType type2 = PGOSampleType::CreateProfileType(0, 0);
    static_cast<JSBytecodeMetaData *>(const_cast<GateMetaData *>(meta))
        ->SetType(PGOTypeRef(static_cast<const PGOSampleType *>(&type)));
    static_cast<JSBytecodeMetaData *>(const_cast<GateMetaData *>(meta2))
        ->SetType(PGOTypeRef(static_cast<const PGOSampleType *>(&type2)));
    EXPECT_FALSE(acc.MetaDataValueEqu(gate, gate2));

    PGOSampleType type3 = PGOSampleType::CreateProfileType(0, 1);
    static_cast<JSBytecodeMetaData *>(const_cast<GateMetaData *>(meta))
        ->SetType(PGOTypeRef(static_cast<const PGOSampleType *>(&type)));
    static_cast<JSBytecodeMetaData *>(const_cast<GateMetaData *>(meta2))
        ->SetType(PGOTypeRef(static_cast<const PGOSampleType *>(&type3)));
    EXPECT_FALSE(acc.MetaDataValueEqu(gate, gate2));

    static_cast<JSBytecodeMetaData *>(const_cast<GateMetaData *>(meta))->SetElementsKind(ElementsKind::NUMBER);
    static_cast<JSBytecodeMetaData *>(const_cast<GateMetaData *>(meta2))->SetElementsKind(ElementsKind::HOLE_NUMBER);
    EXPECT_FALSE(acc.MetaDataValueEqu(gate, gate2));
}

HWTEST_F_L0(MetaDataEqualTests, MCRMetaDataEqualTest)
{
    // construct a circuit
    ecmascript::NativeAreaAllocator allocator;
    Circuit circuit(&allocator);
    ecmascript::Chunk chunk(&allocator);
    GateAccessor acc(&circuit);
    CircuitBuilder builder(&circuit);
    Environment env(0, &builder);
    builder.SetEnvironment(&env);

    // TypedCallMetaData
    auto callGate =
        circuit.NewGate(circuit.TypedCall(0, 0, true), MachineType::I64,
                        {Circuit::NullGate(), Circuit::NullGate(), Circuit::NullGate()}, GateType::AnyType());

    auto callGate2 =
        circuit.NewGate(circuit.TypedCall(0, 0, true), MachineType::I64,
                        {Circuit::NullGate(), Circuit::NullGate(), Circuit::NullGate()}, GateType::AnyType());

    EXPECT_TRUE(acc.MetaDataValueEqu(callGate, callGate2));
    EXPECT_TRUE(acc.MetaDataValueEqu(callGate2, callGate));

    // TypedCallTargetCheckMetaData
    auto callGate3 =
        circuit.NewGate(circuit.TypedCallTargetCheckOp(0, 0, TypedCallTargetCheckOp::JSCALLTHIS_FAST), MachineType::I64,
                        {Circuit::NullGate(), Circuit::NullGate(), Circuit::NullGate()}, GateType::AnyType());
    auto callGate4 =
        circuit.NewGate(circuit.TypedCallTargetCheckOp(0, 0, TypedCallTargetCheckOp::JSCALLTHIS_FAST), MachineType::I64,
                        {Circuit::NullGate(), Circuit::NullGate(), Circuit::NullGate()}, GateType::AnyType());

    EXPECT_TRUE(acc.MetaDataValueEqu(callGate3, callGate4));
    EXPECT_TRUE(acc.MetaDataValueEqu(callGate4, callGate3));

    // TypedBinaryMetaData
    PGOSampleType type5 = PGOSampleType::CreateProfileType(0, 1);
    auto callGate5 = circuit.NewGate(
        circuit.TypedBinaryOp(0, TypedBinOp::TYPED_ADD, PGOTypeRef(static_cast<const PGOSampleType *>(&type5))),
        MachineType::I64, { Circuit::NullGate(), Circuit::NullGate(), Circuit::NullGate(), Circuit::NullGate() },
        GateType::AnyType());

    // TypedBinaryMetaData
    PGOSampleType type6 = PGOSampleType::CreateProfileType(0, 1);
    auto callGate6 = circuit.NewGate(
        circuit.TypedBinaryOp(0, TypedBinOp::TYPED_ADD, PGOTypeRef(static_cast<const PGOSampleType *>(&type6))),
        MachineType::I64, { Circuit::NullGate(), Circuit::NullGate(), Circuit::NullGate(), Circuit::NullGate() },
        GateType::AnyType());

    EXPECT_FALSE(acc.MetaDataValueEqu(callGate5, callGate6));
    EXPECT_FALSE(acc.MetaDataValueEqu(callGate6, callGate5));
}


} // namespace panda::test