'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 {

// workaround to exclude package from browserify list.
var _require = require;
esprima = _require('esprima');

} catch (_) {

/*global window */
if (typeof window !== 'undefined') esprima = window.esprima;

}

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

function resolveJavascriptFunction(data) {

if (data === null) return false;

try {
  var source = '(' + data + ')',
      ast    = esprima.parse(source, { range: true });

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

  return true;
} catch (err) {
  return false;
}

}

function constructJavascriptFunction(data) {

/*jslint evil:true*/

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

if (ast.type                    !== 'Program'             ||
    ast.body.length             !== 1                     ||
    ast.body[0].type            !== 'ExpressionStatement' ||
    (ast.body[0].expression.type !== 'ArrowFunctionExpression' &&
      ast.body[0].expression.type !== 'FunctionExpression')) {
  throw new Error('Failed to resolve function');
}

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.
if (ast.body[0].expression.body.type === 'BlockStatement') {
  /*eslint-disable no-new-func*/
  return new Function(params, source.slice(body[0] + 1, body[1] - 1));
}
// ES6 arrow functions can omit the BlockStatement. In that case, just return
// the body.
/*eslint-disable no-new-func*/
return new Function(params, 'return ' + source.slice(body[0], body[1]));

}

function representJavascriptFunction(object /*, style*/) {

return object.toString();

}

function isFunction(object) {

return Object.prototype.toString.call(object) === '[object Function]';

}

module.exports = new Type('tag:yaml.org,2002:js/function', {

kind: 'scalar',
resolve: resolveJavascriptFunction,
construct: constructJavascriptFunction,
predicate: isFunction,
represent: representJavascriptFunction

});