use crate::errors::*;

use std::iter::FromIterator;
use std::collections::HashMap;
use crate::hlua::AnyLuaValue;
use serde_json::{self, Value, Number, Map};


pub fn decode(x: &str) -> Result<AnyLuaValue> {
    let v: Value = serde_json::from_str(&x)
                        .context("deserialize failed")?;
    let v: LuaJsonValue = v.into();
    Ok(v.into())
}

pub fn encode(v: AnyLuaValue) -> Result<String> {
    let v: LuaJsonValue = v.into();
    let v: Value = v.into();
    let s = serde_json::to_string(&v)
        .context("Serialize failed")?;
    Ok(s)
}

pub fn lua_array_is_list(array: &[(AnyLuaValue, AnyLuaValue)]) -> bool {
    if !array.is_empty() {
        let first = &array[0];
        if let AnyLuaValue::LuaNumber(_) = first.0 {
            true
        } else {
            false
        }
    } else {
        // true // TODO: this breaks unserialize
        false
    }
}

#[derive(Debug)]
pub enum LuaJsonValue {
    Null,
    Bool(bool),
    Number(Number),
    String(String),
    Array(Vec<LuaJsonValue>),
    Object(HashMap<String, LuaJsonValue>),
}

impl Into<AnyLuaValue> for LuaJsonValue {
    fn into(self) -> AnyLuaValue {
        match self {
            LuaJsonValue::Null => AnyLuaValue::LuaNil,
            LuaJsonValue::Bool(v) => AnyLuaValue::LuaBoolean(v),
            // TODO: not sure if this might fail
            LuaJsonValue::Number(v) => AnyLuaValue::LuaNumber(v.as_f64().unwrap()),
            LuaJsonValue::String(v) => AnyLuaValue::LuaString(v),
            LuaJsonValue::Array(v) => AnyLuaValue::LuaArray(v.into_iter().enumerate()
                .map(|(i, x)| (AnyLuaValue::LuaNumber(i as f64), x.into()))
                .collect()
            ),
            LuaJsonValue::Object(v) => AnyLuaValue::LuaArray(v.into_iter()
                .map(|(k, v)| (AnyLuaValue::LuaString(k), v.into()))
                .collect()
            ),
        }
    }
}

impl From<AnyLuaValue> for LuaJsonValue {
    fn from(x: AnyLuaValue) -> LuaJsonValue {
        match x {
            AnyLuaValue::LuaNil => LuaJsonValue::Null,
            AnyLuaValue::LuaBoolean(v) => LuaJsonValue::Bool(v),
            AnyLuaValue::LuaString(v) => LuaJsonValue::String(v),
            AnyLuaValue::LuaAnyString(v) => LuaJsonValue::Array(v.0.into_iter()
                .map(|x| LuaJsonValue::Number(x.into()))
                .collect()
            ),
            AnyLuaValue::LuaNumber(v) => {
                // this is needed or every number is detected as float
                LuaJsonValue::Number(if v % 1f64 == 0f64 {
                    (v as u64).into()
                } else {
                    Number::from_f64(v).expect("invalid LuaJson::Number")
                })
            },
            AnyLuaValue::LuaArray(v) => {
                if lua_array_is_list(&v) {
                    LuaJsonValue::Array(v.into_iter()
                        .map(|(_, v)| v.into())
                        .collect()
                    )
                } else {
                    LuaJsonValue::Object(v.into_iter()
                        .filter_map(|(k, v)| match k {
                            AnyLuaValue::LuaString(k) => Some((k, v.into())),
                            _ => None,
                        })
                        .collect()
                    )
                }
            },
            AnyLuaValue::LuaOther => LuaJsonValue::Null,
        }
    }
}

impl Into<Value> for LuaJsonValue {
    fn into(self) -> Value {
        match self {
            LuaJsonValue::Null => Value::Null,
            LuaJsonValue::Bool(v) => Value::Bool(v),
            LuaJsonValue::Number(v) => Value::Number(v),
            LuaJsonValue::String(v) => Value::String(v),
            LuaJsonValue::Array(v) => Value::Array(v.into_iter()
                .map(|x| x.into())
                .collect()
            ),
            LuaJsonValue::Object(v) => Value::Object(Map::from_iter(v.into_iter()
                .map(|(k, v)| (k, v.into()))
            )),
        }
    }
}

impl From<Value> for LuaJsonValue {
    fn from(x: Value) -> LuaJsonValue {
        match x {
            Value::Null => LuaJsonValue::Null,
            Value::Bool(v) => LuaJsonValue::Bool(v),
            Value::Number(v) => LuaJsonValue::Number(v),
            Value::String(v) => LuaJsonValue::String(v),
            Value::Array(v) => LuaJsonValue::Array(v.into_iter()
                .map(|x| x.into())
                .collect()
            ),
            Value::Object(v) => LuaJsonValue::Object(HashMap::from_iter(v.into_iter()
                .map(|(k, v)| (k, v.into()))
            )),
        }
    }
}
