#![deny(missing_docs)]

/*!
This crate contains a parser for [scopes list notation (SLN)](https://scopes.readthedocs.io/en/latest/dataformat/).

The parser is also configurable, so you can change how the format of the file you want to parse looks like.
*/

use std::cmp::Ordering;
use token_parser::{
    Parser as TokenParser, Span,
    Unit::{self, Parser as ParserUnit, Symbol},
};

#[derive(Debug, PartialEq, Eq)]
/// Represents all kinds of errors that can occur during parsing.
pub enum ErrorKind {
    /// Specified brackets don't match.
    UnmatchedBrackets(char, char),
    /// Some bracket is not closed.
    UnclosedBracket,
    /// Some closing bracket has no matching opening bracket.
    TooManyBrackets,
    /// A string is not closed.
    UnclosedString,
    /// The prefix of a multiline string does not have the expected length.
    MultilinePrefixLength,
    /// The indentation is invalid.
    Indentation,
    /// Some unescapeable character is being escaped.
    UnescapeableCharacter(char),
}

impl std::fmt::Display for ErrorKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use ErrorKind::*;
        match self {
            UnmatchedBrackets(left, right) => {
                write!(f, "Brackets '{left}' and '{right}' don't match")
            }
            UnclosedBracket => write!(f, "Bracket not closed"),
            TooManyBrackets => write!(f, "Too many brackets"),
            UnclosedString => write!(f, "String not closed"),
            MultilinePrefixLength => write!(
                f,
                "Prefix for multiline string has to consist of exactly 4 string delimiters, but has 3"
            ),
            Indentation => write!(f, "Indentation not allowed"),
            UnescapeableCharacter(c) => write!(f, "Tried to escape unescapeable character {c}"),
        }
    }
}

/// Represents the error that might occur during parsing.
pub struct Error {
    /// Indicates in which line the error occurred.
    pub line: usize,
    /// Indicates in which column of that line the error occurred.
    pub column: usize,
    /// Indicates the kind of error that was found.
    pub kind: ErrorKind,
}

impl std::fmt::Debug for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Self { line, column, kind } = self;
        write!(f, "{line}:{column}: {kind:?}")
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Self { line, column, kind } = self;
        write!(f, "{line}:{column}: {kind}")
    }
}

impl std::error::Error for Error {}

/// Represents the result type for parsing.
pub type Result<T> = std::result::Result<T, Error>;

impl ErrorKind {
    fn error(self, line: usize, column: usize) -> Error {
        Error {
            line,
            column,
            kind: self,
        }
    }
}

struct BracketPair {
    left: char,
    right: char,
    prefix: Option<&'static str>,
}

struct StringDelimiter {
    delimiter: char,
    prefix: Option<&'static str>,
}

/// Represents the parser.
#[derive(Default)]
pub struct Parser {
    indent: u8,
    unpack_single: bool,
    allow_multi_indent: bool,
    strings: Vec<StringDelimiter>,
    brackets: Vec<BracketPair>,
    comments: Vec<char>,
    separator: Vec<char>,
    symbol_characters: Vec<char>,
}

impl Parser {
    /// Creates a new parser with minimal enabled features.
    pub fn new() -> Self {
        Self::default()
    }

    /// Creates a new parser with all the features of SLN.
    pub fn scopes() -> Self {
        Self::new()
            .indent(4)
            .unpack_single(true)
            .with_strings('"', Some("string"))
            .with_brackets('(', ')', None)
            .with_brackets('[', ']', Some("square-list"))
            .with_brackets('{', '}', Some("curly-list"))
            .with_comments('#')
            .with_separator(';')
            .with_symbol_character(',')
    }

    /// Returns the parser with the indent changed.
    /// If indent is zero, indentation-based nesting is disabled.
    pub fn indent(self, indent: u8) -> Self {
        Self { indent, ..self }
    }

    /// Returns the parser with unpacking of single elements in lines enabled or disabled.
    /// If enabled, a line that contains only a single element is not interpreted as a list.
    pub fn unpack_single(self, unpack_single: bool) -> Self {
        Self {
            unpack_single,
            ..self
        }
    }

    /// Returns the parser with more multi-indent enabled or disabled.
    /// If enabled, more than one additional indentation per line is allowed.
    pub fn allow_multi_indent(self, allow_multi_indent: bool) -> Self {
        Self {
            allow_multi_indent,
            ..self
        }
    }

    /// Returns the parser with a new string delimiter added.
    /// The string might be parsed as a list prefixed by a symbol.
    pub fn with_strings(mut self, delimiter: char, prefix: Option<&'static str>) -> Self {
        self.strings.push(StringDelimiter { delimiter, prefix });
        self
    }

