'use strict';

/* @jsig */

var assert = require('assert');
var uuid = require('uuid');

var LocationLiteralNode = require('./location-literal.js');
var JsigASTReplacer = require('../type-checker/lib/jsig-ast-replacer.js');

module.exports = FunctionNode;

function FunctionNode(opts) {
    assert(!(opts && opts.label), 'cannot have label on function');
    assert(!(opts && opts.optional), 'cannot have optional on function');

    this.type = 'function';
    this.args = opts.args || [];
    this.result = opts.result;
    this.thisArg = opts.thisArg || null;
    // Brand is used if this function is a constructor
    // This will brand the object instance allocated with `new`
    this.brand = opts.brand || 'Object';
    // specialKind is used to store singleton information
    // For example the assert function has special type narrow
    // semantics so its FunctionNode is the "assert" specialKind
    this.specialKind = opts.specialKind || null;
    this._raw = null;

    if (opts.generics && typeof opts.generics[0] === 'string') {
        /*jsig ignore next: narrowing by array member not implemented*/
        this.generics = this._findGenerics(opts.generics);
    } else {
        this.generics = [];
    }
}

FunctionNode.prototype._findGenerics =
function _findGenerics(generics) {
    var replacer = new GenericReplacer(this, generics);
    var astReplacer = new JsigASTReplacer(replacer, true);
    astReplacer.inlineReferences(this, this, []);

    return replacer.seenGenerics;
};

function GenericReplacer(node, generics) {
    this.node = node;

    this.knownGenerics = generics;
    this.genericUUIDs = Object.create(null);
    for (var i = 0; i < this.knownGenerics.length; i++) {
        this.genericUUIDs[this.knownGenerics[i]] = uuid();
    }
    this.seenGenerics = [];
}

GenericReplacer.prototype.replace = function replace(ast, raw, stack) {
    // console.log('GenericReplacer.replace()', ast);
    if (ast.type === 'typeLiteral') {
        return this.replaceTypeLiteral(ast, stack);
    } else if (ast.type === 'genericLiteral') {
        // TODO: mark the generics in the genericLiteral itself ?
        return ast;
    } else {
        assert(false, 'unexpected other type: ' + ast.type);
    }
};

GenericReplacer.prototype.replaceTypeLiteral =
function replaceTypeLiteral(ast, stack) {
    if (this.knownGenerics.indexOf(ast.name) === -1) {
        return ast;
    }

    var identifierUUID = this.genericUUIDs[ast.name];
    this.seenGenerics.push(
        new LocationLiteralNode(ast.name, stack.slice(), identifierUUID)
    );

    ast.isGeneric = true;
    ast.genericIdentifierUUID = identifierUUID;
    return ast;
};

@@ -82,16 +82,63 @@
 uid');%0A%0A
+var LiteralTypeNode = require('./literal.js');%0A
 var Loca
@@ -185,24 +185,24 @@
 teral.js');%0A
-
 var JsigASTR
@@ -1337,24 +1337,71 @@
 generics) %7B%0A
+    // console.log('_findGenerics', generics);%0A
     var repl
@@ -1550,24 +1550,84 @@
 this, %5B%5D);%0A%0A
+    // console.log('seenGenerics?', replacer.seenGenerics);%0A
     return r
@@ -2028,62 +2028,8 @@
 ) %7B%0A
-    // console.log('GenericReplacer.replace()', ast);%0A
     
@@ -2670,17 +2670,180 @@
 );%0A%0A    
-a
+var copyAst = new LiteralTypeNode(ast.name, ast.builtin, %7B%0A        line: ast.line,%0A        loc: ast.loc,%0A        concreteValue: ast.concreteValue%0A    %7D);%0A%0A    copyA
 st.isGen
@@ -2859,17 +2859,21 @@
 ue;%0A    
-a
+copyA
 st.gener
@@ -2914,20 +2914,24 @@
 %0A    return 
-a
+copyA
 st;%0A%7D;%0A

fix bug caused by mutating type AST