use thiserror::Error;

use serde::{Deserialize, Serialize};
use std::fmt;

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum GameType {
    Go,
    Othello,
    Chess,
    GomokuRenju,
    NineMensMorris,
    Backgammon,
    ChineseChess,
    Shogi,
    LinesOfAction,
    Ataxx,
    Hex,
    Jungle,
    Neutron,
    PhilosophersFootball,
    Quadrature,
    Trax,
    Tantrix,
    Amazons,
    Octi,
    Gess,
    Twixt,
    Zertz,
    Plateau,
    Yinsh,
    Punct,
    Gobblet,
    Hive,
    Exxit,
    Hnefatal,
    Kuba,
    Tripples,
    Chase,
    TumblingDown,
    Sahara,
    Byte,
    Focus,
    Dvonn,
    Tamsk,
    Gipf,
    Kropki,
    Other(String),
}

#[derive(Debug)]
pub enum Error {
    // InvalidField,
    // InvalidBoardSize,
    Incomplete,
    InvalidSgf(VerboseNomError),
}

#[allow(dead_code)]
#[derive(Debug)]
pub struct VerboseNomError(nom::error::VerboseError<String>);

impl From<nom::error::VerboseError<&str>> for VerboseNomError {
    fn from(err: nom::error::VerboseError<&str>) -> Self {
        VerboseNomError(nom::error::VerboseError {
            errors: err
                .errors
                .into_iter()
                .map(|err| (err.0.to_owned(), err.1))
                .collect(),
        })
    }
}

impl From<nom::Err<nom::error::VerboseError<&str>>> for Error {
    fn from(err: nom::Err<nom::error::VerboseError<&str>>) -> Self {
        match err {
            nom::Err::Incomplete(_) => Error::Incomplete,
            nom::Err::Error(e) => Error::InvalidSgf(VerboseNomError::from(e)),
            nom::Err::Failure(e) => Error::InvalidSgf(VerboseNomError::from(e)),
        }
    }
}

#[derive(Debug, PartialEq, Error)]
pub enum ParseError {
    #[error("An unknown error was found")]
    NomError(nom::error::Error<String>),
}

impl From<nom::error::Error<&str>> for ParseError {
    fn from(err: nom::error::Error<&str>) -> Self {
        Self::NomError(nom::error::Error {
            input: err.input.to_owned(),
            code: err.code,
        })
    }
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum Color {
    Black,
    White,
}

impl Color {
    pub fn abbreviation(&self) -> String {
        match self {
            Color::White => "W",
            Color::Black => "B",
        }
        .to_owned()
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum GameResult {
    Draw,
    Black(Win),
    White(Win),
    Void,
    Unknown(String),
}

impl fmt::Display for GameResult {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            GameResult::Draw => write!(f, "draw"),
            GameResult::Black(_) => write!(f, "B"),
            GameResult::White(_) => write!(f, "W"),
            GameResult::Void => write!(f, "void"),
            GameResult::Unknown(s) => write!(f, "{}", s),
        }
    }
}

impl TryFrom<&str> for GameResult {
    type Error = String;
    fn try_from(s: &str) -> Result<GameResult, Self::Error> {
        if s == "0" {
            Ok(GameResult::Draw)
        } else if s == "Void" {
            Ok(GameResult::Void)
        } else {
            let parts = s.split('+').collect::<Vec<&str>>();
            let res = match parts[0].to_ascii_lowercase().as_str() {
                "b" => GameResult::Black,
                "w" => GameResult::White,
                res => return Ok(GameResult::Unknown(res.to_owned())),
            };
            match parts[1].to_ascii_lowercase().as_str() {
                "r" | "resign" => Ok(res(Win::Resignation)),
                "t" | "time" => Ok(res(Win::Time)),
                "f" | "forfeit" => Ok(res(Win::Forfeit)),
                _ => {
                    let score = parts[1].parse::<f32>().unwrap();
                    Ok(res(Win::Score(score)))
                }
            }
        }
    }
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Win {
    Score(f32),
    Resignation,
    Forfeit,
    Time,
    Unknown,
}
