use crate::err::{SJFLError, Span};
use crate::value::Value;
use super::{Parse, skip_whitespaces_and_comments, Session};

#[derive(Clone, Eq, Debug, Hash)]
pub struct Table {
    pub data: Vec<(Box<Value>, Box<Value>)>
}

impl Parse for Table {

    fn parse(s: &[u8], mut ind: usize, session: &mut Session) -> Result<(Self, usize), SJFLError> {
        let mut data = Vec::new();

        if s[ind] != b'{' {
            return Err(SJFLError::UnexpectedChar(s[ind], Span::from_index(ind)));
        }

        ind += 1;

        loop {
            ind = skip_whitespaces_and_comments(s, ind)?;

            if s[ind] == b'}' {
                break;
            }

            let (key, new_ind) = Value::parse(s, ind, session)?;
            ind = new_ind;

            ind = skip_whitespaces_and_comments(s, ind)?;

            if s[ind] != b':' {
                return Err(SJFLError::UnexpectedChar(s[ind], Span::from_index(ind)));
            }

            ind = skip_whitespaces_and_comments(s, ind + 1)?;

            let (value, new_ind) = Value::parse(s, ind, session)?;
            ind = new_ind;

            ind = skip_whitespaces_and_comments(s, ind)?;

            data.push((Box::new(key), Box::new(value)));

            if s[ind] == b',' {
                ind += 1;
            }

            else if s[ind] == b'}' {
                break;
            }

            else {
                return Err(SJFLError::UnexpectedChar(s[ind], Span::from_index(ind)));
            }

        }

        Ok((Table { data }, ind + 1))
    }

}

// it needs a special kind of `Eq` implementation: two tables with the same elements are the same tables, regardless of the order of the elements
impl PartialEq for Table {

    fn eq(&self, other: &Table) -> bool {
        self.data.len() == other.data.len() && {
            // TODO: better implementation
            let mut s = self.data.clone();
            let mut o = other.data.clone();

            'outer: while let Some(e) = s.pop() {

                for i in 0..o.len() {

                    if e == o[i] {
                        o.swap_remove(i);
                        continue 'outer;
                    }

                }

                return false;
            }

            true
        }
    }

}

#[cfg(test)]
mod tests {
    use crate::{evaluate, Session};

    #[test]
    fn eq_test() {
        let mut session = Session::new();
        let a = evaluate("{1: 2, 3: 4,}", &mut session).unwrap();
        let b = evaluate("{3: 4, 1: 2,}", &mut session).unwrap();

        assert_eq!(a, b);
    }

}