// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview
 * A class of server log entries.
 */

'use strict';

/** @suppress {duplicate} */
var remoting = remoting || {};

/**
 * @private
 * @constructor
 */
remoting.ServerLogEntry = function() {
  /** @type Object.<string, string> */ this.dict = {};
};

/** @private */
remoting.ServerLogEntry.KEY_EVENT_NAME_ = 'event-name';
/** @private */
remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_STATE_ =
    'session-state';

/** @private */
remoting.ServerLogEntry.KEY_SESSION_ID_ = 'session-id';

/** @private */
remoting.ServerLogEntry.KEY_ROLE_ = 'role';
/** @private */
remoting.ServerLogEntry.VALUE_ROLE_CLIENT_ = 'client';

/** @private */
remoting.ServerLogEntry.KEY_SESSION_STATE_ = 'session-state';

/**
 * @private
 * @param {remoting.ClientSession.State} state
 * @return {string}
 */
remoting.ServerLogEntry.getValueForSessionState = function(state) {
  switch(state) {
    case remoting.ClientSession.State.UNKNOWN:
      return 'unknown';
    case remoting.ClientSession.State.CREATED:
      return 'created';
    case remoting.ClientSession.State.CONNECTING:
      return 'connecting';
    case remoting.ClientSession.State.INITIALIZING:
      return 'initializing';
    case remoting.ClientSession.State.CONNECTED:
      return 'connected';
    case remoting.ClientSession.State.CLOSED:
      return 'closed';
    case remoting.ClientSession.State.FAILED:
      return 'connection-failed';
    case remoting.ClientSession.State.CONNECTION_DROPPED:
      return 'connection-dropped';
    case remoting.ClientSession.State.CONNECTION_CANCELED:
      return 'connection-canceled';
    default:
      return 'undefined-' + state;
  }
};

/** @private */
remoting.ServerLogEntry.KEY_CONNECTION_ERROR_ = 'connection-error';

/**
 * @private
 * @param {remoting.Error} connectionError
 * @return {string}
 */
remoting.ServerLogEntry.getValueForError =
    function(connectionError) {
  switch(connectionError) {
    case remoting.Error.NONE:
      return 'none';
    case remoting.Error.INVALID_ACCESS_CODE:
      return 'invalid-access-code';
    case remoting.Error.MISSING_PLUGIN:
      return 'missing_plugin';
    case remoting.Error.AUTHENTICATION_FAILED:
      return 'authentication-failed';
    case remoting.Error.HOST_IS_OFFLINE:
      return 'host-is-offline';
    case remoting.Error.INCOMPATIBLE_PROTOCOL:
      return 'incompatible-protocol';
    case remoting.Error.BAD_PLUGIN_VERSION:
      return 'bad-plugin-version';
    case remoting.Error.NETWORK_FAILURE:
      return 'network-failure';
    case remoting.Error.HOST_OVERLOAD:
      return 'host-overload';
    case remoting.Error.P2P_FAILURE:
      return 'p2p-failure';
    case remoting.Error.UNEXPECTED:
      return 'unexpected';
    default:
      return 'unknown-' + connectionError;
  }
};

/** @private */
remoting.ServerLogEntry.KEY_SESSION_DURATION_ = 'session-duration';

/** @private */
remoting.ServerLogEntry.VALUE_EVENT_NAME_CONNECTION_STATISTICS_ =
    "connection-statistics";
/** @private */
remoting.ServerLogEntry.KEY_VIDEO_BANDWIDTH_ = "video-bandwidth";
/** @private */
remoting.ServerLogEntry.KEY_CAPTURE_LATENCY_ = "capture-latency";
/** @private */
remoting.ServerLogEntry.KEY_ENCODE_LATENCY_ = "encode-latency";
/** @private */
remoting.ServerLogEntry.KEY_DECODE_LATENCY_ = "decode-latency";
/** @private */
remoting.ServerLogEntry.KEY_RENDER_LATENCY_ = "render-latency";
/** @private */
remoting.ServerLogEntry.KEY_ROUNDTRIP_LATENCY_ = "roundtrip-latency";

/** @private */
remoting.ServerLogEntry.KEY_OS_NAME_ = 'os-name';
/** @private */
remoting.ServerLogEntry.VALUE_OS_NAME_WINDOWS_ = 'Windows';
/** @private */
remoting.ServerLogEntry.VALUE_OS_NAME_LINUX_ = 'Linux';
/** @private */
remoting.ServerLogEntry.VALUE_OS_NAME_MAC_ = 'Mac';
/** @private */
remoting.ServerLogEntry.VALUE_OS_NAME_CHROMEOS_ = 'ChromeOS';

