'use strict';


var esprima;

// Browserified version does not have esprima
//
// 1. For node.js just require module as deps
// 2. For browser try to require mudule via external AMD system.
//    If not found - try to fallback to window.esprima. If not
//    found too - then fail to parse.
//
try {
  esprima = require('esprima');
} catch (_) {
  /*global window */
  if (window) { esprima = window.esprima; }
}


var Type = require('../../type');


function resolveJavascriptFunction(state) {
  /*jslint evil:true*/

  try {
    var source = '(' + state.result + ')',
        ast    = esprima.parse(source, { range: true }),
        params = [],
        body;

    if ('Program'             !== ast.type         ||
        1                     !== ast.body.length  ||
        'ExpressionStatement' !== ast.body[0].type ||
        'FunctionExpression'  !== ast.body[0].expression.type) {
      return false;
    }

    ast.body[0].expression.params.forEach(function (param) {
      params.push(param.name);
    });

    body = ast.body[0].expression.body.range;

    // Esprima's ranges include the first '{' and the last '}' characters on
    // function expressions. So cut them out.
    state.result = new Function(params, source.slice(body[0]+1, body[1]-1));
    return true;
  } catch (err) {
    return false;
  }
}


function representJavascriptFunction(object /*, style*/) {
  return object.toString();
}


function isFunction(object) {
  return '[object Function]' === Object.prototype.toString.call(object);
}


module.exports = new Type('tag:yaml.org,2002:js/function', {
  loadKind: 'scalar',
  loadResolver: resolveJavascriptFunction,
  dumpPredicate: isFunction,
  dumpRepresenter: representJavascriptFunction
});
