use crate::error::Error;
use std::fmt::{Debug, Display};

macro_rules! parse {
    ($($arg:tt)*) => {
        return Err($crate::error::Error::new_parse(format!($($arg)*)))
    };
}

macro_rules! ufs {
    ($($arg:tt)*) => {
        return Err($crate::error::Error::new_ufs(format!($($arg)*)))
    };
}

#[derive(Debug, Default, Clone)]
pub struct FormatSpec {
    align: Option<(Option<String>, String)>,
    sign: Option<String>,
    hashtag: bool,
    zero: bool,
    width: Option<usize>,
    precision: Option<usize>,
    types: Option<String>,
}

impl FormatSpec {
    pub(crate) fn new(placeholder: &str) -> Result<Self, Error> {
        if placeholder.rfind(':').is_some() {
            let spec = placeholder.split(':').last().unwrap().to_owned();
            Self::validate(spec.clone(), placeholder)?;
            return Ok(Self::parse(spec));
        }

        Ok(Self::default())
    }

    fn validate(mut spec: String, placeholder: &str) -> Result<(), Error> {
        let mut align_index = match (spec.find('<'), spec.find('^'), spec.find('>')) {
            (None, None, None) => None,
            (Some(left), None, None) => Some(left),
            (None, Some(center), None) => Some(center),
            (None, None, Some(right)) => Some(right),
            _ => parse!("multiple align specifiers used in {{{}}}", placeholder),
        };

        if let Some(x) = align_index {
            if x == 1 {
                spec.replace_range((x - 1)..x, "");
                align_index = Some(0);
            }
        }

        let sign_index = match (spec.find('+'), spec.find('-')) {
            (None, None) => None,
            (Some(positive), None) => Some(positive),
            (None, Some(negative)) => Some(negative),
            _ => parse!("multiple signs used in {{{}}}", placeholder),
        };

        let hashtag_index = spec.find('#');
        let mut zero_index = spec.find('0');
        let precision_index = spec.find('.');

        if let Some(x) = zero_index {
            if let Some(y) = precision_index {
                if x > y {
                    zero_index = None;
                }
            }
        }

        let question_index = spec.find('?');

        if let Some(x) = align_index {
            for (spec_name, spec_index) in [
                ("sign", sign_index),
                ("#", hashtag_index),
                ("0 padding and width", zero_index),
                ("precision", precision_index),
                ("?", question_index),
            ]
            .iter()
            {
                if let Some(spec_index) = spec_index {
                    if x > *spec_index {
                        parse!(
                            "align should be used before {} in {{{}}}",
                            spec_name,
                            placeholder
                        );
                    }
                }
            }
        }

        if let Some(x) = sign_index {
            for (spec_name, spec_index) in [
                ("#", hashtag_index),
                ("0 padding and width", zero_index),
                ("precision", precision_index),
                ("?", question_index),
            ]
            .iter()
            {
                if let Some(spec_index) = spec_index {
                    if x > *spec_index {
                        parse!(
                            "sign should be used before {} in {{{}}}",
                            spec_name,
                            placeholder
                        );
                    }
                }
            }
        }

        if let Some(x) = hashtag_index {
            for (spec_name, spec_index) in [
                ("0 padding and width", zero_index),
                ("precision", precision_index),
                ("?", question_index),
            ]
            .iter()
            {
                if let Some(spec_index) = spec_index {
                    if x > *spec_index {
                        parse!(
                            "# should be used before {} in {{{}}}",
                            spec_name,
                            placeholder
                        );
                    }
                }
            }
        }

        if let Some(x) = zero_index {
            for (spec_name, spec_index) in
                [("precision", precision_index), ("?", question_index)].iter()
            {
                if let Some(spec_index) = spec_index {
                    if x > *spec_index {
                        parse!(
                            "0 should be used before {} in {{{}}}",
                            spec_name,
                            placeholder
                        );
                    }
                }
            }
        }

        if let Some(x) = precision_index {
            for (spec_name, spec_index) in [("?", question_index)].iter() {
                if let Some(spec_index) = spec_index {
                    if x > *spec_index {
                        parse!(
                            "precision should be used before {} in {{{}}}",
                            spec_name,
                            placeholder
                        );
                    }
                }
            }

            if let Some(y) = spec.get((x + 1)..(x + 2)) {
                if y.parse::<usize>().is_err() {
                    parse!(
                        "precision value is not a valid usize in {{{}}}",
                        placeholder
                    );
                }
            } else {
                parse!("precision value not supplied in {{{}}}", placeholder);
            }
        }

        // UNSUPPORTED FORMAT SPECS

        if spec.contains('$') {
            ufs!(
                "parameter setting through $ sign argument is not supported but used in {{{}}}",
                placeholder
            );
        }

        if spec.contains(".*") {
            ufs!(
                "asterisk .* formatting is not supported but used in {{{}}}",
                placeholder
            );
        }

        if spec.contains('o') {
            ufs!(
                "o formatting is not supported but used in {{{}}}",
                placeholder
            );
        }

        if spec.contains('x') {
            ufs!(
                "x formatting is not supported but used in {{{}}}",
                placeholder
            );
        }

        if spec.contains('X') {
            ufs!(
                "X formatting is not supported but used in {{{}}}",
                placeholder
            );
        }

        if spec.contains('p') {
            ufs!(
                "p formatting is not supported but used in {{{}}}",
                placeholder
            );
        }

        if spec.contains('b') {
            ufs!(
                "b formatting is not supported but used in {{{}}}",
                placeholder
            );
        }

        if spec.contains('e') {
            ufs!(
                "e formatting is not supported but used in {{{}}}",
                placeholder
            );
        }

        if spec.contains('E') {
            ufs!(
                "E formatting is not supported but used in {{{}}}",
                placeholder
            );
        }

        Ok(())
    }