/** @private */
remoting.ServerLogEntry.KEY_OS_VERSION_ = 'os-version';

/** @private */
remoting.ServerLogEntry.KEY_CPU_ = 'cpu';

/** @private */
remoting.ServerLogEntry.KEY_BROWSER_VERSION_ = 'browser-version';

/** @private */
remoting.ServerLogEntry.KEY_WEBAPP_VERSION_ = 'webapp-version';

/** @private */
remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_ID_OLD_ = 'session-id-old';

/** @private */
remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_ID_NEW_ = 'session-id-new';

/** @private */
remoting.ServerLogEntry.KEY_MODE_ = 'mode';
/** @private */
remoting.ServerLogEntry.VALUE_MODE_IT2ME_ = 'it2me';
/** @private */
remoting.ServerLogEntry.VALUE_MODE_ME2ME_ = 'me2me';
/** @private */
remoting.ServerLogEntry.VALUE_MODE_UNKNOWN_ = 'unknown';

/**
 * Sets one field in this log entry.
 *
 * @private
 * @param {string} key
 * @param {string} value
 */
remoting.ServerLogEntry.prototype.set = function(key, value) {
  this.dict[key] = value;
};

/**
 * Converts this object into an XML stanza.
 *
 * @return {string}
 */
remoting.ServerLogEntry.prototype.toStanza = function() {
  var stanza = '<gr:entry ';
  for (var key in this.dict) {
    stanza += escape(key) + '="' + escape(this.dict[key]) + '" ';
  }
  stanza += '/>';
  return stanza;
};

/**
 * Prints this object on the debug log.
 *
 * @param {number} indentLevel the indentation level
 */
remoting.ServerLogEntry.prototype.toDebugLog = function(indentLevel) {
  /** @type Array.<string> */ var fields = [];
  for (var key in this.dict) {
    fields.push(key + ': ' + this.dict[key]);
  }
  console.log(Array(indentLevel+1).join("  ") + fields.join(', '));
};

/**
 * Makes a log entry for a change of client session state.
 *
 * @param {remoting.ClientSession.State} state
 * @param {remoting.Error} connectionError
 * @param {remoting.ClientSession.Mode} mode
 * @return {remoting.ServerLogEntry}
 */
remoting.ServerLogEntry.makeClientSessionStateChange = function(state,
    connectionError, mode) {
  var entry = new remoting.ServerLogEntry();
  entry.set(remoting.ServerLogEntry.KEY_ROLE_,
            remoting.ServerLogEntry.VALUE_ROLE_CLIENT_);
  entry.set(remoting.ServerLogEntry.KEY_EVENT_NAME_,
            remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_STATE_);
  entry.set(remoting.ServerLogEntry.KEY_SESSION_STATE_,
            remoting.ServerLogEntry.getValueForSessionState(state));
  if (connectionError != remoting.Error.NONE) {
    entry.set(remoting.ServerLogEntry.KEY_CONNECTION_ERROR_,
              remoting.ServerLogEntry.getValueForError(connectionError));
  }
  entry.addModeField(mode);
  return entry;
};

/**
 * Adds a session duration to a log entry.
 *
 * @param {number} sessionDuration
 */
remoting.ServerLogEntry.prototype.addSessionDurationField = function(
    sessionDuration) {
  this.set(remoting.ServerLogEntry.KEY_SESSION_DURATION_,
      sessionDuration.toString());
};

/**
 * Makes a log entry for a set of connection statistics.
 * Returns null if all the statistics were zero.
 *
 * @param {remoting.StatsAccumulator} statsAccumulator
 * @param {remoting.ClientSession.Mode} mode
 * @return {?remoting.ServerLogEntry}
 */
