use std::collections::HashSet;
use std::env;
use std::fmt;
use std::fs::File;
use std::io::prelude::*;
use std::io::{self, BufReader};
use std::path::PathBuf;

/// Complete lines of generated source.
///
/// This enables common generation tasks to be factored out without precluding basic
/// context-specific formatting.
///
/// The convention in this generator is to prefix (not suffix) lines with a newline, so the
/// implementation of `std::fmt::Display` behaves in the same way.
struct Lines {
    indent: usize,
    lines: Vec<String>,
}

impl Lines {
    fn single(line: String) -> Self {
        Self::from(vec![line])
    }
}

impl From<Vec<String>> for Lines {
    fn from(lines: Vec<String>) -> Self {
        Self { indent: 0, lines }
    }
}

impl std::fmt::Display for Lines {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
        for line in self.lines.iter() {
            write!(f, "\n{:width$}{line}", "", width = self.indent)?;
        }
        Ok(())
    }
}

#[derive(Clone, Copy, PartialEq)]
enum TargetFeature {
    Lsx,
    Lasx,
}

impl TargetFeature {
    fn new(ext: &str) -> TargetFeature {
        match ext {
            "lasx" => Self::Lasx,
            _ => Self::Lsx,
        }
    }

    /// A string for use with `#[target_feature(...)]`.
    fn as_target_feature_arg(&self, ins: &str) -> String {
        let vec = match *self {
            // Features included with LoongArch64 LSX and LASX.
            Self::Lsx => "lsx",
            Self::Lasx => "lasx",
        };
        let frecipe = match ins {
            "lsx_vfrecipe_s" | "lsx_vfrecipe_d" | "lsx_vfrsqrte_s" | "lsx_vfrsqrte_d"
            | "lasx_xvfrecipe_s" | "lasx_xvfrecipe_d" | "lasx_xvfrsqrte_s" | "lasx_xvfrsqrte_d" => {
                ",frecipe"
            }
            _ => "",
        };
        format!("{vec}{frecipe}")
    }

    fn attr(name: &str, value: impl fmt::Display) -> String {
        format!(r#"#[{name}(enable = "{value}")]"#)
    }

    /// Generate a target_feature attribute
    fn to_target_feature_attr(self, ins: &str) -> Lines {
        Lines::single(Self::attr(
            "target_feature",
            self.as_target_feature_arg(ins),
        ))
    }

    fn bytes(&self) -> u8 {
        match *self {
            // Features included with LoongArch64 LSX and LASX.
            Self::Lsx => 16,
            Self::Lasx => 32,
        }
    }
}

fn portable_intrinsics() -> HashSet<&'static str> {
    include_str!("portable-intrinsics.txt")
        .lines()
        .map(str::trim)
        .filter(|line| !line.is_empty() && !line.starts_with('#'))
        .collect()
}

fn gen_spec(in_file: String, ext_name: &str) -> io::Result<()> {
    let f = File::open(in_file.clone()).unwrap_or_else(|_| panic!("Failed to open {in_file}"));
    let f = BufReader::new(f);
    let mut out = format!(
        r#"// This code is automatically generated. DO NOT MODIFY.
// ```
// OUT_DIR=`pwd`/crates/stdarch-gen-loongarch cargo run -p stdarch-gen-loongarch -- {in_file}
// ```
"#
    );
    out.push('\n');

    let mut asm_fmts = String::new();
    let mut data_types = String::new();
    let fn_pat = format!("__{ext_name}_");
    let portable_intrinsics = portable_intrinsics();
    for line in f.lines() {
        let line = line.unwrap();
        if line.is_empty() {
            continue;
        }

        if let Some(s) = line.find("/* Assembly instruction format:") {
            let e = line.find('.').unwrap();
            asm_fmts = line.get(s + 31..e).unwrap().trim().to_string();
        } else if let Some(s) = line.find("/* Data types in instruction templates:") {
            let e = line.find('.').unwrap();
            data_types = line.get(s + 39..e).unwrap().trim().to_string();
        } else if let Some(s) = line.find(fn_pat.as_str()) {
            let e = line.find('(').unwrap();
            let name = line.get(s + 2..e).unwrap().trim().to_string();
            out.push_str(&format!("/// {name}\n"));
            if portable_intrinsics.contains(name.as_str()) {
                out.push_str("impl = portable\n");
            }
            out.push_str(&format!("name = {name}\n"));
            out.push_str(&format!("asm-fmts = {asm_fmts}\n"));
            out.push_str(&format!("data-types = {data_types}\n"));
            out.push('\n');
        }
    }

    let out_dir_path: PathBuf = PathBuf::from(env::var("OUT_DIR").unwrap());
    std::fs::create_dir_all(&out_dir_path)?;
    let mut f = File::create(out_dir_path.join(format!("{ext_name}.spec")))?;
    f.write_all(out.as_bytes())?;
    Ok(())
}

