/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

'use strict';

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr, Constructor: CC } = Components;
const ServerSocket = CC("@mozilla.org/network/server-socket;1",
                        "nsIServerSocket",
                        "init");

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');

var testServer = null;
var clientTransport = null;
var serverTransport = null;

var clientBuilder = null;
var serverBuilder = null;

const clientMessage = "Client Message";
const serverMessage = "Server Message";

const address = Cc["@mozilla.org/supports-cstring;1"]
                  .createInstance(Ci.nsISupportsCString);
address.data = "127.0.0.1";
const addresses = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
addresses.appendElement(address, false);

const serverChannelDescription = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]),
  type: 1,
  tcpAddress: addresses,
};

var isClientReady = false;
var isServerReady = false;
var isClientClosed = false;
var isServerClosed = false;

const clientCallback = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportCallback]),
  notifyTransportReady: function () {
    Assert.ok(true, "Client transport ready.");

    isClientReady = true;
    if (isClientReady && isServerReady) {
      run_next_test();
    }
  },
  notifyTransportClosed: function (aReason) {
    Assert.ok(true, "Client transport is closed.");

    isClientClosed = true;
    if (isClientClosed && isServerClosed) {
      run_next_test();
    }
  },
  notifyData: function(aData) {
    Assert.equal(aData, serverMessage, "Client transport receives data.");
    run_next_test();
  },
};

const serverCallback = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportCallback]),
  notifyTransportReady: function () {
    Assert.ok(true, "Server transport ready.");

    isServerReady = true;
    if (isClientReady && isServerReady) {
      run_next_test();
    }
  },
  notifyTransportClosed: function (aReason) {
    Assert.ok(true, "Server transport is closed.");

    isServerClosed = true;
    if (isClientClosed && isServerClosed) {
      run_next_test();
    }
  },
  notifyData: function(aData) {
    Assert.equal(aData, clientMessage, "Server transport receives data.");
    run_next_test();
  },
};

const clientListener = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportBuilderListener]),
  onSessionTransport(aTransport) {
    Assert.ok(true, "Client Transport is built.");
    clientTransport = aTransport;
    clientTransport.callback = clientCallback;

    if (serverTransport) {
      run_next_test();
    }
  }
}

const serverListener = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportBuilderListener]),
  onSessionTransport(aTransport) {
    Assert.ok(true, "Server Transport is built.");
    serverTransport = aTransport;
    serverTransport.callback = serverCallback;
    serverTransport.enableDataNotification();

    if (clientTransport) {
      run_next_test();
    }
  }
}

function TestServer() {
  this.serverSocket = ServerSocket(-1, true, -1);
  this.serverSocket.asyncListen(this)
}

TestServer.prototype = {
  onSocketAccepted: function(aSocket, aTransport) {
    print("Test server gets a client connection.");
    serverBuilder = Cc["@mozilla.org/presentation/presentationtcpsessiontransport;1"]
                      .createInstance(Ci.nsIPresentationTCPSessionTransportBuilder);
    serverBuilder.buildTCPSenderTransport(aTransport, serverListener);
  },
  onStopListening: function(aSocket) {
    print("Test server stops listening.");
  },
  close: function() {
    if (this.serverSocket) {
      this.serverSocket.close();
      this.serverSocket = null;
    }
  }
};

// Set up the transport connection and ensure |notifyTransportReady| triggered
// at both sides.
function setup() {
  clientBuilder = Cc["@mozilla.org/presentation/presentationtcpsessiontransport;1"]
                    .createInstance(Ci.nsIPresentationTCPSessionTransportBuilder);
  clientBuilder.buildTCPReceiverTransport(serverChannelDescription, clientListener);
}

// Test |selfAddress| attribute of |nsIPresentationSessionTransport|.
function selfAddress() {
  var serverSelfAddress = serverTransport.selfAddress;
  Assert.equal(serverSelfAddress.address, address.data, "The self address of server transport should be set.");
  Assert.equal(serverSelfAddress.port, testServer.serverSocket.port, "The port of server transport should be set.");

  var clientSelfAddress = clientTransport.selfAddress;
  Assert.ok(clientSelfAddress.address, "The self address of client transport should be set.");
  Assert.ok(clientSelfAddress.port, "The port of client transport should be set.");

  run_next_test();
}

// Test the client sends a message and then a corresponding notification gets
// triggered at the server side.
function clientSendMessage() {
  clientTransport.send(clientMessage);
}

// Test the server sends a message an then a corresponding notification gets
// triggered at the client side.
function serverSendMessage() {
  serverTransport.send(serverMessage);
  // The client enables data notification even after the incoming message has
  // been sent, and should still be able to consume it.
  clientTransport.enableDataNotification();
}

function transportClose() {
  clientTransport.close(Cr.NS_OK);
}

function shutdown() {
  testServer.close();
  run_next_test();
}

add_test(setup);
add_test(selfAddress);
add_test(clientSendMessage);
add_test(serverSendMessage);
add_test(transportClose);
add_test(shutdown);

function run_test() {
  testServer = new TestServer();
  // Get the port of the test server.
  serverChannelDescription.tcpPort = testServer.serverSocket.port;

  run_next_test();
}
