#ifdef LLVM_AVAILABLE

#include "CPUTranslator.h"

#include "util/v128.hpp"
#include "util/logs.hpp"

LOG_CHANNEL(llvm_log, "LLVM");

llvm::LLVMContext g_llvm_ctx;

llvm::Value* peek_through_bitcasts(llvm::Value* arg)
{
	llvm::CastInst* i;

	while ((i = llvm::dyn_cast_or_null<llvm::CastInst>(arg)) && i->getOpcode() == llvm::Instruction::BitCast)
	{
		arg = i->getOperand(0);
	}

	return arg;
}

cpu_translator::cpu_translator(llvm::Module* _module, bool is_be)
    : m_context(g_llvm_ctx)
	, m_module(_module)
	, m_is_be(is_be)
{
	register_intrinsic("x86_pshufb", [&](llvm::CallInst* ci) -> llvm::Value*
	{
		const auto data0 = ci->getOperand(0);
		const auto index = ci->getOperand(1);
		const auto zeros = llvm::ConstantAggregateZero::get(get_type<u8[16]>());

		if (m_use_ssse3)
		{
#if defined(ARCH_X64)
			return m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::x86_ssse3_pshuf_b_128), {data0, index});
#elif defined(ARCH_ARM64)
			// Modified from sse2neon
			// movi    v2.16b, #143
			// and     v1.16b, v1.16b, v2.16b
			// tbl     v0.16b, { v0.16b }, v1.16b
			auto mask = llvm::ConstantInt::get(get_type<u8[16]>(), 0x8F);
			auto and_mask = llvm::ConstantInt::get(get_type<bool[16]>(), true);
			auto vec_len = llvm::ConstantInt::get(get_type<u32>(), 16);
			auto index_masked = m_ir->CreateCall(get_intrinsic<u8[16]>(llvm::Intrinsic::vp_and), {index, mask, and_mask, vec_len});
			return m_ir->CreateCall(get_intrinsic<u8[16]>(llvm::Intrinsic::aarch64_neon_tbl1), {data0, index_masked});
#else
#error "Unimplemented"
#endif
		}
		else
		{
			// Emulate PSHUFB (TODO)
			const auto mask = m_ir->CreateAnd(index, 0xf);
			const auto loop = llvm::BasicBlock::Create(m_context, "", m_ir->GetInsertBlock()->getParent());
			const auto prev = ci->getParent();
			const auto next = prev->splitBasicBlock(ci->getNextNode());

			llvm::cast<llvm::BranchInst>(m_ir->GetInsertBlock()->getTerminator())->setOperand(0, loop);

			llvm::Value* result;
			//m_ir->CreateBr(loop);
			m_ir->SetInsertPoint(loop);
			const auto i = m_ir->CreatePHI(get_type<u32>(), 2);
			const auto v = m_ir->CreatePHI(get_type<u8[16]>(), 2);
			i->addIncoming(m_ir->getInt32(0), prev);
			i->addIncoming(m_ir->CreateAdd(i, m_ir->getInt32(1)), loop);
			v->addIncoming(zeros, prev);
			result = m_ir->CreateInsertElement(v, m_ir->CreateExtractElement(data0, m_ir->CreateExtractElement(mask, i)), i);
			v->addIncoming(result, loop);
			m_ir->CreateCondBr(m_ir->CreateICmpULT(i, m_ir->getInt32(16)), loop, next);
			m_ir->SetInsertPoint(next->getFirstNonPHIIt());
			result = m_ir->CreateSelect(m_ir->CreateICmpSLT(index, zeros), zeros, result);

			return result;
		}
	});

	register_intrinsic("any_select_by_bit4", [&](llvm::CallInst* ci) -> llvm::Value*
	{
		const auto s = bitcast<s8[16]>(m_ir->CreateShl(bitcast<u64[2]>(ci->getOperand(0)), 3));;
		const auto a = bitcast<u8[16]>(ci->getOperand(1));
		const auto b = bitcast<u8[16]>(ci->getOperand(2));
		return m_ir->CreateSelect(m_ir->CreateICmpSLT(s, llvm::ConstantAggregateZero::get(get_type<s8[16]>())), b, a);
	});
}

