// License: see LICENSE file at root directory of `master` branch

//! # Dia Kit
//!
//! ## Project
//!
//! - Repository: <https://bitbucket.org/haibison/dia-kit>
//! - License: [Free Public License 1.0.0](https://opensource.org/licenses/FPL-1.0.0)
//! - _This project follows [Semantic Versioning 2.0.0]_
//!
//! ---
//!
//! ## What is this?
//!
//! This project contains some kit. They are not worth as independent crates, but important enough to have their own version and release date.
//!
//! All kit follows Semantic Versioning 2.0.0.
//!
//! [Semantic Versioning 2.0.0]: https://semver.org/spec/v2.0.0.html

// ╔═════════════════╗
// ║   IDENTIFIERS   ║
// ╚═════════════════╝

macro_rules! crate_code_name    { () => { "dia-kit" }}
macro_rules! crate_version      { () => { "0.5.0" }}

/// # Crate name
pub const CRATE_NAME: &'static str = "Dia Kit";

/// # Crate code name
pub const CRATE_CODE_NAME: &'static str = crate_code_name!();

/// # Crate version
pub const CRATE_VERSION: &'static str = crate_version!();

/// # Crate release date (year/month/day)
pub const CRATE_RELEASE_DATE: (u16, u8, u8) = (2018, 7, 18);

/// # Unique universally identifier of this crate. Its CRC-32 is `4a2ee1bc`.
pub const UUID: &'static str = "0b2cc375-48d2-4e67-950f-297be67b89e3";

/// # Tag, which can be used for logging...
pub const TAG: &'static str = concat!(crate_code_name!(), "::4a2ee1bc::", crate_version!());

// ╔════════════════════╗
// ║   IMPLEMENTATION   ║
// ╚════════════════════╝

#[test]
fn test_crate_version() {
    assert_eq!(CRATE_VERSION, env!("CARGO_PKG_VERSION"));
}

extern crate dia_go;
extern crate kib;
extern crate yansi;

use std::cmp::{Ord, PartialOrd, Ordering, PartialEq};
use std::env;
use std::fmt;
use std::process;
use std::str::FromStr;

use dia_go::Go;
use yansi::Paint;

#[allow(unused_macros)]
mod cmp_integers;
mod int_ordering;

/// # Kit
#[derive(Eq)]
pub struct Kit {

    /// # Unique universally identifier of this kit
    uuid: &'static str,

    /// # Name
    name: &'static str,

    /// # Version
    version: &'static str,

    /// # Release date
    release_date: (u16, u8, u8),

    /// # Data
    data: &'static str,

}

impl PartialEq for Kit {

    fn eq(&self, other: &Kit) -> bool {
        self.uuid == other.uuid
    }

}

impl fmt::Display for Kit {

    fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(formatter, "[{}] {} - {} {:?}", self.uuid, self.name, self.version, &self.release_date)
    }

}

impl fmt::Debug for Kit {

    fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(formatter, "\"")?;
        fmt::Display::fmt(self, formatter)?;
        write!(formatter, "\"")
    }

}

impl Ord for Kit {

    fn cmp(&self, other: &Self) -> Ordering {
        self.uuid.cmp(other.uuid)
    }

}

impl PartialOrd for Kit {

    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }

}

/// # Kit
mod kits {

    pub const CMP_INTEGERS: super::Kit = super::Kit {
        uuid: ::cmp_integers::UUID,
        name: "Macro: cmp_integers!()",
        version: ::cmp_integers::VERSION,
        release_date: ::cmp_integers::RELEASE_DATE,
        data: include_str!("cmp_integers.rs"),
    };

    pub const INT_ORDERING: super::Kit = super::Kit {
        uuid: ::int_ordering::UUID,
        name: "Trait: IntOrdering<T: Ord>: Ord { fn cmp_int(&self, other: &T) -> Ordering; }",
        version: ::int_ordering::VERSION,
        release_date: ::int_ordering::RELEASE_DATE,
        data: include_str!("int_ordering.rs"),
    };

