Documentation
use std::collections::HashMap;
use std::ops::Deref;
use std::prelude::v1::Result as StdResult;
use std::rc::Rc;

use indexmap::IndexMap;
use regex::Regex;
use std::fmt::Error as FormatError;
use std::io::Error as IoError;
use thiserror::Error;

use crate::json_value::JsonValue;
use crate::reader::Location;
use crate::regex_cache::{RegexCache, RegexCompile};
use crate::selection::Get;

use regex::Error as RegexError;

#[derive(Debug, Error)]
pub enum ProcessError {
    #[error("{0}")]
    Format(#[from] FormatError),
    #[error("{0}")]
    Io(#[from] IoError),
    #[error("{0}")]
    InvalidInputError(&'static str),
}

#[derive(Default)]
pub struct Titles {
    titles: Vec<Rc<String>>,
}

impl Titles {
    pub fn with_title(&self, title: &Rc<String>) -> Self {
        let mut titles = self.titles.clone();
        titles.push(title.clone());
        Titles { titles }
    }

    pub fn len(&self) -> usize {
        self.titles.len()
    }

    pub fn to_list(&self) -> Vec<Option<JsonValue>> {
        let mut lst = Vec::with_capacity(self.titles.len());
        for str in &self.titles {
            let value = Some(str.deref().clone().into());
            lst.push(value);
        }
        lst
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ContextKey {
    Value(JsonValue),
    Results(Vec<Option<JsonValue>>),
}

#[derive(Clone)]
pub struct InputContext {
    pub start_location: Location,
    pub end_location: Location,
    pub file_index: u64,
    pub index: u64,
}

pub struct Context {
    input: Rc<JsonValue>,
    results: Vec<(Rc<String>, Option<JsonValue>)>,
    parent_inputs: Vec<Rc<JsonValue>>,
    variables: Rc<HashMap<String, JsonValue>>,
    definitions: Rc<HashMap<String, Rc<dyn Get>>>,
    input_context: Option<Rc<InputContext>>,
    regex_cache: RegexCache,
}

impl Context {
    pub fn new_empty() -> Self {
        Context {
            input: Rc::new(JsonValue::Null),
            results: Vec::new(),
            parent_inputs: Vec::new(),
            variables: Rc::new(HashMap::new()),
            definitions: Rc::new(HashMap::new()),
            input_context: None,
            regex_cache: RegexCache::new(0),
        }
    }
    pub fn new_with_no_context(input: JsonValue) -> Self {
        Context {
            input: Rc::new(input),
            results: Vec::new(),
            parent_inputs: Vec::new(),
            variables: Rc::new(HashMap::new()),
            definitions: Rc::new(HashMap::new()),
            input_context: None,
            regex_cache: RegexCache::new(0),
        }
    }
    pub fn new_with_input(
        input: JsonValue,
        start_location: Location,
        end_location: Location,
        file_index: u64,
        index: u64,
        regex_cache: &RegexCache,
    ) -> Self {
        let input_context = InputContext {
            start_location,
            end_location,
            file_index,
            index,
        };
        Context {
            input: Rc::new(input),
            results: Vec::new(),
            parent_inputs: Vec::new(),
            variables: Rc::new(HashMap::new()),
            definitions: Rc::new(HashMap::new()),
            input_context: Some(Rc::new(input_context)),
            regex_cache: regex_cache.clone(),
        }
    }
    pub fn with_inupt(&self, value: JsonValue) -> Self {
        let input = Rc::new(value);
        let mut parent_inputs = Vec::with_capacity(self.parent_inputs.len() + 1);
        parent_inputs.push(self.input.clone());
        for i in &self.parent_inputs {
            parent_inputs.push(i.clone());
        }
        Context {
            input,
            results: Vec::new(),
            parent_inputs,
            variables: self.variables.clone(),
            definitions: self.definitions.clone(),
            input_context: self.input_context.clone(),
            regex_cache: self.regex_cache.clone(),
        }
    }
    pub fn with_result(&self, title: &Rc<String>, result: Option<JsonValue>) -> Self {
        let mut results = self.results.clone();
        results.push((title.clone(), result));
        Context {
            input: self.input().clone(),
            results,
            parent_inputs: Vec::new(),
            variables: self.variables.clone(),
            definitions: self.definitions.clone(),
            input_context: self.input_context.clone(),
            regex_cache: self.regex_cache.clone(),
        }
    }
    pub fn with_variable(&self, name: String, value: JsonValue) -> Self {
        let mut variables = HashMap::with_capacity(self.variables.len() + 1);
        for (k, v) in &*self.variables {
            variables.insert(k.clone(), v.clone());
        }
        variables.insert(name, value);
        Context {
            input: self.input().clone(),
            results: self.results.clone(),
            parent_inputs: Vec::new(),
            variables: Rc::new(variables),
            definitions: self.definitions.clone(),
            input_context: self.input_context.clone(),
            regex_cache: self.regex_cache.clone(),
        }
    }
    pub fn with_variables(&self, variables: &Rc<HashMap<String, JsonValue>>) -> Self {
        Context {
            input: self.input().clone(),
            results: self.results.clone(),
            parent_inputs: Vec::new(),
            variables: variables.clone(),
            definitions: self.definitions.clone(),
            input_context: self.input_context.clone(),
            regex_cache: self.regex_cache.clone(),
        }
    }
    pub fn with_definition(&self, name: String, definition: &Rc<dyn Get>) -> Self {
        let mut definitions = HashMap::with_capacity(self.definitions.len() + 1);
        for (k, d) in &*self.definitions {
            definitions.insert(k.clone(), d.clone());
        }
        definitions.insert(name, definition.clone());
        Context {
            input: self.input().clone(),
            results: self.results.clone(),
            parent_inputs: Vec::new(),
            variables: self.variables.clone(),
            definitions: Rc::new(definitions),
            input_context: self.input_context.clone(),
            regex_cache: self.regex_cache.clone(),
        }
    }
    pub fn with_definitions(&self, definitions: &Rc<HashMap<String, Rc<dyn Get>>>) -> Self {
        Context {
            input: self.input().clone(),
            results: self.results.clone(),
            parent_inputs: Vec::new(),
            variables: self.variables.clone(),
            definitions: definitions.clone(),
            input_context: self.input_context.clone(),
            regex_cache: self.regex_cache.clone(),
        }
    }
    pub fn build(&self) -> JsonValue {
        if self.results.is_empty() {
            self.input().deref().clone()
        } else {
            let mut mp = IndexMap::new();
            for (title, value) in &self.results {
                if let Some(value) = value {
                    mp.insert(title.deref().clone(), value.clone());
                }
            }
            JsonValue::Object(mp)
        }
    }
    pub fn to_list(&self) -> Vec<Option<JsonValue>> {
        self.results.iter().map(|i| i.1.clone()).collect()
    }

    pub fn get_variable_value(&self, name: &String) -> Option<&JsonValue> {
        self.variables.get(name)
    }

    pub fn get_definition(&self, name: &String) -> Option<&Rc<dyn Get>> {
        self.definitions.get(name)
    }

    pub fn get_selected(&self, name: &String) -> Option<JsonValue> {
        for (title, result) in &self.results {
            if &**title == name {
                return result.clone();
            }
        }
        None
    }

    pub fn input(&self) -> &Rc<JsonValue> {
        &self.input
    }

    pub fn parent_input(&self, count: usize) -> &JsonValue {
        if count == 0 {
            self.input()
        } else {
            self.parent_inputs
                .get(count - 1)
                .unwrap_or_else(|| self.input())
        }
    }

    pub fn key(&self) -> ContextKey {
        if self.results.is_empty() {
            ContextKey::Value(self.input().deref().clone())
        } else {
            ContextKey::Results(self.to_list())
        }
    }

    pub fn input_context(&self) -> Option<Rc<InputContext>> {
        self.input_context.clone()
    }
}

impl RegexCompile for Context {
    fn compile_regex(&self, regex: &str) -> Rc<StdResult<Regex, RegexError>> {
        self.regex_cache.compile_regex(regex)
    }
}

#[derive(Debug, PartialEq)]
pub enum ProcessDecision {
    Continue,
    Break,
}

pub type Result<T> = std::result::Result<T, ProcessError>;

pub trait Process {
    fn start(&mut self, titles_so_far: Titles) -> Result<()>;
    fn process(&mut self, context: Context) -> Result<ProcessDecision>;
    fn complete(&mut self) -> Result<()>;
}