export import tsutil   = require("./tsutil");
import _ = require("./underscore");

export interface NameFilter{
    (schemaName:string):boolean
}
export interface IConfig{

    /**
     * if set to true it will be possible to pass numbers to string parameters
     * @type {boolean}
     */
    numberIsString:boolean
    /**
     * If it set to true system will create named interfaces for parameters defenition otherwise,
     * it will use structural types
     * Use named interfaces
     */
    createTypesForResources:boolean


    /**
     * If this option is set to true query parameters will be placed as second argument when method has body
     */
    queryParametersSecond:boolean


    /**
     * If this option is set to true .get() will be collapsed to ()
     */
    collapseGet:boolean

    /**
     * If this option is set to true method references will be collapsed if it is only method in the resource
     * foo.get() => foo()
     */
    collapseOneMethod:boolean

    /**
     * If this option is set to true media type parameters will be removed from declarations
     */
    collapseMediaTypes:boolean

    /**
     * For example, let resource 'somerRes' have GET, POST and PUT methods.
     * If 'false', generates get(), post() and put() for 'someResource'
     * If 'true', generates getSomeRes(), postSomeRes() and putSomeRes() for
     * parent of 'someRes'. If 'someRes' does not itself has child resources, it is not generated.
     * @type {boolean}
     */
    methodNamesAsPrefixes:boolean

    /**
     * If this option is set to 'true', the executor combines request and response into a HAR entry
     * and places it into the '__$harEntry__' field of ramlscript response.
     */
    storeHarEntry:boolean

    /**
     * If it set to true system will create named interfaces for parameters defenition otherwise,
     * it will use structural types
     * Use named interfaces
     */
    createTypesForParameters
    /**
     * It true geneartor will try to reuse parameter types when possible
     * and redeclare using type =
     */
    reuseTypeForParameters:boolean

    /**
     * If true will not reuse structural types for schemas
     */
    createTypesForSchemaElements:boolean

    /* It true geneartor will try to reuse parameter types when possible
     * and redeclare using type =
     */
    reuseTypesForSchemaElements:boolean

    /**
     * If 'true', exception is thrown for statuses > 399
     */
    throwExceptionOnIncorrectStatus:boolean

    /**
     * generate asynchronous client
     **/
        async:boolean

    debugOptions:{
        generateImplementation:boolean;
        generateSchemas:boolean;
        generateInterface:boolean;
        schemaNameFilter:NameFilter
    }

    /**
     * Whether to overwrite the 'node_modules' folder for the generated notebook.
     * If the folder is known to be consistent, the option may be set to 'false'
     * in order to save time.
     */
    overwriteModules:boolean
}

export var MODEL_CLASS_MODEL_ELEMENT = '$resource-model-element';

export var MODEL_CLASS_TYPE_DECLARATION = '$type-declaration';

export var MODEL_CLASS_INTERFACE = '$interface-declaration';

export var MODEL_CLASS_CLASS_DECLARATION = '$class-declaration';

export var MODEL_CLASS_ANNOTATION_DECLARATION = '$annotation-declaration';

export var MODEL_CLASS_ENUM_DECLARATION = '$enum-declaration';

export var MODEL_CLASS_TYPE_ASSERTION = '$type-assertion';

export var MODEL_CLASS_API_MODULE = '$api-module';

export var MODEL_CLASS_UNIVERSE = '$universe';

export var MODEL_CLASS_MEMBER = '$member';

export var MODEL_CLASS_UNION_TYPE_REFERENCE = '$union-type-reference';

export var MODEL_CLASS_SIMPLE_TYPE_REFERENCE = '$simple-type-reference';

export var MODEL_CLASS_FUNCTION_REFERENCE = '$function-reference';

export var MODEL_CLASS_ARRAY_REFERENCE = '$array-reference';

export var MODEL_CLASS_DECLARED_INTERFACE_REFERENCE = '$declared-interface-reference';

export var MODEL_CLASS_ANY_TYPE_REFERENCE = '$any-type-reference';

export var MODEL_CLASS_STRUCTURAL_TYPE_REFERENCE = '$structural-type-reference';

export var MODEL_CLASS_PARAM = '$param';

export var MODEL_CLASS_STRING_VALUE = '$string-value';

export var MODEL_CLASS_ARRAY_VALUE = '$array-value';

export var MODEL_CLASS_API_ELEMENT_DECLARATION = '$api-element-declaration';

export var MODEL_CLASS_CONSTRUCTOR = '$constructor';

