// -*- mode: js2; js-run: t -*-
//
// Copyright (c) 2010 Ivan Shvedunov. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials
// provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

var swh = require("./swank-handler");
var readFromString = require("./lisp").readFromString;
var config = require("./config");
var util = require("util");
var assert = require("assert");

var cfg = new config.FakeConfig();
var expected = [];
var executive = new swh.Executive({ config: cfg, pid: 4242 });
var handler = new swh.Handler(executive);

handler.on(
  "response", function (response) {
    // console.log("=> %s", response);
    assert.ok(expected.length > 0);
    assert.ok(typeof(response) == "string");
    // console.log("response: %s", response);
    var expectedResponse = expected.shift();
    if (expectedResponse instanceof RegExp)
      assert.ok(expectedResponse.test(response));
    else
      assert.equal(expectedResponse, response,
                   "got response " + response + " instead of " + expectedResponse);
  });

function expect () {
  for (var i = 0; i < arguments.length; ++i)
    expected.push(arguments[i]);
}

function verifyExpectations () {
  // console.log("expected: %s\n", expected.map(JSON.stringify).join("\n"));
  assert.equal(0, expected.length);
}

function request (str) {
  for (var i = 1; i < arguments.length; ++i)
    expected.push(arguments[i]);
  handler.receive(readFromString(str));
  verifyExpectations();
}

request('(:emacs-rex (swank:connection-info) "COMMON-LISP-USER" t 1)',
        '(:return (:ok (:encoding (:coding-system "utf-8" :external-format "UTF-8") ' +
                       ':lisp-implementation (:name "JS" :type "JS" :version "1.5") ' +
                       ':package (:name "NODE" :prompt "NODE") ' +
                       ':pid 4242 :version "2010-11-13")) ' +
        '1)');

// currently we just ignore swank-require
request('(:emacs-rex (swank:swank-require \'(swank-listener-hooks swank-indentation)) "COMMON-LISP-USER" t 2)',
        '(:return (:ok nil) 2)');

request('(:emacs-rex (swank:create-repl nil) "COMMON-LISP-USER" t 3)',
        '(:return (:ok ("NODE" "NODE")) 3)');

request('(:emacs-rex (swank:listener-eval "3 * 10\n") "NODE" :repl-thread 4)',
        '(:return (:ok (:values "30")) 4)');

request('(:emacs-rex (swank:listener-eval "undefined") "NODE" :repl-thread 5)',
        '(:return (:ok nil) 5)');

request('(:emacs-rex (swank:autodoc \'("zzzz" swank::%cursor-marker%) :print-right-margin 236)' +
        ' "COMMON-LISP-USER" :repl-thread 6)',
        '(:return (:ok :not-available) 6)');

request('(:emacs-rex (swank:listener-eval "_swank.output(\'hello world\\\\n\')") "NODE" :repl-thread 7)',
        '(:write-string "hello world\n")',
        '(:return (:ok nil) 7)');

request('(:emacs-rex (swank:listener-eval "_swank.output(1234)") "NODE" :repl-thread 8)',
        '(:write-string "1234")',
        '(:return (:ok nil) 8)');

request('(:emacs-rex (swank:listener-eval "zzz") "NODE" :repl-thread 9)',
        /^\(:write-string "ReferenceError: zzz is not defined(.|\n)*"\)$/,
        '(:return (:ok nil) 9)');

// TBD: debugger

function FakeRemote (name) {
  this.name = name;
};

util.inherits(FakeRemote, swh.Remote);

FakeRemote.prototype.prompt = function prompt () {
  return "FAKE";
};

FakeRemote.prototype.kind = function kind () {
  return "test";
};

FakeRemote.prototype.id = function id () {
  return this.name;
};

FakeRemote.prototype.evaluate = function evaluate (id, str) {
  this.sendResult(id, [ "R:" + this.name + ":" + str.replace(/^\s*|\s*$/g, "") ]);
};

request('(:emacs-rex (js:list-remotes) "NODE" :repl-thread 10)',
        '(:return (:ok ((1 :direct "node.js" t))) 10)');

expect('(:write-string "Remote attached: (test) test/localhost:8080\n")');
var r1 = new FakeRemote("test/localhost:8080");
executive.attachRemote(r1);
verifyExpectations();

request('(:emacs-rex (js:list-remotes) "NODE" :repl-thread 11)',
        '(:return (:ok ((1 :direct "node.js" t) (2 :test "test/localhost:8080" nil))) 11)');

expect('(:write-string "Remote attached: (test) test/localhost:9999\n")');
var r2 = new FakeRemote("test/localhost:9999");
executive.attachRemote(r2);
verifyExpectations();

request('(:emacs-rex (js:list-remotes) "NODE" :repl-thread 12)',
        '(:return (:ok ((1 :direct "node.js" t) ' +
        '(2 :test "test/localhost:8080" nil) ' +
        '(3 :test "test/localhost:9999" nil))) 12)');

request('(:emacs-rex (swank:listener-eval "3 * 10\n") "NODE" :repl-thread 13)',
        '(:return (:ok (:values "30")) 13)');

request('(:emacs-rex (js:select-remote 2 nil) "NODE" :repl-thread 14)',
        '(:new-package "FAKE" "FAKE")',
        '(:write-string "Remote selected: (test) test/localhost:8080\n")',
        '(:return (:ok nil) 14)');

request('(:emacs-rex (js:list-remotes) "NODE" :repl-thread 15)',
        '(:return (:ok ((1 :direct "node.js" nil) ' +
        '(2 :test "test/localhost:8080" t) ' +
        '(3 :test "test/localhost:9999" nil))) 15)');