    fn parse(mut spec: String) -> Self {
        let spec_copy = spec.clone();
        let align = if let Some(align) = spec_copy.get(0..1) {
            if align == "<" || align == "^" || align == ">" {
                spec.replace_range(0..1, "");
                Some((None, align.to_owned()))
            } else if let Some(align) = spec_copy.get(1..2) {
                if align == "<" || align == "^" || align == ">" {
                    spec.replace_range(0..2, "");
                    Some((
                        Some(spec_copy.get(0..1).unwrap().to_owned()),
                        align.to_owned(),
                    ))
                } else {
                    None
                }
            } else {
                None
            }
        } else {
            None
        };

        let spec_copy = spec.clone();
        let sign = if let Some(sign) = spec_copy.get(0..1) {
            if sign == "+" || sign == "-" {
                spec.replace_range(0..1, "");
                Some(sign.to_owned())
            } else {
                None
            }
        } else {
            None
        };

        let hashtag = if let Some(hastag) = spec.get(0..1) {
            if hastag == "#" {
                spec.replace_range(0..1, "");
                true
            } else {
                false
            }
        } else {
            false
        };

        let zero = if let Some(zero) = spec.get(0..1) {
            if zero == "0" {
                spec.replace_range(0..1, "");
                true
            } else {
                false
            }
        } else {
            false
        };

        let width = crate::utils::usize_token(&spec, 0);

        if let Some(x) = width {
            spec = spec.trim_start_matches(&x.to_string()).to_owned();
        }

        let precision = if let Some(point) = spec.get(0..1) {
            if point == "." {
                crate::utils::usize_token(&spec, 1)
            } else {
                None
            }
        } else {
            None
        };

        if let Some(x) = precision {
            spec = spec.trim_start_matches(&format!(".{}", x)).to_owned();
        }

        let types = if spec.is_empty() {
            None
        } else {
            Some(spec.split(' ').next().unwrap().to_owned())
        };

        Self {
            align,
            sign,
            hashtag,
            zero,
            width,
            precision,
            types,
        }
    }

