use std::{
    borrow::BorrowMut,
    fmt::Display,
    io::{BufReader, Read},
    path::{Path, PathBuf},
    str::FromStr,
};

use super::{CreatureDef, Definitions, SiteDef, Tag, TopLevelTag};

pub trait SliceParse: Sized {
    fn parse(values: &[String]) -> Result<Self, SliceParseError>;
}

trait Parseable: Sized + FromStr {
    fn parse(string: &str) -> Option<Self> {
        string.parse().ok()
    }
}

// Implement for types that tags contain
impl Parseable for String {}
impl Parseable for i8 {}
impl Parseable for u8 {}
impl Parseable for i16 {}
impl Parseable for u16 {}
impl Parseable for i32 {}
impl Parseable for u32 {}
impl Parseable for f32 {}
impl Parseable for bool {}

macro_rules! parse_query_impl {
    ($t:ident) => {
        impl<$t: Parseable> SliceParse for $t {
            fn parse(values: &[String]) -> Result<Self, SliceParseError> {
                let mut iter = values.iter();
                $t::parse(iter.next().ok_or(SliceParseError::MissingValue)?).ok_or(SliceParseError::ParseError)
            }
        }
    };
    ($x:ident, $($t:ident),+) => {
        impl<$x: Parseable, $($t: Parseable),+> SliceParse for ($x, $($t),+) {
            fn parse(values: &[String]) -> Result<Self, SliceParseError> {
                let mut iter = values.iter();
                Ok((
                    $x::parse(iter.next().ok_or(SliceParseError::MissingValue)?).ok_or(SliceParseError::ParseError)?,
                    $($t::parse(iter.next().ok_or(SliceParseError::MissingValue)?).ok_or(SliceParseError::ParseError)?,)+
                ))
            }
        }
        parse_query_impl!($($t),+);
    };
}

// Definitely overkill, but this enables definition files to support up to 20 arguments per tag.
// Although managing that many would be annoying
parse_query_impl!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);

#[derive(Clone, Debug)]
pub struct FilePosition {
    pub line: u32,
    pub column: u32,
}

impl Default for FilePosition {
    fn default() -> Self {
        Self { line: 1, column: 1 }
    }
}

#[derive(Debug)]
pub enum ParseError {
    Lexer(LexerError),
    Syntax {
        error: String,
    },
    TagParse(TagParseError),
    NoDefinitions,
    UnknownTag {
        tag: String,
        id: Option<String>,
        category: Option<TopLevelTag>,
    },
}

impl Display for ParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            // TODO: Format rest of errors some day
            ParseError::Lexer(err) => write!(f, "Lexer error: {err:?}"),
            ParseError::Syntax { error } => write!(f, "Syntax error: {error}"),
            ParseError::TagParse(err) => {
                write!(
                    f,
                    "(line {line}, column {column}) ",
                    line = err.tag().file_position.line,
                    column = err.tag().file_position.column,
                )?;
                match err {
                    TagParseError::MissingValue(tag) => {
                        write!(f, "Missing values from {}", tag.as_def_string())?;
                    }
                    TagParseError::ParseError(tag) => {
                        write!(f, "Could not parse {}", tag.as_def_string())?;
                    }
                }
                write!(f, "")
            }
            ParseError::NoDefinitions => write!(f, "No definitions found"),
            ParseError::UnknownTag { tag, id, category } => match category {
                Some(category) => write!(
                    f,
                    "Unknown tag '{tag}' in [{category}:{}] ",
                    id.as_ref().unwrap_or(&"<unknown>".into())
                ),
                None => write!(f, "Unknown top-level-tag '{tag}'"),
            },
        }
    }
}

pub enum SliceParseError {
    MissingValue,
    ParseError,
}

#[derive(Debug)]
pub enum TagParseError {
    MissingValue(Tag),
    ParseError(Tag),
}

impl TagParseError {
    pub fn tag(&self) -> &Tag {
        match self {
            Self::MissingValue(tag) => tag,
            Self::ParseError(tag) => tag,
        }
    }
}

#[derive(Debug)]
pub enum LexerError {
    Io(std::io::Error),
    UnclosedTag(Tag),
    MissingTagStart(FilePosition),
}

#[derive(Debug)]
pub struct ParserErrorLocation {
    pub file_path: PathBuf,
    pub error: ParseError,
}

#[derive(Debug)]
pub struct DuplicateError {
    pub file_path: PathBuf,
    pub category: TopLevelTag,
    pub id: String,
}

#[derive(Debug)]
pub enum DirectoryParseError {
    Io(std::io::Error),
    Files {
        parser_errors: Vec<ParserErrorLocation>,
        duplicates: Vec<DuplicateError>,
    },
}

impl From<std::io::Error> for DirectoryParseError {
    fn from(value: std::io::Error) -> Self {
        Self::Io(value)
    }
}

pub struct DefinitionParser;

