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

#include <iostream>
#include <string>

#include <gtest/gtest.h>
#include "disassembler.h"

using namespace panda::disasm;

#cmakedefine DISASM_BIN_DIR "@DISASM_BIN_DIR@/"

TEST(instructions_test, test_language_panda_assembly)
{
    Disassembler d {};

    std::stringstream ss {};
    d.Disassemble(std::string(DISASM_BIN_DIR) + "empty_record.bc");
    d.Serialize(ss);

    EXPECT_TRUE(ss.str().find(".language PandaAssembly") != std::string::npos);
}

TEST(instructions_test, test_ins)
{
    Disassembler d {};

    std::stringstream ss {};
    d.Disassemble(std::string(DISASM_BIN_DIR) + "instructions.bc");
    d.Serialize(ss);

    size_t beg_g = ss.str().find("g() <static> {");
    size_t end_g = ss.str().find('}', beg_g);

    ASSERT_TRUE(beg_g != std::string::npos && end_g != std::string::npos) << "function g not found";

    std::string body_g = ss.str().substr(beg_g + strlen("g() {"), end_g - (beg_g + strlen("g() {")));

    EXPECT_TRUE(body_g.find("\tmov v0, v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tmov.64 v2, v3") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tmov.obj v4, v5") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tmovi v0, 0xffffffffffffffff") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tmovi.64 v0, 0x2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tfmovi.64 v0, 0x4008147ae147ae14") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tlda v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tlda.64 v0") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tlda.obj v1") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tldai 0x1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tldai.64 0x2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tfldai.64 0x4008147ae147ae14") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tlda.str \"kek\"") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tlda.type A") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tlda.null") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tsta v0") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tsta.64 v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tsta.obj v2") != std::string::npos);

    EXPECT_TRUE(body_g.find("jump_label_0:\n\tjmp jump_label_0") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tjeq v1, jump_label_1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tldai 0x1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tjmp jump_label_2") != std::string::npos);
    EXPECT_TRUE(body_g.find("jump_label_1:\n\tldai 0x0") != std::string::npos);
    EXPECT_TRUE(body_g.find("jump_label_2:\n\tcmp.64 v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tucmp v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tucmp.64 v3") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tfcmpl.64 v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tfcmpg.64 v1") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tjeqz jump_label_0") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tjnez jump_label_0") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tjltz jump_label_0") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tjgtz jump_label_0") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tjlez jump_label_0") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tjgez jump_label_0") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tjeq v2, jump_label_0") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tjne v2, jump_label_0") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tjlt v2, jump_label_0") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tjgt v2, jump_label_0") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tjle v2, jump_label_0") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tjge v2, jump_label_0") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tfadd2.64 v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tfsub2.64 v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tfmul2.64 v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tfdiv2.64 v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tfmod2.64 v1") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tadd2 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tadd2.64 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tsub2 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tsub2.64 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tmul2 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tmul2.64 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tand2 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tand2.64 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tor2 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tor2.64 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\txor2 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\txor2.64 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tshl2 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tshl2.64 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tshr2 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tshr2.64 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tashr2 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tashr2.64 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tdiv2 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tdiv2.64 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tmod2 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tmod2.64 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tdivu2 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tdivu2.64 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tmodu2 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tmodu2.64 v2") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tadd v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tsub v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tmul v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tand v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tor v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\txor v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tshl v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tshr v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tashr v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tdiv v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tmod v1, v2") != std::string::npos);

    EXPECT_TRUE(body_g.find("\taddi 0x1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tsubi 0x1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tmuli 0x1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tandi 0x1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tori 0x1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\txori 0x1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tshli 0x1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tshri 0x1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tashri 0x1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tdivi 0x1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tmodi 0x1") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tneg") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tneg.64") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tnot") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tnot.64") != std::string::npos);

    EXPECT_TRUE(body_g.find("\ti32tof64") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tu32tof64") != std::string::npos);
    EXPECT_TRUE(body_g.find("\ti64tof64") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tu64tof64") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tf64toi32") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tf64toi64") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tf64tou32") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tf64tou64") != std::string::npos);
    EXPECT_TRUE(body_g.find("\ti32toi64") != std::string::npos);
    EXPECT_TRUE(body_g.find("\ti64toi32") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tu32toi64") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tldarr.8 v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tldarru.8 v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tldarr.16 v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tldarru.16 v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tldarr v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tldarr.64 v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tfldarr.32 v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tfldarr.64 v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tldarr.obj v1") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tstarr.8 v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tstarr.16 v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tstarr v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tstarr.64 v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tfstarr.32 v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tfstarr.64 v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tstarr.obj v1, v2") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tnewobj v6, A") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tinitobj.short A.init:()") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tldobj v0, A.kek") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tldobj.64 v0, A.kek") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tldobj.obj v0, A.kek") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tstobj v1, A.kek") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tstobj.64 v1, A.kek") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tstobj.obj v1, A.kek") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tldstatic A.kek") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tldstatic.64 A.kek") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tldstatic.obj A.kek") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tststatic A.kek") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tststatic.64 A.kek") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tststatic.obj A.kek") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tcheckcast A") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tisinstance A") != std::string::npos);
}