export interface TSModelVisitor {
    startTypeDeclaration(decl:TSTypeDeclaration):boolean;
    endTypeDeclaration(decl:TSTypeDeclaration):void;
    betweenElements():void;
    startVisitElement(decl:TSAPIElementDeclaration):boolean;
    endVisitElement(decl:TSAPIElementDeclaration):void;
}

export interface Serializer{

    serialize(model:TSModelElement<any>, forImplementation?:boolean):string

}

export interface IModelElement{

    modelClass():string;
}

//TODO HIDE Fields from unmanagable modification
//TODO Refine type decl type ref hieararchy a bit more
//TODO add classes, generics, metadata
export class TSModelElement<T extends TSModelElement<any>> implements IModelElement {

    protected _parent:TSModelElement<any>;
    private _children:T[]
    protected _config:IConfig

    _comment:string;

    meta:{[key:string]:any} = {}

    private _annotations:IAnnotationReference[] = []

    annotations():IAnnotationReference[]{
        return this._annotations;
    }

    patchParent(parent:TSModelElement<any>){
        this._parent=parent;//FIXME
    }

    isEmpty():boolean {
        return this._children.length == 0;
    }
    parent():TSModelElement<any>{return this._parent}

    children():T[] {
        return this._children;
    }

    comment():string{ return this._comment; }

    root():TSAPIModule{
        if (this._parent==Universe){
            return <TSAPIModule>(<any>this);
        }
        return this._parent.root();
    }

    constructor(parent:TSModelElement<any> = Universe,config?:IConfig) {
        this.setParent(parent);

        if (config) this._config = config;

        this._children = [];
    }

    protected setParent(parent:TSModelElement<any>) {
        if (!parent) return;
        this._parent = parent;
        this._config = parent._config;
        this._parent.addChild(this);
    }

    removeChild(child:T){
        if (child.parent()==this){
            this._children=this._children.filter(x=>x!=child);
        }
        child.patchParent(Universe);
    }

    addChild(child:T){
        if(child.parent()){
            child.parent().removeChild(child);
        }
        child.patchParent(this);
        this._children.push(child);
    }

    serializeToString(isImpl:boolean=false):string {
        throw new Error("You should override serialize to string always");
    }

    modelClass():string{
        return MODEL_CLASS_MODEL_ELEMENT; }
}
//TODO It should become an interface
export class TSTypeDeclaration extends TSModelElement<TSAPIElementDeclaration> {

    canBeOmmited = () => this.locked ? false : this.children().every( x => x.optional )

    locked:boolean=false;

    extras:string[]=[""];

    addCode(code:string):void{
        this.extras.push(code)
    }

    toReference():TSTypeReference<any>{throw new Error("Implement in subclasses");}

    hash(){ return this.serializeToString(); }

    isFunctor() {
        return this.children().some(x=>x.isAnonymousFunction())
    }

    constructor(parent:TSModelElement<any> = null){
        super(parent);
    }

    getFunctor() {
        return _.find( this.children(), x => x.isAnonymousFunction() )
    }

    visit(v:TSModelVisitor) {
        if (v.startTypeDeclaration(this)) {
            this.children().forEach((x, i, arr) => {
                x.visit(v)
                if (i != arr.length - 1) v.betweenElements();
            })
            v.endTypeDeclaration(this);
        }
    }

    modelClass():string{
        return MODEL_CLASS_TYPE_DECLARATION; }
}

export class TSInterface extends TSTypeDeclaration {


    name:string
    extends:TSTypeReference<any>[]=[];
    implements:TSTypeReference<any>[]=[];
    typeParameters:string[];

    typeSignature():string{
        var modelName = this.name;
        if(this.typeParameters && this.typeParameters.length > 0){
            modelName += "<"+this.typeParameters.join(',')+">";
        }
        return modelName;
    }

    hash(){ return this.children().filter(x=>!x.isPrivate).map( x =>  "\n" + x.serializeToString(this.isImpl()) + "\n" ).join('') }

    toReference():TSTypeReference<any>{return new TSDeclaredInterfaceReference(Universe,this.name,this)}


    constructor(p:TSModelElement<any>, name:string) {
        super(p)
        this.name = name;
    }
    decl(){
        return "interface"
    }

    serializeToString() {
        var body = this.hash();
        return this.insertComment() + "export "+this.decl() +" "+ this.name.concat(this.extendsString()+this.implementsString())+
            "{" +this.extras.join("\n")+ body + "}\n"
    }

