"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        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 extendStatics(d, b);
    }
    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 });
var tsModel = require("ts-structure-model");
var def = require("./definitionSystem");
var _ = require("underscore");
var aHandlers = require("./annotationHandlers");
var services = def;
var FieldWrapper = /** @class */ (function () {
    function FieldWrapper(_field, _clazz) {
        this._field = _field;
        this._clazz = _clazz;
    }
    FieldWrapper.prototype.name = function () {
        return this._field.name;
    };
    FieldWrapper.prototype.range = function () {
        return this._clazz.getModule().typeFor(this._field.type, this._clazz);
    };
    FieldWrapper.prototype.isMultiValue = function () {
        return this._field.type.typeKind == tsModel.TypeKind.ARRAY;
    };
    FieldWrapper.prototype.isKey = function () {
        return _.find(this._field.annotations, function (x) { return x.name == "MetaModel.key"; }) != null;
    };
    FieldWrapper.prototype.isSimpleValue = function () {
        return _.find(this._field.annotations, function (x) { return x.name == "MetaModel.value"; }) != null;
    };
    FieldWrapper.prototype.annotations = function () {
        return this._field.annotations;
    };
    return FieldWrapper;
}());
var FieldConstraint = /** @class */ (function () {
    function FieldConstraint(_field, _clazz) {
        this._field = _field;
        this._clazz = _clazz;
    }
    FieldConstraint.prototype.name = function () {
        return this._field.name;
    };
    FieldConstraint.prototype.value = function () {
        return this._field.valueConstraint;
    };
    return FieldConstraint;
}());
var ClassWrapper = /** @class */ (function () {
    function ClassWrapper(_clazz, mw) {
        this._clazz = _clazz;
        this.mw = mw;
    }
    ClassWrapper.prototype.typeMeta = function () {
        return this._clazz.annotations;
    };
    ClassWrapper.prototype.path = function () {
        return this.mw.path();
    };
    ClassWrapper.prototype.getModule = function () {
        return this.mw;
    };
    ClassWrapper.prototype.typeArgs = function () {
        return this._clazz.typeParameters;
    };
    ClassWrapper.prototype.typConstraints = function () {
        var _this = this;
        return this._clazz.typeParameterConstraint.map(function (x) {
            if (x) {
                return _this.mw.classForName(x);
            }
            return null;
        });
    };
    ClassWrapper.prototype.methods = function () {
        return this._clazz.methods;
    };
    ClassWrapper.prototype.name = function () {
        return this._clazz.name;
    };
    ClassWrapper.prototype.members = function () {
        var _this = this;
        return this._clazz.fields.filter(function (x) { return x.valueConstraint == null; }).map(function (x) { return new FieldWrapper(x, _this); });
    };
    ClassWrapper.prototype.constraints = function () {
        var _this = this;
        return this._clazz.fields.filter(function (x) { return x.valueConstraint != null; }).map(function (x) { return new FieldConstraint(x, _this); });
    };
    ClassWrapper.prototype.isSubTypeOf = function (of) {
        if (this == of) {
            return true;
        }
        var _res = false;
        this.getAllSuperTypes().forEach(function (x) {
            if (!_res) {
                _res = _res || x.isSubTypeOf(of);
            }
        });
        return _res;
    };
    ClassWrapper.prototype.getExtendsClauses = function () {
        return this._clazz.extends;
    };
    ClassWrapper.prototype.getSuperTypes = function () {
        var _this = this;
        var result = [];
        this._clazz.extends.forEach(function (x) {
            var tp = _this.mw.classForName(x.typeName);
            if (tp) {
                result.push(tp);
            }
        });
        return result;
    };
    ClassWrapper.prototype.getAllSuperTypes = function () {
        var _this = this;
        var result = [];
        this._clazz.extends.forEach(function (x) {
            var tp = _this.mw.classForName(x.typeName);
            if (tp) {
                var mm = tp.getAllSuperTypes();
                result.push(tp);
                result.concat(mm);
            }
        });
        return _.unique(result);
    };
    ClassWrapper.prototype.annotationOverridings = function () { return this._clazz.annotationOverridings; };
    return ClassWrapper;
}());
var AbstractSimpleWrapper = /** @class */ (function () {
    function AbstractSimpleWrapper() {
    }
    AbstractSimpleWrapper.prototype.members = function () {
        return []; //this._clazz.members.map(x=>new FieldWrapper(x,this))
    };
    AbstractSimpleWrapper.prototype.methods = function () { return []; };
    AbstractSimpleWrapper.prototype.isSubTypeOf = function (of) {
        return false;
    };
    AbstractSimpleWrapper.prototype.getSuperTypes = function () {
        return [];
    };
    AbstractSimpleWrapper.prototype.getAllSuperTypes = function () {
        return [];
    };
    AbstractSimpleWrapper.prototype.name = function () {
        return null;
    };
    AbstractSimpleWrapper.prototype.constraints = function () {
        return [];
    };
    AbstractSimpleWrapper.prototype.typeMeta = function () {
        return [];
    };
    AbstractSimpleWrapper.prototype.getModule = function () {
        throw new Error("Not implemented");
    };
    AbstractSimpleWrapper.prototype.annotationOverridings = function () { return {}; };
    return AbstractSimpleWrapper;
}());
var EnumWrapper = /** @class */ (function (_super) {
    __extends(EnumWrapper, _super);
    function EnumWrapper(_clazz, mw) {
        var _this = _super.call(this) || this;
        _this._clazz = _clazz;
        _this.mw = mw;
        return _this;
    }
    EnumWrapper.prototype.getModule = function () {
        return this.mw;
    };
    EnumWrapper.prototype.values = function () {
        return this._clazz.members;
    };
    EnumWrapper.prototype.name = function () {
        return this._clazz.name;
    };
    return EnumWrapper;
}(AbstractSimpleWrapper));
var UnionWrapper = /** @class */ (function (_super) {
    __extends(UnionWrapper, _super);
    function UnionWrapper(_clazz, mw) {
        var _this = _super.call(this) || this;
        _this._clazz = _clazz;
        _this.mw = mw;
        return _this;
    }
    UnionWrapper.prototype.elements = function () {
        return this._clazz;
    };
    UnionWrapper.prototype.name = function () {
        return this._clazz.map(function (x) { return x.name(); }).join("|");
    };
    return UnionWrapper;
}(AbstractSimpleWrapper));
var ModuleWrapper = /** @class */ (function () {
    function ModuleWrapper(_univers) {
        var _this = this;
        this._univers = _univers;
        this.name2Class = {};
        this.namespaceToMod = {};
        this._classes = [];
        _univers.classes.forEach(function (x) {
            var c = new ClassWrapper(x, _this);
            _this._classes.push(c);
            _this.name2Class[x.name] = c;
            if (x.moduleName) {
                //FIXME
                _this.name2Class[x.moduleName + "." + x.name] = c;
            }
        });
        _univers.enumDeclarations.forEach(function (x) {
            var c = new EnumWrapper(x, _this);
            _this._classes.push(c);
            _this.name2Class[x.name] = c;
        });
    }
    ModuleWrapper.prototype.typeFor = function (t, ow) {
        var _this = this;
        switch (t.typeKind) {
            case tsModel.TypeKind.BASIC:
                var bt = t;
                var typeName = bt.typeName;
                if (typeName == "string") {
                    typeName = "StringType";
                }
                if (typeName == "number") {
                    typeName = "NumberType";
                }
                if (typeName == "boolean") {
                    typeName = "BooleanType";
                }
                if (typeName == "any") {
                    typeName = "AnyType";
                }
                var ti = _.indexOf(ow.typeArgs(), typeName);
                if (ti != -1) {
                    var cnst = ow.typConstraints()[ti];
                    if (!cnst) {
                        return this.classForName("ValueType");
                    }
                    return cnst;
                }
                return this.classForName(typeName);
            case tsModel.TypeKind.UNION:
                var ut = t;
                return new UnionWrapper(ut.options.map(function (x) { return _this.typeFor(x, ow); }), this);
            case tsModel.TypeKind.ARRAY:
                var at = t;
                return this.typeFor(at.base, ow);
        }
        return null;
    };
    ModuleWrapper.prototype.path = function () {
        return this._univers.name;
    };
    ModuleWrapper.prototype.classForName = function (name, stack) {
        var _this = this;
        if (stack === void 0) { stack = {}; }
        if (!name) {
            return null;
        }
        var result = this.name2Class[name];
        if (!result && !stack[this.path()]) {
            stack[this.path()] = this;
            var nmsp = name.indexOf(".");
            if (nmsp != -1) {
                var actualMod = this.namespaceToMod[name.substring(0, nmsp)];
                if (!actualMod) {
                    throw new Error();
                }
                return actualMod.classForName(name.substring(nmsp + 1), stack);
            }
            Object.keys(this.namespaceToMod).forEach(function (x) {
                if (x != "MetaModel") {
                    var nm = _this.namespaceToMod[x].classForName(name, stack);
                    if (nm) {
                        result = nm;
                    }
                }
            });
        }
        return result;
    };
    ModuleWrapper.prototype.classes = function () {
        return this._classes;
    };
    return ModuleWrapper;
}());
var wrapperToType = function (range, u) {
    if (range) {
        var rangeType;
        if (range instanceof UnionWrapper) {
            var uw = range;
            throw new Error("Union type support was removed from definition system");
            //rangeType = new def.UnionType(uw.elements().map(x=>wrapperToType(x, u)))
        }
        else {
            rangeType = u.type(range.name());
        }
        return rangeType;
    }
    else {
        return;
    }
};
var registerClasses = function (m, u) {
    var valueType = m.classForName("ValueType");
    m.classes().forEach(function (x) {
        if (x instanceof EnumWrapper) {
            var et = new def.EnumType(x.name(), u, x.getModule().path());
            et.values = x.values();
            u.register(et);
            return;
        }
        if (x.isSubTypeOf(valueType)) {
            var st = x.getAllSuperTypes();
            st.push(x);
            var refTo = null;
            st.forEach(function (t) {
                var cs = t.getExtendsClauses();
                cs.forEach(function (z) {
                    if (z.typeKind == tsModel.TypeKind.BASIC) {
                        var bas = z;
                        if (bas.basicName == 'Reference') {
                            var of = bas.typeArguments[0];
                            refTo = of.typeName;
                            var di = refTo.indexOf('.');
                            if (di != -1) {
                                refTo = refTo.substring(di + 1);
                            }
                        }
                    }
                });
            });
            if (refTo) {
                //console.log("New reference type" + x.name())
                var ref = new def.ReferenceType(x.name(), x.getModule().path(), refTo, u);
                ref.setBuiltIn(true);
                u.register(ref);
            }
            else {
                var vt = new def.ValueType(x.name(), u, x.getModule().path());
                vt.setBuiltIn(true);
                u.register(vt);
            }
        }
        else {
            var gt = new def.NodeClass(x.name(), u, x.getModule().path());
            gt.setBuiltIn(true);
            u.register(gt);
        }
    });
};
var registerHierarchy = function (m, u) {
    m.classes().forEach(function (x) {
        x.getSuperTypes().forEach(function (y) {
            var tp0 = u.type(x.name());
            var tp1 = u.type(y.name());
            if (!tp0 || !tp1) {
                var tp0 = u.type(x.name());
                var tp1 = u.type(y.name());
                throw new Error();
            }
            u.registerSuperClass(tp0, tp1);
        });
    });
};
var registerEverything = function (m, u) {
    m.classes().forEach(function (x) {
        var tp = u.type(x.name());
        x.typeMeta().forEach(function (a) {
            var rangeType = wrapperToType(x, u);
            aHandlers.handleTypeAnnotation(a, rangeType);
        });
        x.members().forEach(function (x) {
            var range = x.range();
            var rangeType = wrapperToType(range, u);
            if (rangeType == null) {
                console.log(range + ":" + x.name());
            }
            var custom = x.annotations().map(function (y) { return y.name; }).indexOf('MetaModel.customHandling') >= 0;
            createProp(x, tp, rangeType, custom);
        });
        Object.keys(x.annotationOverridings()).forEach(function (fName) {
            var arr = [].concat(x.annotationOverridings()[fName]);
            var map = {};
            arr.forEach(function (ann) { return map[ann.name] = true; });
            var targetField;
            var stArr = x.getSuperTypes();
            var stMap = {};
            for (var i = 0; i < stArr.length; i++) {
                var st = stArr[i];
                if (stMap[st.name()]) {
                    continue;
                }
                stMap[st.name()] = true;
                st.getSuperTypes().forEach(function (sst) { return stArr.push(sst); });
                var arr1 = st.annotationOverridings()[fName];
                if (arr1) {
                    arr1.filter(function (ann) { return !map[ann.name]; }).forEach(function (ann) {
                        map[ann.name] = true;
                        arr.push(ann);
                    });
                }
                else {
                    var stFields = st.members();
                    for (var j = 0; j < stFields.length; j++) {
                        var stField = stFields[j];
                        if (stField.name() == fName) {
                            targetField = stField;
                            break;
                        }
                    }
                }
                if (targetField) {
                    var arr2 = targetField.annotations();
                    arr2.filter(function (ann) { return !map[ann.name]; }).forEach(function (ann) {
                        map[ann.name] = true;
                        arr.push(ann);
                    });
                    break;
                }
            }
            if (!targetField) {
                return;
            }
            var range = targetField.range();
            var rangeType = wrapperToType(range, u);
            if (rangeType == null) {
                console.log(range + ":" + x.name());
            }
            createProp(targetField, tp, rangeType, false, arr);
        });
        x.methods().forEach(function (x) {
            var at = tp;
            //at.addMethod(x.name, x.text);
            //console.log(x.name);
            //createMember(x, <def.AbstractType>tp, rangeType)
        });
        x.constraints().forEach(function (x) {
            if (x.value().isCallConstraint) {
                throw new Error();
            }
            var mm = x.value();
            tp.addRequirement(x.name(), "" + mm.value);
        });
    });
    u.types().forEach(function (x) {
        var at = x;
        at.getAdapter(services.RAMLService).getAliases().forEach(function (y) { return u.registerAlias(y, at); });
    });
};
var processModule = function (ts, u, used, declared) {
    if (ts.name.indexOf("metamodel.ts") != -1) {
        return; //FIXME
    }
    if (declared[ts.name]) {
        return declared[ts.name];
    }
    var m = new ModuleWrapper(ts);
    used[ts.name] = m;
    declared[ts.name] = m;
    Object.keys(ts.imports).forEach(function (x) {
        var pMod = ts.imports[x];
        if (used[pMod.name]) {
            m.namespaceToMod[x] = used[pMod.name];
            return;
            // throw new Error("Module "+pMod.name+" is part of the cycle "+ts.name+" on stack "+Object.keys(used).join(","))
        }
        var vMod = processModule(pMod, u, used, declared);
        m.namespaceToMod[x] = vMod;
    });
    used[ts.name] = null;
    return m;
};
function toDefSystem(ts, q) {
    var u = new def.Universe(q, "");
    var c = {};
    processModule(ts, u, {}, c);
    Object.keys(c).forEach(function (x) {
        registerClasses(c[x], u);
    });
    Object.keys(c).forEach(function (x) {
        registerHierarchy(c[x], u);
    });
    Object.keys(c).forEach(function (x) {
        registerEverything(c[x], u);
    });
    u.types().forEach(function (x) {
        if (x instanceof def.NodeClass) {
            var cl = x;
            cl.properties().forEach(function (y) {
                var t = y.range();
                var ap = y;
                if (!t.hasValueTypeInHierarchy()) {
                    t.properties().forEach(function (p0) {
                        if (p0.getAdapter(services.RAMLPropertyService).isKey()) {
                            var kp = p0.keyPrefix();
                            if (kp) {
                                ap.withKeyRestriction(kp);
                                ap.merge();
                            }
                            var eo = p0.enumOptions();
                            if (eo) {
                                ap.withEnumOptions(eo);
                                ap.merge();
                            }
                        }
                    });
                }
            });
            if (cl.getAdapter(services.RAMLService).isGlobalDeclaration()) {
                if (cl.getAdapter(services.RAMLService).getActuallyExports() && cl.getAdapter(services.RAMLService).getActuallyExports() != "$self") {
                    var tp = cl.property(cl.getAdapter(services.RAMLService).getActuallyExports()).range();
                    if (tp.hasValueTypeInHierarchy()) {
                        var vt = tp.getAdapter(services.RAMLService);
                        vt.setGloballyDeclaredBy(cl);
                    }
                    //rs+=genRef(tp)
                }
                if (cl.getAdapter(services.RAMLService).getConvertsToGlobal()) {
                    var tp = u.type(cl.getAdapter(services.RAMLService).getConvertsToGlobal());
                    if (tp.hasValueTypeInHierarchy()) {
                        var vt = tp.getAdapter(services.RAMLService);
                        vt.setGloballyDeclaredBy(cl);
                    }
                }
            }
        }
    });
    return u;
}
exports.toDefSystem = toDefSystem;
var processAnnotations = function (x, p, annotations) {
    if (!annotations) {
        annotations = x.annotations();
    }
    annotations.forEach(function (x) {
        var nm = x.name.substring(x.name.lastIndexOf(".") + 1);
        if (!aHandlers.annotationHandlers[nm]) {
            console.log("Can not find handler for:");
        }
        aHandlers.annotationHandlers[nm](x, p);
    });
};
function createProp(x, clazz, t, custom, annotations) {
    var p = def.prop(x.name(), "", clazz, t, custom);
    if (x.isMultiValue()) {
        p.withMultiValue(true);
    }
    p.unmerge();
    if (!t) {
        console.log(x.name() + ":" + clazz.nameId() + " has undefined type");
    }
    if (!t.hasValueTypeInHierarchy()) {
        t.properties().forEach(function (p0) {
            if (p0.getAdapter(services.RAMLPropertyService).isKey()) {
                var kp = p0.keyPrefix();
                if (kp) {
                    p.withKeyRestriction(kp);
                    p.merge();
                }
                var eo = p0.enumOptions();
                if (eo) {
                    p.withEnumOptions(eo);
                    p.merge();
                }
            }
        });
    }
    processAnnotations(x, p, annotations);
}
//# sourceMappingURL=tsStruct2Def.js.map