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

cr.define('serviceworker', function() {
  'use strict';

  function initialize() {
    if (window.location.hash == "#iframe") {
      // This page is loaded from chrome://inspect.
      window.addEventListener('message', onMessage.bind(this), false);
    }
    update();
  }

  function onMessage(event) {
    if (event.origin != 'chrome://inspect') {
      return;
    }
    sendCommand(event.data.action, event.data.worker);
  }

  function update() {
      chrome.send('GetOptions');
      chrome.send('getAllRegistrations');
  }

  function onOptions(options) {
    var template;
    var container = $('serviceworker-options');
    if (container.childNodes) {
      template = container.childNodes[0];
    }
    if (!template) {
      template = jstGetTemplate('serviceworker-options-template');
      container.appendChild(template);
    }
    jstProcess(new JsEvalContext(options), template);
    var inputs = container.querySelectorAll('input[type=\'checkbox\']');
    for (var i = 0; i < inputs.length; ++i) {
      if (!inputs[i].hasClickEvent) {
        inputs[i].addEventListener('click', (function(event) {
          chrome.send('SetOption',
                      [event.target.className, event.target.checked]);
        }).bind(this), false);
        inputs[i].hasClickEvent = true;
      }
    }
  }

  function progressNodeFor(link) {
    return link.parentNode.querySelector('.operation-status');
  }

  // All commands are completed with 'onOperationComplete'.
  var COMMANDS = ['stop', 'sync', 'push', 'inspect', 'unregister', 'start'];
  function commandHandler(command) {
    return function(event) {
      var link = event.target;
      progressNodeFor(link).style.display = 'inline';
      sendCommand(command, link.cmdArgs, (function(status) {
        progressNodeFor(link).style.display = 'none';
      }).bind(null, link));
      return false;
    };
  };

  var commandCallbacks = [];
  function sendCommand(command, args, callback) {
    var callbackId = 0;
    while (callbackId in commandCallbacks) {
      callbackId++;
    }
    commandCallbacks[callbackId] = callback;
    chrome.send(command, [callbackId, args]);
  }

  // Fired from the backend after the command call has completed.
  function onOperationComplete(status, callbackId) {
    var callback = commandCallbacks[callbackId];
    delete commandCallbacks[callbackId];
    if (callback) {
      callback(status);
    }
    update();
  }

  // Send the active ServiceWorker information to chrome://inspect.
  function sendToInspectPage(live_registrations,
                 partition_id) {
    var workers = [];
    live_registrations.forEach(function(registration) {
      [registration.active, registration.waiting].forEach(function(version) {
        if (!version || version.running_status != 'RUNNING') {
          return;
        }
        workers.push({
          'scope': registration.scope,
          'url': registration.script_url,
          'partition_id': partition_id,
          'version_id': version.version_id,
          'process_id': version.process_id,
          'devtools_agent_route_id':
            version.devtools_agent_route_id
        });
      });
    });
    window.parent.postMessage(
        {'partition_id': partition_id, 'workers': workers},
        'chrome://inspect');
  }

  var allLogMessages = {};
  // Set log for a worker version.
  function fillLogForVersion(partition_id, version) {
    if (!version) {
      return;
    }
    if (!(partition_id in allLogMessages)) {
      allLogMessages[partition_id] = {};
    }
    var logMessages = allLogMessages[partition_id];
    if (version.version_id in logMessages) {
      version.log = logMessages[version.version_id];
    } else {
      version.log = '';
    }
  }

  // Get the unregistered workers.
  // |unregistered_registrations| will be filled with the registrations which
  // are in |live_registrations| but not in |stored_registrations|.
  // |unregistered_versions| will be filled with the versions which
  // are in |live_versions| but not in |stored_registrations| nor in
  // |live_registrations|.
  function getUnregisteredWorkers(stored_registrations,
                                  live_registrations,
                                  live_versions,
                                  unregistered_registrations,
                                  unregistered_versions) {
    var registration_id_set = {};
    var version_id_set = {};
    stored_registrations.forEach(function(registration) {
      registration_id_set[registration.registration_id] = true;
    });
    [stored_registrations, live_registrations].forEach(function(registrations) {
      registrations.forEach(function(registration) {
        [registration.active, registration.waiting].forEach(function(version) {
          if (version) {
            version_id_set[version.version_id] = true;
          }
        });
      });
    });
    live_registrations.forEach(function(registration) {
      if (!registration_id_set[registration.registration_id]) {
        registration.unregistered = true;
        unregistered_registrations.push(registration);
      }
    });
    live_versions.forEach(function(version) {
      if (!version_id_set[version.version_id]) {
        unregistered_versions.push(version);
      }
    });
  }

  // Fired once per partition from the backend.
  function onPartitionData(live_registrations,
                           live_versions,
                           stored_registrations,
                           partition_id,
                           partition_path) {
    if (window.location.hash == "#iframe") {
      // This page is loaded from chrome://inspect.
      sendToInspectPage(live_registrations, partition_id);
      return;
    }
    var unregistered_registrations = [];
    var unregistered_versions = [];
    getUnregisteredWorkers(stored_registrations,
                           live_registrations,
                           live_versions,
                           unregistered_registrations,
                           unregistered_versions);
    var template;
    var container = $('serviceworker-list');
    // Existing templates are keyed by partition_id. This allows
    // the UI to be updated in-place rather than refreshing the
    // whole page.
    for (var i = 0; i < container.childNodes.length; ++i) {
      if (container.childNodes[i].partition_id == partition_id) {
        template = container.childNodes[i];
      }
    }
    // This is probably the first time we're loading.
    if (!template) {
      template = jstGetTemplate('serviceworker-list-template');
      container.appendChild(template);
    }
    var fillLogFunc = fillLogForVersion.bind(this, partition_id);
    stored_registrations.forEach(function(registration) {
      [registration.active, registration.waiting].forEach(fillLogFunc);
    });
    unregistered_registrations.forEach(function(registration) {
      [registration.active, registration.waiting].forEach(fillLogFunc);
    });
    unregistered_versions.forEach(fillLogFunc);
    jstProcess(new JsEvalContext({
                 stored_registrations: stored_registrations,
                 unregistered_registrations: unregistered_registrations,
                 unregistered_versions: unregistered_versions,
                 partition_id: partition_id,
                 partition_path: partition_path}),
               template);
    for (var i = 0; i < COMMANDS.length; ++i) {
      var handler = commandHandler(COMMANDS[i]);
      var links = container.querySelectorAll('button.' + COMMANDS[i]);
      for (var j = 0; j < links.length; ++j) {
        if (!links[j].hasClickEvent) {
          links[j].addEventListener('click', handler, false);
          links[j].hasClickEvent = true;
        }
      }
    }
  }

  function onWorkerStarted(partition_id, version_id, process_id, thread_id) {
    update();
  }

  function onWorkerStopped(partition_id, version_id, process_id, thread_id) {
    update();
  }

  function onErrorReported(partition_id,
                           version_id,
                           process_id,
                           thread_id,
                           error_info) {
    outputLogMessage(partition_id,
                     version_id,
                     'Error: ' + JSON.stringify(error_info) + '\n');
  }

  function onConsoleMessageReported(partition_id,
                                    version_id,
                                    process_id,
                                    thread_id,
                                    message) {
    outputLogMessage(partition_id,
                     version_id,
                     'Console: ' + JSON.stringify(message) + '\n');
  }

  function onVersionStateChanged(partition_id, version_id) {
    update();
  }

  function onRegistrationStored(scope) {
    update();
  }

  function onRegistrationDeleted(scope) {
    update();
  }

  function outputLogMessage(partition_id, version_id, message) {
    if (!(partition_id in allLogMessages)) {
      allLogMessages[partition_id] = {};
    }
    var logMessages = allLogMessages[partition_id];
    if (version_id in logMessages) {
      logMessages[version_id] += message;
    } else {
      logMessages[version_id] = message;
    }

    var logAreas = document.querySelectorAll('textarea.serviceworker-log');
    for (var i = 0; i < logAreas.length; ++i) {
      var logArea = logAreas[i];
      if (logArea.partition_id == partition_id &&
        logArea.version_id == version_id) {
        logArea.value += message;
      }
    }
  }

  return {
    initialize: initialize,
    onOptions: onOptions,
    onOperationComplete: onOperationComplete,
    onPartitionData: onPartitionData,
    onWorkerStarted: onWorkerStarted,
    onWorkerStopped: onWorkerStopped,
    onErrorReported: onErrorReported,
    onConsoleMessageReported: onConsoleMessageReported,
    onVersionStateChanged: onVersionStateChanged,
    onRegistrationStored: onRegistrationStored,
    onRegistrationDeleted: onRegistrationDeleted,
  };
});

document.addEventListener('DOMContentLoaded', serviceworker.initialize);