fn gen_bind(in_file: String, ext_name: &str) -> io::Result<()> {
    let f = File::open(in_file.clone()).unwrap_or_else(|_| panic!("Failed to open {in_file}"));
    let f = BufReader::new(f);

    let target: TargetFeature = TargetFeature::new(ext_name);
    let mut para_num;
    let mut current_name: Option<String> = None;
    let mut asm_fmts: Vec<String> = Vec::new();
    let mut link_function_str = String::new();
    let mut function_str = String::new();
    let mut out = String::new();
    let mut skip = false;

    out.push_str(&format!(
        r#"// This code is automatically generated. DO NOT MODIFY.
//
// Instead, modify `{in_file}` and run the following command to re-generate this file:
//
// ```
// OUT_DIR=`pwd`/crates/core_arch cargo run -p stdarch-gen-loongarch -- {in_file}
// ```

use crate::mem::transmute;
use super::super::*;
"#
    ));

    out.push_str(
        r#"
#[allow(improper_ctypes)]
unsafe extern "unadjusted" {
"#,
    );

    for line in f.lines() {
        let line = line.unwrap();
        if line.is_empty() {
            continue;
        }
        if line.starts_with("impl = portable") {
            skip = true;
        } else if let Some(name) = line.strip_prefix("name = ") {
            current_name = Some(String::from(name));
        } else if line.starts_with("asm-fmts = ") {
            asm_fmts = line[10..]
                .split(',')
                .map(|v| v.trim().to_string())
                .collect();
        } else if line.starts_with("data-types = ") {
            let current_name = current_name.clone().unwrap();
            let data_types: Vec<&str> = line
                .get(12..)
                .unwrap()
                .split(',')
                .map(|e| e.trim())
                .collect();
            let in_t;
            let out_t;
            if data_types.len() == 2 {
                in_t = [data_types[1], "NULL", "NULL", "NULL"];
                out_t = data_types[0];
                para_num = 1;
            } else if data_types.len() == 3 {
                in_t = [data_types[1], data_types[2], "NULL", "NULL"];
                out_t = data_types[0];
                para_num = 2;
            } else if data_types.len() == 4 {
                in_t = [data_types[1], data_types[2], data_types[3], "NULL"];
                out_t = data_types[0];
                para_num = 3;
            } else if data_types.len() == 5 {
                in_t = [data_types[1], data_types[2], data_types[3], data_types[4]];
                out_t = data_types[0];
                para_num = 4;
            } else {
                panic!("DEBUG: line: {0} len: {1}", line, data_types.len());
            }

            if skip {
                skip = false;
                continue;
            }

            let (link_function, function) =
                gen_bind_body(&current_name, &asm_fmts, &in_t, out_t, para_num, target);
            link_function_str.push_str(&link_function);
            function_str.push_str(&function);
        }
    }
    out.push_str(&link_function_str);
    out.push_str("}\n");
    out.push_str(&function_str);

    let out_path: PathBuf =
        PathBuf::from(env::var("OUT_DIR").unwrap_or("crates/core_arch".to_string()))
            .join("src")
            .join("loongarch64")
            .join(ext_name);
    std::fs::create_dir_all(&out_path)?;

    let mut file = File::create(out_path.join("generated.rs"))?;
    file.write_all(out.as_bytes())?;
    Ok(())
}

fn gen_bind_body(
    current_name: &str,
    asm_fmts: &[String],
    in_t: &[&str; 4],
    out_t: &str,
    para_num: i32,
    target: TargetFeature,
) -> (String, String) {
    enum TypeKind {
        Vector,
        Intrinsic,
    }
    use TypeKind::*;
    let type_to_rst = |t: &str, s: bool, k: TypeKind| -> &str {
        match (t, s, k) {
            ("V16QI", _, Vector) => "__v16i8",
            ("V16QI", _, Intrinsic) => "m128i",
            ("V32QI", _, Vector) => "__v32i8",
            ("V32QI", _, Intrinsic) => "m256i",
            ("V8HI", _, Vector) => "__v8i16",
            ("V8HI", _, Intrinsic) => "m128i",
            ("V16HI", _, Vector) => "__v16i16",
            ("V16HI", _, Intrinsic) => "m256i",
            ("V4SI", _, Vector) => "__v4i32",
            ("V4SI", _, Intrinsic) => "m128i",
            ("V8SI", _, Vector) => "__v8i32",
            ("V8SI", _, Intrinsic) => "m256i",
            ("V2DI", _, Vector) => "__v2i64",
            ("V2DI", _, Intrinsic) => "m128i",
            ("V4DI", _, Vector) => "__v4i64",
            ("V4DI", _, Intrinsic) => "m256i",
            ("UV16QI", _, Vector) => "__v16u8",
            ("UV16QI", _, Intrinsic) => "m128i",
            ("UV32QI", _, Vector) => "__v32u8",
            ("UV32QI", _, Intrinsic) => "m256i",
            ("UV8HI", _, Vector) => "__v8u16",
            ("UV8HI", _, Intrinsic) => "m128i",
            ("UV16HI", _, Vector) => "__v16u16",
            ("UV16HI", _, Intrinsic) => "m256i",
            ("UV4SI", _, Vector) => "__v4u32",
            ("UV4SI", _, Intrinsic) => "m128i",
            ("UV8SI", _, Vector) => "__v8u32",
            ("UV8SI", _, Intrinsic) => "m256i",
            ("UV2DI", _, Vector) => "__v2u64",
            ("UV2DI", _, Intrinsic) => "m128i",
            ("UV4DI", _, Vector) => "__v4u64",
            ("UV4DI", _, Intrinsic) => "m256i",
            ("SI", _, _) => "i32",
            ("DI", _, _) => "i64",
            ("USI", _, _) => "u32",
            ("UDI", _, _) => "u64",
            ("V4SF", _, Vector) => "__v4f32",
            ("V4SF", _, Intrinsic) => "m128",
            ("V8SF", _, Vector) => "__v8f32",
            ("V8SF", _, Intrinsic) => "m256",
            ("V2DF", _, Vector) => "__v2f64",
            ("V2DF", _, Intrinsic) => "m128d",
            ("V4DF", _, Vector) => "__v4f64",
            ("V4DF", _, Intrinsic) => "m256d",
            ("UQI", _, _) => "u32",
            ("QI", _, _) => "i32",
            ("CVPOINTER", false, _) => "*const i8",
            ("CVPOINTER", true, _) => "*mut i8",
            ("HI", _, _) => "i32",
            (_, _, _) => panic!("unknown type: {t}"),
        }
    };

    let is_mem = in_t.iter().any(|s| s.contains("POINTER"));
    let is_store = current_name.to_string().contains("vst");
    let link_function = {
        let fn_decl = {
            let fn_output = if out_t.to_lowercase() == "void" {
                String::new()
            } else {
                format!(" -> {}", type_to_rst(out_t, is_store, Vector))
            };
            let fn_inputs = match para_num {
                1 => format!("(a: {})", type_to_rst(in_t[0], is_store, Vector)),
                2 => format!(
                    "(a: {}, b: {})",
                    type_to_rst(in_t[0], is_store, Vector),
                    type_to_rst(in_t[1], is_store, Vector)
                ),
                3 => format!(
                    "(a: {}, b: {}, c: {})",
                    type_to_rst(in_t[0], is_store, Vector),
                    type_to_rst(in_t[1], is_store, Vector),
                    type_to_rst(in_t[2], is_store, Vector)
                ),
                4 => format!(
                    "(a: {}, b: {}, c: {}, d: {})",
                    type_to_rst(in_t[0], is_store, Vector),
                    type_to_rst(in_t[1], is_store, Vector),
                    type_to_rst(in_t[2], is_store, Vector),
                    type_to_rst(in_t[3], is_store, Vector)
                ),
                _ => panic!("unsupported parameter number"),
            };
            format!("fn __{current_name}{fn_inputs}{fn_output};")
        };
        let function = format!(
            r#"    #[link_name = "llvm.loongarch.{}"]
    {fn_decl}
"#,
            current_name.replace('_', ".")
        );
        function
    };

    let type_to_imm = |t| -> i8 {
        match t {
            'b' => 4,
            'h' => 3,
            'w' => 2,
            'd' => 1,
            _ => panic!("unsupported type"),
        }
    };
    let mut rustc_legacy_const_generics = "";
    let fn_decl = {
        let fn_output = if out_t.to_lowercase() == "void" {
            String::new()
        } else {
            format!("-> {} ", type_to_rst(out_t, is_store, Intrinsic))
        };
        let mut fn_inputs = match para_num {
            1 => format!("(a: {})", type_to_rst(in_t[0], is_store, Intrinsic)),
            2 => format!(
                "(a: {}, b: {})",
                type_to_rst(in_t[0], is_store, Intrinsic),
                type_to_rst(in_t[1], is_store, Intrinsic)
            ),
            3 => format!(
                "(a: {}, b: {}, c: {})",
                type_to_rst(in_t[0], is_store, Intrinsic),
                type_to_rst(in_t[1], is_store, Intrinsic),
                type_to_rst(in_t[2], is_store, Intrinsic)
            ),
            4 => format!(
                "(a: {}, b: {}, c: {}, d: {})",
                type_to_rst(in_t[0], is_store, Intrinsic),
                type_to_rst(in_t[1], is_store, Intrinsic),
                type_to_rst(in_t[2], is_store, Intrinsic),
                type_to_rst(in_t[3], is_store, Intrinsic)
            ),
            _ => panic!("unsupported parameter number"),
        };
        if para_num == 1 && in_t[0] == "HI" {
            fn_inputs = match asm_fmts[1].as_str() {
                "si13" | "i13" => format!(
                    "<const IMM_S13: {}>()",
                    type_to_rst(in_t[0], is_store, Intrinsic)
                ),
                "si10" => format!(
                    "<const IMM_S10: {}>()",
                    type_to_rst(in_t[0], is_store, Intrinsic)
                ),
                _ => panic!("unsupported assembly format: {}", asm_fmts[1]),
            };
            rustc_legacy_const_generics = "rustc_legacy_const_generics(0)";
        } else if para_num == 2 && (in_t[1] == "UQI" || in_t[1] == "USI") {
            fn_inputs = if asm_fmts[2].starts_with("ui") {
                format!(
                    "<const IMM{2}: {1}>(a: {0})",
                    type_to_rst(in_t[0], is_store, Intrinsic),
                    type_to_rst(in_t[1], is_store, Intrinsic),
                    asm_fmts[2].get(2..).unwrap()
                )
            } else {
                panic!("unsupported assembly format: {}", asm_fmts[2]);
            };
            rustc_legacy_const_generics = "rustc_legacy_const_generics(1)";
        } else if para_num == 2 && in_t[1] == "QI" {
            fn_inputs = if asm_fmts[2].starts_with("si") {
                format!(
                    "<const IMM_S{2}: {1}>(a: {0})",
                    type_to_rst(in_t[0], is_store, Intrinsic),
                    type_to_rst(in_t[1], is_store, Intrinsic),
                    asm_fmts[2].get(2..).unwrap()
                )
            } else {
                panic!("unsupported assembly format: {}", asm_fmts[2]);
            };
            rustc_legacy_const_generics = "rustc_legacy_const_generics(1)";
        } else if para_num == 2 && in_t[0] == "CVPOINTER" && in_t[1] == "SI" {
            fn_inputs = if asm_fmts[2].starts_with("si") {
                format!(
                    "<const IMM_S{2}: {1}>(mem_addr: {0})",
                    type_to_rst(in_t[0], is_store, Intrinsic),
                    type_to_rst(in_t[1], is_store, Intrinsic),
                    asm_fmts[2].get(2..).unwrap()
                )
            } else {
                panic!("unsupported assembly format: {}", asm_fmts[2]);
            };
            rustc_legacy_const_generics = "rustc_legacy_const_generics(1)";
        } else if para_num == 2 && in_t[0] == "CVPOINTER" && in_t[1] == "DI" {
            fn_inputs = match asm_fmts[2].as_str() {
                "rk" => format!(
                    "(mem_addr: {}, b: {})",
                    type_to_rst(in_t[0], is_store, Intrinsic),
                    type_to_rst(in_t[1], is_store, Intrinsic)
                ),
                _ => panic!("unsupported assembly format: {}", asm_fmts[2]),
            };
        } else if para_num == 3 && (in_t[2] == "USI" || in_t[2] == "UQI") {
            fn_inputs = if asm_fmts[2].starts_with("ui") {
                format!(
                    "<const IMM{3}: {2}>(a: {0}, b: {1})",
                    type_to_rst(in_t[0], is_store, Intrinsic),
                    type_to_rst(in_t[1], is_store, Intrinsic),
                    type_to_rst(in_t[2], is_store, Intrinsic),
                    asm_fmts[2].get(2..).unwrap()
                )
            } else {
                panic!("unsupported assembly format: {}", asm_fmts[2])
            };
            rustc_legacy_const_generics = "rustc_legacy_const_generics(2)";
        } else if para_num == 3 && in_t[1] == "CVPOINTER" && in_t[2] == "SI" {
            fn_inputs = match asm_fmts[2].as_str() {
                "si12" => format!(
                    "<const IMM_S12: {2}>(a: {0}, mem_addr: {1})",
                    type_to_rst(in_t[0], is_store, Intrinsic),
                    type_to_rst(in_t[1], is_store, Intrinsic),
                    type_to_rst(in_t[2], is_store, Intrinsic)
                ),
                _ => panic!("unsupported assembly format: {}", asm_fmts[2]),
            };
            rustc_legacy_const_generics = "rustc_legacy_const_generics(2)";
        } else if para_num == 3 && in_t[1] == "CVPOINTER" && in_t[2] == "DI" {
            fn_inputs = match asm_fmts[2].as_str() {
                "rk" => format!(
                    "(a: {}, mem_addr: {}, b: {})",
                    type_to_rst(in_t[0], is_store, Intrinsic),
                    type_to_rst(in_t[1], is_store, Intrinsic),
                    type_to_rst(in_t[2], is_store, Intrinsic)
                ),
                _ => panic!("unsupported assembly format: {}", asm_fmts[2]),
            };
        } else if para_num == 4 {
            fn_inputs = match (asm_fmts[2].as_str(), current_name.chars().last().unwrap()) {
                ("si8", t) => format!(
                    "<const IMM_S8: {2}, const IMM{4}: {3}>(a: {0}, mem_addr: {1})",
                    type_to_rst(in_t[0], is_store, Intrinsic),
                    type_to_rst(in_t[1], is_store, Intrinsic),
                    type_to_rst(in_t[2], is_store, Intrinsic),
                    type_to_rst(in_t[3], is_store, Intrinsic),
                    type_to_imm(t),
                ),
                (_, _) => panic!(
                    "unsupported assembly format: {} for {}",
                    asm_fmts[2], current_name
                ),
            };
            rustc_legacy_const_generics = "rustc_legacy_const_generics(2, 3)";
        }
        format!(
            "pub {}fn {current_name}{fn_inputs} {fn_output}",
            if is_mem { "unsafe " } else { "" }
        )
    };
    let unsafe_start = if !is_mem { "unsafe { " } else { "" };
    let unsafe_end = if !is_mem { " }" } else { "" };
    let mut call_params = {
        match para_num {
            1 => format!("{unsafe_start}transmute(__{current_name}(transmute(a))){unsafe_end}"),
            2 => format!(
                "{unsafe_start}transmute(__{current_name}(transmute(a), transmute(b))){unsafe_end}"
            ),
            3 => format!(
                "{unsafe_start}transmute(__{current_name}(transmute(a), transmute(b), transmute(c))){unsafe_end}"
            ),
            4 => format!(
                "{unsafe_start}transmute(__{current_name}(transmute(a), transmute(b), transmute(c), transmute(d))){unsafe_end}"
            ),
            _ => panic!("unsupported parameter number"),
        }
    };
    if para_num == 1 && in_t[0] == "HI" {
        call_params = match asm_fmts[1].as_str() {
            "si10" => {
                format!(
                    "static_assert_simm_bits!(IMM_S10, 10);\n    {unsafe_start}transmute(__{current_name}(IMM_S10)){unsafe_end}"
                )
            }
            "i13" => {
                format!(
                    "static_assert_simm_bits!(IMM_S13, 13);\n    {unsafe_start}transmute(__{current_name}(IMM_S13)){unsafe_end}"
                )
            }
            _ => panic!("unsupported assembly format: {}", asm_fmts[2]),
        }
    } else if para_num == 2 && (in_t[1] == "UQI" || in_t[1] == "USI") {
        call_params = if asm_fmts[2].starts_with("ui") {
            format!(
                "static_assert_uimm_bits!(IMM{0}, {0});\n    {unsafe_start}transmute(__{current_name}(transmute(a), IMM{0})){unsafe_end}",
                asm_fmts[2].get(2..).unwrap()
            )
        } else {
            panic!("unsupported assembly format: {}", asm_fmts[2])
        };
    } else if para_num == 2 && in_t[1] == "QI" {
        call_params = match asm_fmts[2].as_str() {
            "si5" => {
                format!(
                    "static_assert_simm_bits!(IMM_S5, 5);\n    {unsafe_start}transmute(__{current_name}(transmute(a), IMM_S5)){unsafe_end}"
                )
            }
            _ => panic!("unsupported assembly format: {}", asm_fmts[2]),
        };
    } else if para_num == 2 && in_t[0] == "CVPOINTER" && in_t[1] == "SI" {
        call_params = if asm_fmts[2].starts_with("si") {
            format!(
                "static_assert_simm_bits!(IMM_S{0}, {0});\n    {unsafe_start}transmute(__{current_name}(mem_addr, IMM_S{0})){unsafe_end}",
                asm_fmts[2].get(2..).unwrap()
            )
        } else {
            panic!("unsupported assembly format: {}", asm_fmts[2])
        }
    } else if para_num == 2 && in_t[0] == "CVPOINTER" && in_t[1] == "DI" {
        call_params = match asm_fmts[2].as_str() {
            "rk" => format!(
                "{unsafe_start}transmute(__{current_name}(mem_addr, transmute(b))){unsafe_end}"
            ),
            _ => panic!("unsupported assembly format: {}", asm_fmts[2]),
        };
    } else if para_num == 3 && (in_t[2] == "USI" || in_t[2] == "UQI") {
        call_params = if asm_fmts[2].starts_with("ui") {
            format!(
                "static_assert_uimm_bits!(IMM{0}, {0});\n    {unsafe_start}transmute(__{current_name}(transmute(a), transmute(b), IMM{0})){unsafe_end}",
                asm_fmts[2].get(2..).unwrap()
            )
        } else {
            panic!("unsupported assembly format: {}", asm_fmts[2])
        }
    } else if para_num == 3 && in_t[1] == "CVPOINTER" && in_t[2] == "SI" {
        call_params = match asm_fmts[2].as_str() {
            "si12" => format!(
                "static_assert_simm_bits!(IMM_S12, 12);\n    {unsafe_start}__{current_name}(transmute(a), mem_addr, IMM_S12){unsafe_end}"
            ),
            _ => panic!("unsupported assembly format: {}", asm_fmts[2]),
        };
    } else if para_num == 3 && in_t[1] == "CVPOINTER" && in_t[2] == "DI" {
        call_params = match asm_fmts[2].as_str() {
            "rk" => format!(
                "{unsafe_start}__{current_name}(transmute(a), mem_addr, transmute(b)){unsafe_end}"
            ),
            _ => panic!("unsupported assembly format: {}", asm_fmts[2]),
        };
    } else if para_num == 4 {
        call_params = match (asm_fmts[2].as_str(), current_name.chars().last().unwrap()) {
            ("si8", t) => format!(
                "static_assert_simm_bits!(IMM_S8, 8);\n    static_assert_uimm_bits!(IMM{0}, {0});\n    {unsafe_start}__{current_name}(transmute(a), mem_addr, IMM_S8, IMM{0}){unsafe_end}",
                type_to_imm(t)
            ),
            (_, _) => panic!(
                "unsupported assembly format: {} for {}",
                asm_fmts[2], current_name
            ),
        }
    }
    let function = if !rustc_legacy_const_generics.is_empty() {
        format!(
            r#"
#[inline]{target_feature}
#[{rustc_legacy_const_generics}]
#[unstable(feature = "stdarch_loongarch", issue = "117427")]
{fn_decl}{{
    {call_params}
}}
"#,
            target_feature = target.to_target_feature_attr(current_name)
        )
    } else {
        format!(
            r#"
#[inline]{target_feature}
#[unstable(feature = "stdarch_loongarch", issue = "117427")]
{fn_decl}{{
    {call_params}
}}
"#,
            target_feature = target.to_target_feature_attr(current_name)
        )
    };
    (link_function, function)
}

fn gen_test(in_file: String, ext_name: &str) -> io::Result<()> {
    let f = File::open(in_file.clone()).unwrap_or_else(|_| panic!("Failed to open {in_file}"));
    let f = BufReader::new(f);

    let target: TargetFeature = TargetFeature::new(ext_name);
    let mut para_num;
    let mut current_name: Option<String> = None;
    let mut asm_fmts: Vec<String> = Vec::new();
    let mut impl_function_str = String::new();
    let mut call_function_str = String::new();
    let mut out = String::new();

    out.push_str(&format!(
        r#"/*
 * This code is automatically generated. DO NOT MODIFY.
 *
 * Instead, modify `{in_file}` and run the following command to re-generate this file:
 *
 * ```
 * OUT_DIR=`pwd`/crates/stdarch-gen-loongarch cargo run -p stdarch-gen-loongarch -- {in_file} test
 * ```
 */

#include <stdio.h>
#include <stdint.h>
#include <lsxintrin.h>
#include <lasxintrin.h>

union v16qi
{{
    __m128i v;
    int64_t i64[2];
    int8_t i8[16];
}};

union v32qi
{{
    __m256i v;
    int64_t i64[4];
    int8_t i8[32];
}};

union v8hi
{{
    __m128i v;
    int64_t i64[2];
    int16_t i16[8];
}};

union v16hi
{{
    __m256i v;
    int64_t i64[4];
    int16_t i16[16];
}};

union v4si
{{
    __m128i v;
    int64_t i64[2];
    int32_t i32[4];
}};

union v8si
{{
    __m256i v;
    int64_t i64[4];
    int32_t i32[8];
}};

union v2di
{{
    __m128i v;
    int64_t i64[2];
}};

union v4di
{{
    __m256i v;
    int64_t i64[4];
}};

union uv16qi
{{
    __m128i v;
    uint64_t i64[2];
    uint8_t i8[16];
}};

union uv32qi
{{
    __m256i v;
    uint64_t i64[4];
    uint8_t i8[32];
}};

union uv8hi
{{
    __m128i v;
    uint64_t i64[2];
    uint16_t i16[8];
}};

union uv16hi
{{
    __m256i v;
    uint64_t i64[4];
    uint16_t i16[16];
}};

union uv4si
{{
    __m128i v;
    uint64_t i64[2];
    uint32_t i32[4];
}};

union uv8si
{{
    __m256i v;
    uint64_t i64[4];
    uint32_t i32[8];
}};

union uv2di
{{
    __m128i v;
    uint64_t i64[2];
}};

union uv4di
{{
    __m256i v;
    uint64_t i64[4];
}};

union v4sf
{{
    __m128 v;
    int64_t i64[2];
    uint32_t i32[2];
    float f32[4];
}};

union v8sf
{{
    __m256 v;
    int64_t i64[4];
    uint32_t i32[4];
    float f32[8];
}};

union v2df
{{
    __m128d v;
    uint64_t i64[2];
    double f64[2];
}};

union v4df
{{
    __m256d v;
    uint64_t i64[4];
    double f64[4];
}};
"#
    ));

    for line in f.lines() {
        let line = line.unwrap();
        if line.is_empty() {
            continue;
        }
        if let Some(name) = line.strip_prefix("name = ") {
            current_name = Some(String::from(name));
        } else if line.starts_with("asm-fmts = ") {
            asm_fmts = line[10..]
                .split(',')
                .map(|v| v.trim().to_string())
                .collect();
        } else if line.starts_with("data-types = ") {
            let current_name = current_name.clone().unwrap();
            let data_types: Vec<&str> = line
                .get(12..)
                .unwrap()
                .split(',')
                .map(|e| e.trim())
                .collect();
            let in_t;
            let out_t;
            if data_types.len() == 2 {
                in_t = [data_types[1], "NULL", "NULL", "NULL"];
                out_t = data_types[0];
                para_num = 1;
            } else if data_types.len() == 3 {
                in_t = [data_types[1], data_types[2], "NULL", "NULL"];
                out_t = data_types[0];
                para_num = 2;
            } else if data_types.len() == 4 {
                in_t = [data_types[1], data_types[2], data_types[3], "NULL"];
                out_t = data_types[0];
                para_num = 3;
            } else if data_types.len() == 5 {
                in_t = [data_types[1], data_types[2], data_types[3], data_types[4]];
                out_t = data_types[0];
                para_num = 4;
            } else {
                panic!("DEBUG: line: {0} len: {1}", line, data_types.len());
            }

            let (link_function, function) =
                gen_test_body(&current_name, &asm_fmts, &in_t, out_t, para_num, target);
            impl_function_str.push_str(&link_function);
            call_function_str.push_str(&function);
        }
    }
    out.push_str(&impl_function_str);
    out.push('\n');
    out.push_str("int main(int argc, char *argv[])\n");
    out.push_str("{\n");
    out.push_str("    printf(\"// This code is automatically generated. DO NOT MODIFY.\\n\");\n");
    out.push_str("    printf(\"// See crates/stdarch-gen-loongarch/README.md\\n\\n\");\n");
    out.push_str("    printf(\"use crate::{\\n\");\n");
    out.push_str("    printf(\"    core_arch::{loongarch64::*, simd::*},\\n\");\n");
    out.push_str("    printf(\"    mem::transmute,\\n\");\n");
    out.push_str("    printf(\"};\\n\");\n");
    out.push_str("    printf(\"use std::hint::black_box;\\n\");\n");
    out.push_str("    printf(\"use stdarch_test::simd_test;\\n\");\n");
    out.push_str(&call_function_str);
    out.push_str("    return 0;\n");
    out.push('}');

    let out_dir_path: PathBuf = PathBuf::from(env::var("OUT_DIR").unwrap());
    std::fs::create_dir_all(&out_dir_path)?;
    let mut f = File::create(out_dir_path.join(format!("{ext_name}.c")))?;
    f.write_all(out.as_bytes())?;
    Ok(())
}

fn gen_test_body(
    current_name: &str,
    asm_fmts: &[String],
    in_t: &[&str; 4],
    out_t: &str,
    para_num: i32,
    target: TargetFeature,
) -> (String, String) {
    let rand_i32 = |bits: u8| -> i32 {
        let val = rand::random::<i32>();
        let bits = 32 - bits;
        (val << bits) >> bits
    };
    let rand_u32 = |bits: u8| -> u32 {
        let val = rand::random::<u32>();
        let bits = 32 - bits;
        (val << bits) >> bits
    };
    let rand_i64 = || -> i64 { rand::random::<i64>() };
    let rand_u64 = || -> u64 { rand::random::<u64>() };
    let rand_f32 = || -> f32 { rand::random::<f32>() };
    let rand_f64 = || -> f64 { rand::random::<f64>() };
    let type_to_ct = |t: &str| -> &str {
        match t {
            "V16QI" => "union v16qi",
            "V32QI" => "union v32qi",
            "V8HI" => "union v8hi",
            "V16HI" => "union v16hi",
            "V4SI" => "union v4si",
            "V8SI" => "union v8si",
            "V2DI" => "union v2di",
            "V4DI" => "union v4di",
            "UV16QI" => "union uv16qi",
            "UV32QI" => "union uv32qi",
            "UV8HI" => "union uv8hi",
            "UV16HI" => "union uv16hi",
            "UV4SI" => "union uv4si",
            "UV8SI" => "union uv8si",
            "UV2DI" => "union uv2di",
            "UV4DI" => "union uv4di",
            "SI" => "int32_t",
            "DI" => "int64_t",
            "USI" => "uint32_t",
            "UDI" => "uint64_t",
            "V4SF" => "union v4sf",
            "V8SF" => "union v8sf",
            "V2DF" => "union v2df",
            "V4DF" => "union v4df",
            "UQI" => "uint32_t",
            "QI" => "int32_t",
            "CVPOINTER" => "void*",
            "HI" => "int32_t",
            _ => panic!("unknown type: {t}"),
        }
    };
    let type_to_va = |v: &str, t: &str| -> String {
        let n = if v.starts_with('_') {
            v.get(1..).unwrap()
        } else {
            v
        };
        let mut out = String::new();
        match t {
            "A16QI" => {
                for i in 0..16 {
                    out.push_str(&format!("    {v}.i8[{i}] = {};\n", rand_i32(8)));
                }
                out.push_str(&format!("    printf(\"    let {n}: [i8; 16] = [%d"));
                for _ in 1..16 {
                    out.push_str(", %d");
                }
                out.push_str(&format!("];\\n\",\n    {v}.i8[0]"));
                for i in 1..16 {
                    out.push_str(&format!(", {v}.i8[{i}]"));
                }
            }
            "AM16QI" => {
                for i in 0..16 {
                    out.push_str(&format!("    {v}.i8[{i}] = {};\n", rand_i32(8)));
                }
                out.push_str(&format!("    printf(\"    let mut {n}: [i8; 16] = [%d"));
                for _ in 1..16 {
                    out.push_str(", %d");
                }
                out.push_str(&format!("];\\n\",\n    {v}.i8[0]"));
                for i in 1..16 {
                    out.push_str(&format!(", {v}.i8[{i}]"));
                }
            }
            "V16QI" => {
                for i in 0..16 {
                    out.push_str(&format!("    {v}.i8[{i}] = {};\n", rand_i32(8)));
                }
                out.push_str(&format!("    printf(\"    let {n} = i8x16::new(%d"));
                for _ in 1..16 {
                    out.push_str(", %d");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i8[0]"));
                for i in 1..16 {
                    out.push_str(&format!(", {v}.i8[{i}]"));
                }
            }
            "V32QI" => {
                for i in 0..32 {
                    out.push_str(&format!("    {v}.i8[{i}] = {};\n", rand_i32(8)));
                }
                out.push_str(&format!("    printf(\"    let {n} = i8x32::new(%d"));
                for _ in 1..32 {
                    out.push_str(", %d");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i8[0]"));
                for i in 1..32 {
                    out.push_str(&format!(", {v}.i8[{i}]"));
                }
            }
            "A32QI" => {
                for i in 0..32 {
                    out.push_str(&format!("    {v}.i8[{i}] = {};\n", rand_i32(8)));
                }
                out.push_str(&format!("    printf(\"    let {n}: [i8; 32] = [%d"));
                for _ in 1..32 {
                    out.push_str(", %d");
                }
                out.push_str(&format!("];\\n\",\n    {v}.i8[0]"));
                for i in 1..32 {
                    out.push_str(&format!(", {v}.i8[{i}]"));
                }
            }
            "AM32QI" => {
                for i in 0..32 {
                    out.push_str(&format!("    {v}.i8[{i}] = {};\n", rand_i32(8)));
                }
                out.push_str(&format!("    printf(\"    let mut {n}: [i8; 32] = [%d"));
                for _ in 1..32 {
                    out.push_str(", %d");
                }
                out.push_str(&format!("];\\n\",\n    {v}.i8[0]"));
                for i in 1..32 {
                    out.push_str(&format!(", {v}.i8[{i}]"));
                }
            }
            "V8HI" => {
                for i in 0..8 {
                    out.push_str(&format!("    {v}.i16[{i}] = {};\n", rand_i32(16)));
                }
                out.push_str(&format!("    printf(\"    let {n} = i16x8::new(%d"));
                for _ in 1..8 {
                    out.push_str(", %d");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i16[0]"));
                for i in 1..8 {
                    out.push_str(&format!(", {v}.i16[{i}]"));
                }
            }
            "V16HI" => {
                for i in 0..16 {
                    out.push_str(&format!("    {v}.i16[{i}] = {};\n", rand_i32(16)));
                }
                out.push_str(&format!("    printf(\"    let {n} = i16x16::new(%d"));
                for _ in 1..16 {
                    out.push_str(", %d");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i16[0]"));
                for i in 1..16 {
                    out.push_str(&format!(", {v}.i16[{i}]"));
                }
            }
            "V4SI" => {
                for i in 0..4 {
                    out.push_str(&format!("    {v}.i32[{i}] = {};\n", rand_i32(32)));
                }
                out.push_str(&format!("    printf(\"    let {n} = i32x4::new(%d"));
                for _ in 1..4 {
                    out.push_str(", %d");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i32[0]"));
                for i in 1..4 {
                    out.push_str(&format!(", {v}.i32[{i}]"));
                }
            }
            "V8SI" => {
                for i in 0..8 {
                    out.push_str(&format!("    {v}.i32[{i}] = {};\n", rand_i32(32)));
                }
                out.push_str(&format!("    printf(\"    let {n} = i32x8::new(%d"));
                for _ in 1..8 {
                    out.push_str(", %d");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i32[0]"));
                for i in 1..8 {
                    out.push_str(&format!(", {v}.i32[{i}]"));
                }
            }
            "V2DI" => {
                for i in 0..2 {
                    out.push_str(&format!("    {v}.i64[{i}] = {}L;\n", rand_i64()));
                }
                out.push_str(&format!("    printf(\"    let {n} = i64x2::new(%ld"));
                for _ in 1..2 {
                    out.push_str(", %ld");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i64[0]"));
                for i in 1..2 {
                    out.push_str(&format!(", {v}.i64[{i}]"));
                }
            }
            "V4DI" => {
                for i in 0..4 {
                    out.push_str(&format!("    {v}.i64[{i}] = {}L;\n", rand_i64()));
                }
                out.push_str(&format!("    printf(\"    let {n} = i64x4::new(%ld"));
                for _ in 1..4 {
                    out.push_str(", %ld");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i64[0]"));
                for i in 1..4 {
                    out.push_str(&format!(", {v}.i64[{i}]"));
                }
            }
            "UV16QI" => {
                for i in 0..16 {
                    out.push_str(&format!("    {v}.i8[{i}] = {};\n", rand_u32(8)));
                }
                out.push_str(&format!("    printf(\"    let {n} = u8x16::new(%u"));
                for _ in 1..16 {
                    out.push_str(", %u");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i8[0]"));
                for i in 1..16 {
                    out.push_str(&format!(", {v}.i8[{i}]"));
                }
            }
            "UV32QI" => {
                for i in 0..32 {
                    out.push_str(&format!("    {v}.i8[{i}] = {};\n", rand_u32(8)));
                }
                out.push_str(&format!("    printf(\"    let {n} = u8x32::new(%u"));
                for _ in 1..32 {
                    out.push_str(", %u");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i8[0]"));
                for i in 1..32 {
                    out.push_str(&format!(", {v}.i8[{i}]"));
                }
            }
            "UV8HI" => {
                for i in 0..8 {
                    out.push_str(&format!("    {v}.i16[{i}] = {};\n", rand_u32(16)));
                }
                out.push_str(&format!("    printf(\"    let {n} = u16x8::new(%u"));
                for _ in 1..8 {
                    out.push_str(", %u");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i16[0]"));
                for i in 1..8 {
                    out.push_str(&format!(", {v}.i16[{i}]"));
                }
            }
            "UV16HI" => {
                for i in 0..16 {
                    out.push_str(&format!("    {v}.i16[{i}] = {};\n", rand_u32(16)));
                }
                out.push_str(&format!("    printf(\"    let {n} = u16x16::new(%u"));
                for _ in 1..16 {
                    out.push_str(", %u");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i16[0]"));
                for i in 1..16 {
                    out.push_str(&format!(", {v}.i16[{i}]"));
                }
            }
            "UV4SI" => {
                for i in 0..4 {
                    out.push_str(&format!("    {v}.i32[{i}] = {};\n", rand_u32(32)));
                }
                out.push_str(&format!("    printf(\"    let {n} = u32x4::new(%u"));
                for _ in 1..4 {
                    out.push_str(", %u");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i32[0]"));
                for i in 1..4 {
                    out.push_str(&format!(", {v}.i32[{i}]"));
                }
            }
            "UV8SI" => {
                for i in 0..8 {
                    out.push_str(&format!("    {v}.i32[{i}] = {};\n", rand_u32(32)));
                }
                out.push_str(&format!("    printf(\"    let {n} = u32x8::new(%u"));
                for _ in 1..8 {
                    out.push_str(", %u");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i32[0]"));
                for i in 1..8 {
                    out.push_str(&format!(", {v}.i32[{i}]"));
                }
            }
            "UV2DI" => {
                for i in 0..2 {
                    out.push_str(&format!("    {v}.i64[{i}] = {}UL;\n", rand_u64()));
                }
                out.push_str(&format!("    printf(\"    let {n} = u64x2::new(%lu"));
                for _ in 1..2 {
                    out.push_str(", %lu");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i64[0]"));
                for i in 1..2 {
                    out.push_str(&format!(", {v}.i64[{i}]"));
                }
            }
            "UV4DI" => {
                for i in 0..4 {
                    out.push_str(&format!("    {v}.i64[{i}] = {}UL;\n", rand_u64()));
                }
                out.push_str(&format!("    printf(\"    let {n} = u64x4::new(%lu"));
                for _ in 1..4 {
                    out.push_str(", %lu");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i64[0]"));
                for i in 1..4 {
                    out.push_str(&format!(", {v}.i64[{i}]"));
                }
            }
            "V4SF" => {
                for i in 0..4 {
                    out.push_str(&format!("    {v}.f32[{i}] = {};\n", rand_f32()));
                }
                out.push_str(&format!("    printf(\"    let {n} = u32x4::new(%u"));
                for _ in 1..4 {
                    out.push_str(", %u");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i32[0]"));
                for i in 1..4 {
                    out.push_str(&format!(", {v}.i32[{i}]"));
                }
            }
            "V8SF" => {
                for i in 0..8 {
                    out.push_str(&format!("    {v}.f32[{i}] = {};\n", rand_f32()));
                }
                out.push_str(&format!("    printf(\"    let {n} = u32x8::new(%u"));
                for _ in 1..8 {
                    out.push_str(", %u");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i32[0]"));
                for i in 1..8 {
                    out.push_str(&format!(", {v}.i32[{i}]"));
                }
            }
            "V2DF" => {
                for i in 0..2 {
                    out.push_str(&format!("    {v}.f64[{i}] = {};\n", rand_f64()));
                }
                out.push_str(&format!("    printf(\"    let {n} = u64x2::new(%lu"));
                for _ in 1..2 {
                    out.push_str(", %lu");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i64[0]"));
                for i in 1..2 {
                    out.push_str(&format!(", {v}.i64[{i}]"));
                }
            }
            "V4DF" => {
                for i in 0..4 {
                    out.push_str(&format!("    {v}.f64[{i}] = {};\n", rand_f64()));
                }
                out.push_str(&format!("    printf(\"    let {n} =  u64x4::new(%lu"));
                for _ in 1..4 {
                    out.push_str(", %lu");
                }
                out.push_str(&format!(");\\n\",\n    {v}.i64[0]"));
                for i in 1..4 {
                    out.push_str(&format!(", {v}.i64[{i}]"));
                }
            }
            "SI" | "DI" | "USI" | "UDI" | "UQI" | "QI" | "CVPOINTER" | "HI" => (),
            _ => panic!("unknown type: {t}"),
        }
        if !out.is_empty() {
            out.push_str(");");
        }
        out
    };
    let type_to_rp = |t: &str| -> &str {
        match t {
            "SI" => "    printf(\"    let r: i32 = %d;\\n\", o);",
            "DI" => "    printf(\"    let r: i64 = %ld;\\n\", o);",
            "USI" => "    printf(\"    let r: u32 = %u;\\n\", o);",
            "UDI" => "    printf(\"    let r: u64 = %lu;\\n\", o);",
            "UQI" => "    printf(\"    let r: u32 = %u;\\n\", o);",
            "QI" => "    printf(\"    let r: i32 = %d;\\n\", o);",
            "HI" => "    printf(\"    let r: i32 = %d;\\n\", o);",
            "V32QI" | "V16HI" | "V8SI" | "V4DI" | "UV32QI" | "UV16HI" | "UV8SI" | "UV4DI"
            | "V8SF" | "V4DF" => {
                "    printf(\"    let r = i64x4::new(%ld, %ld, %ld, %ld);\\n\", o.i64[0], o.i64[1], o.i64[2], o.i64[3]);"
            }
            _ => "    printf(\"    let r = i64x2::new(%ld, %ld);\\n\", o.i64[0], o.i64[1]);",
        }
    };
    let type_to_rx = |t: &str| -> &str {
        match t {
            "SI" | "DI" | "USI" | "UDI" | "UQI" | "QI" | "HI" => "o",
            _ => "o.v",
        }
    };
    let type_to_imm = |t| -> i8 {
        match t {
            'b' => 4,
            'h' => 3,
            'w' => 2,
            'd' => 1,
            _ => panic!("unsupported type"),
        }
    };

    let impl_function = {
        let fn_output = if out_t.to_lowercase() == "void" {
            String::new()
        } else {
            format!("    {} o;", type_to_ct(out_t))
        };
        let mut fn_inputs = match para_num {
            1 => format!(
                "    {} a;\n{}",
                type_to_ct(in_t[0]),
                type_to_va("a", in_t[0])
            ),
            2 => format!(
                "    {} a;\n{}\n    {} b;\n{}",
                type_to_ct(in_t[0]),
                type_to_va("a", in_t[0]),
                type_to_ct(in_t[1]),
                type_to_va("b", in_t[1])
            ),
            3 => format!(
                "    {} a;\n{}\n    {} b;\n{}\n    {} c;\n{}",
                type_to_ct(in_t[0]),
                type_to_va("a", in_t[0]),
                type_to_ct(in_t[1]),
                type_to_va("b", in_t[1]),
                type_to_ct(in_t[2]),
                type_to_va("c", in_t[2])
            ),
            4 => format!(
                "    {} a;\n{}\n    {} b;\n{}\n    {} c;\n{}\n    {} d;\n{}",
                type_to_ct(in_t[0]),
                type_to_va("a", in_t[0]),
                type_to_ct(in_t[1]),
                type_to_va("b", in_t[1]),
                type_to_ct(in_t[2]),
                type_to_va("c", in_t[2]),
                type_to_ct(in_t[3]),
                type_to_va("d", in_t[3])
            ),
            _ => panic!("unsupported parameter number"),
        };
        let mut fn_params = match para_num {
            1 => "(a.v)".to_string(),
            2 => "(a.v, b.v)".to_string(),
            3 => "(a.v, b.v, c.v)".to_string(),
            4 => "(a.v, b.v, c.v, d.v)".to_string(),
            _ => "unsupported parameter number".to_string(),
        };
        let mut as_params = match para_num {
            1 => "(black_box(transmute(a)))".to_string(),
            2 => "(black_box(transmute(a)), black_box(transmute(b)))".to_string(),
            3 => "(black_box(transmute(a)), black_box(transmute(b)), black_box(transmute(c)))".to_string(),
            4 => "(black_box(transmute(a)), black_box(transmute(b)), black_box(transmute(c)), black_box(transmute(d)))".to_string(),
            _ => panic!("unsupported parameter number"),
        };
        let mut as_args = String::new();
        if para_num == 1 && in_t[0] == "HI" {
            fn_inputs = "".to_string();
            match asm_fmts[1].as_str() {
                "si13" => {
                    let val = rand_i32(13);
                    fn_params = format!("({val})");
                    as_params = format!("::<{val}>()");
                }
                "i13" => {
                    let val = rand_u32(12);
                    fn_params = format!("({val})");
                    as_params = format!("::<{val}>()");
                }
                "si10" => {
                    let val = rand_i32(10);
                    fn_params = format!("({val})");
                    as_params = format!("::<{val}>()");
                }
                _ => panic!("unsupported assembly format: {}", asm_fmts[1]),
            }
        } else if para_num == 1
            && (in_t[0] == "SI" || in_t[0] == "DI")
            && asm_fmts[1].starts_with("rj")
        {
            fn_params = "(a)".to_string();
            if in_t[0] == "SI" {
                as_params = "(black_box(%d))".to_string();
            } else {
                as_params = "(black_box(%ld))".to_string();
            }
            as_args = ", a".to_string();
        } else if para_num == 2 && (in_t[1] == "UQI" || in_t[1] == "USI") {
            if asm_fmts[2].starts_with("ui") {
                fn_inputs = format!(
                    "    {} a;\n{}",
                    type_to_ct(in_t[0]),
                    type_to_va("a", in_t[0])
                );
                let val = rand_u32(asm_fmts[2].get(2..).unwrap().parse::<u8>().unwrap());
                fn_params = format!("(a.v, {val})");
                as_params = format!("::<{val}>(black_box(transmute(a)))");
            } else {
                panic!("unsupported assembly format: {}", asm_fmts[2]);
            }
        } else if para_num == 2 && in_t[1] == "QI" {
            if asm_fmts[2].starts_with("si") {
                fn_inputs = format!(
                    "    {} a;\n{}",
                    type_to_ct(in_t[0]),
                    type_to_va("a", in_t[0])
                );
                let val = rand_i32(asm_fmts[2].get(2..).unwrap().parse::<u8>().unwrap());
                fn_params = format!("(a.v, {val})");
                as_params = format!("::<{val}>(black_box(transmute(a)))");
            } else {
                panic!("unsupported assembly format: {}", asm_fmts[2]);
            }
        } else if para_num == 2 && in_t[1] == "SI" && asm_fmts[2].starts_with("rk") {
            fn_params = "(a.v, b)".to_string();
            as_params = "(black_box(transmute(a)), %d)".to_string();
            as_args = ", b".to_string();
        } else if para_num == 2 && in_t[0] == "CVPOINTER" && in_t[1] == "SI" {
            if asm_fmts[2].starts_with("si") {
                fn_inputs = format!(
                    "    union v{}qi _a;\n{}\n    {} a = &_a;",
                    target.bytes(),
                    type_to_va(
                        "_a",
                        if target == TargetFeature::Lsx {
                            "A16QI"
                        } else {
                            "A32QI"
                        }
                    ),
                    type_to_ct(in_t[0])
                );
                fn_params = "(a, 0)".to_string();
                as_params = "::<0>(a.as_ptr())".to_string();
            } else {
                panic!("unsupported assembly format: {}", asm_fmts[2]);
            }
        } else if para_num == 2 && in_t[0] == "CVPOINTER" && in_t[1] == "DI" {
            if asm_fmts[2].as_str() == "rk" {
                fn_inputs = format!(
                    "    union v{}qi _a;\n{}\n    {} a = &_a;",
                    target.bytes(),
                    type_to_va(
                        "_a",
                        if target == TargetFeature::Lsx {
                            "A16QI"
                        } else {
                            "A32QI"
                        }
                    ),
                    type_to_ct(in_t[0])
                );
                fn_params = "(a, 0)".to_string();
                as_params = "(a.as_ptr(), 0)".to_string();
            } else {
                panic!("unsupported assembly format: {}", asm_fmts[2]);
            }
        } else if para_num == 3 && in_t[2] == "UQI" && asm_fmts[1].starts_with("rj") {
            if asm_fmts[2].starts_with("ui") {
                fn_inputs = format!(
                    "    {} a;\n{}",
                    type_to_ct(in_t[0]),
                    type_to_va("a", in_t[0])
                );
                let ival = rand_i32(32);
                let uval = rand_u32(asm_fmts[2].get(2..).unwrap().parse::<u8>().unwrap());
                fn_params = format!("(a.v, {ival}, {uval})");
                as_params = format!("::<{uval}>(black_box(transmute(a)), {ival})");
            } else {
                panic!("unsupported assembly format: {}", asm_fmts[2]);
            }
        } else if para_num == 3 && (in_t[2] == "USI" || in_t[2] == "UQI") {
            if asm_fmts[2].starts_with("ui") {
                fn_inputs = format!(
                    "    {} a;\n{}\n    {} b;\n{}",
                    type_to_ct(in_t[0]),
                    type_to_va("a", in_t[0]),
                    type_to_ct(in_t[1]),
                    type_to_va("b", in_t[1]),
                );
                let val = rand_u32(asm_fmts[2].get(2..).unwrap().parse::<u8>().unwrap());
                fn_params = format!("(a.v, b.v, {val})");
                as_params = format!("::<{val}>(black_box(transmute(a)), black_box(transmute(b)))");
            } else {
                panic!("unsupported assembly format: {}", asm_fmts[2]);
            }
        } else if para_num == 3 && in_t[1] == "CVPOINTER" && in_t[2] == "SI" {
            if asm_fmts[2].as_str() == "si12" {
                fn_inputs = format!(
                    "    {} a;\n{}\n    union v{}qi o;\n{}\n    {} b = &o;",
                    type_to_ct(in_t[0]),
                    type_to_va("a", in_t[0]),
                    target.bytes(),
                    type_to_va(
                        "o",
                        if target == TargetFeature::Lsx {
                            "AM16QI"
                        } else {
                            "AM32QI"
                        }
                    ),
                    type_to_ct(in_t[1])
                );
                fn_params = "(a.v, b, 0)".to_string();
                as_params = "::<0>(black_box(transmute(a)), o.as_mut_ptr())".to_string();
            } else {
                panic!("unsupported assembly format: {}", asm_fmts[2]);
            }
        } else if para_num == 3 && in_t[1] == "CVPOINTER" && in_t[2] == "DI" {
            if asm_fmts[2].as_str() == "rk" {
                fn_inputs = format!(
                    "    {} a;\n{}\n    union v{}qi o;\n{}\n    {} b = &o;",
                    type_to_ct(in_t[0]),
                    type_to_va("a", in_t[0]),
                    target.bytes(),
                    type_to_va(
                        "o",
                        if target == TargetFeature::Lsx {
                            "AM16QI"
                        } else {
                            "AM32QI"
                        }
                    ),
                    type_to_ct(in_t[1])
                );
                fn_params = "(a.v, b, 0)".to_string();
                as_params = "(black_box(transmute(a)), o.as_mut_ptr(), 0)".to_string();
            } else {
                panic!("unsupported assembly format: {}", asm_fmts[2]);
            }
        } else if para_num == 4 {
            match (asm_fmts[2].as_str(), current_name.chars().last().unwrap()) {
                ("si8", t) => {
                    fn_inputs = format!(
                        "    {} a;\n{}\n    union v{}qi o;\n{}\n    {} b = &o;",
                        type_to_ct(in_t[0]),
                        type_to_va("a", in_t[0]),
                        target.bytes(),
                        type_to_va(
                            "o",
                            if target == TargetFeature::Lsx {
                                "AM16QI"
                            } else {
                                "AM32QI"
                            }
                        ),
                        type_to_ct(in_t[1])
                    );
                    let val = rand_u32(type_to_imm(t).try_into().unwrap());
                    fn_params = format!("(a.v, b, 0, {val})");
                    as_params = format!("::<0, {val}>(black_box(transmute(a)), o.as_mut_ptr())");
                }
                (_, _) => panic!(
                    "unsupported assembly format: {} for {}",
                    asm_fmts[2], current_name
                ),
            };
        }
        let fn_docall = if out_t.to_lowercase() == "void" {
            format!("    __{current_name}{fn_params};")
        } else {
            format!("    {} = __{current_name}{fn_params};", type_to_rx(out_t))
        };
        let fn_result = if out_t.to_lowercase() == "void" {
            if target == TargetFeature::Lsx {
                type_to_rp("V16QI")
            } else {
                type_to_rp("V32QI")
            }
        } else {
            type_to_rp(out_t)
        };
        let fn_assert = {
            if out_t.to_lowercase() == "void" {
                format!(
                    "    printf(\"\\n    {current_name}{as_params};\\n    assert_eq!(r, transmute(o));\\n\"{as_args});"
                )
            } else if current_name.starts_with("lasx_cast_128") {
                format!(
                    "    printf(\"\\n    assert_eq!(r.as_array()[0..2], transmute::<_, i64x4>({current_name}{as_params}).as_array()[0..2]);\\n\"{as_args});"
                )
            } else {
                format!(
                    "    printf(\"\\n    assert_eq!(r, transmute({current_name}{as_params}));\\n\"{as_args});"
                )
            }
        };
        format!(
            r#"
static void {current_name}(void)
{{
    printf("\n#[simd_test(enable = \"{}\")]\n");
    printf("unsafe fn test_{current_name}() {{\n");
{fn_inputs}
{fn_output}
{fn_docall}
{fn_result}
{fn_assert}
    printf("}}\n");
}}
"#,
            target.as_target_feature_arg(current_name)
        )
    };
    let call_function = format!("    {current_name}();\n");
    (impl_function, call_function)
}

pub fn main() -> io::Result<()> {
    let args: Vec<String> = env::args().collect();
    let in_file = args.get(1).cloned().expect("Input file missing!");
    let in_file_path = PathBuf::from(&in_file);
    let in_file_name = in_file_path
        .file_name()
        .unwrap()
        .to_os_string()
        .into_string()
        .unwrap();

    let ext_name = if in_file_name.starts_with("lasx") {
        "lasx"
    } else {
        "lsx"
    };

    if in_file_name.ends_with(".h") {
        gen_spec(in_file, ext_name)
    } else if args.get(2).is_some() {
        gen_test(in_file, ext_name)
    } else {
        gen_bind(in_file, ext_name)
    }
}
