// Copyright 2012 Google Inc. All Rights Reserved.

/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @author
 * Shan Aminzadeh, shan.aminzadeh@gmail.com
 * Arya Bondarian, aryabond@gmail.com
 * Albert Gau, agau@uci.edu
 * Travis Lai, travisrlai@gmail.com
 * Daniel Nguyen, danielnuwin@gmail.com
 * Nick Mihailovski, api.nickm@gmail.com
 *
 * @fileoverview
 * This library is designed to create an easier way to build a custom
 * Google Analytics Dashboard by visualizing data from Google Analytics
 * API with the Google Chart Tools.
 */


// Loads the core chart and table from the Google Visualization.
google.load('visualization', '1', {'packages': ['corechart', 'table']});


// Create namespace for this library if not already created.
var gadash = gadash || {};


// Namespace for util object. Contains lots of library utilities.
gadash.util = gadash.util || {};


/**
 * Refers to the Google Analytics API scope that the user will need
 * authentication for.
 * @const {String}
 */
gadash.SCOPE = 'https://www.googleapis.com/auth/analytics.readonly';


/**
 * List of functions that are queued for execution. This is only used
 * until all the libraries have fully loaded.
 * @type {Array}
 */
gadash.commandQueue = [];


/**
 * Callback executed once the Google APIs Javascript client library has loaded.
 * The function name is specified in the onload query parameter of URL to load
 * this library. After 1 millisecond, checkAuth is called.
 */
window.gadashInit = function() {
  gapi.client.setApiKey(gadash.apiKey);
  window.setTimeout(gadash.checkAuth, 1);
  gadash.initCallback();
};


gadash.initCallback = function() {
	
	
}

/**
 * Sets the API key and Client ID passed by the user.
 * This information can be found in your Google API Console.
 * @param {Object} settings - Contains the API Key and Client ID variables.
 */
gadash.configKeys = function(settings) {
  gadash.apiKey = settings.apiKey;
  gadash.clientId = settings.clientId;
  gadash.tableId = settings.tableId;
  gadash.authorizeButton = settings.authorizeButton || 'authorize-button';
};


/**
 * Uses the OAuth2.0 clientId to query the Google Accounts service
 * to see if the user has authorized. Once complete, handleAuthResults is
 * called.
 */
gadash.checkAuth = function() {
  gapi.auth.authorize({
    client_id: gadash.clientId,
    scope: gadash.SCOPE,
    immediate: true}, gadash.handleAuthResult);
};


/**
 * Handler that is called once the script has checked to see if the user has
 * authorized access to their Google Analytics data. If the user has authorized
 * access, the analytics api library is loaded and the handleAuthorized
 * function is executed. If the user has not authorized access to their data,
 * the handleUnauthorized function is executed.
 * @param {Object} authResult The result object returned form the authorization
 *     service that determine whether the user has currently authorized access
 *     to their data. If it exists, the user has authorized access.
 */
gadash.handleAuthResult = function(authResult) {
  if (authResult) {
    gapi.client.setApiVersions({'analytics': 'v3'});
    gapi.client.load('analytics', 'v3', gadash.handleAuthorized);
  } else {
    gadash.handleUnAuthorized();
  }
};


/**
 * Updates the UI once the user has authorized this script to access their
 * data by hiding the authorize button. Also, runs executeCommandQueue
 * function to render all charts in the commandQueue. The execution of the
 * command queue only happens once.
 */
gadash.handleAuthorized = function() {
  var authorizeButton = document.getElementById(gadash.authorizeButton);
  authorizeButton.style.visibility = 'hidden';

  gadash.executeCommandQueue();
  gadash.onAuthorized();
  
};

/**
 * Callback when the user is authorized
 */
gadash.onAuthorized = function() {
	
	
}

/**
 * Updates the UI if a user has not yet authorized this script to access
 * their Google Analytics data. This function changes the visibility of
 * some elements on the screen. It also adds the handleAuthClick
 * click handler to the authorize-button.
 */
gadash.handleUnAuthorized = function() {
  var authorizeButton = document.getElementById(gadash.authorizeButton);
  authorizeButton.style.visibility = '';
  authorizeButton.onclick = gadash.handleAuthClick;
  gadash.onUnauthorized();
};

/**
 * Callback when the user is not authorized
 */
gadash.onUnauthorized = function() {
	
	
}

