import { isTypedArray } from "../collection/array/typed/isTypedArray.js";

/**
 *
 * @param {object|number|string|null|array} json
 * @param {function} addToOutput
 */
function stringifyStream(json, addToOutput) {

    /**
     *
     * @param {string} name
     * @param {object|number|string|null|array} value
     */
    function processProperty(name, value) {
        addToOutput('"' + name + '":');
        processValue(value);
    }

    function processNestedObject(o) {
        addToOutput("{");
        let count = 0;
        for (let p in o) {
            if (count++ > 0) {
                addToOutput(",");
            }
            processProperty(p, o[p]);
        }
        addToOutput("}");
    }

    function processArrayObject(o) {
        addToOutput("[");
        const l = o.length;
        if (l > 0) {
            processValue(o[0]);
            for (let i = 1; i < l; i++) {
                addToOutput(",");
                processValue(o[i]);
            }
        }
        addToOutput("]");
    }

    function processValue(o) {
        const type = typeof o;
        switch (type) {
            case "object":
                if (o === null) {
                    addToOutput("null");
                } else if (o instanceof Array || isTypedArray(o)) {
                    processArrayObject(o);
                } else {
                    processNestedObject(o);
                }
                break;
            case "number":
            case "boolean":
            case "string":
                addToOutput(JSON.stringify(o));
                break;
            case "undefined":
                processValue("null");
                break;
            case "symbol":
            case "function":
            default:
                console.error("Failed to stringify value: ", o);
                throw new Error("Can not process object of type " + type);
                break;
        }
    }

    processValue(json);
}

function stringify(json) {
    let result = '';

    function addToOutput(fragment) {
        result += fragment;
    }

    try {
        stringifyStream(json, addToOutput);
    } catch (e) {
        //log result so far and re-throw
        console.error('result so far:', result);
        throw e;
    }
    return result;
}

export {
    stringify,
    stringifyStream
};