    private  extendsString():string{
        if(this.extends.length>0){
            return " extends "+this.extends.map(x=>x.serializeToString()).join(",")
        }
        return "";
    }

    private  implementsString():string{
        if(this.implements.length>0){
            return " implements "+this.implements.map(x=>x.serializeToString()).join(",")
        }
        return "";
    }

    insertComment():string{
        if(!this._comment){
            return '';
        }
        return formattedComment(this._comment,0);
    }

    modelClass():string{
        return MODEL_CLASS_INTERFACE; }
    
    protected isImpl():boolean{
        return false;
    }

}
//TODO INCORRECT INHERITANCE CHAIN
export class TSClassDecl extends TSInterface{

    decl(){
        return "class"
    }

    modelClass():string{
        return MODEL_CLASS_CLASS_DECLARATION; }

    protected isImpl():boolean{
        return true;
    }

}

export class TSAnnotationDecl extends TSInterface{

    decl(){
        return "annotation"
    }

    modelClass():string{
        return MODEL_CLASS_ANNOTATION_DECLARATION; }

    toReference():TSTypeReference<any>{return new TSDeclaredAnnotationReference(Universe,this.name,this)}

}

export class TSEnumDecl extends TSInterface{

    decl(){
        return "enum"
    }

    enumConstants:string[]

    modelClass():string{
        return MODEL_CLASS_ENUM_DECLARATION;
    }

}


export class TSTypeAssertion extends TSTypeDeclaration{

    toReference():TSTypeReference<any>{return new TSSimpleTypeReference(Universe,this._name)}

    constructor(p:TSModelElement<any>, private _name:string,private _ref:TSTypeReference<any>) {
        super(p);
    }

    serializeToString() {
        return "export type " + this._name + "=" + this._ref.serializeToString()+"\n";
    }

    ref():TSTypeReference<any>{ return this._ref; }

    name():string{ return this._name; }

    modelClass():string{
        return MODEL_CLASS_TYPE_ASSERTION; }
}

export class TSUniverse extends TSModelElement<any>{

    constructor() {
        // super(this);
        //commented this call out as the new version of TS compiler does not eat this. For a reason.

        //so now first calling super with a fake parent
        super(new TSModelElement<any>());

        //and now setting a real parent as self
        //(not sure why we do this originally, I would put null here, creating a cycle doesnt look good)
        this.setParent(this);
    }

    addChild(child:any){
    }

    setConfig(cfg:IConfig){ this._config = cfg; }

    getConfig():IConfig{ return this._config; }

    modelClass():string{
        return MODEL_CLASS_UNIVERSE; }
}
export var Universe=new TSUniverse();

export class TSAPIModule extends TSModelElement<TSInterface>{

    serializer:Serializer

    getInterface(nm:string):TSInterface{
        return _.find(this.children(),x=>x.name==nm);
    }

    serializeToString():string {

        var typeMap:{[key:string]:TSInterface} = {};
        this.children().forEach(x=>typeMap[x.name]=x);

        var covered:{[key:string]:boolean} = {};
        var sorted:TSInterface[] = []
        var append = function(t:TSInterface){
            if(covered[t.name]){
                return;
            }
            covered[t.name] = true;
            var refs:TSTypeReference<any>[] = t.extends;
            refs.forEach(ref=> {
                    if (ref instanceof TSSimpleTypeReference) {
                        var name = (<TSSimpleTypeReference>ref).name;
                        var st = typeMap[name];
                        if (st) {
                            append(st);
                        }
                    }
                }
            );
            sorted.push(t);
        }
        this.children().forEach(x=>append(x));
        return sorted.map(x=>x.serializeToString()).join("\n")
    }

    modelClass():string{
        return MODEL_CLASS_API_MODULE; }
}


export interface NoChildren extends TSModelElement<NoChildren> {
}

export class TSMember<T extends TSModelElement<any>> extends TSModelElement<T> {
    optional:boolean;

    modelClass():string{
        return MODEL_CLASS_MEMBER; }
}

export interface TSTypeReference<T extends TSModelElement<any>> extends TSModelElement<T> {

    array():boolean
    locked:boolean;

    canBeOmmited():boolean;


    isFunctor():boolean;

    getFunctor():TSAPIElementDeclaration;

    union(q:TSTypeReference<any>):TSTypeReference<any>
}


export class TSUnionTypeReference extends TSModelElement<TSTypeReference<any>> implements TSTypeReference<TSTypeReference<any>>{

