use heck::ShoutySnakeCase;
use std::collections::HashMap;
use std::mem;
use witx::*;

pub struct Generated {
    pub header: String,
    pub source: String,
}

pub fn to_c(doc: &Document, inputs_str: &str, is64bit: bool) -> Generated {
    let mut header = String::new();

    header.push_str(&format!(
        r#"/**
 * THIS FILE IS AUTO-GENERATED from the following files:
 *   {}
 *
 * To regenerate this file execute:
 *
 *     cargo run --manifest-path tools/wasi-headers/Cargo.toml generate-libc
 */

#ifndef __wasi__
#define __wasi__ 1
#endif

#ifndef __wasi_api_h
#define __wasi_api_h

#include <stddef.h>
#include <stdint.h>

_Static_assert(_Alignof(int8_t) == 1, "non-wasi data layout");
_Static_assert(_Alignof(uint8_t) == 1, "non-wasi data layout");
_Static_assert(_Alignof(int16_t) == 2, "non-wasi data layout");
_Static_assert(_Alignof(uint16_t) == 2, "non-wasi data layout");
_Static_assert(_Alignof(int32_t) == 4, "non-wasi data layout");
_Static_assert(_Alignof(uint32_t) == 4, "non-wasi data layout");
_Static_assert(_Alignof(int64_t) == 8, "non-wasi data layout");
_Static_assert(_Alignof(uint64_t) == 8, "non-wasi data layout");
"#, 
        inputs_str,
    ));

    if is64bit {
        header.push_str(&format!(
        r#"_Static_assert(_Alignof(intptr_t) == 8, "non-wasi data layout");
_Static_assert(_Alignof(uintptr_t) == 8, "non-wasi data layout");
_Static_assert(_Alignof(void*) == 8, "non-wasi data layout");
typedef int64_t __wasi_int_t;
typedef uint64_t __wasi_uint_t;
_Static_assert(_Alignof(__wasi_int_t) == 8, "non-wasi data layout");
_Static_assert(_Alignof(__wasi_uint_t) == 8, "non-wasi data layout");
"#));
    } else {
        header.push_str(&format!(
            r#"_Static_assert(_Alignof(intptr_t) == 4, "non-wasi data layout");
_Static_assert(_Alignof(uintptr_t) == 4, "non-wasi data layout");
_Static_assert(_Alignof(void*) == 4, "non-wasi data layout");
typedef int32_t __wasi_int_t;
typedef uint32_t __wasi_uint_t;
_Static_assert(_Alignof(__wasi_int_t) == 4, "non-wasi data layout");
_Static_assert(_Alignof(__wasi_uint_t) == 4, "non-wasi data layout");
"#));
    }

    header.push_str(&format!(
        r#"#ifdef __cplusplus
extern "C" {{
#endif

// TODO: Encoding this in witx.
#define __WASI_DIRCOOKIE_START (UINT64_C(0))
"#));

    let mut source = String::new();
    source.push_str(&format!(
        r#"/**
 * THIS FILE IS AUTO-GENERATED from the following files:
 *   {}
 *
 * To regenerate this file execute:
 *
 *     cargo run --manifest-path tools/wasi-headers/Cargo.toml generate-libc
 *
 * Modifications to this file will cause CI to fail, the code generator tool
 * must be modified to change this file.
 */

#include <wasi/api.h>
#include <string.h>

"#,
        inputs_str,
    ));

    let mut type_constants = HashMap::new();
    for c in doc.constants() {
        type_constants.entry(&c.ty).or_insert(Vec::new()).push(c);
    }

    for nt in doc.typenames() {
        print_datatype(&mut header, &*nt);

        if let Some(constants) = type_constants.remove(&nt.name) {
            for constant in constants {
                print_constant(&mut header, &constant);
            }
        }
    }

    for m in doc.modules() {
        print_module(&mut header, &mut source, &m);
    }

    header.push_str(
        r#"#ifdef __cplusplus
}
#endif

#endif
"#,
    );

    // Replace the module names
    if is64bit {
        header = header.replace("wasix_module_name", "wasix_64v1");
        source = source.replace("wasix_module_name", "wasix_64v1");
    } else {
        header = header.replace("wasix_module_name", "wasix_32v1");
        source = source.replace("wasix_module_name", "wasix_32v1");
    }

    Generated { header, source }
}

fn print_datatype(ret: &mut String, nt: &NamedType) {
    if !nt.docs.is_empty() {
        ret.push_str("/**\n");
        for line in nt.docs.lines() {
            ret.push_str(&format!(" * {}\n", line));
        }
        ret.push_str(" */\n");
    }

    if nt.hidden {
        ret.push_str("/**\n");
        ret.push_str("*** Hidden type\n");
    }

    match &nt.tref {
        TypeRef::Value(v) => match &**v {
            Type::Record(s) => print_record(ret, &nt.name, s),
            Type::Variant(v) => print_variant(ret, &nt.name, v),
            Type::Handle(h) => print_handle(ret, &nt.name, h),
            Type::Builtin { .. }
            | Type::List { .. }
            | Type::Pointer { .. }
            | Type::ConstPointer { .. } => print_alias(ret, &nt.name, &nt.tref),
        },
        TypeRef::Name(_) => print_alias(ret, &nt.name, &nt.tref),
    }

    if nt.hidden {
        ret.push_str(" */\n");
    }
}

fn print_alias(ret: &mut String, name: &Id, dest: &TypeRef) {
    match &**dest.type_() {
        Type::List(_) => {
            // Don't emit arrays as top-level types; instead we special-case
            // them in places like parameter lists so that we can pass them
            // as pointer and length pairs.
        }
        _ => {
            if name.as_str() == "size" {
                // Special-case "size" as "uint32_t" -- TODO: Encode this in witx.
                ret.push_str(&format!(
                    "typedef uint32_t __wasi_{}_t;\n",
                    ident_name(name)
                ));
            } else {
                ret.push_str(&format!(
                    "typedef {} __wasi_{}_t;\n",
                    typeref_name(dest),
                    ident_name(name)
                ));
            }
            ret.push_str("\n");

            ret.push_str(&format!(
                "_Static_assert(sizeof(__wasi_{}_t) == {}, \"witx calculated size\");\n",
                ident_name(name),
                dest.mem_size_align().size
            ));
            ret.push_str(&format!(
                "_Static_assert(_Alignof(__wasi_{}_t) == {}, \"witx calculated align\");\n",
                ident_name(name),
                dest.mem_size_align().align
            ));

            ret.push_str("\n");
        }
    }
}

fn print_enum(ret: &mut String, name: &Id, v: &Variant) {
    ret.push_str(&format!(
        "typedef {} __wasi_{}_t;\n",
        intrepr_name(v.tag_repr),
        ident_name(name)
    ));
    ret.push_str("\n");

    for (index, case) in v.cases.iter().enumerate() {
        if !case.docs.is_empty() {
            ret.push_str("/**\n");
            for line in case.docs.lines() {
                ret.push_str(&format!(" * {}\n", line));
            }
            ret.push_str(" */\n");
        }
        ret.push_str(&format!(
            "#define __WASI_{}_{} ({}({}))\n",
            ident_name(&name).to_shouty_snake_case(),
            ident_name(&case.name).to_shouty_snake_case(),
            intrepr_const(v.tag_repr),
            index
        ));
        ret.push_str("\n");
    }

    ret.push_str(&format!(
        "_Static_assert(sizeof(__wasi_{}_t) == {}, \"witx calculated size\");\n",
        ident_name(name),
        v.tag_repr.mem_size()
    ));
    ret.push_str(&format!(
        "_Static_assert(_Alignof(__wasi_{}_t) == {}, \"witx calculated align\");\n",
        ident_name(name),
        v.tag_repr.mem_align()
    ));

    ret.push_str("\n");
}

fn print_constant(ret: &mut String, const_: &Constant) {
    if !const_.docs.is_empty() {
        ret.push_str("/**\n");
        for line in const_.docs.lines() {
            ret.push_str(&format!(" * {}\n", line));
        }
        ret.push_str(" */\n");
    }
    ret.push_str(&format!(
        "#define __WASI_{}_{} ((__wasi_{}_t)+({}))\n",
        ident_name(&const_.ty).to_shouty_snake_case(),
        ident_name(&const_.name).to_shouty_snake_case(),
        ident_name(&const_.ty),
        const_.value,
    ));
    ret.push_str("\n");
}

fn print_record(ret: &mut String, name: &Id, s: &RecordDatatype) {
    if let Some(repr) = s.bitflags_repr() {
        ret.push_str(&format!(
            "typedef {} __wasi_{}_t;\n\n",
            intrepr_name(repr),
            ident_name(name)
        ));
        for (i, member) in s.members.iter().enumerate() {
            if !member.docs.is_empty() {
                ret.push_str("/**\n");
                for line in member.docs.lines() {
                    ret.push_str(&format!(" * {}\n", line));
                }
                ret.push_str(" */\n");
            }
            ret.push_str(&format!(
                "#define __WASI_{}_{} ((__wasi_{}_t)+(1 << {}))\n",
                ident_name(name).to_shouty_snake_case(),
                ident_name(&member.name).to_shouty_snake_case(),
                ident_name(name),
                i,
            ));
            ret.push_str("\n");
        }
        return;
    }
    ret.push_str(&format!(
        "typedef struct __wasi_{}_t {{\n",
        ident_name(name)
    ));

    for member in &s.members {
        if !member.docs.is_empty() {
            ret.push_str("    /**\n");
            for line in member.docs.lines() {
                ret.push_str(&format!("     * {}\n", line));
            }
            ret.push_str("     */\n");
        }
        ret.push_str(&format!(
            "    {} {};\n",
            typeref_name(&member.tref),
            ident_name(&member.name)
        ));
        ret.push_str("\n");
    }

    ret.push_str(&format!("}} __wasi_{}_t;\n", ident_name(name)));
    ret.push_str("\n");

    ret.push_str(&format!(
        "_Static_assert(sizeof(__wasi_{}_t) == {}, \"witx calculated size\");\n",
        ident_name(name),
        s.mem_size()
    ));
    ret.push_str(&format!(
        "_Static_assert(_Alignof(__wasi_{}_t) == {}, \"witx calculated align\");\n",
        ident_name(name),
        s.mem_align()
    ));

    for layout in s.member_layout() {
        ret.push_str(&format!(
            "_Static_assert(offsetof(__wasi_{}_t, {}) == {}, \"witx calculated offset\");\n",
            ident_name(name),
            ident_name(&layout.member.name),
            layout.offset
        ));
    }

    ret.push_str("\n");
}

fn print_variant(ret: &mut String, name: &Id, v: &Variant) {
    if v.cases.iter().all(|v| v.tref.is_none()) {
        return print_enum(ret, name, v);
    }
    ret.push_str(&format!(
        "typedef union __wasi_{}_u_t {{\n",
        ident_name(name)
    ));

    for case in &v.cases {
        if let Some(tref) = &case.tref {
            if !case.docs.is_empty() {
                ret.push_str("    /**\n");
                for line in case.docs.lines() {
                    ret.push_str(&format!("    * {}\n", line));
                }
                ret.push_str("    */\n");
            }
            ret.push_str(&format!(
                "    {} {};\n",
                typeref_name(tref),
                ident_name(&case.name)
            ));
        }
    }
    ret.push_str(&format!("}} __wasi_{}_u_t;\n", ident_name(name)));

    ret.push_str(&format!(
        "typedef struct __wasi_{}_t {{\n",
        ident_name(name)
    ));

    ret.push_str(&format!("    {} tag;\n", intrepr_name(v.tag_repr)));
    ret.push_str(&format!("    __wasi_{}_u_t u;\n", ident_name(name)));

    ret.push_str(&format!("}} __wasi_{}_t;\n", ident_name(name)));
    ret.push_str("\n");

    ret.push_str(&format!(
        "_Static_assert(sizeof(__wasi_{}_t) == {}, \"witx calculated size\");\n",
        ident_name(name),
        v.mem_size()
    ));
    ret.push_str(&format!(
        "_Static_assert(_Alignof(__wasi_{}_t) == {}, \"witx calculated align\");\n",
        ident_name(name),
        v.mem_align()
    ));

    ret.push_str("\n");
}

fn print_handle(ret: &mut String, name: &Id, h: &HandleDatatype) {
    ret.push_str(&format!("typedef int __wasi_{}_t;\n\n", ident_name(name)));

    ret.push_str(&format!(
        "_Static_assert(sizeof(__wasi_{}_t) == {}, \"witx calculated size\");\n",
        ident_name(name),
        h.mem_size()
    ));
    ret.push_str(&format!(
        "_Static_assert(_Alignof(__wasi_{}_t) == {}, \"witx calculated align\");\n",
        ident_name(name),
        h.mem_align()
    ));

    ret.push_str("\n");
}

fn print_module(header: &mut String, source: &mut String, m: &Module) {
    header.push_str("/**\n");
    header.push_str(&format!(" * @defgroup {}\n", ident_name(&m.name),));
    for line in m.docs.lines() {
        header.push_str(&format!(" * {}\n", line));
    }
    header.push_str(" * @{\n");
    header.push_str(" */\n");
    header.push_str("\n");

    for func in m.funcs() {
        print_func_header(header, &func);
        print_func_source(source, &func, &m.name);
    }

    header.push_str("/** @} */\n");
    header.push_str("\n");
}

fn print_func_header(ret: &mut String, func: &InterfaceFunc) {
    if !func.docs.is_empty() {
        ret.push_str("/**\n");
        for line in func.docs.lines() {
            ret.push_str(&format!(" * {}\n", line));
        }
        if !func.results.is_empty() {
            let first_result = &func.results[0];
            if !first_result.docs.is_empty() {
                ret.push_str(" * @return\n");
                for line in first_result.docs.lines() {
                    ret.push_str(&format!(" * {}\n", line));
                }
            }
        }
        ret.push_str(" */\n");
    }

    print_func_signature(ret, func, true);

    if func.results.len() > 0 {
        ret.push_str(" __attribute__((__warn_unused_result__))");
    }

    ret.push_str(";\n");
}

fn print_func_signature(ret: &mut String, func: &InterfaceFunc, header: bool) {
    let mut params = Vec::new();
    for param in func.params.iter() {
        add_params(
            &mut params,
            &ident_name(&param.name),
            &param.tref,
            &param.docs,
        );
    }
    match func.results.len() {
        0 => {
            if func.noreturn {
                ret.push_str("_Noreturn ");
            }
            ret.push_str("void ");
        }
        1 => {
            assert!(!func.noreturn);
            push_return_type(ret, &mut params, &func.results[0].tref);
        }
        _ => panic!("unsupported number of return values"),
    }

    ret.push_str(&format!("__wasi_{}(\n", ident_name(&func.name)));
    if params.len() == 0 {
        ret.push_str("    void\n");
    } else {
        for (i, (docs, param)) in params.iter().enumerate() {
            if header {
                if let Some(docs) = docs {
                    ret.push_str("    /**\n");
                    for line in docs.lines() {
                        ret.push_str(&format!("     * {}\n", line));
                    }
                    ret.push_str("     */\n");
                }
            }
            ret.push_str("    ");
            ret.push_str(&param);
            if i != params.len() - 1 {
                ret.push_str(",");
            }
            ret.push_str("\n");
        }
    }
    ret.push_str(")");
}

fn print_func_source(ret: &mut String, func: &InterfaceFunc, module_name: &Id) {
    let (params, results) = func.wasm_signature();

    if func.noreturn {
        ret.push_str("_Noreturn ");
    }
    match results.len() {
        0 => ret.push_str("void "),
        1 => {
            ret.push_str(wasm_type(&results[0]));
            ret.push_str(" ");
        }
        _ => unimplemented!(),
    }

    ret.push_str("__imported_");
    ret.push_str(&ident_name(module_name));
    ret.push_str("_");
    ret.push_str(&ident_name(&func.name));
    ret.push_str("(");
    for (i, param) in params.iter().enumerate() {
        if i > 0 {
            ret.push_str(", ");
        }
        ret.push_str(wasm_type(param));
        ret.push_str(&format!(" arg{}", i));
    }
    ret.push_str(") __attribute__((\n");
    ret.push_str(&format!(
        "    __import_module__(\"{}\"),\n",
        ident_name(module_name)
    ));
    ret.push_str(&format!(
        "    __import_name__(\"{}\")\n",
        ident_name(&func.name)
    ));
    ret.push_str("));\n\n");
    print_func_signature(ret, func, false);
    ret.push_str("{\n");

    // Any string arguments will have their length passed to the raw wasi API,
    // but we only took a `char*` as a parameter. Get the length of all string
    // arguments here for it to get passed along later.
    for param in func.params.iter() {
        if let Type::List(ty) = &**param.tref.type_() {
            if let Type::Builtin(BuiltinType::Char) = &**ty.type_() {
                ret.push_str(&format!(
                    "    size_t {}_len = strlen({0});\n",
                    ident_name(&param.name)
                ));
            }
        }
    }

    func.call_wasm(
        module_name,
        &mut C {
            src: ret,
            params: &func.params,
            block_storage: Vec::new(),
            blocks: Vec::new(),
        },
    );

    ret.push_str("}\n\n");

    /// This is a structure which implements the glue necessary to translate
    /// between the C API of a function and the desired WASI ABI we're being
    /// told it has.
    ///
    /// It's worth nothing that this will, in the long run, get much fancier.
    /// For now this is extremely simple and entirely assumes that the WASI ABI
    /// matches our C ABI. This means it will only really generate valid code
    /// as-is *today* and won't work for any updates to the WASI ABI in the
    /// future.
    ///
    /// It's hoped that this situation will improve as interface types and witx
    /// continue to evolve and there's a more clear path forward for how to
    /// translate an interface types signature to a C API.
    struct C<'a> {
        src: &'a mut String,
        params: &'a [InterfaceFuncParam],
        block_storage: Vec<String>,
        blocks: Vec<String>,
    }

    impl Bindgen for C<'_> {
        type Operand = String;

        fn push_block(&mut self) {
            let prev = mem::replace(self.src, String::new());
            self.block_storage.push(prev);
        }

        fn finish_block(&mut self, operand: Option<String>) {
            let to_restore = self.block_storage.pop().unwrap();
            let src = mem::replace(self.src, to_restore);
            assert!(src.is_empty());
            match operand {
                None => self.blocks.push(String::new()),
                Some(s) => self.blocks.push(s),
            }
        }

        fn allocate_space(&mut self, _: usize, _: &NamedType) {
            // not necessary due to us taking parameters as pointers
        }

        fn emit(
            &mut self,
            inst: &Instruction<'_>,
            operands: &mut Vec<String>,
            results: &mut Vec<String>,
        ) {
            let mut top_as = |cvt: &str| {
                let s = operands.pop().unwrap();
                results.push(format!("({}) {}", cvt, s));
            };

            match inst {
                Instruction::GetArg { nth } => {
                    results.push(ident_name(&self.params[*nth].name));
                }
                // For the C bindings right now any parameter which needs its
                // address taken is already taken as a pointer, so we can just
                // forward the operand to the result.
                Instruction::AddrOf => results.push(format!("{}", operands.pop().unwrap())),

                Instruction::I64FromU64
                | Instruction::I64FromPointer
                | Instruction::I64FromConstPointer
                | Instruction::I64FromUsize => top_as("int64_t"),

                | Instruction::I32FromHandle { .. }
                | Instruction::I32FromChar
                | Instruction::I32FromU8
                | Instruction::I32FromS8
                | Instruction::I32FromChar8
                | Instruction::I32FromU16
                | Instruction::I32FromS16
                | Instruction::I32FromU32
                | Instruction::I32FromPointer
                | Instruction::I32FromConstPointer
                | Instruction::I32FromUsize => top_as("int32_t"),

                Instruction::I32FromBitflags { .. } | Instruction::I64FromBitflags { .. } => {
                    // Rely on C's casting for this
                    results.push(operands.pop().unwrap());
                }

                // No conversion necessary
                Instruction::F32FromIf32
                | Instruction::F64FromIf64
                | Instruction::If32FromF32
                | Instruction::If64FromF64
                | Instruction::I64FromS64
                | Instruction::I32FromS32 => results.push(operands.pop().unwrap()),

                Instruction::ListPointerLength => {
                    let list = operands.pop().unwrap();
                    results.push(format!("(intptr_t) {}", list));
                    results.push(format!("(intptr_t) {}_len", list));
                }
                Instruction::ReturnPointerGet { n } => {
                    // We currently match the wasi ABI with the actual
                    // function's API signature in C, this means when a return
                    // pointer is asked for we can simply forward our parameter
                    // that's a return pointer.
                    results.push(format!("(intptr_t) retptr{}", n));
                }

                Instruction::Load { .. } => {
                    // this is currently skipped because return pointers are
                    // already evident in parameters and so we don't need to
                    // load/store from them again
                    results.push("dummy".to_string());
                }

                Instruction::ReuseReturn => {
                    results.push("ret".to_string());
                }

                Instruction::TupleLift { .. } => {
                    // this is currently skipped because tuples are only used
                    // as return values and those are always returned through
                    // out-ptrs, so the values have already been passed through
                    // at this point.
                    results.push("dummy".to_string());
                }

                Instruction::ResultLift => {
                    let err = self.blocks.pop().unwrap();

                    // Our ok block should either have done nothing (meaning
                    // there's no "ok" payload) or it loaded a return pointer
                    // and/or tuple some values. In all these cases we discard
                    // the results since out pointers have already been written
                    // to and we only return the discriminant from the
                    // function.
                    let ok = self.blocks.pop().unwrap();
                    assert!(ok == "dummy" || ok == "");

                    // Note that we just push the result of the error block.
                    // Our block management asserts that it's an expression,
                    // which in this case will basically always be casting the
                    // error code to an error code variant, which we return.
                    results.push(err);
                }

                // Enums are represented in C simply as the integral tag type
                Instruction::EnumLift { ty } => match &**ty.type_() {
                    Type::Variant(v) => top_as(intrepr_name(v.tag_repr)),
                    _ => unreachable!(),
                },
                Instruction::EnumLower { .. } => top_as("int32_t"),

                Instruction::CallWasm {
                    module,
                    name,
                    params: _,
                    results: func_results,
                } => {
                    assert!(func_results.len() < 2);
                    self.src.push_str("    ");
                    if func_results.len() > 0 {
                        self.src.push_str(wasm_type(&func_results[0]));
                        self.src.push_str(" ret = ");
                        results.push("ret".to_string());
                    }
                    self.src.push_str("__imported_");
                    self.src.push_str(module);
                    self.src.push_str("_");
                    self.src.push_str(name);
                    self.src.push_str("(");
                    self.src.push_str(&operands.join(", "));
                    self.src.push_str(");\n");
                }

                Instruction::Return { amt: 0 } => {}
                Instruction::Return { amt: 1 } => {
                    self.src.push_str("    return ");
                    self.src.push_str(&operands[0]);
                    self.src.push_str(";\n");
                }

                other => panic!("unimplemented instruction {:?}", other),
            }
        }
    }
}

fn push_return_type(ret: &mut String, params: &mut Vec<(Option<String>, String)>, tref: &TypeRef) {
    match &**tref.type_() {
        Type::Variant(v) => {
            assert_eq!(v.cases.len(), 2, "unsupported type as a return value");
            assert!(
                v.cases[0].name == "ok",
                "unsupported type as a return value"
            );
            assert!(
                v.cases[1].name == "err",
                "unsupported type as a return value"
            );
            let err = match &v.cases[1].tref {
                Some(ty) => ty,
                None => panic!("unsupported type as a return value"),
            };
            ret.push_str(&typeref_name(&err));
            ret.push_str(" ");

            let ok = match &v.cases[0].tref {
                Some(ty) => ty,
                None => return,
            };
            match &**ok.type_() {
                Type::Record(r) if r.is_tuple() => {
                    for (i, member) in r.members.iter().enumerate() {
                        params.push((None, format!("{} *retptr{}", typeref_name(&member.tref), i)));
                    }
                }
                _ => {
                    params.push((None, format!("{} *retptr0", typeref_name(ok))));
                }
            }
        }
        _ => panic!("unsupported type as a return value ({})", typeref_name(tref)),
    }
}

fn add_params(params: &mut Vec<(Option<String>, String)>, name: &str, tref: &TypeRef, docs: &str) {
    let docs = if docs.is_empty() {
        None
    } else {
        Some(docs.to_string())
    };
    match &**tref.type_() {
        Type::List(element) => match &**element.type_() {
            Type::Builtin(BuiltinType::Char) => {
                params.push((docs, format!("const char *{}", name)));
            }
            _ => {
                params.push((docs, format!("const {} *{}", typeref_name(&element), name)));
                params.push((
                    Some(format!("The length of the array pointed to by `{}`.", name,)),
                    format!("size_t {}_len", name),
                ));
            }
        },
        _ => {
            params.push((docs, format!("{} {}", typeref_name(tref), name)));
        }
    }
}

fn ident_name(i: &Id) -> String {
    i.as_str().to_string()
}

fn builtin_type_name(b: BuiltinType) -> &'static str {
    match b {
        BuiltinType::U8 { lang_c_char: true } => {
            panic!("no type name for string or char8 builtins")
        }
        BuiltinType::U8 { lang_c_char: false } => "uint8_t",
        BuiltinType::U16 => "uint16_t",
        BuiltinType::U64 {
            lang_ptr_size: true,
        } => "size_t",
        BuiltinType::U64 {
            lang_ptr_size: false,
        } => "uint64_t",
        BuiltinType::U32 {
            lang_ptr_size: true,
        } => "size_t",
        BuiltinType::U32 {
            lang_ptr_size: false,
        } => "uint32_t",
        BuiltinType::Usize => "__wasi_uint_t",
        BuiltinType::S8 => "int8_t",
        BuiltinType::S16 => "int16_t",
        BuiltinType::S32 => "int32_t",
        BuiltinType::S64 => "int64_t",
        BuiltinType::F32 => "float",
        BuiltinType::F64 => "double",
        BuiltinType::Char => "char32_t",
    }
}

fn typeref_name(tref: &TypeRef) -> String {
    match &**tref.type_() {
        Type::Builtin(BuiltinType::U8 { lang_c_char: true }) | Type::List(_) => {
            panic!("unsupported grammar: cannot construct name of string or array",)
        }
        _ => {}
    }

    match tref {
        TypeRef::Name(named_type) => namedtype_name(&named_type),
        TypeRef::Value(anon_type) => match &**anon_type {
            Type::List(_) => unreachable!("arrays excluded above"),
            Type::Builtin(b) => builtin_type_name(*b).to_string(),
            Type::Pointer(p) => format!("{} *", typeref_name(&*p)),
            Type::ConstPointer(p) => format!("const {} *", typeref_name(&*p)),
            Type::Record { .. } | Type::Variant { .. } | Type::Handle { .. } => unreachable!(
                "wasi should not have anonymous structs, unions, enums, flags, handles"
            ),
        },
    }
}

fn namedtype_name(named_type: &NamedType) -> String {
    match &**named_type.type_() {
        Type::Pointer(p) => format!("{} *", typeref_name(&*p)),
        Type::ConstPointer(p) => format!("const {} *", typeref_name(&*p)),
        Type::List(_) => unreachable!("arrays excluded above"),
        _ => format!("__wasi_{}_t", named_type.name.as_str()),
    }
}

fn intrepr_name(i: IntRepr) -> &'static str {
    match i {
        IntRepr::U8 => "uint8_t",
        IntRepr::U16 => "uint16_t",
        IntRepr::U32 => "uint32_t",
        IntRepr::U64 => "uint64_t",
    }
}

fn intrepr_const(i: IntRepr) -> &'static str {
    match i {
        IntRepr::U8 => "UINT8_C",
        IntRepr::U16 => "UINT16_C",
        IntRepr::U32 => "UINT32_C",
        IntRepr::U64 => "UINT64_C",
    }
}

fn wasm_type(wasm: &WasmType) -> &'static str {
    match wasm {
        WasmType::I32 => "int32_t",
        WasmType::I64 => "int64_t",
        WasmType::F32 => "float",
        WasmType::F64 => "double",
    }
}