void cpu_translator::initialize(llvm::LLVMContext& context, llvm::ExecutionEngine& engine)
{
	m_context = context;
	m_engine = &engine;

	auto cpu = m_engine->getTargetMachine()->getTargetCPU();

	if (cpu == "generic")
	{
		// Detection failed, try to guess
		cpu = fallback_cpu_detection();
	}

	// Test SSSE3 feature (TODO)
	if (cpu == "generic" ||
		cpu == "k8" ||
		cpu == "opteron" ||
		cpu == "athlon64" ||
		cpu == "athlon-fx" ||
		cpu == "k8-sse3" ||
		cpu == "opteron-sse3" ||
		cpu == "athlon64-sse3" ||
		cpu == "amdfam10" ||
		cpu == "barcelona")
	{
		m_use_ssse3 = false;
	}

	// Test AVX feature (TODO)
	if (cpu == "sandybridge" ||
		cpu == "ivybridge" ||
		cpu == "bdver1")
	{
		m_use_avx = true;
	}

	// Test FMA feature (TODO)
	if (cpu == "haswell" ||
		cpu == "broadwell" ||
		cpu == "skylake" ||
		cpu == "alderlake" ||
		cpu == "raptorlake" ||
		cpu == "meteorlake" ||
		cpu == "bdver2" ||
		cpu == "bdver3" ||
		cpu == "bdver4" ||
		cpu == "znver1" ||
		cpu == "znver2" ||
		cpu == "znver3" ||
		cpu == "arrowlake" ||
		cpu == "arrowlake-s" ||
		cpu == "lunarlake")
	{
		m_use_fma = true;
		m_use_avx = true;
	}

	// Test AVX-512 feature (TODO)
	if (cpu == "skylake-avx512" ||
		cpu == "cascadelake" ||
		cpu == "cannonlake" ||
		cpu == "cooperlake")
	{
		m_use_avx = true;
		m_use_fma = true;
		m_use_avx512 = true;
	}

	// Test VNNI feature (TODO)
	if (cpu == "cascadelake" ||
		cpu == "cooperlake" ||
		cpu == "alderlake" ||
		cpu == "raptorlake" ||
		cpu == "meteorlake" ||
		cpu == "arrowlake" ||
		cpu == "arrowlake-s" ||
		cpu == "lunarlake")
	{
		m_use_vnni = true;
	}

	// Test GFNI feature (TODO)
	if (cpu == "tremont" ||
		cpu == "gracemont" ||
		cpu == "alderlake" ||
		cpu == "raptorlake" ||
		cpu == "meteorlake" ||
		cpu == "arrowlake" ||
		cpu == "arrowlake-s" ||
		cpu == "lunarlake")
	{
		m_use_gfni = true;
	}

	// Test AVX-512_icelake features (TODO)
	if (cpu == "icelake" ||
		cpu == "icelake-client" ||
		cpu == "icelake-server" ||
		cpu == "tigerlake" ||
		cpu == "rocketlake" ||
		cpu == "sapphirerapids" ||
		(cpu.starts_with("znver") && cpu != "znver1" && cpu != "znver2" && cpu != "znver3"))
	{
		m_use_avx = true;
		m_use_fma = true;
		m_use_avx512 = true;
		m_use_avx512_icl = true;
		m_use_vnni = true;
		m_use_gfni = true;
	}

#ifdef ARCH_ARM64
	if (utils::has_dotprod())
	{
		m_use_dotprod = true;
	}

	if (utils::has_i8mm())
	{
		m_use_i8mm = true;
	}

	if (utils::has_sve() && utils::sve_length() == 128)
	{
		m_use_sve_128 = true;
	}

	if (utils::has_sve2() && utils::sve_length() == 128)
	{
		m_use_sve2_128 = true;
	}
#endif
}

