
use type

class Value {
    + type: type:int
    + object_values: ?Map[Value]
    + array_values: ?Array[Value]
    + int_value: type:int (0)
    + float_value: type:float (0.0)
    + string_value: String ("")
    + bool_value: type:bool (false)

    // #doc "Get object value. If missing: return an empty json value"
    + fn get(key: String) Value {
        this.type = type_object
        let vv = this.object_values
        if isset(vv) : vv.get(key) -> val : return val
        let val = Value{ type: type_null }
        this.set(key, val)
        return val
    }

    // #doc "Get object value. If missing: throw an error"
    + fn find(key: String) Value !missing {
        this.type = type_object
        let vv = this.object_values
        if isset(vv) : vv.get(key) -> val : return val
        throw missing
    }

    // #doc "Set object value"
    + fn set(key: String, val: Value) {
        this.type = type_object
        let vv = this.object_values
        if !isset(vv) {
            vv = Map[Value]{}
            this.object_values = vv
        }
        vv.set(key, val)
    }

    // #doc "Check if object has a key"
    + fn has(key: String) type:bool {
        let vv = this.object_values
        if isset(vv) : return vv.has(key)
        return false
    }
    // #doc "Remove object value"
    + fn remove(key: String) {
        let vv = this.object_values
        if isset(vv) : vv.remove(key)
    }

    // #doc "Append value to array"
    + fn append(value: Value) {
        this.type = type_array
        let vv = this.array_values
        if !isset(vv) {
            vv = type:Array[Value]{}
            this.array_values = vv
        }
        vv.append(value)
    }
    // #doc "Prepend value to array"
    + fn prepend(value: Value) {
        this.type = type_array
        let vv = this.array_values
        if !isset(vv) {
            vv = type:Array[Value]{}
            this.array_values = vv
        }
        vv.prepend(value)
    }

    // #doc "Get array value by index"
    + fn get_index(index: uint) Value !range {
        let vv = this.array_values
        if isset(vv) : return vv.get(index) ! throw range
        throw range
    }

    // #doc "Remove array value by index"
    + fn remove_index(index: uint) {
        let vv = this.array_values
        if isset(vv) : vv.remove(index)
    }
    + fn length() uint {
        if this.is_array() {
            let arr = this.array_values
            if isset(arr) : return arr.length
        }
        if this.is_object() {
            let m = this.object_values
            if isset(m) : return m.length
        }
        if this.is_string() {
            return this.string_value.length
        }
        return 0
    }

    //
    + fn string() String {
        if this.is_string() : return this.string_value
        return ""
    }
    + fn to_string() String $to {
        if this.is_string() : return this.string_value
        return this.encode()
    }
    + fn int() type:int $to {
        if this.is_number() : return this.int_value
        return 0
    }
    + fn float() type:float $to {
        if this.is_number() : return this.float_value
        return 0
    }
    + fn bool() type:bool $to {
        if this.is_bool() : return this.bool_value
        return false
    }
    + fn array() Array[Value] $to {
        if this.is_array() {
            let a = this.array_values
            if isset(a) : return a.copy()
        }
        return type:Array[Value]{}
    }
    + fn map() Map[Value] $to {
        if this.is_object() {
            let a = this.object_values
            if isset(a) : return a.copy()
        }
        return Map[Value]{}
    }

    + fn is_null() type:bool {
        return this.type == type_null
    }
    + fn is_string() type:bool {
        return this.type == type_string
    }
    + fn is_bool() type:bool {
        return this.type == type_bool
    }
    + fn is_number() type:bool {
        return this.type == type_number
    }
    + fn is_array() type:bool {
        return this.type == type_array
    }
    + fn is_object() type:bool {
        return this.type == type_object
    }

    // Quick object properties
    + fn set_null(key: String) SELF {
        this.set(key, new_null())
        return this
    }
    + fn set_string(key: String, value: String) SELF {
        this.set(key, new_string(value))
        return this
    }
    + fn set_number_int(key: String, value: type:int) SELF {
        this.set(key, new_int(value))
        return this
    }
    + fn set_number_uint(key: String, value: type:uint) SELF {
        this.set(key, new_int(value.to(type:int)))
        return this
    }
    + fn set_number_float(key: String, value: type:float) SELF {
        this.set(key, new_float(value))
        return this
    }
    + fn set_bool(key: String, value: type:bool) SELF {
        this.set(key, new_bool(value))
        return this
    }
    + fn set_array(key: String, values: Array[Value]) SELF {
        this.set(key, new_array(values))
        return this
    }
    + fn set_object(key: String, values: Map[Value]) SELF {
        this.set(key, new_object(values))
        return this
    }

    //
    + fn encode(pretty: type:bool (false)) String {
        return encode_value(this, pretty)
    }
}
