import ceylon.language.meta {
    type
}
import ceylon.collection {
    ArrayList
}
"A [[Visitor]] that constructs a [[Value]].
 
 This would usually be used in conjunction with 
 a [[StringParser]]."
by("Tom Bentley")
shared class Builder() satisfies Visitor {
    
    ArrayList<Value> stack = ArrayList<Value>();
    
    //variable Value? current = null;
    variable String? currentKey = null;
    
    "The constructed [[Value]]."
    throws(`class AssertionError`, 
        "The builder has not yet seen enough input to return a fully formed JSON value.")
    shared Value result {
        if (stack.size == 1,
            ! currentKey exists) {
            return stack.pop();
        } else {
            throw AssertionError("currenyKey=``currentKey else "null" ``, stack=``stack``");
        }
    }
    
    void addToCurrent(Value v) {
        value current = stack.last;
        switch(current)
        case (is Object) {
            if (exists ck=currentKey) {
                if (exists old = current.put(ck, v)) {
                    throw AssertionError("duplicate key ``ck``");
                }
                currentKey = null;
            } else {
                "value within object without key"
                assert(false);
            }
        }
        case (is Array) {
            current.add(v);
        }
        case (is Null) {
            
        }
        else {
            throw AssertionError("cannot add value to ``type(current)``");
        }
    }
    
    void push(Value v) {
        if (stack.empty) {
            stack.push(v);
        }
        if (v is Object|Array) {
            stack.push(v);
        }
    }
    
    void pop() {
        stack.pop();
    }
    
    shared actual void onStartObject() {
        Object newObj = Object{};
        addToCurrent(newObj);
        push(newObj);
    }
    shared actual void onKey(String key) {
        this.currentKey = key;
    }
    
    shared actual void onEndObject() {
        pop();
    }
    shared actual void onStartArray() {
        Array newArray = Array();
        addToCurrent(newArray);
        push(newArray);
    }
    
    shared actual void onEndArray() {
        pop();
    }
    shared actual void onNumber(Integer|Float num) {
        addToCurrent(num);
        push(num);
    }
    shared actual void onBoolean(Boolean bool) {
        addToCurrent(bool);
        push(bool);
    }
    shared actual void onNull() {
        addToCurrent(null);
        push(null);
    }
    
    shared actual void onString(String string) {
        addToCurrent(string);
        push(string);
    }
}