remoting.ServerLogEntry.makeStats = function(statsAccumulator, mode) {
  var entry = new remoting.ServerLogEntry();
  entry.set(remoting.ServerLogEntry.KEY_ROLE_,
            remoting.ServerLogEntry.VALUE_ROLE_CLIENT_);
  entry.set(remoting.ServerLogEntry.KEY_EVENT_NAME_,
            remoting.ServerLogEntry.VALUE_EVENT_NAME_CONNECTION_STATISTICS_);
  entry.addModeField(mode);
  var nonZero = false;
  nonZero |= entry.addStatsField(
      remoting.ServerLogEntry.KEY_VIDEO_BANDWIDTH_,
      remoting.ClientSession.STATS_KEY_VIDEO_BANDWIDTH, statsAccumulator);
  nonZero |= entry.addStatsField(
      remoting.ServerLogEntry.KEY_CAPTURE_LATENCY_,
      remoting.ClientSession.STATS_KEY_CAPTURE_LATENCY, statsAccumulator);
  nonZero |= entry.addStatsField(
      remoting.ServerLogEntry.KEY_ENCODE_LATENCY_,
      remoting.ClientSession.STATS_KEY_ENCODE_LATENCY, statsAccumulator);
  nonZero |= entry.addStatsField(
      remoting.ServerLogEntry.KEY_DECODE_LATENCY_,
      remoting.ClientSession.STATS_KEY_DECODE_LATENCY, statsAccumulator);
  nonZero |= entry.addStatsField(
      remoting.ServerLogEntry.KEY_RENDER_LATENCY_,
      remoting.ClientSession.STATS_KEY_RENDER_LATENCY, statsAccumulator);
  nonZero |= entry.addStatsField(
      remoting.ServerLogEntry.KEY_ROUNDTRIP_LATENCY_,
      remoting.ClientSession.STATS_KEY_ROUNDTRIP_LATENCY, statsAccumulator);
  if (nonZero) {
    return entry;
  }
  return null;
};

/**
 * Adds one connection statistic to a log entry.
 *
 * @private
 * @param {string} entryKey
 * @param {string} statsKey
 * @param {remoting.StatsAccumulator} statsAccumulator
 * @return {boolean} whether the statistic is non-zero
 */
remoting.ServerLogEntry.prototype.addStatsField = function(
    entryKey, statsKey, statsAccumulator) {
  var val = statsAccumulator.calcMean(statsKey);
  this.set(entryKey, val.toFixed(2));
  return (val != 0);
};

/**
 * Makes a log entry for a "this session ID is old" event.
 *
 * @param {string} sessionId
 * @param {remoting.ClientSession.Mode} mode
 * @return {remoting.ServerLogEntry}
 */
remoting.ServerLogEntry.makeSessionIdOld = function(sessionId, mode) {
  var entry = new remoting.ServerLogEntry();
  entry.set(remoting.ServerLogEntry.KEY_ROLE_,
            remoting.ServerLogEntry.VALUE_ROLE_CLIENT_);
  entry.set(remoting.ServerLogEntry.KEY_EVENT_NAME_,
            remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_ID_OLD_);
  entry.addSessionIdField(sessionId);
  entry.addModeField(mode);
  return entry;
};

/**
 * Makes a log entry for a "this session ID is new" event.
 *
 * @param {string} sessionId
 * @param {remoting.ClientSession.Mode} mode
 * @return {remoting.ServerLogEntry}
 */
remoting.ServerLogEntry.makeSessionIdNew = function(sessionId, mode) {
  var entry = new remoting.ServerLogEntry();
  entry.set(remoting.ServerLogEntry.KEY_ROLE_,
            remoting.ServerLogEntry.VALUE_ROLE_CLIENT_);
  entry.set(remoting.ServerLogEntry.KEY_EVENT_NAME_,
            remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_ID_NEW_);
  entry.addSessionIdField(sessionId);
  entry.addModeField(mode);
  return entry;
};

/**
 * Adds a session ID field to this log entry.
 *
 * @param {string} sessionId
 */
remoting.ServerLogEntry.prototype.addSessionIdField = function(sessionId) {
  this.set(remoting.ServerLogEntry.KEY_SESSION_ID_, sessionId);
}

/**
 * Adds fields describing the host to this log entry.
 */
remoting.ServerLogEntry.prototype.addHostFields = function() {
  var host = remoting.ServerLogEntry.getHostData();
  if (host) {
    if (host.os_name.length > 0) {
      this.set(remoting.ServerLogEntry.KEY_OS_NAME_, host.os_name);
    }
    if (host.os_version.length > 0) {
      this.set(remoting.ServerLogEntry.KEY_OS_VERSION_, host.os_version);
    }
    if (host.cpu.length > 0) {
      this.set(remoting.ServerLogEntry.KEY_CPU_, host.cpu);
    }
  }
};

