use std::collections::HashMap;
use std::iter::Peekable;
use std::str::{Chars,FromStr};

#[derive(Clone,Debug,PartialEq)]
pub enum Adnot {
    Sum(String, Vec<Adnot>),
    Prod(HashMap<String, Adnot>),
    List(Vec<Adnot>),
    Str(String),
    Sym(String),
    Num(i64),
    Dbl(f64),
}

#[derive(Debug)]
pub struct AdnotError {
    message: String,
}

type Stream<'a> = Peekable<Chars<'a>>;

fn fail<T>(s: &str) -> Result<T, AdnotError> {
    Err(AdnotError { message: s.to_string()})
}

fn parse_val(s: &mut Stream) -> Result<Adnot, AdnotError> {
    if let Some(c) = s.next() {
        match c {
            '[' => parse_list(s),
            '(' => parse_sum(s),
            '{' => parse_prod(s),
            '"' => parse_string(s),
            _ if c.is_digit(10) || c == '-' => parse_num(c, s),
            _ if c.is_alphabetic() =>
                Ok(Adnot::Sym(parse_sym(c, s)?)),
            _ => Err(AdnotError { message: format!("Invalid character: {:?}", c) }),
        }
    } else {
        fail("Unexpected end of input")
    }
}

fn parse_list(s: &mut Stream) -> Result<Adnot, AdnotError> {
    let mut vec = vec![];
    loop {
        skip_space(s);
        if let Some(&']') = s.peek() {
            s.next();
            return Ok(Adnot::List(vec));
        } else {
            vec.push(parse_val(s)?);
        }
    }
}

fn parse_sum(s: &mut Stream) -> Result<Adnot, AdnotError> {
    let mut vec = vec![];
    skip_space(s);
    let first = match s.next() {
        Some(c) if c.is_alphabetic() => c,
        Some(_) => { return fail("Expected a tagname character") }
        None => { return fail("Unexpected end of input") }
    };
    let name = parse_sym(first, s)?;
    loop {
        skip_space(s);
        if let Some(&')') = s.peek() {
            s.next();
            return Ok(Adnot::Sum(name, vec));
        } else {
            vec.push(parse_val(s)?);
        }
    }
}

fn parse_prod(s: &mut Stream) -> Result<Adnot, AdnotError> {
    let mut map = HashMap::new();
    loop {
        skip_space(s);
        if let Some(&'}') = s.peek() {
            s.next();
            return Ok(Adnot::Prod(map));
        } else {
            skip_space(s);
            let first = match s.next() {
                Some(c) if c.is_alphabetic() => c,
                Some(_) => { return fail("Expected a tagname character") }
                None => { return fail("Unexpected end of input") }
            };
            let key = parse_sym(first, s)?;
            skip_space(s);
            let val = parse_val(s)?;
            map.insert(key, val);
        }
    }
}

fn parse_string(s: &mut Stream) -> Result<Adnot, AdnotError> {
    let mut chars = Vec::new();
    while let Some(c) = s.next() {
        if c == '"' {
            break;
        } else if c == '\\' {
            match s.next() {
                Some('n') => chars.push('\n'),
                Some('r') => chars.push('\r'),
                Some('t') => chars.push('\t'),
                Some('\'') => chars.push('\''),
                Some('\"') => chars.push('\"'),
                Some('\\') => chars.push('\\'),
                _    => return fail("Invalid escape sequence"),
            }
        } else {
            chars.push(c);
        }
    };
    Ok(Adnot::Str(chars.iter().cloned().collect()))
}

fn parse_num(c: char, s: &mut Stream) -> Result<Adnot, AdnotError> {
    let mut str = vec![c];
    str.extend(s.take_while(|c| c.is_digit(10)));
    let string: String = str.iter().cloned().collect();
    Ok(Adnot::Num(i64::from_str(&string).unwrap()))
}

fn parse_sym(c: char, s: &mut Stream) -> Result<String, AdnotError> {
    let mut chars = vec![c];
    while let Some(&c) = s.peek() {
        if c.is_alphanumeric() || c == '_' {
            chars.push(s.next().unwrap());
        } else {
            break;
        }
    };
    Ok(chars.iter().cloned().collect())
}

fn skip_space(s: &mut Stream) {
    while let Some(&c) = s.peek() {
        match c {
            '#' => { skip_comment(s); }
            _ if c.is_whitespace() => { s.next(); }
            _ => break,
        }
    }
}

fn skip_comment(s: &mut Stream) {
    s.next();
    while let Some(&c) = s.peek() {
        if c == '\n' || c == '\r' {
            s.next();
            return;
        } else {
            s.next();
        }
    }
}

impl Adnot {
    pub fn parse(s: &str) -> Result<Adnot, AdnotError> {
        let mut stream = s.chars().peekable();
        skip_space(&mut stream);
        parse_val(&mut stream)
    }
}