    array = ():boolean => false;
    locked:boolean;
    getFunctor():TSAPIElementDeclaration {
        return null;
    }

    //TODO FIXIT FIX IT WITH MIX IN
    union(q:TSTypeReference<any>):TSTypeReference<any>{
        var map = {}
        this.children().filter(x=>x instanceof TSSimpleTypeReference)
            .forEach(x=>map[(<TSSimpleTypeReference>x).name] = true);

        var gotNew = false;
        var flat = flattenUnionType(q);
        flat.forEach(x=>{
            if(x instanceof TSSimpleTypeReference) {
                gotNew = gotNew || !map[(<TSSimpleTypeReference>q).name];
            }
            else{
                gotNew = true;
            }
        })
        if(!gotNew){
            return this;
        }
        var r=new TSUnionTypeReference();
        this.children().forEach(x=>r.addChild(x));
        flat.forEach(x=>r.addChild(x));
        return r;
    }



    isFunctor():boolean{
        return false;
    }

    canBeOmmited():boolean{
        return false;
    }

    serializeToString():string {
        return this.children().map(x=>x.serializeToString()).join(" | ");
    }

    removeChild(child:TSTypeReference<any>){}

    addChild(child:TSTypeReference<any>){
        this.children().push(child);
    }

    modelClass():string{
        return MODEL_CLASS_UNION_TYPE_REFERENCE; }
}

export class TSSimpleTypeReference extends TSModelElement<NoChildren> implements TSTypeReference<NoChildren> {

    locked:boolean;

    typeParameters:TSTypeReference<any>[]

    isEmpty():boolean {
        return false;
    }


    getFunctor():TSAPIElementDeclaration {
        return null;
    }

    canBeOmmited():boolean {
        return false;
    }

    isFunctor():boolean {
        return false;
    }
    array = ():boolean => false;

    constructor(p:TSModelElement<any>, tn:string) {
        super(p);
        this.name = tn;
    }

    union(q:TSTypeReference<any>):TSTypeReference<any>{

        var flat = flattenUnionType(q);
        var gotThis = false;
        flat.filter(x=>x instanceof TSSimpleTypeReference).map(x=><TSSimpleTypeReference>x)
            .forEach(x=>gotThis = gotThis || (x.name == this.name));

        if(gotThis){
            return q;
        }
        var r=new TSUnionTypeReference();
        r.addChild(this);
        flat.forEach(x=>r.addChild(x));
        return r;
    }

    name:string

    genericStr = ():string => this.typeParameters && this.typeParameters.length > 0
        ?'<' + this.typeParameters.map( p => p.serializeToString() ).join(',') + '>'
        : '';

    serializeToString() {
        return this.name + this.genericStr();
    }

    modelClass():string{
        return MODEL_CLASS_SIMPLE_TYPE_REFERENCE; }
}
export class TSFunctionReference extends TSModelElement<NoChildren> implements TSTypeReference<NoChildren> {

    locked:boolean;

    rangeType:TSTypeReference<any>=new AnyType();

    parameters:Param[] = [];

    isEmpty():boolean {
        return false;
    }

    getFunctor():TSAPIElementDeclaration {
        return null;
    }

    canBeOmmited():boolean {
        return false;
    }

    isFunctor():boolean {
        return true;
    }
    array = ():boolean => false;

    constructor(p:TSModelElement<any>) {
        super(p);
    }

    union(q:TSTypeReference<any>):TSTypeReference<any>{
        var r=new TSUnionTypeReference();
        r.addChild(this);
        r.addChild(q);
        return r;
    }

    serializeToString() {
        return this.paramStr() + '=>' + this.rangeType.serializeToString();
    }

    paramStr = (appendDefault:boolean=false):string => '(' + this.parameters
        .filter(x=>!x.isEmpty())
        .map( p => p.serializeToString(appendDefault) )
        .join(', ') + ')'

    modelClass():string{
        return MODEL_CLASS_FUNCTION_REFERENCE; }
}


export class TSArrayReference extends TSModelElement<NoChildren> implements TSTypeReference<NoChildren> {

    locked:boolean;

    componentType:TSTypeReference<any>;

    isEmpty():boolean {
        return this.componentType ? true : false;
    }

    getFunctor():TSAPIElementDeclaration {
        return this.componentType.getFunctor();
    }

    canBeOmmited():boolean {
        return false;
    }

    isFunctor():boolean {
        return this.componentType.isFunctor();
    }
    array = ():boolean => true;