    /// Returns the parser with string parsing disabled.
    pub fn without_strings(self) -> Self {
        Self {
            strings: Vec::new(),
            ..self
        }
    }

    /// Returns the parser with a new allowed bracket pair.
    /// Lists created by specific bracket pairs might be prefixed by a symbol.
    pub fn with_brackets(mut self, left: char, right: char, prefix: Option<&'static str>) -> Self {
        self.brackets.push(BracketPair {
            left,
            right,
            prefix,
        });
        self
    }

    /// Returns the parser with no support for brackets.
    pub fn without_brackets(self) -> Self {
        Self {
            brackets: Vec::new(),
            ..self
        }
    }

    /// Returns the parser with a new character for indicating comments added.
    /// A comment might apply to multiple lines.
    pub fn with_comments(mut self, comments: char) -> Self {
        self.comments.push(comments);
        self
    }

    /// Returns the parser with no support for comments.
    pub fn without_comments(self) -> Self {
        Self {
            comments: Vec::new(),
            ..self
        }
    }

    /// Returns the parser with a new character as separator added.
    /// A separator makes it possible to write multiple lists per line without the need of brackets.
    pub fn with_separator(mut self, separator: char) -> Self {
        self.separator.push(separator);
        self
    }

    /// Returns the parser with no support for separators.
    pub fn without_separator(self) -> Self {
        Self {
            separator: Vec::new(),
            ..self
        }
    }

    /// Returns the parser with a new character as symbol character added.
    /// Symbol characters will always be parsed as single characters, even if there is no whitespace before or after them.
    pub fn with_symbol_character(mut self, separator: char) -> Self {
        self.symbol_characters.push(separator);
        self
    }

    /// Returns the parser with no support for symbol characters.
    pub fn without_symbol_character(self) -> Self {
        Self {
            symbol_characters: Vec::new(),
            ..self
        }
    }

