Documentation
use crate::info::*;
use quote::{format_ident, quote};
use rust_embed::RustEmbed;
use serde::Deserialize;
use std::io::Write;

#[cfg(feature = "rustfmt")]
fn rustfmt(input: &str) -> String {
    let mut buf = Vec::new();
    {
        let mut config = rustfmt_nightly::Config::default();
        config.set().emit_mode(rustfmt_nightly::EmitMode::Stdout);
        config.set().edition(rustfmt_nightly::Edition::Edition2018);
        config.set().verbose(rustfmt_nightly::Verbosity::Quiet);
        let mut session = rustfmt_nightly::Session::new(config, Some(&mut buf));
        session
            .format(rustfmt_nightly::Input::Text(input.to_string()))
            .unwrap();
    }
    String::from_utf8(buf).unwrap()
}

#[cfg(not(feature = "rustfmt"))]
fn rustfmt(input: &str) -> String {
    input.to_string()
}

fn generate_filler_field(lsb: u32, size: u32) -> proc_macro2::TokenStream {
    let fieldname = format_ident!("reserved_filler_lsb{}", lsb);
    let fieldtype = format_ident!("B{}", size);
    quote! {
        #fieldname: #fieldtype
    }
}

fn generate_register_struct(register: &Register, word_size: usize) -> proc_macro2::TokenStream {
    // Fill in the register with private "reserved" fields to get to 32 bits
    let regname = format_ident!("{}", register.name);
    let mut enums = vec![];
    let mut fields = vec![];
    let mut current_bit = 0;
    let mut sorted_fields = register.fields.clone();
    sorted_fields.sort_by_key(|field| field.lsb);
    for field in sorted_fields.iter() {
        log::trace!(
            "Processing field {}, [{}:{}]",
            field.name,
            field.msb,
            field.lsb
        );
        if field.lsb != current_bit {
            // Add a reserved field
            log::trace!(
                "Filled to bit {}, field starts at bit {}",
                current_bit,
                field.lsb
            );
            let size = field.lsb - current_bit;
            //log::debug!("Inserting {}-bit field into {}", size, register.name);
            fields.push(generate_filler_field(current_bit, size));
        }

        let fieldname = format_ident!("{}", field.name.to_ascii_lowercase());

        let nbits = proc_macro2::Literal::u32_unsuffixed(field.msb - field.lsb + 1);
        if field.enumerations.len() > 0 {
            let enumerations: Vec<_> = field
                .enumerations
                .iter()
                .map(|enumeration| {
                    let enumname = format_ident!("{}", enumeration.name);
                    let value = enumeration.value as isize;
                    quote! {
                        #enumname = #value
                    }
                })
                .collect();
            let enumname = format_ident!("{}", field.name);
            enums.push(quote! {
                #[derive(BitfieldSpecifier, Debug, PartialEq)]
                #[bits = #nbits]
                pub enum #enumname {
                    #(#enumerations,)*
                }
            });

            fields.push(quote! {
                #[bits = #nbits]
                pub #fieldname: #enumname
            });
        } else {
            let fieldtype = format_ident!("B{}", field.msb - field.lsb + 1);

            fields.push(quote! { pub #fieldname: #fieldtype });
        }

        current_bit = field.msb + 1;
    }
    if current_bit != word_size as u32 {
        let size = word_size as u32 - current_bit;
        log::debug!("Inserting {}-bit field into {}", size, register.name);
        fields.push(generate_filler_field(current_bit, size as u32));
    }

    quote! {
        pub mod #regname {
            use modular_bitfield::prelude::*;

            #(#enums)*

            #[bitfield]
            #[derive(Debug, PartialEq)]
            pub struct #regname {
                #(#fields,)*
            }
        }
    }
}

#[derive(RustEmbed)]
#[folder = "$CARGO_MANIFEST_DIR/src/templates/rust/"]
struct Templates;

fn generate_cargo() -> String {
    let mut tera = tera::Tera::default();
    tera.add_raw_template(
        "Cargo.toml",
        std::str::from_utf8(&Templates::get("Cargo.toml").unwrap()).unwrap(),
    )
    .unwrap();
    tera.render("Cargo.toml", &tera::Context::new()).unwrap()
}