llvm::Value* cpu_translator::bitcast(llvm::Value* val, llvm::Type* type, std::source_location src_loc) const
{
	uint s1 = type->getScalarSizeInBits();
	uint s2 = val->getType()->getScalarSizeInBits();

	if (type->isVectorTy())
		s1 *= llvm::cast<llvm::FixedVectorType>(type)->getNumElements();
	if (val->getType()->isVectorTy())
		s2 *= llvm::cast<llvm::FixedVectorType>(val->getType())->getNumElements();

	if (s1 != s2)
	{
		fmt::throw_exception("cpu_translator::bitcast(): incompatible type sizes (%u vs %u)\nCalled from: %s", s1, s2, src_loc);
	}

	if (val->getType() == type)
	{
		return val;
	}

	llvm::CastInst* i;
	llvm::Value* source_val = val;

	// Try to reuse older bitcasts
	while ((i = llvm::dyn_cast_or_null<llvm::CastInst>(source_val)) && i->getOpcode() == llvm::Instruction::BitCast)
	{
		source_val = i->getOperand(0);

		if (source_val->getType() == type)
		{
			return source_val;
		}
	}

	// Skip use iteration for values that don't have use lists
	if (source_val->hasUseList())
	{
		for (llvm::Value* it_val : source_val->uses())
		{
			if (!it_val)
			{
				continue;
			}

			llvm::CastInst* bci = llvm::dyn_cast_or_null<llvm::CastInst>(it_val);

			// Walk through bitcasts
			while (bci && bci->getOpcode() == llvm::Instruction::BitCast)
			{
				if (bci->getParent() != m_ir->GetInsertBlock())
				{
					break;
				}

				if (bci->getType() == type)
				{
					return bci;
				}

				// Check if bci has use list before accessing use_begin()
				if (!bci->hasUseList())
				{
					break;
				}

				if (bci->use_begin() == bci->use_end())
				{
					break;
				}

				bci = llvm::dyn_cast_or_null<llvm::CastInst>(*bci->use_begin());
			}
		}
	}

	// Do bitcast on the source

	if (const auto c1 = llvm::dyn_cast<llvm::Constant>(source_val))
	{
		return ensure(llvm::ConstantFoldCastOperand(llvm::Instruction::BitCast, c1, type, m_module->getDataLayout()));
	}

	return m_ir->CreateBitCast(source_val, type);
}