/**
 * Extracts host data from the userAgent string.
 *
 * @private
 * @return {{os_name:string, os_version:string, cpu:string} | null}
 */
remoting.ServerLogEntry.getHostData = function() {
  return remoting.ServerLogEntry.extractHostDataFrom(navigator.userAgent);
};

/**
 * Extracts host data from the given userAgent string.
 *
 * @private
 * @param {string} s
 * @return {{os_name:string, os_version:string, cpu:string} | null}
 */
remoting.ServerLogEntry.extractHostDataFrom = function(s) {
  // Sample userAgent strings:
  // 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.2 ' +
  //   '(KHTML, like Gecko) Chrome/15.0.874.106 Safari/535.2'
  // 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.8 ' +
  //   '(KHTML, like Gecko) Chrome/17.0.933.0 Safari/535.8'
  // 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.1 ' +
  //   '(KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1'
  // 'Mozilla/5.0 (X11; CrOS i686 14.811.154) AppleWebKit/535.1 ' +
  //   '(KHTML, like Gecko) Chrome/14.0.835.204 Safari/535.1'
  var match = new RegExp('Windows NT ([0-9\\.]*)').exec(s);
  if (match && (match.length >= 2)) {
    return {
        'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_WINDOWS_,
        'os_version': match[1],
        'cpu': ''
    };
  }
  match = new RegExp('Linux ([a-zA-Z0-9_]*)').exec(s);
  if (match && (match.length >= 2)) {
    return {
        'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_LINUX_,
        'os_version' : '',
        'cpu': match[1]
    };
  }
  match = new RegExp('([a-zA-Z]*) Mac OS X ([0-9_]*)').exec(s);
  if (match && (match.length >= 3)) {
    return {
        'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_MAC_,
        'os_version': match[2].replace(/_/g, '.'),
        'cpu': match[1]
    };
  }
  match = new RegExp('CrOS ([a-zA-Z0-9]*) ([0-9.]*)').exec(s);
  if (match && (match.length >= 3)) {
    return {
        'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_CHROMEOS_,
        'os_version': match[2],
        'cpu': match[1]
    };
  }
  return null;
};

/**
 * Adds a field specifying the browser version to this log entry.
 */
remoting.ServerLogEntry.prototype.addChromeVersionField = function() {
  var version = remoting.ServerLogEntry.getChromeVersion();
  if (version != null) {
    this.set(remoting.ServerLogEntry.KEY_BROWSER_VERSION_, version);
  }
};

/**
 * Extracts the Chrome version from the userAgent string.
 *
 * @private
 * @return {string | null}
 */
remoting.ServerLogEntry.getChromeVersion = function() {
  return remoting.ServerLogEntry.extractChromeVersionFrom(navigator.userAgent);
};

/**
 * Extracts the Chrome version from the given userAgent string.
 *
 * @private
 * @param {string} s
 * @return {string | null}
 */
remoting.ServerLogEntry.extractChromeVersionFrom = function(s) {
  var match = new RegExp('Chrome/([0-9.]*)').exec(s);
  if (match && (match.length >= 2)) {
    return match[1];
  }
  return null;
};

/**
 * Adds a field specifying the webapp version to this log entry.
 */
remoting.ServerLogEntry.prototype.addWebappVersionField = function() {
  var manifest = chrome.runtime.getManifest();
  if (manifest && manifest.version) {
    this.set(remoting.ServerLogEntry.KEY_WEBAPP_VERSION_, manifest.version);
  }
};

/**
 * Adds a field specifying the mode to this log entry.
 *
 * @param {remoting.ClientSession.Mode} mode
 */
remoting.ServerLogEntry.prototype.addModeField = function(mode) {
  this.set(remoting.ServerLogEntry.KEY_MODE_,
      remoting.ServerLogEntry.getModeField(mode));
};

/**
 * Gets the value of the mode field to be put in a log entry.
 *
 * @private
 * @param {remoting.ClientSession.Mode} mode
 * @return {string}
 */
remoting.ServerLogEntry.getModeField = function(mode) {
  switch(mode) {
    case remoting.ClientSession.Mode.IT2ME:
      return remoting.ServerLogEntry.VALUE_MODE_IT2ME_;
    case remoting.ClientSession.Mode.ME2ME:
      return remoting.ServerLogEntry.VALUE_MODE_ME2ME_;
    default:
      return remoting.ServerLogEntry.VALUE_MODE_UNKNOWN_;
  }
};
