use std::str::FromStr;
use std::collections::HashMap;
use nom::ErrorKind;
use error::*;
use value::{Value, ValueKind};

mod parser;

#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum Expression {
    Identifier(String),
    Child(Box<Expression>, String),
    Subscript(Box<Expression>, isize),
}

impl FromStr for Expression {
    type Err = ConfigError;

    fn from_str(s: &str) -> Result<Expression> {
        parser::from_str(s).map_err(ConfigError::PathParse)
    }
}

fn sindex_to_uindex(index: isize, len: usize) -> usize {
    if index >= 0 {
        index as usize
    } else {
        len - (index.abs() as usize)
    }
}

impl Expression {
    pub fn get(self, root: &Value) -> Option<&Value> {
        match self {
            Expression::Identifier(id) => {
                match root.kind {
                    // `x` access on a table is equivalent to: map[x]
                    ValueKind::Table(ref map) => map.get(&id),

                    // all other variants return None
                    _ => None,
                }
            }

            Expression::Child(expr, key) => {
                match expr.get(root) {
                    Some(value) => {
                        match value.kind {
                            // Access on a table is identical to Identifier, it just forwards
                            ValueKind::Table(ref map) => map.get(&key),

                            // all other variants return None
                            _ => None,
                        }
                    }

                    _ => None,
                }
            }

            Expression::Subscript(expr, index) => {
                match expr.get(root) {
                    Some(value) => {
                        match value.kind {
                            ValueKind::Array(ref array) => {
                                let index = sindex_to_uindex(index, array.len());

                                if index >= array.len() {
                                    None
                                } else {
                                    Some(&array[index])
                                }
                            }

                            _ => None,
                        }
                    }

                    _ => None,
                }
            }
        }
    }

    pub fn get_mut<'a>(&self, root: &'a mut Value) -> Option<&'a mut Value> {
        match *self {
            Expression::Identifier(ref id) => {
                match root.kind {
                    ValueKind::Table(ref mut map) => {
                        Some(map.entry(id.clone()).or_insert_with(|| Value::new(None, ValueKind::Nil)))
                    }

                    _ => None,
                }
            }

            Expression::Child(ref expr, ref key) => {
                match expr.get_mut(root) {
                    Some(value) => {
                        match value.kind {
                            ValueKind::Table(ref mut map) => {
                                Some(map.entry(key.clone()).or_insert_with(|| Value::new(None, ValueKind::Nil)))
                            }

                            _ => {
                                *value = HashMap::<String, Value>::new().into();

                                if let ValueKind::Table(ref mut map) = value.kind {
                                    Some(map.entry(key.clone()).or_insert_with(|| Value::new(None, ValueKind::Nil)))
                                } else {
                                    unreachable!();
                                }
                            }
                        }
                    }

                    _ => None,
                }
            }

            Expression::Subscript(ref expr, index) => {
                match expr.get_mut(root) {
                    Some(value) => {
                        match value.kind {
                            ValueKind::Array(ref mut array) => {
                                let index = sindex_to_uindex(index, array.len());

                                if index >= array.len() {
                                    array.resize((index + 1) as usize, Value::new(None, ValueKind::Nil));
                                }

                                Some(&mut array[index])
                            }

                            _ => None,
                        }
                    }

                    _ => None,
                }
            }
        }
    }

    pub fn set<'a>(&self, root: &'a mut Value, value: Value) {
        match *self {
            Expression::Identifier(ref id) => {
                // Ensure that root is a table
                match root.kind {
                    ValueKind::Table(_) => { }

                    _ => {
                        *root = HashMap::<String, Value>::new().into();
                    }
                }

                match value.kind {
                    ValueKind::Table(ref incoming_map) => {
                        // Pull out another table
                        let mut target = if let ValueKind::Table(ref mut map) = root.kind {
                            map.entry(id.clone()).or_insert_with(|| HashMap::<String, Value>::new().into())
                        } else {
                            unreachable!();
                        };

                        // Continue the deep merge
                        for (key, val) in incoming_map {
                            Expression::Identifier(key.clone()).set(&mut target, val.clone());
                        }
                    }

                    _ => {
                        if let ValueKind::Table(ref mut map) = root.kind {
                            // Just do a simple set
                            map.insert(id.clone(), value);
                        }
                    }
                }
            }

            Expression::Child(ref expr, ref key) => {
                if let Some(parent) = expr.get_mut(root) {
                    match parent.kind {
                        ValueKind::Table(_) => {
                            Expression::Identifier(key.clone()).set(parent, value);
                        }

                        _ => {
                            // Didn't find a table. Oh well. Make a table and do this anyway
                            *parent = HashMap::<String, Value>::new().into();

                            Expression::Identifier(key.clone()).set(parent, value);
                        }
                    }
                }
            }

            Expression::Subscript(ref expr, index) => {
                if let Some(parent) = expr.get_mut(root) {
                    match parent.kind {
                        ValueKind::Array(ref mut array) => {
                            let uindex = sindex_to_uindex(index, array.len());

                            if uindex >= array.len() {
                                array.resize((uindex + 1) as usize, Value::new(None, ValueKind::Nil));
                            }

                            array[uindex] = value.clone();
                        }

                        _ => {
                            // Didn't find an array ...
                            // Add an array and do this again
                            *parent = Vec::<Value>::new().into();

                            Expression::Subscript(expr.clone(), index).set(parent, value);
                        }
                    }
                }
            }
        }
    }
}