template <>
std::pair<bool, v128> cpu_translator::get_const_vector<v128>(llvm::Value* c, u32 _pos, u32 _line)
{
	// Bitcasts do not matter
	c = peek_through_bitcasts(c);

	v128 result{};

	if (!llvm::isa<llvm::Constant>(c))
	{
		return {false, result};
	}

	const auto t = c->getType();

	if (!t->isVectorTy())
	{
		if (const auto ci = llvm::dyn_cast<llvm::ConstantInt>(c); ci && ci->getBitWidth() == 128)
		{
			const auto& cv = ci->getValue();

			result._u64[0] = cv.extractBitsAsZExtValue(64, 0);
			result._u64[1] = cv.extractBitsAsZExtValue(64, 64);

			return {true, result};
		}

		fmt::throw_exception("[0x%x, %u] Not a vector", _pos, _line);
	}

	if (auto v = llvm::cast<llvm::FixedVectorType>(t); v->getScalarSizeInBits() * v->getNumElements() != 128)
	{
		fmt::throw_exception("[0x%x, %u] Bad vector size: i%ux%u", _pos, _line, v->getScalarSizeInBits(), v->getNumElements());
	}

	const auto cv = llvm::dyn_cast<llvm::ConstantDataVector>(c);

	if (!cv)
	{
		if (llvm::isa<llvm::ConstantAggregateZero>(c))
		{
			return {true, result};
		}

		std::string result;
		llvm::raw_string_ostream out(result);
		c->print(out, true);
		out.flush();

		if (llvm::isa<llvm::ConstantExpr>(c))
		{
			// Sorry, if we cannot evaluate it we cannot use it
			fmt::throw_exception("[0x%x, %u] Constant Expression!\n%s", _pos, _line, result);
		}

		fmt::throw_exception("[0x%x, %u] Unexpected constant type!\n%s", _pos, _line, result);
	}

	const auto sct = t->getScalarType();

	if (sct->isIntegerTy(8))
	{
		for (u32 i = 0; i < 16; i++)
		{
			result._u8[i] = static_cast<u8>(cv->getElementAsInteger(i));
		}
	}
	else if (sct->isIntegerTy(16))
	{
		for (u32 i = 0; i < 8; i++)
		{
			result._u16[i] = static_cast<u16>(cv->getElementAsInteger(i));
		}
	}
	else if (sct->isIntegerTy(32))
	{
		for (u32 i = 0; i < 4; i++)
		{
			result._u32[i] = static_cast<u32>(cv->getElementAsInteger(i));
		}
	}
	else if (sct->isIntegerTy(64))
	{
		for (u32 i = 0; i < 2; i++)
		{
			result._u64[i] = cv->getElementAsInteger(i);
		}
	}
	else if (sct->isFloatTy())
	{
		for (u32 i = 0; i < 4; i++)
		{
			result._f[i] = cv->getElementAsFloat(i);
		}
	}
	else if (sct->isDoubleTy())
	{
		for (u32 i = 0; i < 2; i++)
		{
			result._d[i] = cv->getElementAsDouble(i);
		}
	}
	else
	{
		fmt::throw_exception("[0x%x, %u] Unexpected vector element type", _pos, _line);
	}

	return {true, result};
}

template <>
llvm::Constant* cpu_translator::make_const_vector<v128>(v128 v, llvm::Type* t, u32 _line)
{
	if (const auto ct = llvm::dyn_cast<llvm::IntegerType>(t); ct && ct->getBitWidth() == 128)
	{
		return llvm::ConstantInt::get(t, llvm::APInt(128, llvm::ArrayRef(reinterpret_cast<const u64*>(v._bytes), 2)));
	}

	ensure(t->isVectorTy());
	ensure(128 == t->getScalarSizeInBits() * llvm::cast<llvm::FixedVectorType>(t)->getNumElements());

	const auto sct = t->getScalarType();

	if (sct->isIntegerTy(8))
	{
		return llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(reinterpret_cast<const u8*>(v._bytes), 16));
	}
	if (sct->isIntegerTy(16))
	{
		return llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(reinterpret_cast<const u16*>(v._bytes), 8));
	}
	if (sct->isIntegerTy(32))
	{
		return llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(reinterpret_cast<const u32*>(v._bytes), 4));
	}
	if (sct->isIntegerTy(64))
	{
		return llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(reinterpret_cast<const u64*>(v._bytes), 2));
	}
	if (sct->isFloatTy())
	{
		return llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(reinterpret_cast<const f32*>(v._bytes), 4));
	}
	if (sct->isDoubleTy())
	{
		return llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(reinterpret_cast<const f64*>(v._bytes), 2));
	}

	fmt::throw_exception("[line %u] No supported constant type", _line);
}

