"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var jsyaml = require("../jsyaml/jsyaml2lowLevel");
var defs = require("raml-definition-system");
var ll = require("../lowLevelAST");
var _ = require("underscore");
var yaml = require("yaml-ast-parser");
var hlimpl = require("../highLevelImpl");
var universes = require("../tools/universe");
var universeHelpers = require("../tools/universeHelpers");
var services = defs;
var linter = require("./linter");
var messageRegistry = require("../../../resources/errorMessages");
function findLastAttributeIndex(n) {
    var last = -1;
    var children = n.lowLevel().children();
    for (var i = 0; i < children.length; i++) {
        var node = children[i];
        if (!node.isMapping())
            continue;
        var name = node.asMapping().key.value;
        var property = n.definition().property(name);
        if (!property)
            continue;
        if (property.isFromParentValue() || property.range().hasValueTypeInHierarchy()) {
            last = i;
        }
    }
    //console.log('last attr index1: ' + last);
    return last;
}
function findInsertionPointLowLevel(node, llnode, property, attr) {
    //console.log('LL find insertion: node is attr: ' + attr);
    var insertionPoint = null;
    var embed = property && property.getAdapter(services.RAMLPropertyService).isEmbedMap();
    if (embed && _.find(node.lowLevel().children(), function (x) { return x.key() == property.nameId(); })) {
        embed = false;
    }
    if (attr || embed) {
        var last = findLastAttribute(node);
        if (!last) {
            //insertionPoint = new jsyaml.InsertionPoint(jsyaml.InsertionPointType.START);
            insertionPoint = jsyaml.InsertionPoint.atStart();
        }
        else {
            insertionPoint = last;
        }
        //insertionPoint.show('Insertion');
    }
    return insertionPoint;
}
function asMapping(lowLevelNode) {
    var original = lowLevelNode;
    while (original.originalNode && original.originalNode()) {
        original = original.originalNode();
    }
    return original.asMapping();
}
function findInsertionPoint(where, node) {
    //console.log('find insertion point for node (HL): ' + node.property().name() + '; attr: ' + node.isAttr());
    //console.log('node1: ' + node.lowLevel().text());
    //always insert attributes at start
    if (!where.isStub()) {
        where.clearChildrenCache();
    }
    var ch = where.children();
    var embed = node.property() && node.property().getAdapter(services.RAMLPropertyService).isEmbedMap();
    if (embed && _.find(where.lowLevel().children(), function (x) { return x.key() == node.property().nameId(); })) {
        embed = false;
    }
    var uh = universeHelpers;
    if ((node.isAttr() || embed) && !(universeHelpers.isMethodType(where.definition()) && node.property && universeHelpers.isIsProperty(node.property()))) {
        var toRet = null;
        for (var i = 0; i < ch.length; i++) {
            if (!ch[i].isAttr()) {
                break;
            }
            if (ch[i].property && ch[i].property() && universeHelpers.isIsProperty(ch[i].property())) {
                continue;
            }
            else {
                toRet = ch[i].lowLevel();
            }
        }
        if (toRet == null) {
            toRet = where.lowLevel();
        }
        return toRet;
    }
    else {
        var pname = node.property().nameId();
        var cls = where.definition();
        var props = cls.allProperties();
        //console.log('class: ' + cls.name());
        //props.forEach(x=> console.log('  prop: ' + x.name()));
        var llchilds = where.lowLevel().children();
        var nodeProperty = node.property();
        if (universeHelpers.isTraitsProperty(nodeProperty) || universeHelpers.isResourceTypesProperty(nodeProperty)) {
            return _.find(llchilds, function (llch) {
                if (!llch.isMapping())
                    return false;
                return asMapping(llch).key.value === node.property().nameId();
            });
        }
        var pindex = cls.propertyIndex(pname);
        if (pindex < 0) {
            return null;
            //throw 'unknown property: ' + cls.name() + '.' + pname;
        }
        //console.log('lookup: ' + pname + ' index: ' + pindex + ' childs: ' + llchilds.length);
        for (var i = 0; i < llchilds.length; i++) {
            var llch = llchilds[i];
            //console.log('  child: ' + llch.kindName());
            if (!llch.isMapping())
                continue;
            var cpnme = asMapping(llch).key.value;
            var pi = cls.propertyIndex(cpnme);
            //console.log('  property: ' + cpnme + ' index: ' + pi + ' at pos: ' + i);
            if (pi > pindex) {
                //console.log('  property: ' + cpnme + ' - found');
                var lastok = i - 1;
                //console.log('lastok: ' + lastok);
                if (lastok < 0) {
                    //TODO insert at the very beginning
                    //console.log('insert to very beginning');
                    return null;
                }
                else {
                    console.log('insert to node: ' + lastok);
                    return llchilds[lastok];
                }
            }
            else {
            }
        }
        return null;
    }
    //console.log('HL insertion: ' + toRet);
    //return toRet;
}
function removeNodeFrom(source, node) {
    if (source.isStub()) {
        if (hlimpl.ASTNodeImpl.isInstance(node)) {
            var cm = node;
            if (cm.isInEdit) {
                return;
            }
        }
        if (!source._children) {
            return;
        }
        source._children = source._children.filter(function (x) { return x != node; });
        return;
    }
    var command = new ll.CompositeCommand();
    if (hlimpl.ASTNodeImpl.isInstance(node)) {
        var aNode = node;
        if (!aNode.property().getAdapter(services.RAMLPropertyService).isMerged()) {
            if (source.elementsOfKind(aNode.property().nameId()).length == 1) {
                command.commands.push(ll.removeNode(source.lowLevel(), aNode.lowLevel().parent().parent()));
            }
            else {
                command.commands.push(ll.removeNode(source.lowLevel(), aNode.lowLevel()));
            }
        }
        else {
            command.commands.push(ll.removeNode(source.lowLevel(), aNode.lowLevel()));
        }
    }
    else {
        command.commands.push(ll.removeNode(source.lowLevel(), node.lowLevel()));
    }
    source.lowLevel().execute(command);
}
exports.removeNodeFrom = removeNodeFrom;
function initEmptyRAMLFile(node) {
    var llroot = node.lowLevel().root();
    var command = new ll.CompositeCommand();
    var newroot = jsyaml.createMap([]);
    command.commands.push(ll.initRamlFile(node.lowLevel(), newroot));
    node.lowLevel().execute(command);
    var root = node.root();
    //console.log('root: ' + root);
    root._node = newroot;
    root.clearChildrenCache();
}
exports.initEmptyRAMLFile = initEmptyRAMLFile;
function setValue(node, value) {
    if (value == node.value())
        return;
    var c = new ll.CompositeCommand();
    if (typeof value === 'string') {
        var val = value;
        //FIXME actually isFromParentKey should be enough in future does not changing it now for safety reasons
        if (node.property().isFromParentKey() || node.property().isAnnotation()) {
            if (node.property().isAnnotation()) {
                val = '(' + val + ')';
            }
            c.commands.push(ll.setKey(node.lowLevel(), val));
        }
        else {
            if ((!val || val.length == 0) && !node.isEmbedded()) {
                c.commands.push(ll.removeNode(node.lowLevel().parent(), node.lowLevel()));
                node.parent().clearChildrenCache();
            }
            else {
                if (!val)
                    val = '';
                c.commands.push(ll.setAttr(node.lowLevel(), val));
            }
        }
    }
    else {
        if (node.property().isFromParentKey()) {
            throw new Error(linter.applyTemplate(messageRegistry.COULD_NOT_SET_STRUCTURED_VALUE, { key: node.property().nameId() }));
        }
        var sval = value;
        c.commands.push(ll.setAttrStructured(node.lowLevel(), sval));
    }
    node.lowLevel().execute(c);
}
exports.setValue = setValue;
function addStringValue(attr, value) {
    var sc = jsyaml.createScalar(value);
    var target = attr.lowLevel();
    //console.log('add to target: ' + target.kindName());
    if (target.isScalar()) {
        target = target.parent();
    }
    else if (target.isMapping()) {
    }
    var command = new ll.CompositeCommand();
    command.commands.push(ll.insertNode(target, sc, null, true));
    attr.lowLevel().execute(command);
    attr.parent().clearChildrenCache();
}
exports.addStringValue = addStringValue;
function addStructuredValue(attr, sv) {
    //var sc = jsyaml.createScalar(value);
    var target = attr.lowLevel();
    //console.log('add to target: ' + target.kindName());
    if (target.isScalar()) {
        target = target.parent();
    }
    else if (target.isMapping()) {
        var ln = attr.lowLevel();
        //target = new jsyaml.ASTNode(target.asMapping().value, <jsyaml.CompilationUnit>ln.unit(), ln, null, null);
    }
    var command = new ll.CompositeCommand();
    command.commands.push(ll.insertNode(target, sv.lowLevel(), null, true));
    attr.lowLevel().execute(command);
    attr.parent().clearChildrenCache();
}
exports.addStructuredValue = addStructuredValue;
function removeAttr(attr) {
    var llparent = attr.lowLevel().parent();
    if (!attr.property().isMultiValue() && attr.isEmbedded()) {
        // it's embedded value, need to clean scalar instead
        //console.log('embedded!');
        attr.setValue('');
    }
    else {
        var command = new ll.CompositeCommand();
        command.commands.push(ll.removeNode(llparent, attr.lowLevel()));
        attr.lowLevel().execute(command);
        attr.parent().clearChildrenCache();
    }
}
exports.removeAttr = removeAttr;
function setValues(attr, values) {
    if (!attr.property().isMultiValue())
        throw new Error(messageRegistry.SETVALUE_ONLY_MULTI_VALUES_PROPERTIES.message);
    var node = attr.parent();
    if (node && isTypeShortcut(node)) {
        var command = new ll.CompositeCommand();
        command.commands.push(ll.setAttr(node.lowLevel(), values[0]));
        node.lowLevel().execute(command);
        return;
    }
    if (attr.isEmpty()) {
        // nothing to remove so...
    }
    else {
        var llnode = node.lowLevel();
        var attrs = node.attributes(attr.name());
        attrs.forEach(function (attr) { return attr.remove(); });
    }
    if (values.length == 1 && attr.property().isDescriminator()) {
        node.attrOrCreate(attr.name()).setValue(values[0]);
    }
    else {
        values.forEach(function (val) {
            return node.attrOrCreate(attr.name()).addValue(val);
        });
    }
}
exports.setValues = setValues;
function setKey(node, value) {
    if (value == node.name())
        return;
    var c = new ll.CompositeCommand();
    c.commands.push(ll.setKey(node.lowLevel(), value));
    node.lowLevel().execute(c);
}
exports.setKey = setKey;
function isTypeShortcut(node) {
    if (node.definition() && node.definition().isAssignableFrom(universes.Universe10.TypeDeclaration.name)) {
        if (node.lowLevel() && node.lowLevel().valueKind() === yaml.Kind.SCALAR) {
            return true;
        }
    }
    return false;
}
function createAttr(node, n, v) {
    var mapping = jsyaml.createMapping(n, v);
    var map;
    if (node.definition() && node.definition().isAssignableFrom(universes.Universe10.TypeDeclaration.name)) {
        if (node.lowLevel() && (node.lowLevel().valueKind() === yaml.Kind.SCALAR || !node.lowLevel()._node.value)) {
            var typePropertyName = n;
            var typeMapping = jsyaml.createMapping(typePropertyName, node.lowLevel().value());
            var command = new ll.CompositeCommand();
            command.commands.push(ll.setAttr(node.lowLevel(), ""));
            node.lowLevel().execute(command);
            command = new ll.CompositeCommand();
            var insertionPoint = findInsertionPointLowLevel(node, typeMapping, node.definition().property(typePropertyName), true);
            command.commands.push(ll.insertNode(node.lowLevel(), typeMapping, insertionPoint));
            node.lowLevel().execute(command);
            node.resetChildren();
            var attribute = node.attr(typePropertyName) && node.attr(typePropertyName).lowLevel();
            if (attribute) {
                command = new ll.CompositeCommand();
                command.commands.push(ll.setAttr(attribute, v));
                attribute.execute(command);
                node.clearChildrenCache();
                return;
            }
        }
    }
    //console.log('create attribute: ' + n);
    if (node.isStub()) {
        //console.log('create-attr: stub case');
        var insertionIndex = findLastAttributeIndex(node);
        //console.log('stub insertion index: ' + insertionIndex);
        node.lowLevel().addChild(mapping, insertionIndex + 1);
        //console.log('insertion: ' + insertionPoint);
        //insertionPoint.show("INSERTION");
        //node.lowLevel().show("ADDED");
    }
    else {
        //console.log('root: ' + llroot.kindName());
        if (node.isEmptyRamlFile()) {
            node.initRamlFile();
        }
        //console.log('create-attr: real node case');
        //node._node.addChild(mapping);
        //node.clearChildrenCache();
        var command = new ll.CompositeCommand();
        var insertionPoint = findInsertionPointLowLevel(node, mapping, node.definition().property(n), true);
        //command.commands.push(ll.insertNode(node.lowLevel(), mapping, null));
        //var toseq = node.property().isMultiValue();
        command.commands.push(ll.insertNode(node.lowLevel(), mapping, insertionPoint));
        node.lowLevel().execute(command);
        //insertionPoint.show("INSERTION");
        //node.lowLevel().show("ADDED");
    }
    node.clearChildrenCache();
}
exports.createAttr = createAttr;
function findLastAttribute(node) {
    var childs = node.lowLevel().children();
    var index = findLastAttributeIndex(node);
    //console.log('last attr index2: ' + index);
    return (index < 0) ? null : childs[index];
}
function addToNode(target, node) {
    if (!target.isStub() && target.isEmptyRamlFile()) {
        target.initRamlFile();
    }
    var llnode = node.lowLevel();
    if (!target._children) {
        target._children = [];
    }
    if (!node.property()) {
        //now we should find correct property;
        var an = node;
        var allProps = target.definition().allProperties();
        var cp = null;
        allProps.forEach(function (x) {
            var r = x.range();
            if (r == an.definition()) {
                cp = x;
            }
            var isOk = _.find(an.definition().allSuperTypes(), function (x) { return x == r; });
            if (isOk) {
                cp = x;
            }
        });
        if (!cp) {
            throw new Error(messageRegistry.UNABLE_TO_FIND_CHILD.message);
        }
        else {
            an.patchProp(cp);
        }
    }
    var insertionPoint = findInsertionPoint(target, node);
    //console.log('high level op: ' + target.property() + '.add ' + node.property().name());
    if (insertionPoint) {
        //insertionPoint.show('add: insertion point: ');
    }
    //var newLowLevel:ll.ILowLevelASTNode=null;
    var command = new ll.CompositeCommand();
    //now we need to understand to which low level node it should go
    //command.commands.push(ll.insertNode(target.lowLevel(), node.lowLevel()))
    var insertionTarget = null;
    if (node.property().getAdapter(services.RAMLPropertyService).isMerged() || node.property().range().hasValueTypeInHierarchy()) {
        //console.log('CASE 1');
        //newLowLevel = node.lowLevel();
        command.commands.push(ll.insertNode(target.lowLevel(), node.lowLevel(), insertionPoint));
        insertionTarget = target.lowLevel();
    }
    else {
        //console.log('CASE 2');
        var name = node.property().nameId();
        var target2 = target.lowLevel();
        //target.show('TARGET:');
        //llnode.show('NODE:');
        var found = target.lowLevel().find(name);
        insertionTarget = found;
        if (!found) {
            //console.log('node not found');
            var nn = null;
            //var nn: jsyaml.ASTNode = jsyaml.createSeqNode(name);
            //var mapping = <yaml.YAMLMapping>nn._actualNode();
            //var seq: yaml.YAMLSequence = <yaml.YAMLSequence>mapping.value;
            //if(!seq.items) seq.items = [];
            //seq.items.push((<jsyaml.ASTNode>node.lowLevel())._actualNode());
            if (node.property().getAdapter(services.RAMLPropertyService).isEmbedMap()) {
                var v10 = target.definition().universe().version() == 'RAML10';
                if (llnode.isValueMap() && v10)
                    nn = jsyaml.createMapNode(name);
                else if (universeHelpers.isLibraryType(target.definition()) && universeHelpers.isTraitsProperty(node.property()) && v10) {
                    nn = jsyaml.createMapNode(name);
                }
                else if (universeHelpers.isLibraryType(target.definition()) && universeHelpers.isResourceTypesProperty(node.property()) && v10) {
                    nn = jsyaml.createMapNode(name);
                }
                else if (universeHelpers.isLibraryBaseSibling(target.definition()) && universeHelpers.isTypesProperty(node.property()) && v10) {
                    nn = jsyaml.createMapNode(name);
                }
                else {
                    nn = jsyaml.createSeqNode(name);
                }
                //console.log('NN: ' + yaml.Kind[nn._actualNode().kind]);
                nn.addChild(node.lowLevel());
                //newLowLevel=nn;
            }
            else {
                nn = jsyaml.createNode(name);
                nn.addChild(node.lowLevel());
                //newLowLevel=nn;
            }
            //nn.show('WRAPPED NODE:');
            //target.show('INSERT WRAPPED NODE TO:');
            command.commands.push(ll.insertNode(target2, nn, insertionPoint));
            insertionTarget = target2;
        }
        else {
            var isEmptyTypes = found.value() === null && found.key && found.key() === universes.Universe10.Api.properties.types.name;
            var needSeq = !isEmptyTypes && node.property().getAdapter(services.RAMLPropertyService).isEmbedMap();
            command.commands.push(ll.insertNode(found, node.lowLevel(), insertionPoint, needSeq));
        }
    }
    if (target.isStub()) {
        var insertionIndex = findLastAttributeIndex(target);
        if (insertionIndex < 0) {
            target._children.push(node);
        }
        else {
            //TODO behavior should be smarter we are ignoring insertion points now
            target._children.splice(insertionIndex, 0, node);
        }
        command.commands.forEach(function (x) { return insertionTarget.addChild(x.value); });
        return;
    }
    target.lowLevel().execute(command);
    target._children.push(node);
    //now we need to add new child to our children;
    node.setParent(target);
}
exports.addToNode = addToNode;
//# sourceMappingURL=mutators.js.map