/**
 * Checks to see if user is authenticated, calls handleAuthResult
 * @return {boolean} false.
 * @param {Object} event - event when button is clicked.
 */
gadash.handleAuthClick = function(event) {
  gapi.auth.authorize({
    client_id: gadash.clientId,
    scope: gadash.SCOPE,
    immediate: false}, gadash.handleAuthResult);
  return false;
};


/**
 * Iterates through all commands on the commandQueue and executes them.
 */
gadash.executeCommandQueue = function() {
  for (var i = 0, command; command = gadash.commandQueue[i]; ++i) {
    command();
  }
};


/**
 * A Chart object is the primary object in this library.
 * A Chart accepts an optional configuration object that contains all the
 * parameters of the chart. Also changes start and end date of
 * the query, if last-n-days is set in the config.
 * @param {?Object} config - Contains all configuration variables
 *     of a Chart object. This parameter is passed by value, and a deep
 *     copy is made. Once set, the original object can be modified and
 *     it will not affect this object.
 * @return {Object} this Returns a reference to the newly instantiated
 *     Chart instance. Useful for chaining methods together.
 * @constructor
 */
gadash.Chart = function(opt_config) {
  /**
   * The main configuration object.
   * @type {Object}
   */
  this.config = {};

  if (opt_config) {
    gadash.util.extend(opt_config, this.config);
  }

  return this;
};


/**
 * Extends the values in the chart's config object with the keys in
 * the config parameters. If a key in config already exists in the chart,
 * and the value is not an object, the new value overwrites the old. 
 * @param {Object} config The config object to set inside this object.
 * @return {Object} The current instance of the Chart object. Useful
 *     for chaining methods.
 */
gadash.Chart.prototype.set = function(config) {
  gadash.util.extend(config, this.config);
  return this;
};


/**
 * First checks to see if the GA library is loaded. If it is then the
 * chart can be rendered right away. Otherwise, other operations are queued,
 * so the render command is pushed to the command queue to be executed in
 * the same order as originally called.
 * @this Points to the current chart instance.
 * @return {Object} The current instance of this chart object. Useful for
 *     chaining methods.
 */
gadash.Chart.prototype.render = function() {

  // If the client library has loaded.
  if (gapi.client.analytics) {
    this.renderFunction();
  } else {
    var renderFunction = gadash.util.bindMethod(this, this.renderFunction);
    gadash.commandQueue.push(renderFunction);
  }

  return this;
};


/**
 * Makes a request to the Google Analytics API.
 * Updates the start and end date if last-n-days
 * has been set. The function also creates and executes a Google Analytics
 * API request using the Chart objects callback method. The callback
 * is bound to the Chart instance so a reference back to this chart is
 * maintained within the callback.
 */
gadash.Chart.prototype.renderFunction = function() {

  // Update the start and end dates based on last n days.
  if (this.config['last-n-days']) {
    this.config.query['end-date'] = gadash.util.lastNdays(0);
    this.config.query['start-date'] =
        gadash.util.lastNdays(this.config['last-n-days']);
  }
  var request = gapi.client.analytics.data.ga.get(this.config.query);
  request.execute(gadash.util.bindMethod(this, this.callback));
}



/**
 * Callback function that is called after a GA query is executed.
 * First, the function checks to see if there are any errors on the
 * response. Then check to see if a onSuccess function was declared
 * in the config. If present, call onSuccess by first binding it to
 * this (ie this chart object instance). If not defined, just use
 * the default callback. The entire JSON response from the API
 * is passed to either defined or default callback.
 * @param {Object} response - Google Analytics API JSON response.
 */
gadash.Chart.prototype.callback = function(response) {
  if (response.error) {
    this.defaultOnError(response.error.code + ' ' + response.error.message);
  } else {

    if (this.config.onSuccess) {
      gadash.util.bindMethod(this, this.config.onSuccess)(response);
    } else {
      this.defaultOnSuccess(response);
    }
  }
};


/**
 * Checks to see if onError parameter is set in config. If it is,
 * use the user defined error function else check to see if an error
 * div is created. If not, create an error div. Print error message
 * to the error div.
 * @param {String} message - error message to print.
 */