fn generate_periph(periph: &Peripheral) -> String {
    let periph_name = format_ident!("{}", periph.name);
    let accessortype = format_ident!("u{}", periph.word_size);
    let periph_struct = quote! {
        pub struct #periph_name<ACCESSOR: RegmapAccessor<#accessortype>> {
            accessor: ACCESSOR,
        }
    };

    let registers: Vec<_> = periph
        .regs
        .iter()
        .map(|reg| generate_register_struct(&reg, periph.word_size))
        .collect();

    let word = format_ident!("u{}", periph.word_size);

    let accessors: Vec<_> = periph
        .regs
        .iter()
        .map(|reg| {
            let getter_name = format_ident!("get_{}", reg.name.to_ascii_lowercase());
            let setter_name = format_ident!("set_{}", reg.name.to_ascii_lowercase());
            let regname = format_ident!("{}", reg.name);
            let offset = match periph.word_size {
                32 => proc_macro2::Literal::u32_suffixed(reg.offset as u32),
                64 => proc_macro2::Literal::u64_suffixed(reg.offset),
                _ => {
                    panic!("Unsupported word size!");
                }
            };
            if reg.is_raw {
                quote! {
                    pub fn #getter_name(&mut self) -> #word {
                        let value = self.accessor.peek(#offset as #word);
                        value
                    }

                    pub fn #setter_name(&mut self, value: #word) {
                        self.accessor.poke(#offset as #word, value);
                    }
                }
            } else {
                quote! {
                    pub fn #getter_name(&mut self) -> ::std::result::Result<#regname::#regname, ACCESSOR::Error> {
                        let value = self.accessor.peek(#offset)?;
                        let value = value.to_le_bytes();
                        Ok(#regname::#regname::from_bytes(value))
                    }

                    pub fn #setter_name(&mut self, value: #regname::#regname) -> ::std::result::Result<(), ACCESSOR::Error> {
                        let raw = value.into_bytes();
                        let raw = #word::from_le_bytes(raw);
                        self.accessor.poke(#offset, raw)
                    }
                }
            }
        })
        .collect();

    let code = quote! {
        pub mod #periph_name {
            use mockall::*;
            use mockall::predicate::*;
            use modular_bitfield::prelude::*;
            use std::convert::TryInto;
            use regmap_support::*;

            #periph_struct

            #(#registers)*

            impl<ACCESSOR: RegmapAccessor<#accessortype>> #periph_name<ACCESSOR> {
                pub fn new(accessor: ACCESSOR) -> Self {
                    Self {
                        accessor,
                    }
                }

                #(#accessors)*
            }
        }
    };
    rustfmt(&format!("{}", code))
}

#[derive(Debug, Deserialize)]
pub struct RustConfig {
    pub path: String,
    pub out_filename: Option<String>,
}

pub fn generate_rust(config: &RustConfig, periphs: &Vec<Peripheral>) {
    //std::fs::create_dir(&config.path);
    //std::fs::create_dir(&format!("{}/src", config.path));

    //let mut file = std::fs::File::create(&format!("{}/Cargo.toml", config.path)).unwrap();
    //let cargo_toml = generate_cargo();
    //file.write_all(cargo_toml.as_bytes()).unwrap();

    let mut file = std::fs::File::create(&format!("{}", config.path)).unwrap();
    //std::fs::File::create(&format!("{}/src/{}.rs", config.path, periph.name)).unwrap();
    for periph in periphs.iter() {
        /*let mut file =
        std::fs::File::create(&format!("{}/src/{}.rs", config.path, periph.name)).unwrap();*/
        let lib_code = generate_periph(periph);
        file.write_all(lib_code.as_bytes()).unwrap();
    }

    /*let mut file = std::fs::File::create(&format!("{}/src/lib.rs", config.path)).unwrap();
    let librs_code: Vec<String> = periphs
        .iter()
        .map(|p| format!("pub mod {};\n", p.name.to_string()))
        .collect();
    let librs_code: String = librs_code.join("");
    file.write_all(librs_code.as_bytes()).unwrap();*/

    /*if let Some(out_filename) = &config.out_filename {
        let mut file = std::fs::File::create(&format!("{}/src/{}", config.path, out_filename)).unwrap();
        let lib_code = generate_periph(periph);
        file.write_all(lib_code.as_bytes()).unwrap();
    } else {
        std::fs::create_dir(&config.path);
        std::fs::create_dir(&format!("{}/src", config.path));

        let mut file = std::fs::File::create(&format!("{}/Cargo.toml", config.path)).unwrap();
        let cargo_toml = generate_cargo();
        file.write_all(cargo_toml.as_bytes()).unwrap();

        let mut file = std::fs::File::create(&format!("{}/src/lib.rs", config.path)).unwrap();
        let lib_code = generate_periph(periph);
        file.write_all(lib_code.as_bytes()).unwrap();
    }*/
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::info::*;

    #[test]
    fn test_can_generate() {
        let periph = Peripheral {
            name: "TestPeripheral".to_string(),
            word_size: 32,
            regs: vec![Register {
                name: "TestRegister".to_string(),
                offset: 4,
                is_raw: false,
                fields: vec![Field {
                    name: "TestField".to_string(),
                    lsb: 0,
                    msb: 5,
                    enumerations: vec![
                        Enumeration {
                            name: "Value1".to_string(),
                            value: 1,
                        },
                        Enumeration {
                            name: "Value21".to_string(),
                            value: 21,
                        },
                    ],
                }],
            }],
        };
        let s = generate_periph(&periph);
        assert!(s.len() > 0);
    }
}