pub mod pandora {

    use std::{
        collections::HashMap,
        fs::{self, create_dir, remove_dir_all, File},
        io::Write,
        path::Path,
        process::{exit, Command, ExitCode},
    };

    pub const EXIT_SUCCESS: i32 = 0;
    pub const EXIT_FAILURE: i32 = 1;

    pub struct Helpful {}

    impl Helpful {
        pub fn new() -> Helpful {
            Self {}
        }
        ///
        /// # Get a shell instance
        ///
        /// - `program` The program to use
        ///
        pub fn shell(self, program: &str) -> Command {
            Command::new(program)
        }

        ///
        /// # Get a shell instance
        ///
        /// - `program` The program to use
        ///
        pub fn open(self, filename: &str) -> File {
            File::open(filename).expect("failed to open filename")
        }

        ///
        /// # Print a message with new line
        ///
        /// - `message` The message to print
        ///
        pub fn echo(self, message: &str) -> Helpful {
            println!("{}", message);
            self
        }

        ///
        /// # Check if a directory exist
        ///
        /// - `path` The directory to check
        ///
        ///
        pub fn directory_exist(self, directory: &str) -> bool {
            Path::new(directory).is_dir()
        }

        ///
        /// # Check if a path is a symlink path
        ///
        /// - `path` The path to check
        ///
        ///
        pub fn symlink_exist(self, filename: &str) -> bool {
            Path::new(filename).is_symlink()
        }

        ///
        /// # Check if a path is a ralative path
        ///
        /// - `path` The path to check
        ///
        ///
        pub fn is_relative(self, path: &str) -> bool {
            Path::new(path).is_relative()
        }

        ///
        /// # Check if a path is an absolute path
        ///
        /// - `path` The path to check
        ///
        ///
        pub fn is_absolute(&mut self, path: &str) -> bool {
            Path::new(path).is_absolute()
        }

        ///
        /// # Check if a file exist
        ///
        /// - `filename`    The filename to check
        ///
        ///
        pub fn file_exist(self, filename: &str) -> bool {
            Path::new(filename).is_file()
        }

        ///
        /// # Print a message without new line
        ///
        /// - `message` The message to print
        ///  
        pub fn put(self, message: &str) -> Helpful {
            print!("{}", message);
            self
        }
    }

    impl Default for Helpful {
        fn default() -> Self {
            Self::new()
        }
    }

    ///
    /// # To create or test program
    ///
    pub struct Program {
        program: String,
        description: String,
        doc: String,
        args: Vec<String>,
        helper: Helpful,
    }

    impl Program {
        ///
        /// # Program constructor
        ///
        /// - `name`        The program name
        /// - `description` The program description
        /// - `docs`        The program documentation
        ///
        pub fn new(
            name: &str,
            description: &str,
            docs: HashMap<&str, &str>,
            arguments: Vec<String>,
        ) -> Program {
            let mut r = Self {
                program: name.to_string(),
                description: description.to_string(),
                doc: String::new(),
                args: arguments,
                helper: Helpful::default(),
            };

            for (&k, &v) in docs.iter() {
                r.doc.push_str(format!("{} : {}\n", k, v).as_str());
            }
            r
        }

        ///
        /// # Program help
        ///
        /// Display the program help
        ///
        pub fn help(self) -> Result<String, String> {
            println!("{}", self.doc);
            self.ok()
        }

        ///
        /// # Exit the program
        ///
        /// - `code` The exit code
        ///
        ///
        pub fn quit(self, code: i32) -> ExitCode {
            exit(code);
        }

        ///
        /// # Check if args contains a value
        ///
        /// - `expected` The value to search
        ///
        ///
        ///
        pub fn has(self, expected: &str) -> bool {
            self.args.contains(&expected.to_string())
        }

        ///
        /// # Get arguments
        ///
        pub fn args(self) -> Vec<String> {
            self.args
        }

        ///
        /// Check if a param matche a pattern
        ///
        /// - `arg` The arg to check
        /// - `x`   The expected value
        ///
        ///
        pub fn matches(self, arg: &String, x: &str) -> bool {
            arg.eq(x)
        }

        ///
        /// # Create a directory
        ///
        /// - `d` The directory to create
        ///
        pub fn mkdir(self, d: &str) -> bool {
            if self.helper.directory_exist(d) {
                return false;
            }
            create_dir(d).expect("Failed to create the directory");
            true
        }