    constructor(componentType:TSTypeReference<any>=new AnyType()) {
        super(Universe);
        this.componentType = componentType;
    }

    union(q:TSTypeReference<any>):TSTypeReference<any>{
        var r=new TSUnionTypeReference();
        r.addChild(this);
        r.addChild(q);
        return r;
    }

    serializeToString() {
        return this.componentType.serializeToString() + '[]';
    }

    modelClass():string{
        return MODEL_CLASS_ARRAY_REFERENCE; }
}

export interface IAnnotationReference{

    name:string

    values:{[key:string]:Value}

    value(key:string):Value
}


export class TSDeclaredInterfaceReference extends TSSimpleTypeReference{

    isEmpty():boolean {
        return false;
    }



    getFunctor():TSAPIElementDeclaration {
        return null;
    }

    canBeOmmited():boolean {
        return false;
    }

    constructor(p:TSModelElement<any>, tn:string,private _data:TSInterface) {
        super(p, tn);
    }

    getOriginal(){
        return this._data;
    }

    modelClass():string{
        return MODEL_CLASS_DECLARED_INTERFACE_REFERENCE; }
}

export class TSAnnotationReference extends TSSimpleTypeReference implements IAnnotationReference{

    constructor(p:TSModelElement<any>, tn:string, values:{[key:string]:Value} = {}) {
        super(p,tn);
        this.values = values;
    }

    values:{[key:string]:Value}

    value(key:string='value'):Value{ return this.values[key] }
}

export class TSDeclaredAnnotationReference extends TSDeclaredInterfaceReference implements IAnnotationReference{

    values:{[key:string]:Value} = {}

    value(key:string='value'):Value{ return this.values[key] }
}

export class AnyType extends TSSimpleTypeReference{

    constructor(nm:string="any") {
        super(Universe, nm);
    }
    union(q:TSTypeReference<any>):TSTypeReference<any>{
        return q;
    }

    modelClass():string{
        return MODEL_CLASS_ANY_TYPE_REFERENCE; }
}

export class TSStructuralTypeReference extends TSTypeDeclaration implements TSTypeReference<TSAPIElementDeclaration> {
    visitReturnType(v:TSModelVisitor) {
        //v.visitStructuralReturn(this);
        this.visit(v);
    }
    toReference():TSTypeReference<any>{return this}


    union(q:TSTypeReference<any>):TSTypeReference<any>{
        var r=new TSUnionTypeReference();
        r.addChild(this);
        r.addChild(q);
        return r;
    }

    array = ():boolean => false;


    constructor(parent:TSModelElement<any> = Universe){
        super(parent);
    }

    serializeToString() {
        var body = this.children().map(x=> `\n${x.serializeToString()}\n` ).join('')
        return "{" + body + "}";
    }

    canBeOmmited = () => this.locked ? false : this.children().every( x => x.optional )

    modelClass():string{
        return MODEL_CLASS_STRUCTURAL_TYPE_REFERENCE; }
}

export enum ParamLocation{
    URI, BODY, OPTIONS, OTHER
}

export class Param extends TSModelElement<TSTypeReference<any>> {
    name:string
    ptype:TSTypeReference<any>;
    optional:boolean;
    location:ParamLocation;
    defaultValue:any;

    isEmpty(): boolean {
        return this.ptype.isEmpty()
    }

    constructor(
        p:TSAPIElementDeclaration,
        nm:string,
        location:ParamLocation,
        tp:TSTypeReference<any> = new TSSimpleTypeReference(Universe, "string"),
        defaultValue?:any) {
        super(p);
        this.name = nm;
        this.ptype = tp;
        this.location = location;
        this.defaultValue = defaultValue;
    }

    serializeToString(appendDefault:boolean = false) {
        //return this.name + (this.optional ? "?" : "") + ":" + this.ptype.serializeToString() + (this.ptype.canBeOmmited() ? "?" : "");
        return this.name + (this.optional || ((this.defaultValue != null) && !appendDefault) ? "?" : "")
            + (":" + this.ptype.serializeToString() + (this.ptype.canBeOmmited() ? "?" : ""))
            + (appendDefault && (this.defaultValue!=null) ? '='+JSON.stringify(this.defaultValue) : '');
    }

    modelClass():string{
        return MODEL_CLASS_PARAM; }
}
export interface Value extends TSMember<NoChildren>{

    value():any
}
export class StringValue extends TSMember<NoChildren> implements Value{

    constructor(private _value:string){
        super();
    }

    value():string {
        return this._value;
    }