TEST(instructions_test, test_calls)
{
    Disassembler d {};

    std::stringstream ss {};
    d.Disassemble(std::string(DISASM_BIN_DIR) + "calls.bc");
    d.Serialize(ss);

    size_t beg_g = ss.str().find("g(u1 a0) <static> {");
    size_t end_g = ss.str().find('}', beg_g);

    ASSERT_TRUE(beg_g != std::string::npos && end_g != std::string::npos) << "function g not found";

    std::string body_g =
        ss.str().substr(beg_g + strlen("g(u1 a0) <static> {"), end_g - (beg_g + strlen("g(u1 a0) <static> {")));

    EXPECT_TRUE(body_g.find("\tcall.virt.short B.Bhandler_unspec:(B), v4") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tcall.virt.short B.Bhandler_short:(B,u1), v4, v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tcall.virt B.Bhandler_short2:(B,u1[],i64), v4, v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tcall.virt B.Bhandler_long:(B,i8,i16,i32), v4, v0, v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tcall.virt.range B.Bhandler_range:(B,i8,i16,i32,i8,i16,i32), v4") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tcall.short handler_unspec:()") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tcall.short handler_short:(u1), v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tcall.short handler_short2:(u1,i64), v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tcall handler_long:(i8,i16,i32), v0, v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tcall handler_long2:(i8,i16,i32,f64), v0, v1, v2, v3") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tcall.range handler_range:(i8,i16,i32,i8,i16,i32), v0") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tinitobj.short B.Bhandler_unspec:(B)") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tinitobj.short B.Bhandler_short:(B,u1), v1") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tinitobj.short B.Bhandler_short2:(B,u1[],i64), v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tinitobj B.Bhandler_long:(B,i8,i16,i32), v0, v1, v2") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tinitobj B.Bhandler_long2:(B,i8,i16,i32,i64), v0, v1, v2, v3") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tinitobj.range B.Bhandler_range:(B,i8,i16,i32,i8,i16,i32), v0") != std::string::npos);

    EXPECT_TRUE(body_g.find("\tcall.acc.short handler_short:(u1), v0, 0x0") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tcall.acc.short handler_short2:(u1,i64), a0, 0x1") != std::string::npos);

    EXPECT_TRUE(
        ss.str().find(".function u16 long_function(i8 a0, i16 a1, i32 a2, i8 a3, i16 a4, i32 a5, i64 a6, f32 a7)") !=
        std::string::npos);

    EXPECT_TRUE(body_g.find("\tcalli.dyn.short 0x1, v0") != std::string::npos);
}

TEST(instructions_test, test_returns)
{
    Disassembler d {};

    std::stringstream ss {};
    d.Disassemble(std::string(DISASM_BIN_DIR) + "returns.bc");
    d.Serialize(ss);

    EXPECT_TRUE(ss.str().find("\treturn") != std::string::npos);
    EXPECT_TRUE(ss.str().find("\treturn.64") != std::string::npos);
    EXPECT_TRUE(ss.str().find("\treturn.obj") != std::string::npos);
    EXPECT_TRUE(ss.str().find("\treturn.void") != std::string::npos);
}

TEST(instructions_test, test_newarr)
{
    Disassembler d {};

    std::stringstream ss {};
    d.Disassemble(std::string(DISASM_BIN_DIR) + "newarrs.bc");
    d.Serialize(ss);

    size_t beg_g = ss.str().find("g(u1 a0) <static> {");
    size_t end_g = ss.str().find('}', beg_g);

    ASSERT_TRUE(beg_g != std::string::npos && end_g != std::string::npos) << "function g not found";

    std::string body_g = ss.str().substr(beg_g + strlen("g() {"), end_g - (beg_g + strlen("g() {")));

    EXPECT_TRUE(body_g.find("\tnewarr v0, a0, u1[]") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tnewarr v0, a0, i8[]") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tnewarr v0, a0, u8[]") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tnewarr v0, a0, i16[]") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tnewarr v0, a0, u16[]") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tnewarr v0, a0, i32[]") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tnewarr v0, a0, u32[]") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tnewarr v0, a0, f32[]") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tnewarr v0, a0, f64[]") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tnewarr v0, a0, i64[]") != std::string::npos);
    EXPECT_TRUE(body_g.find("\tnewarr v0, a0, u64[]") != std::string::npos);
}

TEST(instructions_test, test_debug_info)
{
    Disassembler d;

    std::stringstream ss;
    d.Disassemble(std::string(DISASM_BIN_DIR) + "instructions.bc");
    d.CollectInfo();
    d.Serialize(ss, true, true);

    size_t beg_g = ss.str().find("g() <static> {");
    size_t end_g = ss.str().find('}', beg_g);

    ASSERT_TRUE(beg_g != std::string::npos && end_g != std::string::npos) << "function g not found";

    std::string body_g = ss.str().substr(beg_g + strlen("g() {"), end_g - (beg_g + strlen("g() {")));

    ASSERT_NE(body_g.find("#   LINE_NUMBER_TABLE:"), std::string::npos);
    ASSERT_NE(body_g.find("#\tline 26: 0\n"), std::string::npos);

    size_t code_start = body_g.find("#   CODE:\n");
    ASSERT_NE(code_start, std::string::npos) << "Code section in function g not found";
    size_t code_end = body_g.find("\n\n");  // First gap in function body is code section end
    ASSERT_NE(code_end, std::string::npos) << "Gap after code section in function g not found";
    ASSERT_LT(code_start, code_end);
    std::string instructions =
        body_g.substr(code_start + strlen("#   CODE:\n"), code_end + 1 - (code_start + strlen("#   CODE:\n")));
    size_t instruction_count = std::count(instructions.begin(), instructions.end(), '\n');

    const ProgInfo &prog_info = d.GetProgInfo();
    auto g_it = prog_info.methods_info.find("g:()");
    ASSERT_NE(g_it, prog_info.methods_info.end());
    // In case of pandasm the table should contain entry on each instruction
    ASSERT_EQ(g_it->second.line_number_table.size(), instruction_count);

    // There should be no local variables for panda assembler
    ASSERT_EQ(body_g.find("#   LOCAL_VARIABLE_TABLE:"), std::string::npos);
}

#undef DISASM_BIN_DIR