use std::num::{ParseFloatError, ParseIntError};
use std::str::FromStr;
use thiserror::Error;
use crate::{c, Palette, r};

#[derive(Error, Debug, Clone)]
pub enum PaletteParseError {
    #[error("unknown command `{0}`")]
    UnknownCommand(String),
    #[error("invalid `color:` command, expected 5 or 8 tokens but found `{0}`")]
    IncorrectColorCommandLength(usize),
    #[error("invalid `color4:` command, expected 6 or 10 tokens but found `{0}`")]
    IncorrectColor4CommandLength(usize),
    #[error("invalid float")]
    InvalidFloat(#[from] ParseFloatError),
    #[error("invalid int")]
    InvalidInt(#[from] ParseIntError),
    #[error("palette is empty")]
    PaletteEmpty
}

pub fn parse(pal_str: &str) -> Result<Palette, PaletteParseError> {
    // to lowercase
    let input_lowercase = pal_str.to_lowercase();
    // iterate over each line

    // will be used to build the ranged palette
    // value r1 g1 b1 a1 r2 g2 b2 a2
    let mut parsed_data: Vec<(f64, u8, u8, u8, u8, u8, u8, u8, u8)> = vec![];

    for line in input_lowercase.lines() {
        let tokens = line.split_whitespace().collect::<Vec<_>>();
        match tokens[0] {
            "color:" => {
                if tokens.len() == 5 {
                    // color: val r g b (4 tkns + command = 5 total)
                    let value = f64::from_str(tokens[1])?;
                    let r = u8::from_str(tokens[2])?;
                    let g = u8::from_str(tokens[3])?;
                    let b = u8::from_str(tokens[4])?;
                    parsed_data.push((value, r, g, b, 255, r, g, b, 255));
                } else if tokens.len() == 8 {
                    // color: val r1 g1 b1 r2 b2 g2 (7 tkns + command = 8 total)
                    let value = f64::from_str(tokens[1])?;
                    let r1 = u8::from_str(tokens[2])?;
                    let g1 = u8::from_str(tokens[3])?;
                    let b1 = u8::from_str(tokens[4])?;
                    let r2 = u8::from_str(tokens[5])?;
                    let g2 = u8::from_str(tokens[6])?;
                    let b2 = u8::from_str(tokens[7])?;
                    parsed_data.push((value, r1, g1, b1, 255, r2, g2, b2, 255));
                } else {
                    return Err(PaletteParseError::IncorrectColorCommandLength(tokens.len()));
                }
            },
            "color4:" => {
                if tokens.len() == 6 {
                    // color4: val r g b a (5 tkns + command = 6 total)
                    let value = f64::from_str(tokens[1])?;
                    let r = u8::from_str(tokens[2])?;
                    let g = u8::from_str(tokens[3])?;
                    let b = u8::from_str(tokens[4])?;
                    let a = u8::from_str(tokens[5])?;
                    parsed_data.push((value, r, g, b, a, r, g, b, a));
                } else if tokens.len() == 10 {
                    // color4: val r1 g1 b1 a1 r2 b2 g2 a2 (9 tkns + command = 10 total)
                    let value = f64::from_str(tokens[1])?;
                    let r1 = u8::from_str(tokens[2])?;
                    let g1 = u8::from_str(tokens[3])?;
                    let b1 = u8::from_str(tokens[4])?;
                    let a1 = u8::from_str(tokens[5])?;
                    let r2 = u8::from_str(tokens[6])?;
                    let g2 = u8::from_str(tokens[7])?;
                    let b2 = u8::from_str(tokens[8])?;
                    let a2 = u8::from_str(tokens[9])?;
                    parsed_data.push((value, r1, g1, b1, a1, r2, g2, b2, a2));
                } else {
                    return Err(PaletteParseError::IncorrectColorCommandLength(tokens.len()));
                }
            },
            "product:" => {}, // valid but ignored
            "units:" => {}, // valid but ignored
            "step:" => {}, // valid but ignored
            unknown => return Err(PaletteParseError::UnknownCommand(unknown.to_string()))
        }
    }

    if parsed_data.is_empty() {
        return Err(PaletteParseError::PaletteEmpty)
    }

    let mut intermediate_with_infinities_added: Vec<(f64, u8, u8, u8, u8, u8, u8, u8, u8)> = vec![];

    intermediate_with_infinities_added.push(
        (
            f64::NEG_INFINITY,
            parsed_data[0].1,
            parsed_data[0].2,
            parsed_data[0].3,
            parsed_data[0].4,
            parsed_data[0].1,
            parsed_data[0].2,
            parsed_data[0].3,
            parsed_data[0].4,
        )
    );
    intermediate_with_infinities_added.append(&mut parsed_data);
    intermediate_with_infinities_added.push(
        (
            f64::INFINITY,
            intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].1,
            intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].2,
            intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].3,
            intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].4,
            intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].1,
            intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].2,
            intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].3,
            intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].4,
        )
    );

    let mut output = vec![];

    for range in intermediate_with_infinities_added.windows(2) {
        output.push(
            (r!(range[0].0, range[1].0), (c!(range[0].1, range[0].2, range[0].3, range[0].4), c!(range[0].5, range[0].6, range[0].7, range[0].8)))
        )
    }

    Ok(output)
}

#[cfg(test)]
mod tests {
    use crate::parser::parse;
    use crate::{c, r};

    #[test]
    fn almanydesigns_radaromega_nws_default() {
        assert_eq!(
            parse(r#"color: -30 165 165 165 8 230 230
color: 10 0 165 255 0 8 197
color: 20 16 255 8 10 126 3
color: 35 251 238 0 210 112 2
color: 50 255 0 0 171 0 1
color: 65 247 1 249 136 63 174
color: 75 255 255 255 184 184 184
color: 85 184 184 184
color: 95 184 184 184"#).unwrap(),
            vec![
                (r!(f64::NEG_INFINITY, -30.0), (c!(165, 165, 165), c!(165, 165, 165))),
                (r!(-30.0, 10.0), (c!(165, 165, 165), c!(8, 230, 230))),
                (r!(10.0, 20.0), (c!(0, 165, 255), c!(0, 8, 197))),
                (r!(20.0, 35.0), (c!(16, 255, 8), c!(10, 126, 3))),
                (r!(35.0, 50.0), (c!(251, 238, 0), c!(210, 112, 2))),
                (r!(50.0, 65.0), (c!(255, 0, 0), c!(171, 0, 1))),
                (r!(65.0, 75.0), (c!(247, 1, 249), c!(136, 63, 174))),
                (r!(75.0, 85.0), (c!(255, 255, 255), c!(184, 184, 184))),
                (r!(85.0, 95.0), (c!(184, 184, 184), c!(184, 184, 184))),
                (r!(95.0, f64::INFINITY), (c!(184, 184, 184), c!(184, 184, 184)))
            ]
        )
    }
}