// Copyright 2013 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.

/**
 * A global object that gets used by the C++ interface.
 */
var media = (function() {
  'use strict';

  var manager = null;

  // A number->string mapping that is populated through the backend that
  // describes the phase that the network entity is in.
  var eventPhases = {};

  // A number->string mapping that is populated through the backend that
  // describes the type of event sent from the network.
  var eventTypes = {};

  // A mapping of number->CacheEntry where the number is a unique id for that
  // network request.
  var cacheEntries = {};

  // A mapping of url->CacheEntity where the url is the url of the resource.
  var cacheEntriesByKey = {};

  var requrestURLs = {};

  var media = {
    BAR_WIDTH: 200,
    BAR_HEIGHT: 25
  };

  /**
   * Users of |media| must call initialize prior to calling other methods.
   */
  media.initialize = function(theManager) {
    manager = theManager;
  };

  media.onReceiveEverything = function(everything) {
    for (var component in everything) {
      media.updateAudioComponent(everything[component]);
    }
  };

  media.onReceiveConstants = function(constants) {
    for (var key in constants.eventTypes) {
      var value = constants.eventTypes[key];
      eventTypes[value] = key;
    }

    for (var key in constants.eventPhases) {
      var value = constants.eventPhases[key];
      eventPhases[value] = key;
    }
  };

  media.cacheForUrl = function(url) {
    return cacheEntriesByKey[url];
  };

  media.onNetUpdate = function(updates) {
    updates.forEach(function(update) {
      var id = update.source.id;
      if (!cacheEntries[id])
        cacheEntries[id] = new media.CacheEntry;

      switch (eventPhases[update.phase] + '.' + eventTypes[update.type]) {
        case 'PHASE_BEGIN.DISK_CACHE_ENTRY_IMPL':
          var key = update.params.key;

          // Merge this source with anything we already know about this key.
          if (cacheEntriesByKey[key]) {
            cacheEntriesByKey[key].merge(cacheEntries[id]);
            cacheEntries[id] = cacheEntriesByKey[key];
          } else {
            cacheEntriesByKey[key] = cacheEntries[id];
          }
          cacheEntriesByKey[key].key = key;
          break;

        case 'PHASE_BEGIN.SPARSE_READ':
          cacheEntries[id].readBytes(update.params.offset,
                                      update.params.buff_len);
          cacheEntries[id].sparse = true;
          break;

        case 'PHASE_BEGIN.SPARSE_WRITE':
          cacheEntries[id].writeBytes(update.params.offset,
                                       update.params.buff_len);
          cacheEntries[id].sparse = true;
          break;

        case 'PHASE_BEGIN.URL_REQUEST_START_JOB':
          requrestURLs[update.source.id] = update.params.url;
          break;

        case 'PHASE_NONE.HTTP_TRANSACTION_READ_RESPONSE_HEADERS':
          // Record the total size of the file if this was a range request.
          var range = /content-range:\s*bytes\s*\d+-\d+\/(\d+)/i.exec(
              update.params.headers);
          var key = requrestURLs[update.source.id];
          delete requrestURLs[update.source.id];
          if (range && key) {
            if (!cacheEntriesByKey[key]) {
              cacheEntriesByKey[key] = new media.CacheEntry;
              cacheEntriesByKey[key].key = key;
            }
            cacheEntriesByKey[key].size = range[1];
          }
          break;
      }
    });
  };

  media.onRendererTerminated = function(renderId) {
    util.object.forEach(manager.players_, function(playerInfo, id) {
      if (playerInfo.properties['render_id'] == renderId) {
        manager.removePlayer(id);
      }
    });
  };

  media.updateAudioComponent = function(component) {
    var uniqueComponentId = component.owner_id + ':' + component.component_id;
    switch (component.status) {
      case 'closed':
        manager.removeAudioComponent(
            component.component_type, uniqueComponentId);
        break;
      default:
        manager.updateAudioComponent(
            component.component_type, uniqueComponentId, component);
        break;
    }
  };

  media.onPlayerOpen = function(id, timestamp) {
    manager.addPlayer(id, timestamp);
  };

  media.onMediaEvent = function(event) {
    var source = event.renderer + ':' + event.player;

    // Although this gets called on every event, there is nothing we can do
    // because there is no onOpen event.
    media.onPlayerOpen(source);
    manager.updatePlayerInfoNoRecord(
        source, event.ticksMillis, 'render_id', event.renderer);
    manager.updatePlayerInfoNoRecord(
        source, event.ticksMillis, 'player_id', event.player);

    var propertyCount = 0;
    util.object.forEach(event.params, function(value, key) {
      key = key.trim();

      // These keys get spammed *a lot*, so put them on the display
      // but don't log list.
      if (key === 'buffer_start' ||
          key === 'buffer_end' ||
          key === 'buffer_current' ||
          key === 'is_downloading_data') {
        manager.updatePlayerInfoNoRecord(
            source, event.ticksMillis, key, value);
      } else {
        manager.updatePlayerInfo(source, event.ticksMillis, key, value);
      }
      propertyCount += 1;
    });

    if (propertyCount === 0) {
      manager.updatePlayerInfo(
          source, event.ticksMillis, 'EVENT', event.type);
    }
  };

  // |chrome| is not defined during tests.
  if (window.chrome && window.chrome.send) {
    chrome.send('getEverything');
  }
  return media;
}());