void cpu_translator::replace_intrinsics(llvm::Function& f)
{
	for (llvm::BasicBlock& bb : f)
	{
		std::set<std::string, std::less<>> names;

		using InstListType = llvm::BasicBlock::InstListType;

		std::function<InstListType::iterator(InstListType::iterator)> fix_funcs;

		fix_funcs = [&](InstListType::iterator inst_bit)
		{
			auto ci = llvm::dyn_cast<llvm::CallInst>(&*inst_bit);

			if (!ci)
			{
				return std::next(inst_bit);
			}

			const auto cf = ci->getCalledFunction();

			if (!cf)
			{
				return std::next(inst_bit);
			}

			std::string_view func_name{cf->getName().data(), cf->getName().size()};

			const auto it = m_intrinsics.find(func_name);

			if (it == m_intrinsics.end())
			{
				return std::next(inst_bit);
			}

			if (!names.empty())
			{
				llvm_log.trace("cpu_translator::replace_intrinsics(): function '%s' names_size=%d, names[0]=%s", func_name, names.size(), *names.begin());
			}

			if (names.contains(func_name))
			{
				fmt::throw_exception("cpu_translator::replace_intrinsics(): Recursion detected at function '%s'!", func_name);
			}

			names.emplace(std::string(func_name));

			// Set insert point after call instruction
			// In order to obtain a clear range of the inserted instructions
			if (llvm::Instruction* next = ci->getNextNode())
			{
				m_ir->SetInsertPoint(next);
			}
			else
			{
				m_ir->SetInsertPoint(std::addressof(bb));
			}

			ci->replaceAllUsesWith(it->second(ci));

			InstListType::iterator end = m_ir->GetInsertPoint();

			for (InstListType::iterator next_it = ci->eraseFromParent(), inner = next_it; inner != end;)
			{
				if (llvm::isa<llvm::CallInst>(&*inner))
				{
					inner = fix_funcs(inner);
				}
				else
				{
					inner++;
				}
			}

			// TODO: Simplify in C++23 with 'names.erase(func_name);'
			names.erase(ensure(names.find(func_name), FN(x != names.end())));
			return end;
		};

		for (auto bit = bb.begin(); bit != bb.end(); bit = fix_funcs(bit))
		{
		}
	}
}

void cpu_translator::run_transforms(llvm::Function& f)
{
	// This pass must run first because the other passes may depend on resolved names.
	replace_intrinsics(f);

	for (auto& pass : m_transform_passes)
	{
		pass->run(m_ir, f);
	}
}

void cpu_translator::register_transform_pass(std::unique_ptr<translator_pass>& pass)
{
	m_transform_passes.emplace_back(std::move(pass));
}

void cpu_translator::clear_transforms()
{
	m_transform_passes.clear();
}

void cpu_translator::reset_transforms()
{
	for (auto& pass : m_transform_passes)
	{
		pass->reset();
	}
}

void cpu_translator::erase_stores(llvm::ArrayRef<llvm::Value*> args)
{
	for (auto v : args)
	{
		// Skip use iteration for values that don't have use lists
		if (!v->hasUseList())
			continue;

		for (llvm::Value* i : v->uses())
		{
			llvm::CastInst* bci = nullptr;

			// Walk through bitcasts
			while (i && (bci = llvm::dyn_cast<llvm::CastInst>(i)) && bci->getOpcode() == llvm::Instruction::BitCast)
			{
				// Check if bci has use list before accessing use_begin()
				if (!bci->hasUseList())
					break;

				i = *bci->use_begin();
			}

			if (auto si = llvm::dyn_cast_or_null<llvm::StoreInst>(i))
			{
				si->eraseFromParent();
			}
		}
	}
}

