use std::collections::HashMap;

use cranelift_codegen_shared::constant_hash::{generate_table, simple_hash};

use crate::cdsl::camel_case;
use crate::cdsl::settings::{
    BoolSetting, Predicate, Preset, Setting, SettingGroup, SpecificSetting,
};
use crate::error;
use crate::srcgen::{Formatter, Match};
use crate::unique_table::UniqueSeqTable;

pub enum ParentGroup {
    None,
    Shared,
}

/// Emits the constructor of the Flags structure.
fn gen_constructor(group: &SettingGroup, parent: ParentGroup, fmt: &mut Formatter) {
    let args = match parent {
        ParentGroup::None => "builder: Builder",
        ParentGroup::Shared => "shared: &settings::Flags, builder: Builder",
    };
    fmtln!(fmt, "impl Flags {");
    fmt.indent(|fmt| {
        fmt.doc_comment(format!("Create flags {} settings group.", group.name));
        fmtln!(fmt, "#[allow(unused_variables)]");
        fmtln!(fmt, "pub fn new({}) -> Self {{", args);
        fmt.indent(|fmt| {
            fmtln!(fmt, "let bvec = builder.state_for(\"{}\");", group.name);
            fmtln!(
                fmt,
                "let mut {} = Self {{ bytes: [0; {}] }};",
                group.name,
                group.byte_size()
            );
            fmtln!(
                fmt,
                "debug_assert_eq!(bvec.len(), {});",
                group.settings_size
            );
            fmtln!(
                fmt,
                "{}.bytes[0..{}].copy_from_slice(&bvec);",
                group.name,
                group.settings_size
            );

            // Now compute the predicates.
            for p in &group.predicates {
                fmt.comment(format!("Precompute #{}.", p.number));
                fmtln!(fmt, "if {} {{", p.render(group));
                fmt.indent(|fmt| {
                    fmtln!(
                        fmt,
                        "{}.bytes[{}] |= 1 << {};",
                        group.name,
                        group.bool_start_byte_offset + p.number / 8,
                        p.number % 8
                    );
                });
                fmtln!(fmt, "}");
            }

            fmtln!(fmt, group.name);
        });
        fmtln!(fmt, "}");
    });
    fmtln!(fmt, "}");
}