    pub fn format<T: Display + Debug>(&self, value: T) -> String {
        let mut fmtval = format!("{}", value);

        if let Some(precision) = self.precision {
            if self.zero && self.hashtag {
                fmtval = format!("{:#0.1$}", value, precision);
            } else if self.hashtag {
                fmtval = format!("{:#.1$}", value, precision);
            } else {
                fmtval = format!("{:.1$}", value, precision);
            }
        }

        if let Some(types) = &self.types {
            if self.hashtag {
                if types == "?" {
                    return format!("{:#?}", value);
                } else if types == "x?" {
                    return format!("{:#x?}", value);
                } else if types == "X?" {
                    return format!("{:#X?}", value);
                }
            } else if types == "?" {
                return format!("{:?}", value);
            } else if types == "x?" {
                return format!("{:x?}", value);
            } else if types == "X?" {
                return format!("{:X?}", value);
            }
        }

        if let Some(sign) = &self.sign {
            if sign == "+" && !self.zero && crate::utils::is_number_and_positive(&fmtval) {
                fmtval = "+".to_owned() + &fmtval;
            }
        }

        match &self.align {
            Some((Some(fill), align)) => {
                fmtval = fmtval.trim().to_owned();
                let chars_count = fmtval.chars().count();
                let width = self.width.unwrap_or(0);

                if width > chars_count {
                    if align == "<" {
                        fmtval = fmtval + &fill.repeat(width - chars_count);
                    } else if align == "^" {
                        let factor = usize::from(chars_count % 2 != 0);
                        let start = fill.repeat((width - chars_count - factor) / 2);
                        let end = fill.repeat((width - chars_count + factor) / 2);
                        fmtval = start + &fmtval + &end;
                    } else if align == ">" {
                        fmtval = fill.repeat(width - chars_count) + &fmtval;
                    }
                }
            }
            Some((None, align)) => {
                if align == "<" {
                    fmtval = format!("{:<1$}", fmtval, self.width.unwrap_or(0));
                } else if align == "^" {
                    fmtval = format!("{:^1$}", fmtval, self.width.unwrap_or(0));
                } else if align == ">" {
                    fmtval = format!("{:>1$}", fmtval, self.width.unwrap_or(0));
                }
            }
            None => (),
        }

        if let Some(width) = self.width {
            if crate::utils::is_number(&fmtval) {
                let chars_count = fmtval.chars().count();

                if self.zero && width > chars_count {
                    if let Some(sign) = &self.sign {
                        if sign == "+" {
                            if crate::utils::is_number_and_positive(&fmtval) {
                                fmtval =
                                    "+".to_owned() + &"0".repeat(width - chars_count - 1) + &fmtval;
                            } else {
                                fmtval = "-".to_owned()
                                    + &"0".repeat(width - chars_count)
                                    + &fmtval[1..];
                            }
                        } else if sign == "-" {
                            fmtval =
                                "-".to_owned() + &"0".repeat(width - chars_count) + &fmtval[1..];
                        }
                    } else if fmtval.starts_with('-') {
                        fmtval = "-".to_owned() + &"0".repeat(width - chars_count) + &fmtval[1..];
                    } else {
                        fmtval = "0".repeat(width - chars_count) + &fmtval;
                    }
                } else if width > chars_count {
                    fmtval = " ".repeat(width - chars_count) + &fmtval;
                }
            } else if self.zero && self.hashtag {
                fmtval = format!("{:#01$}", fmtval, width);
            } else if self.zero {
                fmtval = format!("{:01$}", fmtval, width);
            } else if self.hashtag {
                fmtval = format!("{:#1$}", fmtval, width);
            } else {
                fmtval = format!("{:1$}", fmtval, width);
            }
        }

        fmtval
    }
}
