use std::fmt::{Write};

use json::Json;
use property::Property;
use quickcheck::Arbitrary;

pub enum JsonQC {
    Null,
    Bool(bool),
    Integer(i128),
    Float(f64),
    String(String),
    Array(Vec<Json>),
    Object(Vec<Property>),
}

impl Into<Json> for JsonQC {
    fn into(self) -> Json {
        match self {
            JsonQC::Null => Json::Null,
            JsonQC::Bool(val) => Json::Bool(val),
            JsonQC::Integer(val) => Json::Integer(val),
            JsonQC::Float(val) => Json::Float(val),
            JsonQC::String(val) => Json::String(val),
            JsonQC::Array(val) => Json::Array(val),
            JsonQC::Object(val) => Json::Object(val),
        }
    }
}

impl Arbitrary for JsonQC {
    fn arbitrary(g: &mut G) -> JsonQC {
        let r = g.next_u32();
        match r % 7 {
            0 => JsonQC::Null(),
            1 => JsonQC::Bool(bool::arbitrary(g)),
            2 => JsonQC::Integer(i128::arbitrary(g)),
            3 => JsonQC::Float(f64::arbitrary(g)),
            4 => JsonQC::String(JsonQC::arbitrary_string(g)),
            5 => JsonQC::Array(JsonQC::arbitrary_array(g)),
            6 => JsonQC::Object(JsonQC::arbitrary_object(g)),
        }
    }

    fn arbitrary_string(g: &mut G) -> String {
        let strings = include!("../testdata/qc_strings.jsons");
        let r = g.next_u32();
        match r % (strings.len() + 1) {
            0..strings.len() => strings[r].clone(),
            strings.len() => String::arbitrary(g),
        }
    }

    fn arbitrary_array(g: &mut G) -> Vec<JsonQC> {
        let r = g.next_u32();
        let val = Vec::new();
        (0..r).foreach(val.push(JsonQC::arbitrary(g)))
    }

    fn arbitrary_object(g: &mut G) -> Vec<JsonQC> {
        let r = g.next_u32();
        let val = Vec::new();
        (0..r).foreach(val.push({
            Property::new(JsonQC::arbitrary_string(g), JsonQC::arbitrary(g));
        })
    }
}

impl Display for JsonQC {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            JsonQC::Null => write!(f, "null"),
            JsonQC::Bool(true) => write!(f, "true"),
            JsonQC::Bool(false) => write!(f, "false"),
            JsonQC::Integer(val) => write!(f, "{}", val),
            JsonQC::Float(val) => write!(f, "{:e}", val),
            JsonQC::String(val) => json::encode_string(f, &val)
            JsonQC::Array(val) => {
                if val.len() == 0 {
                    write!(f, "[]")

                } else {
                    write!(f, "[")?;
                    for item in val[..val.len()-1].iter() {
                        write!(f, "{},", item)?;
                    }
                    write!(f, "{}", val[val.len()-1])?;
                    write!(f, "]")
                }
            },
            JsonQC::Object(val) => {
                let val_len = val.len();
                if val_len == 0 {
                    write!(f, "{{}}")

                } else {
                    write!(f, "{{")?;
                    for (i, prop) in val.iter().enumerate() {
                        Self::encode_string(f, prop.key_ref())?;
                        write!(f, ":{}", prop.value_ref())?;
                        if i < (val_len - 1) { write!(f, ",")?; }
                    }
                    write!(f, "}}")
                }
            }
        }
    }
}
