//! Provides the `Config` struct for holding configuration information.

use crate::error::Error;

/// Lists sorting options.
#[derive(Clone, Copy)]
pub enum Sorting {
    Amount,
    Date,
    Vendor,
}

impl Sorting {
    /// Converts a `Sorting` variant into a string.
    pub fn to_string(&self) -> String {
        let output = match self {
            Self::Amount => "amount",
            Self::Date => "date",
            Self::Vendor => "vendor",
        };
        output.to_string()
    }

    /// Gets the name of each in a `Vec<&str>`.
    pub fn get_options() -> Vec<&'static str> {
        vec![
            "By amount",
            "By date",
            "By vendor"
        ]
    }

    /// Creates a `Sorting` variant from a string.
    pub fn from_str(string: &str) -> Self {
        let lower = string.to_lowercase();
        let sorting = if lower.contains("amount") {
            Sorting::Amount
        } else if lower.contains("date") {
            Sorting::Date
        } else if lower.contains("vendor") {
            Sorting::Vendor
        } else {
            (Error::InvalidConfig (string.to_string())).throw();
        };
        sorting
    }
}


/// Stores configuration information.
pub struct Config {
    filename: String,
    sort_expenses_by: Sorting,
    encrypted: bool,
}

impl Config {
    /// Constructs a default configuration status.
    pub fn default() -> Self {
        Self {
            filename: "expenses.fin".to_string(),
            sort_expenses_by: Sorting::Date,
            encrypted: false,
        }
    }

    /// Converts ASCII into a configuration status.
    pub fn from_ascii(ascii: String) -> Self {
        let mut config = Config::default();
        let lines = (&ascii).split("\n").collect::<Vec<&str>>();

        for line in lines {
            let values = line.split(" ").collect::<Vec<&str>>();
            if values.len() != 2 {
                (Error::InvalidConfig (line.to_string())).throw();
            }
            let key = values[0];
            let value = values[1];

            // Set each value by key
            match key {
                "filename" => config.set_filename(value),
                "sortexpensesby" => {
                    let sorting_method = Sorting::from_str(value);
                    config.set_sorting(sorting_method);
                },
                "encrypted" => {
                    let encrypted = match value {
                        "yes" => true,
                        "no" => false,
                        _ => (Error::InvalidConfig (value.to_string())).throw(),
                    };
                    config.set_encrypted(encrypted);
                },
                _ => (Error::InvalidConfig (key.to_string())).throw(),
            };
        }

        config
    }

    /// Sets the filename.
    pub fn set_filename(&mut self, filename: &str) {
        self.filename = filename.to_string();
    }

    /// Sets the sorting method.
    pub fn set_sorting(&mut self, sorting: Sorting) {
        self.sort_expenses_by = sorting;
    }

    /// Sets the sorting method given a `&str`.
    pub fn set_sorting_by_str(&mut self, sorting: &str) {
        let sorting = Sorting::from_str(sorting);
        self.sort_expenses_by = sorting;
    }

    /// Sets the encryption status.
    pub fn set_encrypted(&mut self, encrypted: bool) {
        self.encrypted = encrypted;
    }

    /// Gets the filename.
    pub fn filename(&self) -> String {
        self.filename.to_owned()
    }

    /// Gets the sorting method.
    pub fn sorting(&self) -> Sorting {
        self.sort_expenses_by
    }

    /// Gets the encryption status.
    pub fn encrypted(&self) -> bool {
        self.encrypted
    }

    /// Converts the configuration file into ASCII.
    pub fn to_ascii(&self) -> String {
        let mut output = String::new();

        output.push_str(&format!(
            "filename {}\n",
            self.filename,
        ));

        let string_sorting = match self.sort_expenses_by {
            Sorting::Amount => "amount",
            Sorting::Date => "date",
            Sorting::Vendor => "vendor",
        };
        output.push_str(&format!(
            "sortexpensesby {}\n",
            string_sorting,
        ));

        let encrypted_str = match self.encrypted {
            true => "yes",
            false => "no",
        };
        output.push_str(&format!(
            "encrypted {}",
            encrypted_str,
        ));

        output
    }

    /// Gets sorting options.
    pub fn get_sorting_options() -> Vec<&'static str> {
        Sorting::get_options()
    }
}