gadash.Chart.prototype.defaultOnError = function(message) {

  // If onError param exists, use that as error handling function.
  if (this.config.onError) {
    this.config.onError(message);
  } else {

    var errorDiv = document.getElementById('errors');

    // Create error div if not already made.
    if (!errorDiv) {
      errorDiv = document.createElement('div');
      errorDiv.style.color = 'red';
      errorDiv.setAttribute('id', 'errors');
      errorDiv.innerHTML = 'ERRORS:' + '<br />';
      document.body.appendChild(errorDiv);
    }

    // Prints chart divContainer and message to error div.
    errorDiv.innerHTML += this.config.divContainer + ' error: ' +
        message + '<br />';
  }
};


/**
 * Default callback for creating Google Charts with a response. First, the
 * response is put into a DataTable object Second, the corresponding chart
 * is returned. The two are then combined to draw a chart that is populated
 * with the GA data.
 * @param {Object} resp - A Google Analytics API JSON response.
 */
gadash.Chart.prototype.defaultOnSuccess = function(resp) {
  var dataTable = gadash.util.getDataTable(resp);
  var chart = gadash.util.getChart(this.config.divContainer, this.config.type);
  gadash.util.draw(chart, dataTable, this.config.chartOptions);
  gadash.onSuccess();
};

/**
 * Callback when a chart has been rendered
 */
gadash.onSuccess = function()
{
	
}

/**
 * Creates a DataTable object using a GA response.
 * @param {Object} resp - A Google Analytics response.
 * @param {?String} opt_chartType - The chart type. Provides a hint on
 *     how to parse the API results into a data table.
 * @return {Object} data - A Google DataTable object populated
 *     with the GA response data.
 */
gadash.util.getDataTable = function(resp, opt_chartType) {

  var chartType = opt_chartType || false;

  var data = new google.visualization.DataTable();
  var numOfColumns = resp.columnHeaders.length;
  var numOfRows;

  // Throw an error if there are no rows returned.
  if (resp.rows && resp.rows.length) {
    numOfRows = resp.rows.length;
  } else {
    this.defaultOnError('No rows returned for that query.');
  }

  /*
   * Looks at the resp column headers to set names and types for each column.
   * Since bar and column chart don't support date object, set type as string
   * rather than a Date.
   */
  for (var i = 0; i < numOfColumns; i++) {
    var dataType = resp.columnHeaders[i].dataType;
    var name = resp.columnHeaders[i].name;

    if (name == 'ga:date' &&
        !(chartType == 'ColumnChart' || chartType == 'BarChart')) {

      dataType = 'date';
    } else if (dataType == 'STRING') {
      dataType = 'string';
    } else {
      dataType = 'number';
    }
    data.addColumn(dataType, gadash.util.formatGAString(name));
  }

  /*
   * Populates the rows by using the resp.rows array. If the type
   * is an int then parse the INT. If it is a percent, then round
   * to last two decimal places and store as INT.
   */
  for (var i = 0; i < numOfRows; i++) {
    var arrayMetrics = [];
    for (var j = 0; j < numOfColumns; j++) {
      var name = resp.columnHeaders[j].name;
      var dataType = resp.columnHeaders[j].dataType;

      if (name == 'ga:date' &&
          !(chartType == 'ColumnChart' || chartType == 'BarChart')) {

        arrayMetrics.push(gadash.util.stringToDate(resp.rows[i][j]));
      } else if (dataType == 'INTEGER') {
        arrayMetrics.push(parseInt(resp.rows[i][j]));
      } else if (dataType == 'PERCENT' || dataType == 'TIME' ||
          dataType == 'FLOAT') {
        arrayMetrics.push(Math.round((resp.rows[i][j]) * 100) / 100);
      } else {
        arrayMetrics.push(resp.rows[i][j]);
      }
    }
    data.addRow(arrayMetrics);
  }

  return data;
};


/**
 * Checks to see if the type of chart in the config is valid.
 * If it is, get its chart instance, else return a Table instance.
 * @param {String} id The ID of the HTML element in which to render
 *     the chart.
 * @param {String} chartType The type of the Chart to render.
 * @return {Object} visualization - returns the Chart instance.
 */
gadash.util.getChart = function(id, chartType) {
  var elem = document.getElementById(id);

  if (google.visualization[chartType]) {
    return new google.visualization[chartType](elem);
  }

  return new google.visualization.Table(elem);
};


/**
 * Draws a chart to its declared div using a DataTable.
 * @param {Object} chart - The Chart instance you wish to draw the data into.
 * @param {Object} dataTable - The Google DataTable object holding
 *     the response data.
 * @param {Object} options - The optional configuration parameters to pass
 *     into the chart.
 */