llvm::KnownBits cpu_translator::get_known_bits_fallback(llvm::Value* value)
{
	// TODO: Improve it - add support for integer addition/subtraction and more stuff

	const auto type = value->getType();

	if (!type->isVectorTy())
	{
		if (llvm::isa<llvm::IntegerType>(type))
		{
			if (auto bin_inst = llvm::dyn_cast<llvm::BinaryOperator>(value))
			{
				llvm::Value* lhs = ensure(bin_inst->getOperand(0));
				llvm::Value* rhs = ensure(bin_inst->getOperand(1));

				llvm::ConstantInt* constant_value = llvm::dyn_cast<llvm::ConstantInt>(rhs) ? llvm::dyn_cast<llvm::ConstantInt>(rhs) : llvm::dyn_cast<llvm::ConstantInt>(lhs);

				if (!constant_value)
				{
					return llvm::KnownBits(type->getScalarSizeInBits());
				}

				if (bin_inst->getOpcode() == llvm::Instruction::Or)
				{
					llvm::KnownBits ret(type->getScalarSizeInBits());
					ret.One = constant_value->getValue();
					return ret;
				}

				if (bin_inst->getOpcode() == llvm::Instruction::And)
				{
					llvm::KnownBits ret(type->getScalarSizeInBits());
					ret.Zero = constant_value->getValue();
					ret.Zero.flipAllBits();
					return ret;
				}

				return llvm::KnownBits(type->getScalarSizeInBits());
			}
		}

		fmt::throw_exception("Bad KnownBits type: i%ux", type->getScalarSizeInBits());
	}

	if (auto v = llvm::cast<llvm::FixedVectorType>(type); v->getScalarSizeInBits() * v->getNumElements() != 128)
	{
		// Unsupported
		return llvm::KnownBits(type->getScalarSizeInBits());
	}

	const auto original_value = peek_through_bitcasts(value);

	auto bin_inst = llvm::dyn_cast<llvm::BinaryOperator>(original_value);

	if (!bin_inst)
	{
		return llvm::KnownBits(type->getScalarSizeInBits());
	}

	llvm::Value* lhs = ensure(bin_inst->getOperand(0));
	llvm::Value* rhs = ensure(bin_inst->getOperand(1));

	llvm::Value* constant_value = llvm::dyn_cast<llvm::ConstantDataVector>(rhs) ? llvm::dyn_cast<llvm::ConstantDataVector>(rhs) : llvm::dyn_cast<llvm::ConstantDataVector>(lhs);

	if (!constant_value)
	{
		return llvm::KnownBits(value->getType()->getScalarSizeInBits());
	}

	const auto [ok, v128_const] = get_const_vector(constant_value, -1);

	ensure(ok);

	llvm::APInt all_lanes{};
	llvm::APInt any_lanes{};

	auto combine_bits = [&](const auto& array, u32 size)
	{
		auto all = +array[0];
		auto any = +array[0];

		for (u32 i = 1; i < size; i++)
		{
			all &= +array[i];
			any |= +array[i];
		}

		return std::make_pair(all, any);
	};

	if (type->getScalarType()->isIntegerTy(8))
	{
		const auto [all, any] = combine_bits(v128_const._u8, 16);
		all_lanes = llvm::APInt(8, all);
		any_lanes = llvm::APInt(8, any);
	}
	else if (type->getScalarType()->isIntegerTy(16))
	{
		const auto [all, any] = combine_bits(v128_const._u16, 8);
		all_lanes = llvm::APInt(16, all);
		any_lanes = llvm::APInt(16, any);
	}
	else if (type->getScalarType()->isIntegerTy(32))
	{
		const auto [all, any] = combine_bits(v128_const._u32, 4);
		all_lanes = llvm::APInt(32, all);
		any_lanes = llvm::APInt(32, any);
	}
	else // if (type->getScalarType()->isIntegerTy(64))
	{
		return llvm::KnownBits(type->getScalarSizeInBits());
	}

	if (bin_inst->getOpcode() == llvm::Instruction::Or)
	{
		llvm::KnownBits ret(type->getScalarSizeInBits());
		ret.One = all_lanes;
		return ret;
	}

	if (bin_inst->getOpcode() == llvm::Instruction::And)
	{
		llvm::KnownBits ret(type->getScalarSizeInBits());
		ret.Zero = any_lanes;
		ret.Zero.flipAllBits();
		return ret;
	}

	return llvm::KnownBits(type->getScalarSizeInBits());
}
#endif
