use crate::gen::block::Block;
use crate::gen::ifndef;
use crate::gen::out::{Content, OutFile};

#[derive(Default, PartialEq)]
pub struct Builtins<'a> {
    pub panic: bool,
    pub rust_string: bool,
    pub rust_str: bool,
    pub rust_slice: bool,
    pub rust_box: bool,
    pub rust_vec: bool,
    pub rust_fn: bool,
    pub rust_isize: bool,
    pub opaque: bool,
    pub unsafe_bitcopy: bool,
    pub rust_error: bool,
    pub manually_drop: bool,
    pub maybe_uninit: bool,
    pub trycatch: bool,
    pub ptr_len: bool,
    pub rust_str_new_unchecked: bool,
    pub rust_str_repr: bool,
    pub rust_slice_new: bool,
    pub rust_slice_repr: bool,
    pub exception: bool,
    pub relocatable: bool,
    pub friend_impl: bool,
    pub is_complete: bool,
    pub deleter_if: bool,
    pub content: Content<'a>,
}

impl<'a> Builtins<'a> {
    pub fn new() -> Self {
        Builtins::default()
    }
}

pub(super) fn write(out: &mut OutFile) {
    if out.builtin == Default::default() {
        return;
    }

    let include = &mut out.include;
    let builtin = &mut out.builtin;
    let out = &mut builtin.content;

    if builtin.rust_string {
        include.array = true;
        include.cstdint = true;
        include.string = true;
    }

    if builtin.rust_str {
        include.cstdint = true;
        include.string = true;
        builtin.friend_impl = true;
    }

    if builtin.rust_slice {
        include.iterator = true;
        include.type_traits = true;
        builtin.friend_impl = true;
    }

    if builtin.rust_box {
        include.new = true;
        include.type_traits = true;
        include.utility = true;
    }

    if builtin.rust_vec {
        include.algorithm = true;
        include.array = true;
        include.initializer_list = true;
        include.iterator = true;
        include.new = true;
        include.type_traits = true;
        include.utility = true;
        builtin.panic = true;
        builtin.unsafe_bitcopy = true;
    }

    if builtin.rust_fn {
        include.utility = true;
    }

    if builtin.rust_error {
        include.exception = true;
        builtin.friend_impl = true;
    }

    if builtin.rust_isize {
        include.basetsd = true;
    }

    if builtin.relocatable {
        include.type_traits = true;
    }

    out.begin_block(Block::Namespace("rust"));
    out.begin_block(Block::InlineNamespace("cxxbridge1"));
    writeln!(out, "// #include \"rust/cxx.h\"");

    ifndef::write(out, builtin.panic, "CXXBRIDGE1_PANIC");

    if builtin.rust_string {
        out.next_section();
        writeln!(out, "struct unsafe_bitcopy_t;");
    }

    if builtin.friend_impl {
        out.begin_block(Block::AnonymousNamespace);
        writeln!(out, "template <typename T>");
        writeln!(out, "class impl;");
        out.end_block(Block::AnonymousNamespace);
    }

    if builtin.rust_str && !builtin.rust_string {
        out.next_section();
        writeln!(out, "class String;");
    }

    ifndef::write(out, builtin.rust_string, "CXXBRIDGE1_RUST_STRING");
    ifndef::write(out, builtin.rust_str, "CXXBRIDGE1_RUST_STR");
    ifndef::write(out, builtin.rust_slice, "CXXBRIDGE1_RUST_SLICE");
    ifndef::write(out, builtin.rust_box, "CXXBRIDGE1_RUST_BOX");
    ifndef::write(out, builtin.unsafe_bitcopy, "CXXBRIDGE1_RUST_BITCOPY");
    ifndef::write(out, builtin.rust_vec, "CXXBRIDGE1_RUST_VEC");
    ifndef::write(out, builtin.rust_fn, "CXXBRIDGE1_RUST_FN");
    ifndef::write(out, builtin.rust_error, "CXXBRIDGE1_RUST_ERROR");
    ifndef::write(out, builtin.rust_isize, "CXXBRIDGE1_RUST_ISIZE");
    ifndef::write(out, builtin.opaque, "CXXBRIDGE1_RUST_OPAQUE");
    ifndef::write(out, builtin.relocatable, "CXXBRIDGE1_RELOCATABLE");

    if builtin.manually_drop {
        out.next_section();
        include.utility = true;
        writeln!(out, "template <typename T>");
        writeln!(out, "union ManuallyDrop {{");
        writeln!(out, "  T value;");
        writeln!(
            out,
            "  ManuallyDrop(T &&value) : value(::std::move(value)) {{}}",
        );
        writeln!(out, "  ~ManuallyDrop() {{}}");
        writeln!(out, "}};");
    }

    if builtin.maybe_uninit {
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(out, "union MaybeUninit {{");
        writeln!(out, "  T value;");
        writeln!(out, "  MaybeUninit() {{}}");
        writeln!(out, "  ~MaybeUninit() {{}}");
        writeln!(out, "}};");
    }

    out.begin_block(Block::AnonymousNamespace);

    if builtin.ptr_len {
        out.begin_block(Block::Namespace("repr"));
        writeln!(out, "struct PtrLen final {{");
        writeln!(out, "  void *ptr;");
        writeln!(out, "  size_t len;");
        writeln!(out, "}};");
        out.end_block(Block::Namespace("repr"));
    }

    if builtin.rust_str_new_unchecked || builtin.rust_str_repr {
        out.next_section();
        writeln!(out, "template <>");
        writeln!(out, "class impl<Str> final {{");
        writeln!(out, "public:");
        if builtin.rust_str_new_unchecked {
            writeln!(
                out,
                "  static Str new_unchecked(repr::PtrLen repr) noexcept {{",
            );
            writeln!(out, "    Str str;");
            writeln!(out, "    str.ptr = static_cast<const char *>(repr.ptr);");
            writeln!(out, "    str.len = repr.len;");
            writeln!(out, "    return str;");
            writeln!(out, "  }}");
        }
        if builtin.rust_str_repr {
            writeln!(out, "  static repr::PtrLen repr(Str str) noexcept {{");
            writeln!(
                out,
                "    return repr::PtrLen{{const_cast<char *>(str.ptr), str.len}};",
            );
            writeln!(out, "  }}");
        }
        writeln!(out, "}};");
    }

    if builtin.rust_slice_new || builtin.rust_slice_repr {
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(out, "class impl<Slice<T>> final {{");
        writeln!(out, "public:");
        if builtin.rust_slice_new {
            writeln!(
                out,
                "  static Slice<T> slice(repr::PtrLen repr) noexcept {{",
            );
            writeln!(out, "    return {{static_cast<T *>(repr.ptr), repr.len}};");
            writeln!(out, "  }}");
        }
        if builtin.rust_slice_repr {
            include.type_traits = true;
            writeln!(
                out,
                "  static repr::PtrLen repr(Slice<T> slice) noexcept {{",
            );
            writeln!(out, "    return repr::PtrLen{{");
            writeln!(
                out,
                "        const_cast<typename ::std::remove_const<T>::type *>(slice.ptr),",
            );
            writeln!(out, "        slice.len}};");
            writeln!(out, "  }}");
        }
        writeln!(out, "}};");
    }

    if builtin.rust_error {
        out.next_section();
        writeln!(out, "template <>");
        writeln!(out, "class impl<Error> final {{");
        writeln!(out, "public:");
        writeln!(out, "  static Error error(repr::PtrLen repr) noexcept {{");
        writeln!(out, "    Error error;");
        writeln!(out, "    error.msg = static_cast<const char *>(repr.ptr);");
        writeln!(out, "    error.len = repr.len;");
        writeln!(out, "    return error;");
        writeln!(out, "  }}");
        writeln!(out, "}};");
    }

    if builtin.is_complete {
        include.type_traits = true;
        out.next_section();
        writeln!(out, "template <typename T, typename = size_t>");
        writeln!(out, "struct is_complete : std::false_type {{}};");
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(
            out,
            "struct is_complete<T, decltype(sizeof(T))> : std::true_type {{}};",
        );
    }

    if builtin.deleter_if {
        out.next_section();
        writeln!(out, "template <bool> struct deleter_if {{");
        writeln!(out, "  template <typename T> void operator()(T *) {{}}");
        writeln!(out, "}};");
        out.next_section();
        writeln!(out, "template <> struct deleter_if<true> {{");
        writeln!(
            out,
            "  template <typename T> void operator()(T *ptr) {{ ptr->~T(); }}",
        );
        writeln!(out, "}};");
    }

    out.end_block(Block::AnonymousNamespace);
    out.end_block(Block::InlineNamespace("cxxbridge1"));

    if builtin.trycatch {
        out.begin_block(Block::Namespace("behavior"));
        include.exception = true;
        include.type_traits = true;
        include.utility = true;
        writeln!(out, "class missing {{}};");
        writeln!(out, "missing trycatch(...);");
        writeln!(out);
        writeln!(out, "template <typename Try, typename Fail>");
        writeln!(out, "static typename ::std::enable_if<");
        writeln!(
            out,
            "    ::std::is_same<decltype(trycatch(::std::declval<Try>(), ::std::declval<Fail>())),",
        );
        writeln!(out, "                 missing>::value>::type");
        writeln!(out, "trycatch(Try &&func, Fail &&fail) noexcept try {{");
        writeln!(out, "  func();");
        writeln!(out, "}} catch (const ::std::exception &e) {{");
        writeln!(out, "  fail(e.what());");
        writeln!(out, "}}");
        out.end_block(Block::Namespace("behavior"));
    }

    out.end_block(Block::Namespace("rust"));

    if builtin.exception {
        out.begin_block(Block::ExternC);
        writeln!(
            out,
            "const char *cxxbridge1$exception(const char *, size_t);",
        );
        out.end_block(Block::ExternC);
    }
}
