/*jslint indent: 2*/
/*global require: true*/
/**
 * For more details about the ClientLogin authentication check out this:
 * http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html
 */
var EventEmitter = require('events').EventEmitter,
    util = require('util');

// useragent string
const userAgent = 'GCLNodejs';
// version string
const ver = '0.2.2';

const loginURL = '/accounts/ClientLogin';
const googleHost = 'www.google.com';
const captchaRequiredError = 'CaptchaRequired';

// error messages
const errors = {
  captchaMissing: 'User entered captcha is missing',
  tokenMissing: 'Login token is missing',
  loginFailed: 'Login failed'
};

const events = {
  login: 'login',
  error: 'error'
};

// Google account types 
const accountTypes = {
  google: 'GOOGLE', // get authorization for a Google account only
  hosted: 'HOSTED', // get authorization for a hosted account only
  hostedOrGoogle: 'HOSTED_OR_GOOGLE' // get authorization first for a hosted account; if attempt fails, get authorization for a Google account
};
// http://code.google.com/apis/gdata/faq.html#clientlogin
const services = {
  adwords: 'adwords',
  analytics: 'analytics',
  apps: 'apps',
  base: 'gbase',
  sites: 'jotspot',
  blogger: 'blogger',
  book: 'print',
  calendar: 'cl',
  codesearch: 'codesearch',
  contacts: 'cp',
  docs: 'writely',
  finance: 'finance',
  mail: 'mail',
  health: 'health',
  weaver: 'weaver',
  maps: 'local',
  picasaweb: 'lh2',
  reader: 'reader',
  sidewiki: 'annotateweb',
  spreadsheets: 'wise',
  webmastertools: 'sitemaps',
  youtube: 'youtube',
  c2dm: 'ac2dm',
  voice: 'grandcentral',
  fusiontables: 'fusiontables'
};


/**
 * Helps to log in to any google service with the clientlogin method
 * Google returns 3 values when login was success:
 * Auth, SID, LSID
 *
 * After the login you need to include the Auth value into
 * the Authorization HTTP header on each request:
 *
 * client.request('GET', '...', {
 *   ...,
 *   'Authorization':'GoogleLogin auth=' + googleClientLoginInstance.getAuthId()
 * })
 *
 * @class GoogleClientLogin
 * @constructor
 * @param Object conf An object, with two properties: email and password
 */
var GoogleClientLogin = function (conf) {
  this.conf = conf || {};
  // stores the authentication data
  this.auths = {};
  this.loginProcessing = false;
};
GoogleClientLogin.prototype = {};
util.inherits(GoogleClientLogin, EventEmitter);

/**
 * Splits response data into key-value pairs,
 * Only for internal usage
 * @method _parseData
 */
GoogleClientLogin.prototype._parseData = function (data) {
  this.auths = {};
  data.split('\n').forEach(function (dataStr) {
    var data = dataStr.split('=');
    this.auths[data[0]] = data[1];
  }.bind(this));
};

/**
 * Parses the response of the login
 * emits error and login event
 * @method _parseLoginResponse
 * @param {http.ClientResponse} response The response object
 */
GoogleClientLogin.prototype._parseLoginResponse = function (response) {

  var data = '';

  response.on('data', function (chunk) {
    data += chunk;
  }.bind(this));

  response.on('error', function (e) {
    this.emit(events.error, e);
  }.bind(this));

  response.on('end', function () {
    this.loginProcessing = false;
    var statusCode = response.statusCode, error;
    this._parseData(data);
    if (statusCode >= 200 && statusCode < 300) {
      /**
       * Fires when login was success
       * @event login
       */
      this.emit(events.login);
    } else {
      /**
       * Fires when login failed
       * @event loginFailed
       */
      error = new Error(errors.loginFailed);
      error.data = data;
      error.response = response;
      this.emit(events.error, error);
    }
  }.bind(this));
};

/**
 * Method to find out which account type should we use, default is HOSTED_OR_GOOGLE
 * Only for internal usage
 * @method _getAccountType
 * @returns string
 */
