var rest = require('restler')
  , Backbone = require('backbone')
  , _ = require('underscore')

  , BASECAMP_API_VERSION = exports.BASECAMP_API_VERSION = 'v1'
  , BASECAMP_ACCOUNT
  , basecamp;

// Basecamp Model {{{1

var BasecampModel = Backbone.Model.extend({
    type : 'none'
  , types : 'none'
  , project : false

  , initialize : function() {
    if (!_.isUndefined(this.get('project'))) {
      this.project = this.get('project');
    }
  }

  , getProject : function(attr) {
    return this.project instanceof Backbone.Model ? this.project.get('id')
      : _.isNumber(this.project) ? this.project
      : false;
  }

  , url : function () {
    return '/' +
      BASECAMP_ACCOUNT + '/' +
      'api/' + BASECAMP_API_VERSION + '/' +
      (this.getProject() ? 'projects/' + this.getProject() + '/' : '') +
      this.types + '/' +
      this.id + '.json';
  }
  , toJSON : function () {
    var data = Backbone.Model.prototype.toJSON.apply(this)
      , result = {};

    result[this.type] = data;
    return result;
  }

  , parse : function (response) {
    return (response[this.type]) ? response[this.type] : response;
  }
});

function basecampModel(type, types) {
  return BasecampModel.extend({
      type : type
    , types : types || (type + 's')
  });
} // }}}

// Basecamp Collection {{{1

var BasecampCollection = Backbone.Collection.extend({
  initialize : function(models, project) {
    if (!_.isUndefined(project)) {
      this.project = project;
    }
  }
  , url : function () {
    var project = this.getProject();

    return '/' +
      BASECAMP_ACCOUNT + '/' +
      'api/' + BASECAMP_API_VERSION + '/' +
      (project ? 'projects/' + project + '/' : '') +
      this.model.prototype.types + '.json';
  }
});

function basecampCollection(model) {
  return BasecampCollection.extend({
    model : model
    , getProject : model.prototype.getProject
  });
} // }}}

// Access the Basecamp REST service {{{1

var method_map = {
    'create' : 'POST'
  , 'update' : 'PUT'
  , 'delete' : 'DELETE'
  , 'read'   : 'GET'
};

Backbone.sync = function (method, model, options) {
  var type = method_map[method]
    , url = model.url();

  options || (options = {});

  if (model && _.include(['create', 'update'], method)) {
    _.extend(options, {
        data : JSON.stringify(model.toJSON())
      , headers : { 'Content-Type' : 'application/json' }
    });
  }

  switch (type) {
    case 'POST':
      return basecamp.post(url, options)
        .on('complete', options.success)
        .on('error', options.error);
    case 'PUT':
      return basecamp.put(url, options)
        .on('complete', function(data) {
          if (!data.data) options.error(data);
          else options.success(data);
        })
        .on('error', function(err) {
          options.error(err);
        });
    case 'DELETE':
      return basecamp.delete(url, options)
        .on('complete', options.success)
        .on('error', options.error);
    case 'GET':
      return basecamp.get(url, options)
        .on('complete', function(data, resp) {
          options.success(data);
        })
        .on('error', function(err) {
          options.error(err);
        });
  }
}; // }}}

// Export the API {{{1

function createExport(singular, plural) {
  plural || (plural = singular + 's');
  exports[singular] = basecampModel(singular.toLowerCase());
  exports[plural] = basecampCollection(exports[singular]);
}

createExport('Project');
createExport('Person', 'People');
createExport('Access', 'Accesses');
createExport('Event');
createExport('Topic');
createExport('Message');
createExport('Comment');
createExport('TodoList');
createExport('Todo');
createExport('Document');
createExport('Attachment');
createExport('Upload');
createExport('Calendar');
createExport('CalendarEvent');

var Basecamp = rest.service(function (options) {
  _.extend(this.defaults, options);
}, {
    baseURL : 'https://basecamp.com'
  , headers : {
      'Accept': '*/*'
    , 'User-Agent' : 'Genero (mail@oxy.fi)'
  }
}, {
});
exports.Basecamp = Basecamp;

exports.init = function (options) {
  BASECAMP_ACCOUNT = options.account;
  delete options.account;

  basecamp = new Basecamp(options);
}; // }}}

@@ -1138,149 +1138,16 @@
 %0A%7D);
-%0A%0Afunction basecampModel(type, types) %7B%0A  return BasecampModel.extend(%7B%0A      type : type%0A    , types : types %7C%7C (type + 's')%0A  %7D);%0A%7D
  // %7D%7D%7D%0A
 %0A// 
@@ -1142,17 +1142,16 @@
  // %7D%7D%7D%0A
-%0A
 // Basec
@@ -1602,107 +1602,8 @@
   %7D%0A
-%7D);%0A%0Afunction basecampCollection(model) %7B%0A  return BasecampCollection.extend(%7B%0A    model : model%0A  
   , 
@@ -1615,17 +1615,25 @@
 oject : 
-m
+BasecampM
 odel.pro
@@ -1654,23 +1654,19 @@
 ect%0A
-  
 %7D);
-%0A%7D
  // %7D%7D%7D%0A
 %0A// 
@@ -1661,17 +1661,16 @@
  // %7D%7D%7D%0A
-%0A
 // Acces
@@ -2959,17 +2959,16 @@
  // %7D%7D%7D%0A
-%0A
 // Expor
@@ -3082,25 +3082,25 @@
 singular%5D = 
-b
+B
 asecampModel
@@ -3103,31 +3103,92 @@
 odel
-(singular.toLowerCase()
+.extend(%7B%0A      type : singular.toLowerCase()%0A    , types : plural.toLowerCase()%0A  %7D
 );%0A 
@@ -3206,17 +3206,17 @@
 ural%5D = 
-b
+B
 asecampC
@@ -3224,17 +3224,38 @@
 llection
-(
+.extend(%7B%0A    model : 
 exports%5B
@@ -3263,16 +3263,20 @@
 ingular%5D
+%0A  %7D
 );%0A%7D%0A%0Acr

Clean up