var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
function isJSONNull(json) {
    return typeof json === "undefined";
}
function isJSONList(json) {
    return json instanceof Array;
}
function isJSONDictionary(json) {
    return typeof json === "object";
}
function isJSONIdentifier(json) {
    return isJSONKey(json) && canBeIdentifier(json.toString());
}
function isJSONStringBlock(json) {
    return isJSONKey(json) && !canBeIdentifier(json.toString());
}
function isJSONKey(json) {
    return typeof json === "number" || typeof json === "string";
}
function canBeIdentifier(text) {
    return /^[0-9a-zA-Z][0-9a-zA-Z_]*$/.test(text);
}
function makeKey(space, json) {
    var text = json.toString();
    if (canBeIdentifier(text)) {
        return new Identifier(space, text);
    }
    else {
        return new StringBlock(space, text);
    }
}
function isKVPDictionary(kvp) {
    return isDictionary(kvp.value);
}
exports.isKVPDictionary = isKVPDictionary;
function isDictionary(node) {
    return node.kind === "Dictionary";
}
exports.isDictionary = isDictionary;
function isList(node) {
    return node.kind === "List";
}
exports.isList = isList;
function isIdentifier(node) {
    return node.kind === "Identifier";
}
exports.isIdentifier = isIdentifier;
function isStringBlock(node) {
    return node.kind === "StringBlock";
}
exports.isStringBlock = isStringBlock;
function isNull(node) {
    return node.kind === "Null";
}
exports.isNull = isNull;
var Node = (function () {
    function Node() {
        var children = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            children[_i] = arguments[_i];
        }
        var _this = this;
        children.forEach(function (child) { return child.parent = _this; });
    }
    Object.defineProperty(Node.prototype, "kvps", {
        /**
         * If the node is Dictionary, returns array with its KeyValuePair items. Returns empty array otherwise.
         */
        get: function () { return []; },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Node.prototype, "items", {
        /**
         * If the node is List, returns array with its Value nodes. Returns empty array otherwise.
         */
        get: function () { return []; },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Node.prototype, "json", {
        /**
         * Returns JSON object matching the AST.
         */
        get: function () { return undefined; },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Node.prototype, "indent", {
        /**
         * Get the indentation that should be used for the children of this Node.
         * For example the indent property of a List will be used when new items are added, and applied to preserve good formatting.
         */
        get: function () { return this.parent ? this.parent.indent : ""; },
        enumerable: true,
        configurable: true
    });
    Node.prototype.get = function (key) {
        return Null.instance;
    };
    return Node;
}());
exports.Node = Node;
var Null = (function (_super) {
    __extends(Null, _super);
    function Null() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return Null;
}(Node));
Null.instance = new Null();
exports.Null = Null;
var Document = (function (_super) {
    __extends(Document, _super);
    function Document(root, _s1) {
        var _this = _super.call(this, root, _s1) || this;
        _this.root = root;
        _this._s1 = _s1;
        return _this;
    }
    Object.defineProperty(Document.prototype, "json", {
        get: function () {
            return this.root.json;
        },
        enumerable: true,
        configurable: true
    });
    Document.prototype.get = function (key) {
        return this.root.get(key);
    };
    Document.prototype.forEach = function (cb) {
        this.root.kvps.forEach(cb);
    };
    Document.prototype.toString = function () {
        return "// !$*UTF8*$!" + this.root + this._s1;
    };
    return Document;
}(Node));
exports.Document = Document;
Document.prototype.kind = "Document";
var Dictionary = (function (_super) {
    __extends(Dictionary, _super);
    function Dictionary(_s1, _content, _s2) {
        var _this = _super.apply(this, [_s1].concat(_content, [_s2])) || this;
        _this._s1 = _s1;
        _this._content = _content;
        _this._s2 = _s2;
        return _this;
    }
    Object.defineProperty(Dictionary.prototype, "indent", {
        get: function () {
            return this.parent.indent + "\t";
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Dictionary.prototype, "kvps", {
        get: function () {
            return this._content;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Dictionary.prototype, "json", {
        get: function () {
            return this._content.reduce(function (acc, kvp) {
                acc[kvp.key.json] = kvp.value.json;
                return acc;
            }, {});
        },
        enumerable: true,
        configurable: true
    });
    Dictionary.prototype.get = function (key) {
        var kvp = this.kvps.find(function (kvp) {
            return kvp.key.json == key;
        });
        return kvp ? kvp.value : Null.instance;
    };
    /**
     * Perform "RFC 7386 - JSON Merge Patch" on the json version of the AST
     * and tries to propagate the changes back to the AST.
     *
     * A Node can not change its own type based on the merge.
     * Arrays are completely overwritten.
     */
    Dictionary.prototype.patch = function (json) {
        for (var key in json) {
            var value = json[key];
            var current = this.get(key);
            if (isJSONNull(value)) {
                this.delete(key);
            }
            else if (isJSONList(value) && isList(current)) {
                // TODO:
                throw new Error("Not implemented setting List from Array");
            }
            else if (isJSONDictionary(value) && isDictionary(current)) {
                current.patch(value);
            }
            else if (isJSONIdentifier(value) && isIdentifier(current)) {
                current.text = value.toString();
            }
            else if (isJSONStringBlock(value) && isStringBlock(current)) {
                current.text = value.toString();
            }
            else {
                if (isJSONKey(value)) {
                    var astValue = makeKey(new Space([new WhiteSpace(" ")]), value);
                    this.set(key, astValue);
                }
                else if (isJSONDictionary(value)) {
                    var astValue = new Dictionary(new Space([new WhiteSpace(" ")]), [], new Space([new WhiteSpace("\n" + this.indent)]));
                    this.set(key, astValue);
                    astValue.patch(value);
                }
                else if (isJSONList(value)) {
                    // TODO:
                    throw new Error("Not implemented making dictionary from json Array");
                }
                else {
                    // TODO:
                    throw new Error("Not implemented making ast from json " + value);
                }
            }
        }
    };
    Dictionary.prototype.set = function (key, value) {
        var kvp = this._content.find(function (kvp) { return kvp.key.text === key; });
        if (kvp) {
            kvp.value = value;
        }
        else {
            var space = new Space([new WhiteSpace(this.indent ? "\n" + this.indent : " ")]);
            kvp = new KeyValuePair(makeKey(space, key), new Space([new WhiteSpace(" ")]), value, new Space([]));
            var index = this._content.findIndex(function (p) { return p.key.text > key; });
            if (index === -1) {
                this._content.push(kvp);
            }
            else {
                this._content.splice(index, 0, kvp);
            }
            kvp.parent = this;
        }
    };
    Dictionary.prototype.delete = function (key) {
        for (var i = this._content.length - 1; i >= 0; i--) {
            if (this._content[i].key.text === key) {
                this._content.splice(i, 1);
                return;
            }
        }
    };
    Dictionary.prototype.toString = function () {
        return this._s1 + "{" + this._content.join("") + this._s2 + "}";
    };
    return Dictionary;
}(Node));
exports.Dictionary = Dictionary;
Dictionary.prototype.kind = "Dictionary";
var KeyValuePair = (function (_super) {
    __extends(KeyValuePair, _super);
    function KeyValuePair(_key, _s2, value, _s3) {
        var _this = _super.call(this, _key, _s2, value, _s3) || this;
        _this._key = _key;
        _this._s2 = _s2;
        _this.value = value;
        _this._s3 = _s3;
        return _this;
    }
    Object.defineProperty(KeyValuePair.prototype, "key", {
        get: function () { return this._key; },
        enumerable: true,
        configurable: true
    });
    KeyValuePair.prototype.toString = function () {
        return "" + this._key + this._s2 + "=" + this.value + this._s3 + ";";
    };
    KeyValuePair.prototype.get = function (key) {
        return this.value ? this.value.get(key) : Null.instance;
    };
    return KeyValuePair;
}(Node));
exports.KeyValuePair = KeyValuePair;
KeyValuePair.prototype.kind = "KeyValuePair";
var StringBlock = (function (_super) {
    __extends(StringBlock, _super);
    function StringBlock(_s1, text) {
        var _this = 
        // TODO: Escape the text.
        _super.call(this, _s1) || this;
        _this._s1 = _s1;
        _this.text = text;
        return _this;
    }
    Object.defineProperty(StringBlock.prototype, "json", {
        get: function () { return this.text; },
        enumerable: true,
        configurable: true
    });
    StringBlock.prototype.toString = function () {
        // TODO: Unescape the text.
        return this._s1 + '"' + this.text + '"';
    };
    return StringBlock;
}(Node));
exports.StringBlock = StringBlock;
StringBlock.prototype.kind = "StringBlock";
var CommentBlock = (function (_super) {
    __extends(CommentBlock, _super);
    function CommentBlock(_text) {
        var _this = _super.call(this) || this;
        _this._text = _text;
        return _this;
    }
    Object.defineProperty(CommentBlock.prototype, "text", {
        get: function () { return this._text; },
        enumerable: true,
        configurable: true
    });
    CommentBlock.prototype.toString = function () {
        return "/*" + this._text + "*/";
    };
    return CommentBlock;
}(Node));
exports.CommentBlock = CommentBlock;
CommentBlock.prototype.kind = "CommentBlock";
var List = (function (_super) {
    __extends(List, _super);
    function List(_s1, _content, _s2) {
        var _this = _super.apply(this, [_s1].concat(List.flatten(_content), [_s2])) || this;
        _this._s1 = _s1;
        _this._content = _content;
        _this._s2 = _s2;
        return _this;
    }
    Object.defineProperty(List.prototype, "indent", {
        get: function () {
            return this.parent.indent + "\t";
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(List.prototype, "items", {
        get: function () {
            return this._content.map(function (e) { return e[0]; });
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(List.prototype, "json", {
        get: function () {
            return this._content.map(function (e) { return e[0].json; });
        },
        enumerable: true,
        configurable: true
    });
    List.prototype.toString = function () {
        return this._s1 + "(" + this._content.map(function (l) { return l.join(""); }).join("") + this._s2 + ")";
    };
    List.flatten = function (arr) {
        return arr.reduce(function (acc, child) {
            acc.push(child[0]);
            acc.push(child[1]);
            return acc;
        }, []);
    };
    return List;
}(Node));
exports.List = List;
List.prototype.kind = "List";
var Identifier = (function (_super) {
    __extends(Identifier, _super);
    function Identifier(_s1, text) {
        var _this = _super.call(this, _s1) || this;
        _this._s1 = _s1;
        _this.text = text;
        return _this;
    }
    Object.defineProperty(Identifier.prototype, "json", {
        get: function () { return this.text; },
        enumerable: true,
        configurable: true
    });
    Identifier.prototype.toString = function () { return this._s1 + this.text; };
    return Identifier;
}(Node));
exports.Identifier = Identifier;
Identifier.prototype.kind = "Identifier";
var WhiteSpace = (function (_super) {
    __extends(WhiteSpace, _super);
    function WhiteSpace(text) {
        var _this = 
        // TODO: Unescape the text
        _super.call(this) || this;
        _this.text = text;
        return _this;
    }
    WhiteSpace.prototype.toString = function () {
        // TODO: Escape the text
        return this.text;
    };
    return WhiteSpace;
}(Node));
exports.WhiteSpace = WhiteSpace;
WhiteSpace.prototype.kind = "WhiteSpace";
var Space = (function (_super) {
    __extends(Space, _super);
    function Space(_content) {
        var _this = _super.apply(this, _content) || this;
        _this._content = _content;
        return _this;
    }
    Space.prototype.toString = function () {
        return this._content.join("");
    };
    return Space;
}(Node));
exports.Space = Space;
Space.prototype.kind = "Space";
//# sourceMappingURL=ast.js.map