    serializeToString():string {
        return `"${this._value}"`
    }

    modelClass():string{
        return MODEL_CLASS_STRING_VALUE; }
}

export class ArrayValue extends TSMember<NoChildren> implements Value{

    constructor(private _values:Value[]){
        super();
    }

    value():string {
        return this.serializeToString();
    }

    serializeToString():string {
        return `[ ${this._values.map(x=>x.value()).join(', ')} ]`
    }

    values():Value[] {
        return this._values;
    }

    modelClass():string{
        return MODEL_CLASS_ARRAY_VALUE; }
}


export class TSAPIElementDeclaration extends TSMember<TSTypeReference<any>> {
    name:string;
    rangeType:TSTypeReference<any>=new AnyType();
    parameters:Param[]
    optional:boolean;
    value:Value=null;

    isPrivate:boolean;
    isStatic:boolean=false;
    isFunc:boolean;

    _body:string;

    visit(v:TSModelVisitor) {
        if (v.startVisitElement(this)) {
            if (this.rangeType) {
                if (this.rangeType instanceof TSStructuralTypeReference && !this.isInterfaceMethodWithBody()) {
                    (<TSStructuralTypeReference>this.rangeType).visitReturnType(v);
                }
            }
            v.endVisitElement(this);
        }
    }

    constructor(p:TSModelElement<any>, name:string) {
        super(p);
        this.name = name;
        this.parameters = [];
        this.rangeType = null;
        this.optional = false;
    }

    paramStr = (appendDefault:boolean=false):string => '( ' + this.parameters
        .filter(x=>!x.isEmpty())
        .map( p => this.serializeParam(p,appendDefault) )
        .join(',') + ' )'

    protected serializeParam = (p:Param, appendDefault:boolean):string => p.serializeToString(appendDefault)

    isFunction = ():boolean => this.parameters.length != 0||this.isFunc

    isAnonymousFunction = ():boolean => this.isFunction() && this.name === ''

    returnStr = ():string => this.rangeType ?  ':' + this.rangeType.serializeToString() : ''

    commentCode(){

        if(this.comment()){
            return formattedComment(this._comment,2);
        }

        return '';
    }

    serializeToString(isImpl:boolean=false) {
        var x = (this.isPrivate ? 'private ' : '')
            + (this.isStatic ? 'static ' : '')
            + this.escapeDot(this.name)
            + (this.optional ? "?" : "")
            + (this.isFunction()? this.paramStr(isImpl) : "")
            + this.returnStr();

        if (this.value){
            x+='='+this.value.value();
        }
        return this.commentCode() + x + (this.isFunction()&&this.isInterfaceMethodWithBody() ? '' : this.body());
    }
    body(){
        if (this._body==null)return "";
        return "{"+this._body+"}"
    }

    private escapeDot( name:string ): string {
        var escaped = tsutil.escapeTypescriptPropertyName( name );
        return escaped;
    }

    isInterfaceMethodWithBody():boolean{ return false; }


    modelClass():string{
        return MODEL_CLASS_API_ELEMENT_DECLARATION; }
}

export class TSConstructor extends TSAPIElementDeclaration {
    constructor(p:TSModelElement<any>) {
        super(p,'constructor');
    }

    protected serializeParam = (p:Param, appendDefault:boolean):string => 'protected ' + p.serializeToString(appendDefault)

    modelClass():string{
        return MODEL_CLASS_CONSTRUCTOR; }
}


function flattenUnionType(ref:TSTypeReference<any>):TSTypeReference<any>[]{

    if(!(ref instanceof  TSUnionTypeReference)){
        return [ ref ];
    }

    var map:{[key:string]:TSSimpleTypeReference} = {};
    var arr:TSTypeReference<any>[] = [];
    ref.children().forEach(x=>this.flattenUnionType(<TSUnionTypeReference>x).forEach(y=>{
        if (y instanceof TSSimpleTypeReference) {
            var st = <TSSimpleTypeReference>y;
            var name = st.name;
            map[name] = st;
        }
        else {
            arr.push(y);
        }
    }));
    return arr.concat(_.sortBy(Object.keys(map).map(x=>map[x]),'name'));
}

function formattedComment(str:string,indents:number):string {

    var shift = '';
    for(var i = 0 ; i < indents ; i++){ shift += '    '; }

    return `
${shift}/**
${str.replace(/\*\//g, '* /').replace(/\r\n/g, '\n').split('\n').map(x=>shift + ' * ' + x.trim()).join('\n')}
${shift} **/
`;
}