/*
 * meli -
 *
 * Copyright  Manos Pitsidianakis
 *
 * This file is part of meli.
 *
 * meli is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * meli is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with meli. If not, see <http://www.gnu.org/licenses/>.
 */

use std::fs::File;
use std::io::prelude::*;
use std::process::{Command, Stdio};

use quote::{format_ident, quote};

// Write ConfigStructOverride to overrides.rs
pub fn override_derive(filenames: &[(&str, &str)]) {
    let mut output_file =
        File::create("src/conf/overrides.rs").expect("Unable to open output file");
    let mut output_string = r##"/*
 * meli - conf/overrides.rs
 *
 * Copyright 2020 Manos Pitsidianakis
 *
 * This file is part of meli.
 *
 * meli is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * meli is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with meli. If not, see <http://www.gnu.org/licenses/>.
 */

//! This module is automatically generated by build.rs.
use super::*;

"##
    .to_string();

    'file_loop: for (filename, ident) in filenames {
        println!("cargo:rerun-if-changed={}", filename);
        let mut file = File::open(&filename)
            .unwrap_or_else(|err| panic!("Unable to open file `{}` {}", filename, err));

        let mut src = String::new();
        file.read_to_string(&mut src).expect("Unable to read file");

        let syntax = syn::parse_file(&src).expect("Unable to parse file");
        if syntax.items.iter().any(|item| {
            if let syn::Item::Struct(s) = item {
                if s.ident.to_string().ends_with("Override") {
                    println!("ident {} exists, skipping {}", ident, filename);
                    return true;
                }
            }
            false
        }) {
            continue 'file_loop;
        }

        for item in syntax.items.iter() {
            if let syn::Item::Struct(s) = item {
                if s.ident != ident {
                    continue;
                }
                if s.ident.to_string().ends_with("Override") {
                    unreachable!();
                }
                let override_ident: syn::Ident = format_ident!("{}Override", s.ident);
                let mut field_tokentrees = vec![];
                let mut attrs_tokens = vec![];
                for attr in &s.attrs {
                    if let Ok(syn::Meta::List(ml)) = attr.parse_meta() {
                        if ml.path.get_ident().is_some() && ml.path.get_ident().unwrap() == "cfg" {
                            attrs_tokens.push(attr);
                        }
                    }
                }
                let mut field_idents = vec![];
                for f in &s.fields {
                    let ident = &f.ident;
                    let ty = &f.ty;
                    let attrs = f
                        .attrs
                        .iter()
                        .filter_map(|f| {
                            let mut new_attr = f.clone();
                            if let quote::__private::TokenTree::Group(g) =
                                f.tokens.clone().into_iter().next().unwrap()
                            {
                                let attr_inner_value = f.tokens.to_string();
                                if !attr_inner_value.starts_with("( default")
                                    && !attr_inner_value.starts_with("( default =")
                                    && !attr_inner_value.starts_with("(default")
                                    && !attr_inner_value.starts_with("(default =")
                                {
                                    return Some(new_attr);
                                }
                                if attr_inner_value.starts_with("( default =")
                                    || attr_inner_value.starts_with("(default =")
                                {
                                    let rest = g.stream().into_iter().skip(4);
                                    new_attr.tokens = quote! { ( #(#rest)*) };
                                    match new_attr.tokens.to_string().as_str() {
                                        "( )" | "()" => {
                                            return None;
                                        }
                                        _ => {}
                                    }
                                } else if attr_inner_value.starts_with("( default")
                                    || attr_inner_value.starts_with("(default")
                                {
                                    let rest = g.stream().into_iter().skip(2);
                                    new_attr.tokens = quote! { ( #(#rest)*) };
                                    match new_attr.tokens.to_string().as_str() {
                                        "( )" | "()" => {
                                            return None;
                                        }
                                        _ => {}
                                    }
                                }
                            }

                            Some(new_attr)
                        })
                        .collect::<Vec<_>>();
                    let t = quote! {
                        #(#attrs)*
                        #[serde(default)]
                        pub #ident : Option<#ty>
                    };
                    field_idents.push(ident);
                    field_tokentrees.push(t);
                }
                //let fields = &s.fields;

                let literal_struct = quote! {
                    #(#attrs_tokens)*
                    #[derive(Debug, Serialize, Deserialize, Clone)]
                    #[serde(deny_unknown_fields)]
                    pub struct #override_ident {
                        #(#field_tokentrees),*
                    }


                    #(#attrs_tokens)*
                    impl Default for #override_ident {
                        fn default() -> Self {
                            #override_ident {
                                #(#field_idents: None),*
                            }
                        }
                    }
                };
                output_string.push_str(&literal_struct.to_string());
                output_string.push_str("\n\n");
            }
        }
    }

    let rustfmt_closure = move |output_file: &mut File, output_string: &str| {
        let mut rustfmt = Command::new("rustfmt")
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .map_err(|err| format!("failed to execute rustfmt {}", err))?;

        {
            // limited borrow of stdin
            let stdin = rustfmt
                .stdin
                .as_mut()
                .ok_or("failed to get rustfmt stdin")?;
            stdin
                .write_all(output_string.as_bytes())
                .map_err(|err| format!("failed to write to rustfmt stdin {}", err))?;
        }

        let output = rustfmt
            .wait_with_output()
            .map_err(|err| format!("failed to wait on rustfmt child {}", err))?;
        if !output.stderr.is_empty() {
            return Err(format!(
                "rustfmt invocation replied with: `{}`",
                String::from_utf8_lossy(&output.stderr)
            ));
        }

        output_file
            .write_all(&output.stdout)
            .expect("failed to write to src/conf/overrides.rs");
        Ok(())
    };
    if let Err(err) = rustfmt_closure(&mut output_file, &output_string) {
        println!("Tried rustfmt on overrides module, got error: {}", err);
        output_file.write_all(output_string.as_bytes()).unwrap();
    }
}