    /// # Lists all kits
    pub fn list() -> Vec<::Kit> {
        vec![CMP_INTEGERS, INT_ORDERING]
    }

    #[test]
    fn test_kits() {
        use std::collections::BTreeSet;

        // We should keep same IDs across versions
        let list = list();
        assert_eq!(list[0], CMP_INTEGERS);
        assert_eq!(list[1], INT_ORDERING);

        // Check UUIDs
        assert_eq!(list.iter().collect::<BTreeSet<_>>().len(), list.len());
    }

}

const CMD_HELP: &'static str = "help";
const CMD_VERSION_INFO: &'static str = "version-info";
const CMD_LIST: &'static str = "list";
const CMD_EXPORT: &'static str = "export";

const VERSION_INFO: &'static str = include_str!("../version-info.md");

fn main() {
    let go = Go::default();
    handle_args(&go);
}

/// # Handles arguments
fn handle_args(go: &Go) {
    let mut args = env::args().skip(1);
    match args.next() {
        Some(ref arg) => match arg.as_str().trim() {
            ::CMD_HELP => print_help_and_exit(),
            ::CMD_VERSION_INFO => {
                print!("{}", VERSION_INFO);
                process::exit(0);
            },
            ::CMD_LIST => match args.next() {
                Some(_) => go.exit(format!("{:?} command doesn't take arguments", CMD_LIST)),
                None => {
                    let kit = kits::list();
                    go.s(format!("Available kits ({}):", kit.len()));
                    for (index, kit) in kit.iter().enumerate() {
                        match index.checked_add(1) {
                            Some(user_index) => go.i(format!("{}. {}", &user_index, kit.name)).up_with(|go| {
                                go.i(format!("UUID      : {}", kit.uuid));
                                go.i(format!("Version   : {} {:?}", kit.version, kit.release_date));
                                go.i(format!("Size      : {}", kib::format(kit.data.len() as u128)));
                                false
                            }),
                            None => go.exit(format!("Failed getting kit index for '{}'", &index)),
                        };
                    }
                },
            },
            ::CMD_EXPORT => match (args.next(), args.next()) {
                (Some(ref arg), None) => match usize::from_str(arg.trim()) {
                    Ok(user_index) => match user_index.checked_sub(1) {
                        Some(real_index) => match kits::list().get(real_index) {
                            Some(kit) => print!("{}", kit.data),
                            None => go.exit(format!("Invalid kit index: {}", &user_index)),
                        },
                        None => go.exit(format!("Invalid kit index: {}", &user_index)),
                    },
                    Err(err) => go.exit(format!("Invalid kit index: {:?} -> {}", &arg, &err)),
                },
                (None, None) => go.exit("Missing kit index"),
                _ => go.exit(format!("Invalid arguments for {:?}", CMD_EXPORT)),
            },
            other => go.exit(format!("Command not supported: {:?}", &other)),
        },
        None => print_help_and_exit(),
    };
}

/// # Prints help and exits
fn print_help_and_exit() -> ! {
    let go = Go::default();

    go.s(format!("{} - {} {:?}", Paint::new(CRATE_NAME).bold(), CRATE_VERSION, CRATE_RELEASE_DATE));
    go.i("Supported commands:").up_with(|go| {
        go.i(format!("{}", Paint::new(CMD_HELP).italic())).up_with(|go| {
            go.i("Print this message and exit.");
            false
        });
        go.i(format!("{}", Paint::new(CMD_VERSION_INFO).italic())).up_with(|go| {
            go.i("Print version-info.md and exit.");
            false
        });
        go.i(format!("{}", Paint::new(CMD_LIST).italic())).up_with(|go| {
            go.i("List all kits.");
            false
        });
        go.i(Paint::new(format!("{} <id>", CMD_EXPORT)).italic().to_string()).up_with(|go| {
            go.i("Export a kit content (via its ID) to stdout.");
            false
        });
        false
    });

    process::exit(0);
}