gadash.util.draw = function(chart, dataTable, chartOptions) {
  chart.draw(dataTable, chartOptions);
};


/**
 * Binds a method to its object.
 * @param {Object} object The main object to bind to.
 * @param {Object} method The method to bind to the object.
 * @return {function} the function passed in boound to the object parameter.
 */
gadash.util.bindMethod = function(object, method) {
  return function() {
    return method.apply(object, arguments);
  };
};


/**
 * Utility method to return the lastNdays from today in the format yyyy-MM-dd.
 * @param {Number} n The number of days in the past from tpday that we should
 *     return a date. Value of 0 returns today.
 * @return {String} date - The adjusted date value represented as a String.
 */
gadash.util.lastNdays = function(n) {
  var today = new Date();
  var before = new Date();
  before.setDate(today.getDate() - n);

  var year = before.getFullYear();

  var month = before.getMonth() + 1;
  if (month < 10) {
    month = '0' + month;
  }

  var day = before.getDate();
  if (day < 10) {
    day = '0' + day;
  }

  return [year, month, day].join('-');
};


/**
 * Utility method to return Date from a String in the format yyyy-MM-dd.
 * This function is used for a Chart that has a Time Series.
 * @param {String} date - The String representation of the date.
 * @return {Date} date - Corresponding JS Date object.
 */
gadash.util.stringToDate = function(date) {
  var year = date.substring(0, 4);
  var month = date.substring(4, 6);
  var day = date.substring(6, 8);

  if (month < 10) {
    month = month.substring(1, 2);
  }

  month = month - 1;

  if (day < 10) {
    day = day.substring(1, 2);
  }

  var dateObj = new Date(year, month, day);
  return dateObj;
};


/**
 * Formats the Google Metrics and Dimensions into readable strings
 * Strips away the 'ga' and capitalizes first letter. Also puts a space
 * between any lowercase and capital letters.
 * ie: "ga:percentNewVisits" ---> "Percent New Visits"
 * @param {String} gaString - the String name of Metric/Dimension from GA.
 * @return {String} newString - Metric/Dimension formatted nicely.
 */
gadash.util.formatGAString = function(gaString) {
  var newString = gaString.substring(3);
  newString = newString.charAt(0).toUpperCase() + newString.slice(1);

  // Check for a capital letter in the string. If found,
  // put a space between that char and the char before it.
  for (var i = 1; i < newString.length; i++) {
    if (newString.charAt(i) == newString.charAt(i).toUpperCase()) {
      var left = newString.substring(0, i);
      var right = newString.substring(i, newString.length);
      newString = [left, right].join(' ');
      i++;
    }
  }

  return newString;
};


/**
 * Recursively copies the values in the from object into the to object.
 * If a key in from object already exists, and has child values,
 * the child values are copied over. So:
 *     extend({'a': {'b': 2}}, {'a': {'c': 1}}) will result in:
 *     {'a': {'b': 2, 'c': 1}}
 * Once run, modifying the from object will not impact the to object.
 * NOTE: Arrays will write over each other.
 * NOTE: This is unsafe in that circular references are not checked. Calling
 * this method with a circular reference could cause an infinite loop.
 * @param {Object} from The object to copy values from.
 * @param {Object} to The object to copy values into.
 */
gadash.util.extend = function(from, to) {
  for (var key in from) {
    var type = gadash.util.getType(from[key]);
    if (type == 'object') {
      to[key] = to[key] || {};
      gadash.util.extend(from[key], to[key]);
    } else {
      to[key] = from[key];
    }
  }
}


/**
 * Returns the native type (class property) of this object.
 * General idea grabbed from here: http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
 * Per ECMA-262:
 *
 *     15.2.4.2 Object.prototype.toString ( )
 *     When the toString method is called, the following steps are taken:
 *     1. Get the [[Class]] property of this object.
 *     2. Compute a string value by concatenating the three
 *        strings "[object ", | Result(1), and "]".
 *     3. Return Result(2).
 *
 * @param {Object} value Any type.
 * @returns {String} The lower class property of the object. Undefined if value
 *     is undefined or null.
 */
gadash.util.getType = function(value) {
  var classStringName = Object.prototype.toString.call(value);
  return ({
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regex',
    '[object Object]' : 'object',
  })[classStringName];
}

