angular.module('chttp', [])
.factory('chttp', ['$http', function($http) {
  /*
    Allow a url to be passed instead of an object, since its
    by far the most common use-case
   */
  function normalize(config) {
    if('string' === typeof config)
      return {url: config};
    return config;
  }

  /*
    Merge two configs
   */
  function merge(oldConfig, newConfig) {
    oldConfig = normalize(oldConfig);
    newConfig = normalize(newConfig);

    var oldUrl = oldConfig.url;
    var newUrl = newConfig.url;
    var config = _.merge({}, oldConfig, newConfig, function(a, b) {
      if(_.isArray(a) && _.isArray(b))
        return a.concat(b);
      return undefined;
    });

    // Concatenate urls, so that we can do things like
    // var api = chttp({url: apiServerUrl})
    // var user = api({url: '/user'})
    // user.get('/me');
    if(oldUrl && newUrl)
      config.url = oldUrl + newUrl;
    return config;
  }

  /*
    Final preparation of a config before handing it off to $http
    Currently only does parameter transformation.
   */
  function prepare(config) {
    if(config.transformParams) {
      [].concat(config.transformParams).forEach(function(transform) {
        config.params = transform(config.params);
      });
    }

    return config;
  }

  /*
    Decorate a function with the $http service's methods
   */
  function decorate(fn, config) {
    // No data methods
    ['get', 'head', 'delete', 'jsonp'].forEach(function(method) {
      fn[method] = function(url, newConfig) {
        newConfig = newConfig || {};
        newConfig.url = url || '';
        newConfig.method = method;
        return $http(prepare(merge(config, newConfig)));
      };
    });

    // Data methods
    ['post', 'put'].forEach(function(method) {
      fn[method] = function(url, data, newConfig) {
        newConfig = newConfig || {};
        newConfig.url = url || '';
        newConfig.method = method;
        newConfig.data = data;
        return $http(prepare(merge(config, newConfig)));
      };
    });

    return fn;
  }

  function factory(config) {
    return decorate(function(newConfig) {
      newConfig = merge(config, newConfig);
      return factory(newConfig);
    }, config);
  }

  return factory({});
}]);