use super::OSU_FILE_HEADER;

#[cfg(not(all(
    feature = "osu",
    feature = "taiko",
    feature = "fruits",
    feature = "mania"
)))]
use super::GameMode;

use std::error::Error as StdError;
use std::fmt;
use std::io::Error as IOError;
use std::num::{ParseFloatError, ParseIntError};

pub type ParseResult<T> = Result<T, ParseError>;

#[derive(Debug)]
pub enum ParseError {
    IOError(IOError),
    IncorrectFileHeader,
    BadLine,
    InvalidCurvePoints,
    InvalidInteger,
    InvalidFloatingPoint,
    InvalidMode,
    InvalidPathType,
    InvalidTimingSignature,
    MissingField(&'static str),
    UnknownHitObjectKind,

    #[cfg(not(all(
        feature = "osu",
        feature = "taiko",
        feature = "fruits",
        feature = "mania"
    )))]
    UnincludedMode(GameMode),
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::IOError(_) => f.write_str("IO error"),
            Self::IncorrectFileHeader => {
                write!(f, "expected `{}` at file begin", OSU_FILE_HEADER)
            }
            Self::BadLine => f.write_str("line not in `Key:Value` pattern"),
            Self::InvalidCurvePoints => f.write_str("invalid curve point"),
            Self::InvalidInteger => f.write_str("invalid integer"),
            Self::InvalidFloatingPoint => f.write_str("invalid float number"),
            Self::InvalidMode => f.write_str("invalid mode"),
            Self::InvalidPathType => f.write_str("invalid path type"),
            Self::InvalidTimingSignature => f.write_str("invalid timing signature"),
            Self::MissingField(field) => write!(f, "missing field `{}`", field),
            Self::UnknownHitObjectKind => f.write_str("unsupported hitobject kind"),

            #[cfg(not(all(
                feature = "osu",
                feature = "taiko",
                feature = "fruits",
                feature = "mania"
            )))]
            Self::UnincludedMode(mode) => write!(
                f,
                "cannot process {:?} map its mode's feature has not been included",
                mode
            ),
        }
    }
}

impl StdError for ParseError {
    fn source(&self) -> Option<&(dyn StdError + 'static)> {
        match self {
            Self::IOError(inner) => Some(inner),
            Self::IncorrectFileHeader => None,
            Self::BadLine => None,
            Self::InvalidCurvePoints => None,
            Self::InvalidInteger => None,
            Self::InvalidFloatingPoint => None,
            Self::InvalidMode => None,
            Self::InvalidPathType => None,
            Self::InvalidTimingSignature => None,
            Self::MissingField(_) => None,
            Self::UnknownHitObjectKind => None,

            #[cfg(not(all(
                feature = "osu",
                feature = "taiko",
                feature = "fruits",
                feature = "mania"
            )))]
            Self::UnincludedMode(_) => None,
        }
    }
}

impl From<IOError> for ParseError {
    fn from(other: IOError) -> Self {
        Self::IOError(other)
    }
}

impl From<ParseIntError> for ParseError {
    fn from(_: ParseIntError) -> Self {
        Self::InvalidInteger
    }
}

impl From<ParseFloatError> for ParseError {
    fn from(_: ParseFloatError) -> Self {
        Self::InvalidFloatingPoint
    }
}