impl DefinitionParser {
    pub fn parse(source: impl Read) -> Result<(Vec<SiteDef>, Vec<CreatureDef>), ParseError> {
        let tags = match Self::lexer(source) {
            Ok(tags) => tags,
            Err(err) => return Err(ParseError::Lexer(err)),
        };

        let mut unparsed_defs: Vec<(Tag, Vec<Tag>)> = Vec::new();

        let mut definition_builder: Option<(Tag, Vec<Tag>)> = None;
        for tag in tags {
            match Self::match_top_level_tag(&tag.name) {
                Some(_) => {
                    if let Some(builder) = definition_builder {
                        unparsed_defs.push(builder);
                    }
                    definition_builder = Some((tag, vec![]));
                }
                None => match definition_builder.borrow_mut() {
                    Some(builder) => builder.1.push(tag.clone()),
                    None => {
                        return Err(ParseError::Syntax {
                            error: "First tag must be a top-level-tag".into(),
                        })
                    }
                },
            }
        }

        match definition_builder {
            Some(builder) => unparsed_defs.push(builder),
            None => {
                return Err(ParseError::NoDefinitions);
            }
        }

        let mut sites = vec![];
        let mut creatures = vec![];
        for (header, tags) in unparsed_defs {
            match Self::match_top_level_tag(&header.name) {
                Some(def_type) => match def_type {
                    TopLevelTag::Site => sites.push(SiteDef::parse(header, &tags)?),
                    TopLevelTag::Creature => creatures.push(CreatureDef::parse(header, &tags)?),
                },
                None => {
                    return Err(ParseError::UnknownTag {
                        tag: header.name,
                        id: None,
                        category: None,
                    });
                }
            }
        }
        Ok((sites, creatures))
    }

    /// Parses every file with `.def` extension in given directory.
    pub fn parse_directory(directory: &Path) -> Result<Definitions, DirectoryParseError> {
        let mut parse_error_files = vec![];
        let mut duplicate_error_files = vec![];
        let mut result = Definitions::default();
        // NOTE: `.flatten()` hides any error results from `read_dir()` elements.
        // This shouldn't be a problem, but it could be the source of hard-to-find errors.
        for entry in std::fs::read_dir(directory)
            .map_err(DirectoryParseError::from)?
            .flatten()
        {
            if entry.file_name().to_string_lossy().ends_with(".def") {
                let source = BufReader::new(
                    std::fs::File::open(entry.path()).map_err(DirectoryParseError::from)?,
                );
                match Self::parse(source) {
                    Ok(defs) => {
                        for def in defs.0 {
                            if let Some(duplicate) = result.sites.insert(def.id.clone(), def) {
                                duplicate_error_files.push(DuplicateError {
                                    file_path: entry.path(),
                                    category: TopLevelTag::Site,
                                    id: duplicate.id,
                                })
                            }
                        }
                        for def in defs.1 {
                            if let Some(duplicate) = result.creatures.insert(def.id.clone(), def) {
                                duplicate_error_files.push(DuplicateError {
                                    file_path: entry.path(),
                                    category: TopLevelTag::Creature,
                                    id: duplicate.id,
                                })
                            }
                        }
                    }
                    Err(err) => {
                        parse_error_files.push(ParserErrorLocation {
                            file_path: entry.path(),
                            error: err,
                        });
                    }
                }
            }
        }

        if !parse_error_files.is_empty() || !duplicate_error_files.is_empty() {
            return Err(DirectoryParseError::Files {
                parser_errors: parse_error_files,
                duplicates: duplicate_error_files,
            });
        }

        Ok(result)
    }

    fn lexer(mut source: impl Read) -> Result<Vec<Tag>, LexerError> {
        let mut tags = vec![];
        let mut tag_builder: Option<Tag> = None;
        let mut buffer = String::new();
        source.read_to_string(&mut buffer).map_err(LexerError::Io)?;
        let mut file_position = FilePosition::default();
        let mut in_comment = false;
        for c in buffer.chars() {
            if c == '\n' {
                in_comment = false;
                file_position.line += 1;
                file_position.column = 0;
            } else {
                file_position.column += 1;
            }
            if in_comment {
                continue;
            }
            match tag_builder.borrow_mut() {
                None => match c {
                    '[' => {
                        tag_builder = Some(Tag {
                            file_position: file_position.clone(),
                            ..Default::default()
                        });
                    }
                    ']' => return Err(LexerError::MissingTagStart(file_position.clone())),
                    '#' => in_comment = true,
                    _ => {}
                },
                Some(tag) => match c {
                    ']' => {
                        tags.push(tag.clone());
                        tag_builder = None;
                    }
                    ':' => tag.values.push("".into()),
                    '[' => return Err(LexerError::UnclosedTag(tag.clone())),
                    _ => {
                        if let Some(last) = tag.values.last_mut() {
                            last.push(c)
                        } else {
                            tag.name.push(c)
                        }
                    }
                },
            }
        }

        if let Some(tag) = tag_builder {
            return Err(LexerError::UnclosedTag(tag.clone()));
        }

        Ok(tags)
    }

    fn match_top_level_tag(tag: &str) -> Option<TopLevelTag> {
        Some(match tag {
            "SITE" => TopLevelTag::Site,
            "CREATURE" => TopLevelTag::Creature,
            _ => return None,
        })
    }
}