/// Emit Display and FromStr implementations for enum settings.
fn gen_to_and_from_str(name: &str, values: &[&'static str], fmt: &mut Formatter) {
    fmtln!(fmt, "impl fmt::Display for {} {{", name);
    fmt.indent(|fmt| {
        fmtln!(
            fmt,
            "fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {"
        );
        fmt.indent(|fmt| {
            fmtln!(fmt, "f.write_str(match *self {");
            fmt.indent(|fmt| {
                for v in values.iter() {
                    fmtln!(fmt, "Self::{} => \"{}\",", camel_case(v), v);
                }
            });
            fmtln!(fmt, "})");
        });
        fmtln!(fmt, "}");
    });
    fmtln!(fmt, "}");

    fmtln!(fmt, "impl str::FromStr for {} {{", name);
    fmt.indent(|fmt| {
        fmtln!(fmt, "type Err = ();");
        fmtln!(fmt, "fn from_str(s: &str) -> Result<Self, Self::Err> {");
        fmt.indent(|fmt| {
            fmtln!(fmt, "match s {");
            fmt.indent(|fmt| {
                for v in values.iter() {
                    fmtln!(fmt, "\"{}\" => Ok(Self::{}),", v, camel_case(v));
                }
                fmtln!(fmt, "_ => Err(()),");
            });
            fmtln!(fmt, "}");
        });
        fmtln!(fmt, "}");
    });
    fmtln!(fmt, "}");
}

/// Emit real enum for the Enum settings.
fn gen_enum_types(group: &SettingGroup, fmt: &mut Formatter) {
    for setting in group.settings.iter() {
        let values = match setting.specific {
            SpecificSetting::Bool(_) | SpecificSetting::Num(_) => continue,
            SpecificSetting::Enum(ref values) => values,
        };
        let name = camel_case(setting.name);

        fmt.doc_comment(format!("Values for `{}.{}`.", group.name, setting.name));
        fmtln!(fmt, "#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]");
        fmtln!(fmt, "pub enum {} {{", name);
        fmt.indent(|fmt| {
            for v in values.iter() {
                fmt.doc_comment(format!("`{}`.", v));
                fmtln!(fmt, "{},", camel_case(v));
            }
        });
        fmtln!(fmt, "}");

        gen_to_and_from_str(&name, values, fmt);
    }
}

/// Emit a getter function for `setting`.
fn gen_getter(setting: &Setting, fmt: &mut Formatter) {
    fmt.doc_comment(setting.comment);
    match setting.specific {
        SpecificSetting::Bool(BoolSetting {
            predicate_number, ..
        }) => {
            fmtln!(fmt, "pub fn {}(&self) -> bool {{", setting.name);
            fmt.indent(|fmt| {
                fmtln!(fmt, "self.numbered_predicate({})", predicate_number);
            });
            fmtln!(fmt, "}");
        }
        SpecificSetting::Enum(ref values) => {
            let ty = camel_case(setting.name);
            fmtln!(fmt, "pub fn {}(&self) -> {} {{", setting.name, ty);
            fmt.indent(|fmt| {
                let mut m = Match::new(format!("self.bytes[{}]", setting.byte_offset));
                for (i, v) in values.iter().enumerate() {
                    m.arm_no_fields(format!("{}", i), format!("{}::{}", ty, camel_case(v)));
                }
                m.arm_no_fields("_", "panic!(\"Invalid enum value\")");
                fmt.add_match(m);
            });
            fmtln!(fmt, "}");
        }
        SpecificSetting::Num(_) => {
            fmtln!(fmt, "pub fn {}(&self) -> u8 {{", setting.name);
            fmt.indent(|fmt| {
                fmtln!(fmt, "self.bytes[{}]", setting.byte_offset);
            });
            fmtln!(fmt, "}");
        }
    }
}

fn gen_pred_getter(predicate: &Predicate, group: &SettingGroup, fmt: &mut Formatter) {
    fmt.doc_comment(format!("Computed predicate `{}`.", predicate.render(group)));
    fmtln!(fmt, "pub fn {}(&self) -> bool {{", predicate.name);
    fmt.indent(|fmt| {
        fmtln!(fmt, "self.numbered_predicate({})", predicate.number);
    });
    fmtln!(fmt, "}");
}

/// Emits getters for each setting value.
fn gen_getters(group: &SettingGroup, fmt: &mut Formatter) {
    fmt.doc_comment("User-defined settings.");
    fmtln!(fmt, "#[allow(dead_code)]");
    fmtln!(fmt, "impl Flags {");
    fmt.indent(|fmt| {
        fmt.doc_comment("Get a view of the boolean predicates.");
        fmtln!(
            fmt,
            "pub fn predicate_view(&self) -> crate::settings::PredicateView {"
        );
        fmt.indent(|fmt| {
            fmtln!(
                fmt,
                "crate::settings::PredicateView::new(&self.bytes[{}..])",
                group.bool_start_byte_offset
            );
        });
        fmtln!(fmt, "}");

        if !group.settings.is_empty() {
            fmt.doc_comment("Dynamic numbered predicate getter.");
            fmtln!(fmt, "fn numbered_predicate(&self, p: usize) -> bool {");
            fmt.indent(|fmt| {
                fmtln!(
                    fmt,
                    "self.bytes[{} + p / 8] & (1 << (p % 8)) != 0",
                    group.bool_start_byte_offset
                );
            });
            fmtln!(fmt, "}");
        }

        for setting in &group.settings {
            gen_getter(&setting, fmt);
        }
        for predicate in &group.predicates {
            gen_pred_getter(&predicate, &group, fmt);
        }
    });
    fmtln!(fmt, "}");
}

#[derive(Hash, PartialEq, Eq)]
enum SettingOrPreset<'a> {
    Setting(&'a Setting),
    Preset(&'a Preset),
}

impl<'a> SettingOrPreset<'a> {
    fn name(&self) -> &str {
        match *self {
            SettingOrPreset::Setting(s) => s.name,
            SettingOrPreset::Preset(p) => p.name,
        }
    }
}

/// Emits DESCRIPTORS, ENUMERATORS, HASH_TABLE and PRESETS.
fn gen_descriptors(group: &SettingGroup, fmt: &mut Formatter) {
    let mut enum_table = UniqueSeqTable::new();

    let mut descriptor_index_map: HashMap<SettingOrPreset, usize> = HashMap::new();

    // Generate descriptors.
    fmtln!(
        fmt,
        "static DESCRIPTORS: [detail::Descriptor; {}] = [",
        group.settings.len() + group.presets.len()
    );
    fmt.indent(|fmt| {
        for (idx, setting) in group.settings.iter().enumerate() {
            fmtln!(fmt, "detail::Descriptor {");
            fmt.indent(|fmt| {
                fmtln!(fmt, "name: \"{}\",", setting.name);
                fmtln!(fmt, "offset: {},", setting.byte_offset);
                match setting.specific {
                    SpecificSetting::Bool(BoolSetting { bit_offset, .. }) => {
                        fmtln!(
                            fmt,
                            "detail: detail::Detail::Bool {{ bit: {} }},",
                            bit_offset
                        );
                    }
                    SpecificSetting::Enum(ref values) => {
                        let offset = enum_table.add(values);
                        fmtln!(
                            fmt,
                            "detail: detail::Detail::Enum {{ last: {}, enumerators: {} }},",
                            values.len() - 1,
                            offset
                        );
                    }
                    SpecificSetting::Num(_) => {
                        fmtln!(fmt, "detail: detail::Detail::Num,");
                    }
                }

                descriptor_index_map.insert(SettingOrPreset::Setting(setting), idx);
            });
            fmtln!(fmt, "},");
        }

        for (idx, preset) in group.presets.iter().enumerate() {
            fmtln!(fmt, "detail::Descriptor {");
            fmt.indent(|fmt| {
                fmtln!(fmt, "name: \"{}\",", preset.name);
                fmtln!(fmt, "offset: {},", (idx as u8) * group.settings_size);
                fmtln!(fmt, "detail: detail::Detail::Preset,");
            });
            fmtln!(fmt, "},");

            let whole_idx = idx + group.settings.len();
            descriptor_index_map.insert(SettingOrPreset::Preset(preset), whole_idx);
        }
    });
    fmtln!(fmt, "];");

    // Generate enumerators.
    fmtln!(fmt, "static ENUMERATORS: [&str; {}] = [", enum_table.len());
    fmt.indent(|fmt| {
        for enum_val in enum_table.iter() {
            fmtln!(fmt, "\"{}\",", enum_val);
        }
    });
    fmtln!(fmt, "];");

    // Generate hash table.
    let mut hash_entries: Vec<SettingOrPreset> = Vec::new();
    hash_entries.extend(group.settings.iter().map(|x| SettingOrPreset::Setting(x)));
    hash_entries.extend(group.presets.iter().map(|x| SettingOrPreset::Preset(x)));

    let hash_table = generate_table(hash_entries.iter(), hash_entries.len(), |entry| {
        simple_hash(entry.name())
    });
    fmtln!(fmt, "static HASH_TABLE: [u16; {}] = [", hash_table.len());
    fmt.indent(|fmt| {
        for h in &hash_table {
            match *h {
                Some(setting_or_preset) => fmtln!(
                    fmt,
                    "{},",
                    &descriptor_index_map
                        .get(setting_or_preset)
                        .unwrap()
                        .to_string()
                ),
                None => fmtln!(fmt, "0xffff,"),
            }
        }
    });
    fmtln!(fmt, "];");

    // Generate presets.
    fmtln!(
        fmt,
        "static PRESETS: [(u8, u8); {}] = [",
        group.presets.len() * (group.settings_size as usize)
    );
    fmt.indent(|fmt| {
        for preset in &group.presets {
            fmt.comment(preset.name);
            for (mask, value) in preset.layout(&group) {
                fmtln!(fmt, "(0b{:08b}, 0b{:08b}),", mask, value);
            }
        }
    });
    fmtln!(fmt, "];");
}

fn gen_template(group: &SettingGroup, fmt: &mut Formatter) {
    let mut default_bytes: Vec<u8> = vec![0; group.settings_size as usize];
    for setting in &group.settings {
        *default_bytes.get_mut(setting.byte_offset as usize).unwrap() |= setting.default_byte();
    }

    let default_bytes: Vec<String> = default_bytes
        .iter()
        .map(|x| format!("{:#04x}", x))
        .collect();
    let default_bytes_str = default_bytes.join(", ");

    fmtln!(
        fmt,
        "static TEMPLATE: detail::Template = detail::Template {"
    );
    fmt.indent(|fmt| {
        fmtln!(fmt, "name: \"{}\",", group.name);
        fmtln!(fmt, "descriptors: &DESCRIPTORS,");
        fmtln!(fmt, "enumerators: &ENUMERATORS,");
        fmtln!(fmt, "hash_table: &HASH_TABLE,");
        fmtln!(fmt, "defaults: &[{}],", default_bytes_str);
        fmtln!(fmt, "presets: &PRESETS,");
    });
    fmtln!(fmt, "};");

    fmt.doc_comment(format!(
        "Create a `settings::Builder` for the {} settings group.",
        group.name
    ));
    fmtln!(fmt, "pub fn builder() -> Builder {");
    fmt.indent(|fmt| {
        fmtln!(fmt, "Builder::new(&TEMPLATE)");
    });
    fmtln!(fmt, "}");
}

fn gen_display(group: &SettingGroup, fmt: &mut Formatter) {
    fmtln!(fmt, "impl fmt::Display for Flags {");
    fmt.indent(|fmt| {
        fmtln!(
            fmt,
            "fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {"
        );
        fmt.indent(|fmt| {
            fmtln!(fmt, "writeln!(f, \"[{}]\")?;", group.name);
            fmtln!(fmt, "for d in &DESCRIPTORS {");
            fmt.indent(|fmt| {
                fmtln!(fmt, "if !d.detail.is_preset() {");
                fmt.indent(|fmt| {
                    fmtln!(fmt, "write!(f, \"{} = \", d.name)?;");
                    fmtln!(
                        fmt,
                        "TEMPLATE.format_toml_value(d.detail, self.bytes[d.offset as usize], f)?;",
                    );
                    fmtln!(fmt, "writeln!(f)?;");
                });
                fmtln!(fmt, "}");
            });
            fmtln!(fmt, "}");
            fmtln!(fmt, "Ok(())");
        });
        fmtln!(fmt, "}")
    });
    fmtln!(fmt, "}");
}

fn gen_group(group: &SettingGroup, parent: ParentGroup, fmt: &mut Formatter) {
    // Generate struct.
    fmtln!(fmt, "#[derive(Clone)]");
    fmt.doc_comment(format!("Flags group `{}`.", group.name));
    fmtln!(fmt, "pub struct Flags {");
    fmt.indent(|fmt| {
        fmtln!(fmt, "bytes: [u8; {}],", group.byte_size());
    });
    fmtln!(fmt, "}");

    gen_constructor(group, parent, fmt);
    gen_enum_types(group, fmt);
    gen_getters(group, fmt);
    gen_descriptors(group, fmt);
    gen_template(group, fmt);
    gen_display(group, fmt);
}

pub(crate) fn generate(
    settings: &SettingGroup,
    parent_group: ParentGroup,
    filename: &str,
    out_dir: &str,
) -> Result<(), error::Error> {
    let mut fmt = Formatter::new();
    gen_group(&settings, parent_group, &mut fmt);
    fmt.update_file(filename, out_dir)?;
    Ok(())
}
