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

/**
 * Custom bindings for the Serial API.
 *
 * The bindings are implemented by asynchronously delegating to the
 * serial_service module. The functions that apply to a particular connection
 * are delegated to the appropriate method on the Connection object specified by
 * the ID parameter.
 */

var binding = require('binding').Binding.create('serial');
var context = requireNative('v8_context');
var eventBindings = require('event_bindings');
var utils = require('utils');

var serialServicePromise = function() {
  // getBackgroundPage is not available in unit tests so fall back to the
  // current page's serial_service module.
  if (!chrome.runtime.getBackgroundPage)
    return requireAsync('serial_service');

  // Load the serial_service module from the background page if one exists. This
  // is necessary for serial connections created in one window to be usable
  // after that window is closed. This is necessary because the Mojo async
  // waiter only functions while the v8 context remains.
  return utils.promise(chrome.runtime.getBackgroundPage).then(function(bgPage) {
    return context.GetModuleSystem(bgPage).requireAsync('serial_service');
  }).catch(function() {
    return requireAsync('serial_service');
  });
}();

function forwardToConnection(methodName) {
  return function(connectionId) {
    var args = $Array.slice(arguments, 1);
    return serialServicePromise.then(function(serialService) {
      return serialService.getConnection(connectionId);
    }).then(function(connection) {
      return $Function.apply(connection[methodName], connection, args);
    });
  };
}

binding.registerCustomHook(function(bindingsAPI) {
  var apiFunctions = bindingsAPI.apiFunctions;
  apiFunctions.setHandleRequestWithPromise('getDevices', function() {
    return serialServicePromise.then(function(serialService) {
      return serialService.getDevices();
    });
  });

  apiFunctions.setHandleRequestWithPromise('connect', function(path, options) {
    return serialServicePromise.then(function(serialService) {
      return serialService.createConnection(path, options);
    }).then(function(result) {
      var id = result.info.connectionId;
      result.connection.onData = function(data) {
        eventBindings.dispatchEvent(
            'serial.onReceive', [{connectionId: id, data: data}]);
      };
      result.connection.onError = function(error) {
        eventBindings.dispatchEvent(
            'serial.onReceiveError', [{connectionId: id, error: error}]);
      };
      return result.info;
    }).catch (function(e) {
      throw new Error('Failed to connect to the port.');
    });
  });

  apiFunctions.setHandleRequestWithPromise(
      'disconnect', forwardToConnection('close'));
  apiFunctions.setHandleRequestWithPromise(
      'getInfo', forwardToConnection('getInfo'));
  apiFunctions.setHandleRequestWithPromise(
      'update', forwardToConnection('setOptions'));
  apiFunctions.setHandleRequestWithPromise(
      'getControlSignals', forwardToConnection('getControlSignals'));
  apiFunctions.setHandleRequestWithPromise(
      'setControlSignals', forwardToConnection('setControlSignals'));
  apiFunctions.setHandleRequestWithPromise(
      'flush', forwardToConnection('flush'));
  apiFunctions.setHandleRequestWithPromise(
      'setPaused', forwardToConnection('setPaused'));
  apiFunctions.setHandleRequestWithPromise(
      'send', forwardToConnection('send'));

  apiFunctions.setHandleRequestWithPromise('getConnections', function() {
    return serialServicePromise.then(function(serialService) {
      return serialService.getConnections();
    }).then(function(connections) {
      var promises = [];
      for (var id in connections) {
        promises.push(connections[id].getInfo());
      }
      return Promise.all(promises);
    });
  });
});

exports.binding = binding.generate();