use crate::position::AllPosition;
use crate::position::BooleanPosition::*;
use crate::position::CategoryPosition::*;
use crate::position::PhonePosition::*;
use crate::position::SignedRangePosition::*;
use crate::position::UndefinedPotision::*;
use crate::position::UnsignedRangePosition::*;
use AllPosition::*;
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
pub enum PositionError {
    #[error("No matching position found")]
    NoMatchingPosition,
    #[error("The first character should be asterisk in this position")]
    MissingPrefixAsterisk,
    #[error("The last character should be asterisk in this position")]
    MissingSuffixAsterisk,
    #[error("Prefix has unknown sequence")]
    PrefixVerifyError,
    #[error("Suffix has unknown sequence")]
    SuffixVerifyError,
    #[error("Range is empty")]
    EmptyRange,
}
pub(crate) fn estimate_position(pattern: &str) -> Result<(AllPosition, &str), PositionError> {
    let split = PositionSplit::new(pattern);
    let position = split.match_position()?;
    split.verify(position)?;
    Ok((position, split.into_range()?))
}
struct PositionSplit<'a> {
    prefix: &'a str,
    range: &'a str,
    suffix: &'a str,
    asterisks: (bool, bool),
}
impl<'a> PositionSplit<'a> {
    pub fn new(pattern: &'a str) -> Self {
        let (pattern, asterisks) = Self::trim_asterisk(pattern);
        let mut prefix = pattern
            .bytes()
            .position(|b| "!#%&+-=@^_|:".contains(b as char))
            .map(|i| i + 1)
            .unwrap_or(0);
        let mut suffix = pattern
            .bytes()
            .rev()
            .position(|b| "!#%&+-=@^_|/".contains(b as char))
            .map(|i| pattern.len() - i - 1)
            .unwrap_or(pattern.len());
        if prefix > suffix {
            if prefix == pattern.len() {
                prefix = 0;
            } else {
                suffix = pattern.len();
            }
        }
        Self {
            prefix: &pattern[..prefix],
            range: &pattern[prefix..suffix],
            suffix: &pattern[suffix..],
            asterisks,
        }
    }
    fn trim_asterisk(mut pattern: &str) -> (&str, (bool, bool)) {
        let mut stars = (false, false);
        if pattern.starts_with('*') {
            pattern = &pattern[1..];
            stars.0 = true;
        }
        if pattern.ends_with('*') {
            pattern = &pattern[..pattern.len() - 1];
            stars.1 = true;
        }
        (pattern, stars)
    }
    pub fn match_position(&self) -> Result<AllPosition, PositionError> {
        if self.suffix.is_empty() && !self.asterisks.1 {
            return Ok(UnsignedRange(K3));
        }
        if let Some(position) = prefix_match(self.prefix) {
            return Ok(position);
        }
        if let Some(position) = suffix_match(self.suffix) {
            return Ok(position);
        }
        if let (Some(pchar), Some(schar)) =
            (self.prefix.bytes().next_back(), self.suffix.bytes().next())
        {
            if let Some(position) = combination_match(pchar, schar) {
                return Ok(position);
            }
        }
        Err(PositionError::NoMatchingPosition)
    }
    pub fn verify(&self, position: AllPosition) -> Result<(), PositionError> {
        if position != Phone(P1) && !self.asterisks.0 {
            return Err(PositionError::MissingPrefixAsterisk);
        }
        if position != UnsignedRange(K3) && !self.asterisks.1 {
            return Err(PositionError::MissingSuffixAsterisk);
        }
        let (rprefix, rsuffix) = reverse_hint(position);
        if !rprefix.ends_with(self.prefix) {
            return Err(PositionError::PrefixVerifyError);
        }
        if !rsuffix.starts_with(self.suffix) {
            return Err(PositionError::SuffixVerifyError);
        }
        Ok(())
    }
    pub fn into_range(self) -> Result<&'a str, PositionError> {
        if self.range.is_empty() {
            return Err(PositionError::EmptyRange);
        }
        Ok(self.range)
    }
}
fn prefix_match(prefix: &str) -> Option<AllPosition> {
    let mut bytes = prefix.bytes();
    match bytes.next_back()? {
        b'^' => Some(Phone(P2)),
        b'=' => Some(Phone(P5)),
        b'!' => Some(Boolean(E3)),
        b'#' => Some(Boolean(F3)),
        b'%' => Some(Boolean(G3)),
        b'&' => Some(UnsignedRange(I5)),
        b':' => match bytes.next_back()? {
            b'A' => Some(SignedRange(A1)),
            b'B' => Some(Category(B1)),
            b'C' => Some(Category(C1)),
            b'D' => Some(Category(D1)),
            b'E' => Some(UnsignedRange(E1)),
            b'F' => Some(UnsignedRange(F1)),
            b'G' => Some(UnsignedRange(G1)),
            b'H' => Some(UnsignedRange(H1)),
            b'I' => Some(UnsignedRange(I1)),
            b'J' => Some(UnsignedRange(J1)),
            b'K' => Some(UnsignedRange(K1)),
            _ => None,
        },
        _ => None,
    }
}
fn suffix_match(suffix: &str) -> Option<AllPosition> {
    let mut bytes = suffix.bytes();
    match bytes.next()? {
        b'^' => Some(Phone(P1)),
        b'=' => Some(Phone(P4)),
        b'!' => Some(UnsignedRange(E2)),
        b'#' => Some(UnsignedRange(F2)),
        b'%' => Some(UnsignedRange(G2)),
        b'&' => Some(UnsignedRange(I4)),
        b'/' => match bytes.next()? {
            b'A' => Some(Phone(P5)),
            b'B' => Some(UnsignedRange(A3)),
            b'C' => Some(Category(B3)),
            b'D' => Some(Category(C3)),
            b'E' => Some(Category(D3)),
            b'F' => Some(Boolean(E5)),
            b'G' => Some(UnsignedRange(F8)),
            b'H' => Some(Boolean(G5)),
            b'I' => Some(UnsignedRange(H2)),
            b'J' => Some(UnsignedRange(I8)),
            b'K' => Some(UnsignedRange(J2)),
            _ => None,
        },
        _ => None,
    }
}
fn combination_match(prefix: u8, suffix: u8) -> Option<AllPosition> {
    match (prefix, suffix) {
        (b'-', b'+') => Some(Phone(P3)),
        (b'+', b'+') => Some(UnsignedRange(A2)),
        (b'-', b'_') => Some(Category(B2)),
        (b'_', b'+') => Some(Category(C2)),
        (b'+', b'_') => Some(Category(D2)),
        (b'_', b'-') => Some(Undefined(E4)),
        (b'-', b'/') => Some(Boolean(E5)),
        (b'_', b'@') => Some(Undefined(F4)),
        (b'@', b'_') => Some(UnsignedRange(F5)),
        (b'_', b'|') => Some(UnsignedRange(F6)),
        (b'|', b'_') => Some(UnsignedRange(F7)),
        (b'_', b'_') => Some(Undefined(G4)),
        (b'-', b'@') => Some(UnsignedRange(I2)),
        (b'@', b'+') => Some(UnsignedRange(I3)),
        (b'-', b'|') => Some(UnsignedRange(I6)),
        (b'|', b'+') => Some(UnsignedRange(I7)),
        (b'+', b'-') => Some(UnsignedRange(K2)),
        _ => None,
    }
}
fn reverse_hint(position: AllPosition) -> (&'static str, &'static str) {
    match position {
        Phone(P1) => ("", "^"),
        Phone(P2) => ("^", "-"),
        Phone(P3) => ("-", "+"),
        Phone(P4) => ("+", "="),
        Phone(P5) => ("=", "/A:"),
        SignedRange(A1) => ("/A:", "+"),
        UnsignedRange(A2) => ("+", "+"),
        UnsignedRange(A3) => ("+", "/B:"),
        Category(B1) => ("/B:", "-"),
        Category(B2) => ("-", "_"),
        Category(B3) => ("_", "/C:"),
        Category(C1) => ("/C:", "_"),
        Category(C2) => ("_", "+"),
        Category(C3) => ("+", "/D:"),
        Category(D1) => ("/D:", "+"),
        Category(D2) => ("+", "_"),
        Category(D3) => ("_", "/E:"),
        UnsignedRange(E1) => ("/E:", "_"),
        UnsignedRange(E2) => ("_", "!"),
        Boolean(E3) => ("!", "_"),
        Undefined(E4) => ("_", "-"),
        Boolean(E5) => ("-", "/F:"),
        UnsignedRange(F1) => ("/F:", "_"),
        UnsignedRange(F2) => ("_", "#"),
        Boolean(F3) => ("#", "_"),
        Undefined(F4) => ("_", "@"),
        UnsignedRange(F5) => ("@", "_"),
        UnsignedRange(F6) => ("_", "|"),
        UnsignedRange(F7) => ("|", "_"),
        UnsignedRange(F8) => ("_", "/G:"),
        UnsignedRange(G1) => ("/G:", "_"),
        UnsignedRange(G2) => ("_", "%"),
        Boolean(G3) => ("%", "_"),
        Undefined(G4) => ("_", "_"),
        Boolean(G5) => ("_", "/H:"),
        UnsignedRange(H1) => ("/H:", "_"),
        UnsignedRange(H2) => ("_", "/I:"),
        UnsignedRange(I1) => ("/I:", "-"),
        UnsignedRange(I2) => ("-", "@"),
        UnsignedRange(I3) => ("@", "+"),
        UnsignedRange(I4) => ("+", "&"),
        UnsignedRange(I5) => ("&", "-"),
        UnsignedRange(I6) => ("-", "|"),
        UnsignedRange(I7) => ("|", "+"),
        UnsignedRange(I8) => ("+", "/J:"),
        UnsignedRange(J1) => ("/J:", "_"),
        UnsignedRange(J2) => ("_", "/K:"),
        UnsignedRange(K1) => ("/K:", "+"),
        UnsignedRange(K2) => ("+", "-"),
        UnsignedRange(K3) => ("-", ""),
    }
}
#[cfg(test)]
mod tests {
    use crate::{
        parse_position::{estimate_position, PositionError},
        position::{
            AllPosition::*, BooleanPosition::*, CategoryPosition::*, PhonePosition::*,
            SignedRangePosition::*, UndefinedPotision::*, UnsignedRangePosition::*,
        },
    };
    #[test]
    fn basic() {
        assert_eq!(estimate_position("a^*"), Ok((Phone(P1), "a")));
        assert_eq!(estimate_position("*/A:-1+*"), Ok((SignedRange(A1), "-1")));
        assert_eq!(estimate_position("*/A:-??+*"), Ok((SignedRange(A1), "-??")));
        assert_eq!(estimate_position("*|?+*"), Ok((UnsignedRange(I7), "?")));
        assert_eq!(estimate_position("*-1"), Ok((UnsignedRange(K3), "1")));
        assert_eq!(estimate_position("*_42/I:*"), Ok((UnsignedRange(H2), "42")));
        assert_eq!(estimate_position("*/B:17-*"), Ok((Category(B1), "17")));
        assert_eq!(estimate_position("*_xx-*"), Ok((Undefined(E4), "xx")));
        assert_eq!(estimate_position("*_xx@*"), Ok((Undefined(F4), "xx")));
        assert_eq!(estimate_position("*_xx_*"), Ok((Undefined(G4), "xx")));
    }
    #[test]
    fn basic_fail() {
        assert_eq!(estimate_position("*"), Err(PositionError::EmptyRange));
        assert_eq!(
            estimate_position(":*"),
            Err(PositionError::NoMatchingPosition)
        );
        assert_eq!(estimate_position("*/A:*"), Err(PositionError::EmptyRange));
        assert_eq!(
            estimate_position("*/A:0/B:*"),
            Err(PositionError::SuffixVerifyError)
        );
        assert_eq!(
            estimate_position("*/B:0+*"),
            Err(PositionError::SuffixVerifyError)
        );
        assert_eq!(
            estimate_position("*/B :0+*"),
            Err(PositionError::NoMatchingPosition)
        );
        assert_eq!(
            estimate_position("*_0/Z:*"),
            Err(PositionError::NoMatchingPosition)
        );
        assert_eq!(
            estimate_position("a^"),
            Err(PositionError::MissingSuffixAsterisk)
        );
        assert_eq!(
            estimate_position("/B:17-*"),
            Err(PositionError::MissingPrefixAsterisk)
        );
        assert_eq!(
            estimate_position("-1"),
            Err(PositionError::MissingPrefixAsterisk)
        );
    }
    #[test]
    fn advanced() {
        assert_eq!(estimate_position("*#1*"), Ok((Boolean(F3), "1")));
        assert_eq!(estimate_position("*%1*"), Ok((Boolean(G3), "1")));
        assert_eq!(estimate_position("*_01/C*"), Ok((Category(B3), "01")));
        assert_eq!(estimate_position("*-1/*"), Ok((Boolean(E5), "1")));
        assert_eq!(
            estimate_position("*-1/H:*"),
            Err(PositionError::PrefixVerifyError)
        );
    }
}