request('(:emacs-rex (swank:listener-eval "3 * 10\n") "NODE" :repl-thread 16)',
        '(:return (:ok (:values "R:test/localhost:8080:3 * 10")) 16)');

expect('(:write-string "Remote detached: (test) test/localhost:8080\n")',
       '(:new-package "NODE" "NODE")',
       '(:write-string "Remote selected (auto): (direct) node.js\n")');
r1.disconnect();
verifyExpectations();

request('(:emacs-rex (swank:listener-eval "3 * 10\n") "NODE" :repl-thread 17)',
        '(:return (:ok (:values "30")) 17)');

// TBD: add higher-level functions for testing remotes

request('(:emacs-rex (js:list-remotes) "NODE" :repl-thread 18)',
        '(:return (:ok ((1 :direct "node.js" t) ' +
        '(3 :test "test/localhost:9999" nil))) 18)');

expect('(:write-string "Remote detached: (test) test/localhost:9999\n")');
r2.disconnect();
verifyExpectations();

request('(:emacs-rex (swank:listener-eval "3 * 10\n") "NODE" :repl-thread 19)',
        '(:return (:ok (:values "30")) 19)');

request('(:emacs-rex (js:list-remotes) "NODE" :repl-thread 20)',
        '(:return (:ok ((1 :direct "node.js" t))) 20)');

request('(:emacs-rex (js:select-remote 2 nil) "NODE" :repl-thread 21)',
        '(:write-string "WARNING: bad remote index\n")',
        '(:return (:ok nil) 21)');

request('(:emacs-rex (swank:listener-eval "3 * 10\n") "NODE" :repl-thread 22)',
        '(:return (:ok (:values "30")) 22)');

request('(:emacs-rex (js:select-remote 1 nil) "NODE" :repl-thread 23)',
        '(:write-string "WARNING: remote already selected: (direct) node.js\n")',
        '(:return (:ok nil) 23)');

assert.equal(null, cfg.getNow("stickyRemote"));

// test sticky remote selection
expect('(:write-string "Remote attached: (test) test/localhost:8001\n")');
var r3 = new FakeRemote("test/localhost:8001");
executive.attachRemote(r3);
verifyExpectations();

request('(:emacs-rex (js:list-remotes) "NODE" :repl-thread 24)',
        '(:return (:ok ((1 :direct "node.js" t) (4 :test "test/localhost:8001" nil))) 24)');

request('(:emacs-rex (js:select-remote 4 t) "NODE" :repl-thread 25)',
        '(:new-package "FAKE" "FAKE")',
        '(:write-string "Remote selected (sticky): (test) test/localhost:8001\n")',
        '(:return (:ok nil) 25)');

assert.equal("(test) test/localhost:8001", cfg.getNow("stickyRemote"));

expect('(:write-string "Remote detached: (test) test/localhost:8001\n")',
       '(:new-package "NODE" "NODE")',
       '(:write-string "Remote selected (auto): (direct) node.js\n")');
r3.disconnect();
verifyExpectations();

expect('(:write-string "Remote attached: (test) test/localhost:8001\n")',
       '(:new-package "FAKE" "FAKE")',
       '(:write-string "Remote selected (auto): (test) test/localhost:8001\n")');
var r5 = new FakeRemote("test/localhost:8001");
executive.attachRemote(r5);
verifyExpectations();

assert.equal("(test) test/localhost:8001", cfg.getNow("stickyRemote"));

request('(:emacs-rex (js:select-remote 1 nil) "NODE" :repl-thread 26)',
        '(:new-package "NODE" "NODE")',
        '(:write-string "Remote selected: (direct) node.js\n")',
        '(:return (:ok nil) 26)');

request('(:emacs-rex (js:select-remote 5 nil) "NODE" :repl-thread 27)',
        '(:new-package "FAKE" "FAKE")',
        '(:write-string "Remote selected: (test) test/localhost:8001\n")',
        '(:return (:ok nil) 27)');

assert.equal(null, cfg.getNow("stickyRemote"));

expect('(:write-string "Remote detached: (test) test/localhost:8001\n")',
       '(:new-package "NODE" "NODE")',
       '(:write-string "Remote selected (auto): (direct) node.js\n")');
r5.disconnect();
verifyExpectations();

expect('(:write-string "Remote attached: (test) test/localhost:8001\n")');
var r6 = new FakeRemote("test/localhost:8001");
executive.attachRemote(r6);
verifyExpectations();

assert.equal(null, cfg.getNow("stickyRemote"));

request('(:emacs-rex (js:set-target-url "http://localhost:1234/") "NODE" :repl-thread 28)',
        '(:return (:ok nil) 28)');

assert.equal("http://localhost:1234/", cfg.getNow("targetUrl"));

request('(:emacs-rex (js:set-target-url "zzz") "NODE" :repl-thread 29)',
        '(:write-string "WARNING: the URL must contain host and port\n")',
        '(:return (:ok nil) 29)');

assert.equal("http://localhost:1234/", cfg.getNow("targetUrl"));

assert.equal(null, cfg.getNow("slimeVersion"));

request('(:emacs-rex (js:set-slime-version "2010-11-28") "NODE" :repl-thread 30)',
        '(:return (:ok nil) 30)');

assert.equal("2010-11-28", cfg.getNow("slimeVersion"));

// TBD: use ## instead of numbers in the tests above (request() should take care of it)
// TBD: test output from an inactive remote
// TBD: are out-of-order results for :emacs-rex ok? look at slime sources

/*

list/select remotes along the lines of

catching errors on the client: window.onerror
http://stackoverflow.com/questions/951791/javascript-global-error-handling
*/

// TBD: add \n to messages from remotes / executive