        ///
        /// # Remove a directory
        ///
        /// - `d` The directory to remove
        ///
        pub fn rmdir(self, d: &str) -> bool {
            if self.helper.directory_exist(d) {
                remove_dir_all(d).expect("Failed to remove the directory");
                return true;
            }
            false
        }

        ///
        /// # Create a file
        ///
        /// - `file`    The filename to create
        ///
        pub fn touch(self, file: &str) -> bool {
            if self.helper.file_exist(file) {
                return false;
            }
            std::fs::File::create(file).expect("failed to create the file");
            true
        }

        ///
        /// # Remove a file
        ///
        /// - `file` The filename to remove
        ///
        pub fn rm(self, file: &str) -> bool {
            if self.helper.file_exist(file) {
                fs::remove_file(file).expect("failed to remove the file");
                return true;
            }
            false
        }

        ///
        /// # Rename a file
        ///
        /// - `file`  The filename to remove
        /// - `x`     The new filename
        ///
        pub fn rename(self, file: &str, x: &str) -> bool {
            if Path::new(file).is_file() && !Path::new(x).is_file() {
                std::fs::rename(file, x).expect("Failed to rename filename");
                return true;
            }
            false
        }

        ///
        /// # Create a file with a content
        ///
        /// - `file`    The filename to create  
        /// - `content`  The file content
        ///  
        pub fn touch_with_content(self, file: &str, content: &str) -> bool {
            if Path::new(file).is_file() {
                return false;
            }
            let mut f = std::fs::File::create(file).expect("failed to create the file");
            f.write_all(content.as_bytes())
                .expect("Failed to write fiel content");
            true
        }

        ///
        /// # Call the a or b method in function to the boolean
        ///
        /// - `boolean` The condition
        /// - `a`       The first callback
        /// - `b`       The second callback
        ///
        pub fn call(
            self,
            boolean: bool,
            a: fn(Program) -> Result<String, String>,
            b: fn(Program) -> Result<String, String>,
        ) -> Result<String, String> {
            if boolean {
                a(self)
            } else {
                b(self)
            }
        }

        ///
        /// # Call the a or b method in function to the boolean
        ///
        /// - `boolean` The condition
        /// - `to`      The callback
        ///
        pub fn run(self, callback: fn() -> Result<String, String>) -> Result<String, String> {
            callback()
        }

        pub fn no_args(self) -> bool {
            self.args.is_empty()
        }

        pub fn get(self, index: usize) -> Option<String> {
            let x: bool = self.args.get(index).is_none();
            if x {
                None
            } else {
                Some(x.to_string())
            }
        }

        ///
        /// # Return a ok Result
        ///
        pub fn ok(self) -> Result<String, String> {
            Ok(String::from("success"))
        }

        ///
        /// # Return a Err Result
        ///
        pub fn ko(self) -> Result<String, String> {
            Err(String::from("failure"))
        }

        ///
        /// # Get the program description
        ///
        pub fn get_description(self) -> String {
            self.description
        }

        ///
        /// # Get the program name
        ///
        pub fn get_program(self) -> String {
            self.program
        }

        ///
        /// # Call the main function of the program
        ///
        /// - `callback` The main function
        ///
        pub fn go(self, callback: fn(Program) -> Result<String, String>) -> Result<String, String> {
            callback(self)
        }
    }
}

#[cfg(test)]
mod tests {

    use super::pandora::Program;
    use std::{collections::HashMap, vec};

    fn init_project(p: Program) -> Result<String, String> {
        p.ok()
    }

    fn main(p: Program) -> Result<String, String> {
        init_project(p)
    }

    #[test]
    pub fn ok() {
        let mut doc = HashMap::new();

        doc.insert("zuu", "Run all test based on zuu.yml");
        doc.insert("zuu init", "Initialyse a new repository");

        let program: Program = Program::new("zuu", "A toolkit program", doc, vec![]);

        assert!(program.go(main).is_ok());
    }

    #[test]
    pub fn program() {
        let mut doc: HashMap<&str, &str> = HashMap::new();

        doc.insert("zuu", "Run all test based on zuu.yml");
        doc.insert("zuu init", "Initialyse a new repository");

        let program: Program = Program::new("zuu", "A toolkit program", doc, vec![]);

        assert!(program.go(main).is_ok());
    }
}