GoogleClientLogin.prototype._getAccountType = function (params) {
  var output = accountTypes.hostedOrGoogle;

  if (typeof params === 'object' && params.accountType === 'string' &&
    typeof accountTypes[params.accountType] === 'string') {
      output = accountTypes[params.accountType];
  } else if (typeof this.conf.accountType === 'string') {
      output = accountTypes[this.conf.accountType];
  }

  return output;
};

/**
 * Method to create the content of the login request
 * Only for internal usage
 * @method _getRequestContent
 * @param {Object} params (Optional) You can pass the logincaptcha and
 * logintoken and the accountType as properties
 * @returns string
 */
GoogleClientLogin.prototype._getRequestContent = function (params) {
  var output, hasCaptcha, hasToken, error;

  output = {
    accountType: this._getAccountType(params),
    Email: this.conf.email,
    Passwd: this.conf.password,
    service: services[this.conf.service],
    source: userAgent + '_' + ver
  };

  if (typeof params === 'object') {

    hasCaptcha = typeof params.logincaptcha === 'string';
    hasToken = typeof params.logintoken === 'string';

    if (hasCaptcha && hasToken) {
      output.logincaptcha = params.logincaptcha;
      output.logintoken = params.logintoken;
    // if the captcha or the token is given the other also required
    } else if (!hasCaptcha && hasToken) {
      error = errors.captchaMissing;
    } else if (!hasToken && hasCaptcha) {
      error = errors.tokenMissing;
    }
  }

  if (error) {
    this.emit(events.error, new Error(error));
    output = false;
  } else {
    output = require('querystring').stringify(output);
  }

  return output;
};

/**
 * Logs in the user
 * @method login
 * @param {Object} params (optional) You can pass the logincaptcha and
 * logintoken and the accountType as properties
 */
GoogleClientLogin.prototype.login = function (params) {
  // don't try to log in, if one is already in progress
  if (!this.loginProcessing) {
    this.loginProcessing = true;

    var content, request;

    content = this._getRequestContent(params);

    if (content !== false) {
      request = require('https').request(
        {
          host: 'www.google.com',
          port: 443,
          path: loginURL,
          method: 'POST',
          headers: {
            'Content-Length': content.length,
            'Content-Type': 'application/x-www-form-urlencoded'
          }
        },
        this._parseLoginResponse.bind(this)
      );
      request.write(content);
      request.end();
    }
  }
};

/**
 * Method to get the AuthId property
 * @method getAuthId
 * @returns the AuthId or undefined
 */
GoogleClientLogin.prototype.getAuthId = function () {
  return this.auths.Auth;
};

/**
 * Method to ge the SID property
 * @method getSID
 * @returns the value of the SID property or undefined
 */
GoogleClientLogin.prototype.getSID = function () {
  return this.auths.SID;
};

/**
 * Method to get the LSID property
 * @method getLSID
 * @returns the value of the LSID property or undefined
 */
GoogleClientLogin.prototype.getLSID = function () {
  return this.auths.LSID;
};

/**
 * Method to get the error code
 * @method getError
 * @returns the error code or undefined
 */
GoogleClientLogin.prototype.getError = function () {
  return this.auths.Error;
};

/**
 * Method to know if captcha is required
 * @method isCaptchaRequired
 * @returns boolean
 */
GoogleClientLogin.prototype.isCaptchaRequired = function () {
  return this.getError() === captchaRequiredError;
};

/**
 * Method to get the captcha url
 * @method getCaptchaUrl
 * @returns the value of the CaptchaUrl property or undefined
 */
GoogleClientLogin.prototype.getCaptchaUrl = function () {
  return this.auths.CaptchaUrl;
};

/**
  * Returns the value of the CaptchaToken property
  * @method getCaptchaToken
 * @returns string or undefined
  */
GoogleClientLogin.prototype.getCaptchaToken = function () {
  return this.auths.CaptchaToken;
};

GoogleClientLogin.errors = errors;
GoogleClientLogin.events = events;
GoogleClientLogin.accountTypes = accountTypes;

exports.GoogleClientLogin = GoogleClientLogin;
