
/*!
 * stats - parser
 * Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var parse = require('./uglify/parser').parse
  , sys = require('util');

/**
 * Return stats for the given javascript `str`.
 *
 * @param {String} str
 * @param {Object} options
 * @return {Object}
 * @api public
 */

module.exports = function(str, options){
  var options = options || {}
    , stats = {
      loc: str.split('\n').length
    , bytes: Buffer.byteLength(str)
    , statements: 0
    , assignments: 0
    , functions: 0
    , stringBytes: 0
    , arrayLiterals: 0
    , objectLiterals: 0
    , objectsCreated: 0
    , regexpLiterals: 0
    , strings: 0
    , numbers: 0
    , throws: 0
  };

  // strip shebang
  if ('#' == str[0]) str = str.slice(str.indexOf('\n'));

  function visit(node) {
    if (!node) return;
    var name = node[0];

    // array support
    if ('string' != typeof name) {
      for (var i = 0, len = node.length; i < len; ++i) {
        visit(node[i]);
      }
      return;
    }

    // rename "name" to "ident"
    if ('name' == name) name = 'ident';

    // visit the node
    if (!visit[name]) throw new Error('no visitor implemented for "' + name + '"');
    visit[name](node);
  }

  visit['toplevel'] = function(node){
    visit(node[1]);
  };

  visit['seq'] = function(node){
    visit(node[1]);
    visit(node[2]);
  };

  visit['for-in'] = function(node){
    visit(node[1]);
    visit(node[2]);
    visit(node[3]);
    visit(node[4]);
  };

  visit['switch'] = function(node){
    ++stats.statements;
    visit(node[1]);
    node[2].forEach(function(node){
      visit(node[1]);
      visit(node[2]);
    });
  };

  visit['with'] = function(node){
    ++stats.statements;
    visit(node[1]);
    visit(node[2]);
  };

  visit['try'] = function(node){
    ++stats.statements;
    visit(node[1]);
    node[2] && visit(node[2][1]);
  };

  visit['conditional'] = function(node){
    visit(node[1]);
    visit(node[2]);
    visit(node[3]);
  };

  visit['break'] = function(node){
    ++stats.statements;
  };

  visit['array'] = function(node){
    ++stats.arrayLiterals;
    visit(node[1]);
  };

  visit['object'] = function(node){
    ++stats.objectLiterals;
    node[1].forEach(function(pair){
      visit(pair[1]);
    });
  };

  visit['var'] = 
  visit['const'] = function(node){
    node[1].forEach(function(node){
      if (node[1]) ++stats.assignments;
      visit(node[1]);
    });
    ++stats.statements;
  };

  visit['regexp'] = function(node){
    ++stats.regexpLiterals;
  };

  visit['num'] = function(node){
    ++stats.numbers;
  };

  visit['do'] = function(node){
    ++stats.statements;
    visit(node[1]);
    visit(node[2]);
  };

  visit['label'] = function(node){
    visit(node[2]);
  };

  visit['atom'] = function(node){};

  visit['unary-prefix'] = function(node){};
  
  visit['unary-postfix'] = function(node){};

  visit['continue'] = function(node){};

  visit['return'] = function(node){};

  visit['binary'] = function(node){};

  visit['ident'] = function(node){};

  visit['new'] = function(node){
    ++stats.objectsCreated;
    visit(node[1]);
    visit(node[2]);
  };

  visit['throw'] = function(node){
    ++stats.throws;
    visit(node[1]);
  };

  visit['for'] = function(node){
    visit(node[1]);
    visit(node[2]);
    visit(node[3]);
    visit(node[4]);
  };

  visit['sub'] = function(node){
    visit(node[1]);
    visit(node[2]);
  };

  visit['string'] = function(node){
    ++stats.strings;
    stats.stringBytes += Buffer.byteLength(node[1]);
  };

  visit['dot'] = function(node){
    visit(node[1]);
  };

  visit['block'] = function(node){
    visit(node[1]);
  };

  visit['while'] = function(node){
    ++stats.statements;
    visit(node[2]);
  };

  visit['if'] = function(node){
    ++stats.statements;
    visit(node[1]);
    visit(node[2]);
    visit(node[3]);
  };

  visit['defun'] = function(node){
    ++stats.functions;
    ++stats.statements;
    visit(node[3]);
  };

  visit['function'] = function(node){
    ++stats.functions;
    visit(node[3]);
  };

  visit['call'] = function(node){
    visit(node[1]);
    visit(node[2]);
  };

  visit['assign'] = function(node){
    ++stats.assignments;
    visit(node[3]);
  };

  visit['stat'] = function(node){
    ++stats.statements;
    visit(node[1]);
  };

  try {
    visit(parse(str));
  } catch (err) {
    if ('JS_Parse_Error' != err.constructor.name) throw err;
    var line = str.split('\n')[err.line];
    err = new SyntaxError(err.message
        + ' on line '
        + err.line
        + ':' + err.col
        + ':\n    ' + sys.inspect(line));
    if (!options.ignoreParseErrors) throw err;  
  }

  return stats;
};
