use erl_tokenize::tokens::{AtomToken, SymbolToken, VariableToken};
use erl_tokenize::values::Symbol;
use erl_tokenize::{LexicalToken, Position, PositionRange};
use trackable::error::ErrorKindExt;

use super::commons::parts::{BinaryOp, UnaryOp};
use super::patterns;
use super::Literal;
use crate::traits::{Parse, TokenRead};
use crate::{ErrorKind, Parser, Result};

#[derive(Debug, Clone)]
pub enum Pattern {
    Literal(Literal),
    Variable(VariableToken),
    Tuple(Box<patterns::Tuple>),
    Map(Box<patterns::Map>),
    Record(Box<patterns::Record>),
    RecordFieldIndex(Box<patterns::RecordFieldIndex>),
    List(Box<patterns::List>),
    Bits(Box<patterns::Bits>),
    Parenthesized(Box<patterns::Parenthesized>),
    UnaryOpCall(Box<patterns::UnaryOpCall>),
    BinaryOpCall(Box<patterns::BinaryOpCall>),
    Match(Box<patterns::Match>),
}
impl Parse for Pattern {
    fn parse_non_left_recor<T>(parser: &mut Parser<T>) -> Result<Self>
    where
        T: TokenRead,
    {
        let kind = track!(parser.peek(|parser| HeadKind::guess(parser)))?;
        let pattern = match kind {
            HeadKind::Literal => Pattern::Literal(track!(parser.parse())?),
            HeadKind::Variable => Pattern::Variable(track!(parser.parse())?),
            HeadKind::Tuple => Pattern::Tuple(track!(parser.parse())?),
            HeadKind::Map => Pattern::Map(track!(parser.parse())?),
            HeadKind::Record => Pattern::Record(track!(parser.parse())?),
            HeadKind::RecordFieldIndex => Pattern::RecordFieldIndex(track!(parser.parse())?),
            HeadKind::List => Pattern::List(track!(parser.parse())?),
            HeadKind::Bits => Pattern::Bits(track!(parser.parse())?),
            HeadKind::UnaryOpCall => Pattern::UnaryOpCall(track!(parser.parse())?),
            HeadKind::Parenthesized => Pattern::Parenthesized(track!(parser.parse())?),
        };
        Ok(pattern)
    }
    fn parse<T>(parser: &mut Parser<T>) -> Result<Self>
    where
        T: TokenRead,
    {
        let head = track!(Pattern::parse_non_left_recor(parser))?;
        let tail_kind = track!(parser.peek(|parser| TailKind::guess(parser)))?;
        match tail_kind {
            TailKind::BinaryOpCall => Ok(Pattern::BinaryOpCall(track!(parser.parse_tail(head))?)),
            TailKind::Match => Ok(Pattern::Match(track!(parser.parse_tail(head))?)),
            TailKind::None => Ok(head),
        }
    }
}
impl PositionRange for Pattern {
    fn start_position(&self) -> Position {
        match *self {
            Pattern::Literal(ref x) => x.start_position(),
            Pattern::Variable(ref x) => x.start_position(),
            Pattern::Tuple(ref x) => x.start_position(),
            Pattern::Map(ref x) => x.start_position(),
            Pattern::Record(ref x) => x.start_position(),
            Pattern::RecordFieldIndex(ref x) => x.start_position(),
            Pattern::List(ref x) => x.start_position(),
            Pattern::Bits(ref x) => x.start_position(),
            Pattern::Parenthesized(ref x) => x.start_position(),
            Pattern::UnaryOpCall(ref x) => x.start_position(),
            Pattern::BinaryOpCall(ref x) => x.start_position(),
            Pattern::Match(ref x) => x.start_position(),
        }
    }
    fn end_position(&self) -> Position {
        match *self {
            Pattern::Literal(ref x) => x.end_position(),
            Pattern::Variable(ref x) => x.end_position(),
            Pattern::Tuple(ref x) => x.end_position(),
            Pattern::Map(ref x) => x.end_position(),
            Pattern::Record(ref x) => x.end_position(),
            Pattern::RecordFieldIndex(ref x) => x.end_position(),
            Pattern::List(ref x) => x.end_position(),
            Pattern::Bits(ref x) => x.end_position(),
            Pattern::Parenthesized(ref x) => x.end_position(),
            Pattern::UnaryOpCall(ref x) => x.end_position(),
            Pattern::BinaryOpCall(ref x) => x.end_position(),
            Pattern::Match(ref x) => x.end_position(),
        }
    }
}

#[derive(Debug)]
enum HeadKind {
    Literal,
    Variable,
    Tuple,
    Map,
    Record,
    RecordFieldIndex,
    List,
    Bits,
    UnaryOpCall,
    Parenthesized,
}
impl HeadKind {
    fn guess<T: TokenRead>(parser: &mut Parser<T>) -> Result<Self> {
        Ok(match track!(parser.parse())? {
            LexicalToken::Symbol(t) => match t.value() {
                Symbol::OpenBrace => HeadKind::Tuple,
                Symbol::DoubleLeftAngle => HeadKind::Bits,
                Symbol::OpenParen => HeadKind::Parenthesized,
                Symbol::OpenSquare => HeadKind::List,
                Symbol::Sharp => {
                    if parser.parse::<AtomToken>().is_ok() {
                        let next = parser.parse::<SymbolToken>().map(|t| t.value()).ok();
                        if next == Some(Symbol::Dot) {
                            HeadKind::RecordFieldIndex
                        } else {
                            HeadKind::Record
                        }
                    } else {
                        HeadKind::Map
                    }
                }
                _ => track!(UnaryOp::from_token(t.into())
                    .map(|_| HeadKind::UnaryOpCall)
                    .map_err(|e| ErrorKind::UnexpectedToken(e).error()))?,
            },
            LexicalToken::Keyword(t) => track!(UnaryOp::from_token(t.into())
                .map(|_| HeadKind::UnaryOpCall)
                .map_err(|e| ErrorKind::UnexpectedToken(e).error()))?,
            LexicalToken::Variable(_) => HeadKind::Variable,
            _ => HeadKind::Literal,
        })
    }
}

#[derive(Debug)]
enum TailKind {
    BinaryOpCall,
    Match,
    None,
}
impl TailKind {
    fn guess<T: TokenRead>(parser: &mut Parser<T>) -> Result<Self> {
        let is_eos = track!(parser.eos())?;
        if is_eos {
            return Ok(TailKind::None);
        }
        Ok(match track!(parser.parse())? {
            LexicalToken::Symbol(ref t) if t.value() == Symbol::Match => TailKind::Match,
            token => {
                if BinaryOp::from_token(token).is_ok() {
                    TailKind::BinaryOpCall
                } else {
                    TailKind::None
                }
            }
        })
    }
}
