use :node;

var Node = module.require('../Node').Node,

Parameter = module.require('../Parameter').Parameter,
CallExpression = module.require('../expressions/CallExpression').CallExpression;

fn FunctionExpression (id, params, body, inheritsFrom, operator)

extends Node {

if operator == "=>" {
  this.type = "ArrowFunctionExpression";
} else {
  this.type = 'FunctionExpression'; 
}

this.defaults = [];
this.rest = null;
this.generator = false;
this.expression = false;  
this.operator = operator;

this.id = id;

if this.id? {
  this.id.parent = this;
}

this.body = body;
this.params = params;

body.parent = this;

for param in params {
  param.parent = this;
}

this.inheritsFrom = inheritsFrom;

if this.inheritsFrom? {
  this.inheritsFrom.parent = this;
}

if this.body.type != 'BlockStatement' {
  this.autoBlock = true;
  this.body = {
    "type": "BlockStatement",
    "body": [
      {
        "type": "ReturnStatement",
        "argument": this.body
      }
    ]
  };

  var self = this;
  this.getContext = () -> {
    return { 
      node: self.body,
      position: -1
    };
  };
}

}

FunctionExpression.prototype.codegen = () -> {

if !super.codegen() {
  return;
}

if this.id? {
  this.id = this.id.codegen(false);
}

Parameter.generateFunctionBody(this, this.params, this.body, this.defaults);

if this.autoBlock {
  this.body.body[0].argument = this.body.body[this.body.body.length - 1].argument.codegen();
} else {
  this.body = this.body.codegen();
}

if this.inheritsFrom? {
  if this.inheritsFrom.type != 'CallExpression' {
    this.inheritsFrom = new CallExpression(this.inheritsFrom, []);
    this.inheritsFrom.parent = this;
  }

  var context = this.getContext();

  this.body.body.splice(0, 0, {
    "type": "ExpressionStatement",
    "expression": {
      "type": "CallExpression",
      "callee": {
        "type": "MemberExpression",
        "computed": false,
        "object": this.inheritsFrom.callee,
        "property": {
          "type": "Identifier",
          "name": "call"
        }
      },
      "arguments": [
        { "type": "ThisExpression" }
      ].concat(this.inheritsFrom.arguments)
    }
  });

  var id = {
    "type": "Identifier",
    "name": FunctionExpression.getNextVariableName()
  };

  context.node.body.splice(context.position, 0, {
    "type": "VariableDeclaration",
    "declarations": [
      {
        "type": "VariableDeclarator",
        "id": id,
        "init": this
      }
    ],
    "kind": "let",
    "codeGenerated": true
  });

  context.node.body.splice(context.position + 1, 0, {
    "type": "ExpressionStatement",
    "codeGenerated": "true",
    "expression": {
      "type": "AssignmentExpression",
      "operator": "=",
      "left": {
        "type": "MemberExpression",
        "computed": false,
        "object": id,
        "property": {
          "type": "Identifier",
          "name": "prototype"
        }
      },
      "right": {
        "type": "CallExpression",
        "callee": {
          "type": "MemberExpression",
          "computed": false,
          "object": {
            "type": "Identifier",
            "name": "Object"
          },
          "property": {
            "type": "Identifier",
            "name": "create"
          }
        },
        "arguments": [
          this.inheritsFrom.callee
        ]
      }
    }
  });

  return id;
}

return this;

};

FunctionExpression.prototype.hasCallExpression = () -> true;

FunctionExpression.getNextVariableName = () -> {

if !this.functionExpressionIndex? { 
  this.functionExpressionIndex = 0; 
}

return "functionExpression" + this.functionExpressionIndex++;

};

FunctionExpression.resetVariableNames = () -> {

this.functionExpressionIndex = 0;

};

exports.FunctionExpression = FunctionExpression;