    /// Parses the text into a token parser, which can be used to parse all kinds of data structures.
    pub fn parse(&self, chars: impl IntoIterator<Item = char>) -> Result<TokenParser> {
        fn string_unit(name: &str, prefix: Option<&'static str>, span: Span) -> Unit {
            if let Some(prefix) = prefix {
                ParserUnit(
                    TokenParser::new(vec![Symbol(prefix.into(), span), Symbol(name.into(), span)])
                        .with_span(span),
                )
            } else {
                Symbol(name.into(), span)
            }
        }

        #[derive(Default)]
        enum Mode {
            Default,
            #[default]
            Newline,
            Comment(usize),
            Symbol(String, Span),
            String {
                prefix: Option<&'static str>,
                delimiter: char,
                content: String,
                escape: bool,
                span: Span,
            },
            MultilinePrefix {
                prefix: Option<&'static str>,
                delimiter: char,
                count: usize,
                span: Span,
            },
            MultilineString {
                prefix: Option<&'static str>,
                content: String,
                column: usize,
                span: Span,
            },
        }

        impl Mode {
            fn finish(self, units: &mut Vec<Unit>, line: usize, column: usize) -> Result<()> {
                match self {
                    Self::Symbol(name, span) => units.push(Symbol(name.into(), span)),
                    Self::String {
                        content,
                        prefix,
                        span,
                        ..
                    } => units.push(string_unit(&content, prefix, span)),
                    Self::MultilineString {
                        content,
                        prefix,
                        span,
                        ..
                    } => units.push(string_unit(content.trim_end_matches('\n'), prefix, span)),
                    Self::MultilinePrefix {
                        count,
                        prefix,
                        span,
                        ..
                    } => {
                        if count != 2 {
                            return Err(ErrorKind::MultilinePrefixLength.error(line, column));
                        }

                        units.push(string_unit("", prefix, span));
                    }
                    _ => (),
                }

                Ok(())
            }
        }

        #[derive(Default)]
        struct UnitStack {
            current: Vec<Unit>,
            stack: Vec<Vec<Unit>>,
            span_stack: Vec<Span>,
        }

        impl UnitStack {
            fn pop_stack(&mut self, unpack_single: bool) {
                let Some(next) = self.stack.pop() else {
                    return;
                };
                let span = self.span_stack.pop().unwrap_or_default();

                let mut child_units = std::mem::replace(&mut self.current, next);
                let unit = if unpack_single && child_units.len() == 1 {
                    unsafe { child_units.pop().unwrap_unchecked() }
                } else {
                    ParserUnit(TokenParser::new(child_units).with_span(span))
                };
                self.current.push(unit);
            }

            fn push_stack(&mut self, span: Span) {
                self.stack.push(std::mem::take(&mut self.current));
                self.span_stack.push(span);
            }
        }

        #[derive(Default)]
        struct ParsingData {
            mode: Mode,
            line: usize,
            column: usize,
            units: UnitStack,
            list_stack: Vec<char>,
        }

        let mut data = ParsingData::default();
        data.units.push_stack(Span::default());

        fn parse_character(
            parser: &Parser,
            char: char,
            mut data: ParsingData,
        ) -> Result<ParsingData> {
            if char == '\n' {
                match data.mode {
                    Mode::Comment(_) => (),
                    Mode::MultilineString {
                        mut content,
                        column,
                        prefix,
                        span,
                    } => {
                        content.push('\n');
                        data.mode = Mode::MultilineString {
                            content,
                            column,
                            prefix,
                            span,
                        };
                    }
                    Mode::String { .. } => {
                        return Err(ErrorKind::UnclosedString.error(data.line, data.column));
                    }
                    _ => {
                        data.mode
                            .finish(&mut data.units.current, data.line, data.column)?;
                        data.mode = Mode::Newline;
                    }
                }
                data.column = 0;
                data.line += 1;
                return Ok(data);
            }
            if let Mode::Comment(column) = data.mode
                && data.column > column
            {
                return Ok(data);
            }
            for &comment_char in &parser.comments {
                if char == comment_char {
                    data.mode
                        .finish(&mut data.units.current, data.line, data.column)?;
                    data.mode = Mode::Comment(data.column);
                    data.column += 1;
                    return Ok(data);
                }
            }
            if let Mode::Newline | Mode::Comment(_) = data.mode
                && parser.indent > 0
                && data.list_stack.is_empty()
            {
                if char == ' ' {
                    data.column += 1;
                    return Ok(data);
                }
                data.mode = Mode::Default;
                let old_level = data.units.stack.len().saturating_sub(1);
                let mut level = 0;
                loop {
                    match data.column.cmp(&(level * parser.indent as usize)) {
                        Ordering::Greater => level += 1,
                        Ordering::Less => {
                            return Err(ErrorKind::Indentation.error(data.line, data.column));
                        }
                        Ordering::Equal => break,
                    }
                }

                let line_span = Span {
                    line: data.line,
                    column: data.column,
                };

                let cmp = level.cmp(&old_level);
                if cmp == Ordering::Less {
                    for _ in 0..(old_level - level) {
                        data.units.pop_stack(parser.unpack_single);
                    }
                }
                if cmp == Ordering::Greater {
                    let additional = level - old_level;
                    if !parser.allow_multi_indent && additional > 1 {
                        return Err(ErrorKind::Indentation.error(data.line, data.column));
                    }
                    for _ in 0..additional {
                        data.units.push_stack(line_span);
                    }
                } else if !data.units.current.is_empty() {
                    data.units.pop_stack(parser.unpack_single);
                    data.units.push_stack(line_span);
                }
            }

            data.column += 1;

            let current_span = Span {
                line: data.line,
                column: data.column - 1,
            };

            let mut default_parsing = false;
            data.mode = match data.mode {
                Mode::String {
                    delimiter,
                    mut content,
                    escape,
                    prefix,
                    span,
                } => {
                    if escape {
                        let escape_char = match char {
                            'n' => '\n',
                            'r' => '\r',
                            't' => '\t',
                            '\\' => '\\',
                            _ => {
                                if char != delimiter {
                                    return Err(ErrorKind::UnescapeableCharacter(char)
                                        .error(data.line, data.column));
                                }
                                char
                            }
                        };
                        content.push(escape_char);

                        Mode::String {
                            delimiter,
                            content,
                            escape: false,
                            prefix,
                            span,
                        }
                    } else if char == '\\' {
                        Mode::String {
                            delimiter,
                            content,
                            escape: true,
                            prefix,
                            span,
                        }
                    } else if char == delimiter {
                        if content.is_empty() {
                            Mode::MultilinePrefix {
                                delimiter,
                                count: 2,
                                prefix,
                                span,
                            }
                        } else {
                            data.units.current.push(string_unit(&content, prefix, span));
                            Mode::Default
                        }
                    } else {
                        content.push(char);
                        Mode::String {
                            delimiter,
                            content,
                            escape,
                            prefix,
                            span,
                        }
                    }
                }

                Mode::MultilinePrefix {
                    delimiter,
                    count,
                    prefix,
                    span,
                } => {
                    if char == delimiter {
                        let count = count + 1;
                        if count == 4 {
                            Mode::MultilineString {
                                content: String::new(),
                                column: data.column,
                                prefix,
                                span,
                            }
                        } else {
                            Mode::MultilinePrefix {
                                delimiter,
                                count,
                                prefix,
                                span,
                            }
                        }
                    } else {
                        if count != 2 {
                            return Err(
                                ErrorKind::MultilinePrefixLength.error(data.line, data.column)
                            );
                        }

                        data.units.current.push(string_unit("", prefix, span));
                        default_parsing = true;
                        Mode::Default
                    }
                }

                Mode::MultilineString {
                    mut content,
                    column,
                    prefix,
                    span,
                } => {
                    if data.column <= column {
                        default_parsing = true;
                    } else {
                        content.push(char);
                    }
                    Mode::MultilineString {
                        content,
                        column,
                        prefix,
                        span,
                    }
                }

                _ => {
                    let mut mode = data.mode;
                    default_parsing = true;
                    for &StringDelimiter { delimiter, prefix } in &parser.strings {
                        if char == delimiter {
                            mode.finish(&mut data.units.current, data.line, data.column)?;
                            mode = Mode::String {
                                delimiter: char,
                                content: String::new(),
                                escape: false,
                                prefix,
                                span: current_span,
                            };
                            default_parsing = false;
                            break;
                        }
                    }
                    mode
                }
            };

            if default_parsing {
                if let Mode::MultilineString { .. } = data.mode
                    && char == ' '
                {
                    return Ok(data);
                }
                for &separator in &parser.separator {
                    if char == separator {
                        data.mode
                            .finish(&mut data.units.current, data.line, data.column)?;
                        data.mode = Mode::Default;
                        data.units.pop_stack(false);
                        data.units.push_stack(current_span);
                        return Ok(data);
                    }
                }
                for &symbol_char in &parser.symbol_characters {
                    if char == symbol_char {
                        data.mode
                            .finish(&mut data.units.current, data.line, data.column)?;
                        let mut name = String::new();
                        name.push(char);
                        data.units.current.push(Symbol(name.into(), current_span));
                        data.mode = Mode::Default;
                        return Ok(data);
                    }
                }

                enum BracketType {
                    Open,
                    Close,
                }

                let mut bracket = None;
                for pair in &parser.brackets {
                    let &BracketPair { left, right, .. } = pair;
                    if char == left {
                        bracket = Some((BracketType::Open, pair));
                    } else if char == right {
                        bracket = Some((BracketType::Close, pair));
                    } else {
                        continue;
                    }
                    break;
                }
                if char == ' ' || bracket.is_some() {
                    data.mode
                        .finish(&mut data.units.current, data.line, data.column)?;
                    data.mode = Mode::Default;
                } else if let Mode::Symbol(name, span) = &mut data.mode {
                    name.push(char);
                    let _ = span;
                } else {
                    let mut name = String::new();
                    name.push(char);
                    data.mode
                        .finish(&mut data.units.current, data.line, data.column)?;
                    data.mode = Mode::Symbol(name, current_span)
                }

                if let Some((bracket_type, pair)) = bracket {
                    match bracket_type {
                        BracketType::Open => {
                            data.units.push_stack(current_span);
                            data.list_stack.push(pair.left);
                        }
                        BracketType::Close => {
                            let Some(bracket) = data.list_stack.pop() else {
                                return Err(
                                    ErrorKind::TooManyBrackets.error(data.line, data.column)
                                );
                            };

                            if bracket != pair.left {
                                return Err(ErrorKind::UnmatchedBrackets(bracket, pair.right)
                                    .error(data.line, data.column));
                            }

                            if let Some(prefix) = pair.prefix {
                                let bracket_span =
                                    data.units.span_stack.last().copied().unwrap_or_default();
                                data.units
                                    .current
                                    .insert(0, Symbol(prefix.into(), bracket_span));
                            }
                            data.units.pop_stack(false);
                        }
                    }
                }
            }

            Ok(data)
        }

        for char in chars {
            data = parse_character(self, char, data)?;
        }
        if !data.list_stack.is_empty() {
            return Err(ErrorKind::UnclosedBracket.error(data.line, data.column));
        }
        if let Mode::String { .. } = data.mode {
            return Err(ErrorKind::UnclosedString.error(data.line, data.column));
        }
        data.mode
            .finish(&mut data.units.current, data.line, data.column)?;
        if data.units.current.is_empty() {
            let Some(next) = data.units.stack.pop() else {
                return Ok(TokenParser::new(data.units.current));
            };

            data.units.current = next;
        } else {
            data.units.pop_stack(self.unpack_single);
        }
        while !data.units.stack.is_empty() {
            data.units.pop_stack(false);
        }

        Ok(TokenParser::new(data.units.current))
    }
}
