Documentation
use std::borrow::Cow;

use crate::parser::inner_parser::{Rule, SgfParser};
use crate::{Base, Figure, Move, Player, Point, PointRange, PointText, SgfToolError, Token};

use pest::iterators::{Pair, Pairs};
use pest::Parser;

pub(crate) mod inner_parser {
    use pest_derive::Parser;
    #[derive(Parser)]
    #[grammar = "sgf.pest"]
    pub(crate) struct SgfParser;
}

fn parse_string(mut rules: Pairs<'_, Rule>) -> Result<&'_ str, SgfToolError> {
    if let Some(inner_rule) = rules.next() {
        return Ok(inner_rule.as_str());
    }

    Err(SgfToolError::InvalidString)
}

fn parse_figure(mut rules: Pairs<'_, Rule>) -> Result<Option<Figure<'_>>, SgfToolError> {
    let node = match rules.next() {
        Some(node) => node.as_str(),
        None => return Ok(None),
    };

    if let Some(index) = node.find(':') {
        let text = &node[index + 1..];
        let number = node[0..index]
            .parse::<usize>()
            .map_err(|_| SgfToolError::InvalidNumber)?;
        return Ok(Some(Figure(number, text)));
    }

    Ok(None)
}

fn parse_usize(mut rules: Pairs<'_, Rule>) -> Result<usize, SgfToolError> {
    if let Some(inner_rule) = rules.next() {
        return inner_rule
            .as_str()
            .parse::<usize>()
            .map_err(|_| SgfToolError::InvalidNumber);
    }

    Err(SgfToolError::InvalidNumber)
}

fn parse_float(mut rules: Pairs<'_, Rule>) -> Result<f32, SgfToolError> {
    if let Some(inner_rule) = rules.next() {
        return inner_rule
            .as_str()
            .parse::<f32>()
            .map_err(|_| SgfToolError::InvalidFloat);
    }

    Err(SgfToolError::InvalidFloat)
}

fn parse_player(mut rules: Pairs<'_, Rule>) -> Result<Player, SgfToolError> {
    if let Some(inner_rule) = rules.next() {
        return Ok(match &inner_rule.as_str().to_uppercase()[..] {
            "B" => Player::Black,
            "W" => Player::White,
            _ => return Err(SgfToolError::PlayerInformationNotValid),
        });
    }

    Err(SgfToolError::InvalidNumber)
}

fn parse_stones(rules: Pairs<'_, Rule>) -> Result<Vec<Point<'_>>, SgfToolError> {
    let mut stones = Vec::new();

    for rule in rules {
        stones.push(Point(rule.as_str()));
    }

    Ok(stones)
}

fn parse_stone_texts(rules: Pairs<'_, Rule>) -> Result<Vec<PointText<'_>>, SgfToolError> {
    let mut stone_texts = Vec::new();

    for rule in rules {
        if let Some(index) = rule.as_str().find(':') {
            let stone = Point(&rule.as_str()[index + 1..]);
            let text = &rule.as_str()[0..index];
            stone_texts.push(PointText(stone, text));
        }
    }

    Ok(stone_texts)
}

fn parse_move(mut rules: Pairs<'_, Rule>) -> Result<Move<'_>, SgfToolError> {
    if let Some(inner_rule) = rules.next() {
        return Ok(Move::Move(Point(inner_rule.as_str())));
    }
    Ok(Move::Pass)
}

fn parse_point_range(mut rules: Pairs<'_, Rule>) -> Result<PointRange<'_>, SgfToolError> {
    if let Some(inner_rule) = rules.next() {
        let stones = inner_rule.as_str().split(':').collect::<Vec<_>>();
        return Ok(PointRange(Point(stones[0]), Point(stones[1])));
    }

    Err(SgfToolError::PointInformationNotValid)
}

fn parse_point_ranges(rules: Pairs<'_, Rule>) -> Result<Vec<PointRange<'_>>, SgfToolError> {
    let mut ranges = Vec::new();
    for rule in rules {
        let stones = rule.as_str().split(':').collect::<Vec<_>>();
        ranges.push(PointRange(Point(stones[0]), Point(stones[1])));
    }
    Ok(ranges)
}

fn parse_node(pair: Pair<'_, Rule>) -> Result<Token<'_>, SgfToolError> {
    let mut inner_rules = pair.into_inner();

    if let Some(rule) = inner_rules.next() {
        if let Rule::node_type = rule.as_rule() {
            rule.as_str();
            let result = match &rule.as_str().to_uppercase()[..] {
                "AP" => Token::Application(parse_string(inner_rules)?),
                "C" => Token::Comment(parse_string(inner_rules)?),
                "CP" => Token::Copyright(parse_string(inner_rules)?),
                "PB" => Token::BlackName(parse_string(inner_rules)?),
                "PW" => Token::WhiteName(parse_string(inner_rules)?),
                "BT" => Token::BlackTeam(parse_string(inner_rules)?),
                "WT" => Token::WhiteTeam(parse_string(inner_rules)?),
                "FF" => Token::FileFormat(parse_usize(inner_rules)?),
                "GM" => Token::GameType(parse_usize(inner_rules)?),
                "CA" => Token::Charset(parse_string(inner_rules)?),
                "ST" => Token::VariationShown(parse_usize(inner_rules)?),
                "PL" => Token::WhoseTurn(parse_player(inner_rules)?),
                "AB" => Token::BlackStones(parse_stones(inner_rules)?),
                "AW" => Token::WhiteStones(parse_stones(inner_rules)?),
                "SO" => Token::Source(parse_string(inner_rules)?),
                "GN" => Token::GameName(parse_string(inner_rules)?),
                "N" => Token::NodeName(parse_string(inner_rules)?),
                "B" => Token::BlackMove(parse_move(inner_rules)?),
                "W" => Token::WhiteMove(parse_move(inner_rules)?),
                "RU" => Token::Rule(parse_string(inner_rules)?),
                "KM" => Token::Komi(parse_float(inner_rules)?),
                "AR" => Token::DrawArrow(parse_point_range(inner_rules)?),
                "CR" => Token::DrawCircle(parse_stones(inner_rules)?),
                "DD" => Token::GreyOut(parse_stones(inner_rules)?),
                "MA" => Token::MarkX(parse_stones(inner_rules)?),
                "SQ" => Token::DrawSquare(parse_stones(inner_rules)?),
                "TR" => Token::DrawTriangle(parse_stones(inner_rules)?),
                "AN" => Token::PersonWhoProvidesAnnotations(parse_string(inner_rules)?),
                "BR" => Token::BlackPlayerRank(parse_string(inner_rules)?),
                "WR" => Token::WhitePlayerRank(parse_string(inner_rules)?),
                "HA" => Token::Handicap(parse_usize(inner_rules)?),
                "RE" => Token::Result(parse_string(inner_rules)?),
                "FG" => Token::Figure(parse_figure(inner_rules)?),
                "PM" => Token::Printing(parse_usize(inner_rules)?),
                "TM" => Token::TimeLimit(parse_usize(inner_rules)?),
                "DT" => Token::Date(parse_string(inner_rules)?),
                "EV" => Token::Event(parse_string(inner_rules)?),
                "LB" => Token::PointText(parse_stone_texts(inner_rules)?),
                "RO" => Token::Round(parse_string(inner_rules)?),
                "US" => Token::SGFCreator(parse_string(inner_rules)?),
                "VW" => Token::ViewOnly(parse_point_ranges(inner_rules)?),
                "MN" => Token::MoveNumber(parse_usize(inner_rules)?),
                "SZ" => {
                    let size = parse_usize(inner_rules)?;
                    Token::BoardSize(size, size)
                }
                _ => {
                    debug_assert!(false, "Unknown rule: {:?}", rule);
                    Token::Unknown(rule.as_str())
                }
            };
            return Ok(result);
        }
    }

    Err(SgfToolError::NodeInformationNotValid)
}

fn parse_rule(pair: Pair<'_, Rule>) -> Result<Token<'_>, SgfToolError> {
    match pair.as_rule() {
        Rule::node => parse_node(pair),
        Rule::object => {
            let mut base = Base::default();

            for pair in pair.into_inner() {
                base.tokens.push(Cow::Owned(parse_rule(pair)?));
            }
            Ok(Token::Variation(base))
        }
        _ => Err(SgfToolError::ParseFailed),
    }
}

fn parse_pair(pair: Pair<'_, Rule>) -> Result<Base<'_>, SgfToolError> {
    let mut base = Base { tokens: Vec::new() };

    for inner_pair in pair.into_inner() {
        base.tokens.push(Cow::Owned(parse_rule(inner_pair)?));
    }

    Ok(base)
}

pub fn parse(text: &str) -> Result<Base<'_>, SgfToolError> {
    let pairs = SgfParser::parse(Rule::file, text).map_err(|_| SgfToolError::SyntaxIssue)?;

    if let Some(object) = pairs.into_iter().next() {
        return parse_pair(object);
    }

    Err(SgfToolError::RootObjectNotFound)
}