/*!
 * socket.io-node
 * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var fs = require('fs')
  , socket = require('../lib/io');

/**
 * License headers.
 *
 * @api private
 */

var template = '/*! Socket.IO.%ext% build:' + socket.version + ', %type%. Copyright(c) 2011 LearnBoost <dev@learnboost.com> MIT Licensed */\n'
  , development = template.replace('%type%', 'development').replace('%ext%', 'js')
  , production = template.replace('%type%', 'production').replace('%ext%', 'min.js');

/**
 * If statements, these allows you to create serveride & client side compatible
 * code using specially designed `if` statements that remove serverside
 * designed code from the source files
 *
 * @api private
 */

var starttagIF = '// if node'
  , endtagIF = '// end node';

/**
 * The modules that are required to create a base build of Socket.IO.
 *
 * @const
 * @type {Array}
 * @api private
 */

var base = [
    'io.js'
  , 'util.js'
  , 'events.js'
  , 'json.js'
  , 'parser.js'
  , 'transport.js'
  , 'socket.js'
  , 'namespace.js'
  ];

/**
 * The available transports for Socket.IO. These are mapped as:
 * 
 *   - `key` the name of the transport
 *   - `value` the dependencies for the transport
 *
 * @const
 * @type {Object}
 * @api public
 */

var baseTransports = {
    'websocket': ['transports/websocket.js']
  , 'flashsocket': [
        'transports/websocket.js'
      , 'transports/flashsocket.js'
      , 'vendor/web-socket-js/swfobject.js'
      , 'vendor/web-socket-js/web_socket.js'
    ] 
  , 'htmlfile': ['transports/xhr.js', 'transports/htmlfile.js']
  /* FIXME: re-enable me once we have multi-part support
  , 'xhr-multipart': ['transports/xhr.js', 'transports/xhr-multipart.js'] */
  , 'xhr-polling': ['transports/xhr.js', 'transports/xhr-polling.js']
  , 'jsonp-polling': [
        'transports/xhr.js'
      , 'transports/xhr-polling.js'
      , 'transports/jsonp-polling.js'
    ]
};

/**
 * Wrappers for client-side usage.
 * This enables usage in top-level browser window, client-side CommonJS systems and AMD loaders.
 * If doing a node build for server-side client, this wrapper is NOT included.
 * @api private
 */
var wrapperPre = "\nvar io = ('undefined' === typeof module ? {} : module.exports);\n(function() {\n";

var wrapperPost = "\nif (typeof define === \"function\" && define.amd) {" +
                  "\n  define([], function () { return io; });" +
                  "\n}\n})();";


/**
 * Builds a custom Socket.IO distribution based on the transports that you
 * need. You can configure the build to create development build or production
 * build (minified).
 *
 * @param {Array} transports The transports that needs to be bundled.
 * @param {Object} [options] Options to configure the building process.
 * @param {Function} callback Last argument should always be the callback
 * @callback {String|Boolean} err An optional argument, if it exists than an error
 *    occurred during the build process.
 * @callback {String} result The result of the build process.
 * @api public
 */

var builder = module.exports = function () {
  var transports, options, callback, error = null
    , args = Array.prototype.slice.call(arguments, 0)
    , settings = {
        minify: true
      , node: false
      , custom: []
      };

  // Fancy pancy argument support this makes any pattern possible mainly
  // because we require only one of each type
  args.forEach(function (arg) {
    var type = Object.prototype.toString.call(arg)
        .replace(/\[object\s(\w+)\]/gi , '$1' ).toLowerCase();

    switch (type) {
      case 'array':
        return transports = arg;
      case 'object':
        return options = arg;
      case 'function':
        return callback = arg;
    }
  });

  // Add defaults
  options = options || {};
  transports = transports || Object.keys(baseTransports);

  // Merge the data
  for(var option in options) {
    settings[option] = options[option];
  }

  // Start creating a dependencies chain with all the required files for the
  // custom Socket.IO bundle.
  var files = [];
  base.forEach(function (file) {
    files.push(__dirname + '/../lib/' + file);
  });

  transports.forEach(function (transport) {
    var dependencies = baseTransports[transport];
    if (!dependencies) {
      error = 'Unsupported transport `' + transport + '` supplied as argument.';
      return;
    }

    // Add the files to the files list, but only if they are not added before
    dependencies.forEach(function (file) {
      var path = __dirname + '/../lib/' + file;
      if (!~files.indexOf(path)) files.push(path);
    })
  });

  // check to see if the files tree compilation generated any errors.
  if (error) return callback(error);

  var results = {};
  files.forEach(function (file) {
    fs.readFile(file, function (err, content) {
      if (err) error = err;
      results[file] = content;

      // check if we are done yet, or not.. Just by checking the size of the result
      // object.
      if (Object.keys(results).length !== files.length) return;

      // we are done, did we error?
      if (error) return callback(error);

      // start with the license header
      var code = development
        , ignore = 0;

      // pre-wrapper for non-server-side builds
      if (!settings.node) code += wrapperPre;

      // concatenate the file contents in order
      files.forEach(function (file) {
        code += results[file];
      });

      // check if we need to add custom code
      if (settings.custom.length) {
        settings.custom.forEach(function (content) {
          code += content;
        });
      }

      // post-wrapper for non-server-side builds
      if (!settings.node) {
          code += wrapperPost;
      }

      // Search for conditional code blocks that need to be removed as they
      // where designed for a server side env. but only if we don't want to
      // make this build node compatible.
      if (!settings.node) {
        code = code.split('\n').filter(function (line) {
          // check if there are tags in here
          var start = line.indexOf(starttagIF) >= 0
            , end = line.indexOf(endtagIF) >= 0
            , ret = ignore;

          // ignore the current line
          if (start) {
            ignore++;
            ret = ignore;
          }

          // stop ignoring the next line
          if (end) {
            ignore--;
          }

          return ret == 0;
        }).join('\n');
      }

      callback(error, code);
    })
  })
};

/**
 * Builder version is also the current client version
 * this way we don't have to do another include for the
 * clients version number and we can just include the builder.
 *
 * @type {String}
 * @api public
 */
 
builder.version = socket.version;

/**
 * A list of all build in transport types.
 *
 * @type {Object}
 * @api public
 */
 
builder.transports = baseTransports;

/**
 * Command line support, this allows us to generate builds without having
 * to load it as module.
 */
 
if (!module.parent){
  // the first 2 are `node` and the path to this file, we don't need them
  var args = process.argv.slice(2);

  // build a development build
  builder(args.length ? args : false, { minify:false }, function (err, content) {
    if (err) return console.error(err);

    fs.write(
        fs.openSync(__dirname + '/../dist/socket.io.js', 'w')
      , content
      , 0
      , 'utf8'
    );
    console.log('Successfully generated the development build: socket.io.js');
  });

  // and build a production build
  builder(args.length ? args : false, function (err, content) {
    if (err) return console.error(err);
 
    fs.write(
        fs.openSync(__dirname + '/../dist/socket.io.min.js', 'w')
      , content
      , 0
      , 'utf8'
    );
    console.log('Successfully generated the production build: socket.io.min.js');
  });
}
