/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

const { setTimeout } = ChromeUtils.importESModule(
  "resource://gre/modules/Timer.sys.mjs"
);

var { GenericConvIMPrototype } = ChromeUtils.importESModule(
  "resource:///modules/jsProtoHelper.sys.mjs"
);

var _id = 0;
function Conversation(name) {
  this._name = name;
  this._observers = [];
  this._date = Date.now() * 1000;
  this.id = ++_id;
  this.typingState = undefined;
}
Conversation.prototype = {
  __proto__: GenericConvIMPrototype,
  _account: {
    imAccount: {
      protocol: { name: "Fake Protocol" },
      alias: "",
      name: "Fake Account",
    },
    ERROR(e) {
      throw e;
    },
    DEBUG() {},
  },
  setTypingState(newState) {
    this.typingState = newState;
  },
};

// ROT13, used as an example transformation.
function rot13(aString) {
  return aString.replace(/[a-zA-Z]/g, function (c) {
    return String.fromCharCode(
      c.charCodeAt(0) + (c.toLowerCase() < "n" ? 1 : -1) * 13
    );
  });
}

// A test that cancels a message before it can be sent.
add_task(function test_cancel_send_message() {
  const conv = new Conversation();
  conv.dispatchMessage = function () {
    ok(
      false,
      "The message should have been halted in the conversation service."
    );
  };

  let sending = false;
  conv.addObserver({
    observe(aObject, aTopic) {
      switch (aTopic) {
        case "sending-message":
          ok(
            aObject.QueryInterface(Ci.imIOutgoingMessage),
            "Wrong message type."
          );
          aObject.cancelled = true;
          sending = true;
          break;
        case "new-text":
          ok(
            false,
            "No other notification should be fired for a cancelled message."
          );
          break;
      }
    },
  });
  conv.sendMsg("Hi!");
  ok(sending, "The sending-message notification was never fired.");
});

// A test that ensures protocols get a chance to prepare a message before
// sending and displaying.
add_task(function test_prpl_message_prep() {
  const conv = new Conversation();
  conv.dispatchMessage = function (aMsg) {
    this.writeMessage("user", aMsg, { outgoing: true });
  };

  conv.prepareForSending = function (aMsg) {
    ok(aMsg.QueryInterface(Ci.imIOutgoingMessage), "Wrong message type.");
    equal(aMsg.message, msg, "Expected the original message.");
    prepared = true;
    return [prefix + aMsg.message];
  };

  conv.prepareForDisplaying = function (aMsg) {
    equal(aMsg.displayMessage, prefix + msg, "Expected the prefixed message.");
    aMsg.displayMessage = aMsg.displayMessage.slice(prefix.length);
  };

  const msg = "Hi!";
  const prefix = "test> ";

  let prepared = false;
  let receivedMsg = false;
  conv.addObserver({
    observe(aObject, aTopic) {
      if (aTopic === "preparing-message") {
        equal(aObject.message, msg, "Expected the original message");
      } else if (aTopic === "sending-message") {
        equal(aObject.message, prefix + msg, "Expected the prefixed message.");
      } else if (aTopic === "new-text") {
        ok(aObject.QueryInterface(Ci.prplIMessage), "Wrong message type.");
        ok(prepared, "The message was not prepared before sending.");
        equal(aObject.message, prefix + msg, "Expected the prefixed message.");
        receivedMsg = true;
        aObject.displayMessage = aObject.originalMessage;
        conv.prepareForDisplaying(aObject);
        equal(aObject.displayMessage, msg, "Expected the original message");
      }
    },
  });

  conv.sendMsg(msg);
  ok(receivedMsg, "The new-text notification was never fired.");
});

// A test that ensures protocols can split messages before they are sent.
add_task(function test_split_message_before_sending() {
  let msgCount = 0;
  let prepared = false;

  const msg = "This is a looo\nooong message.\nThis one is short.";
  const msgs = msg.split("\n");

  const conv = new Conversation();
  conv.dispatchMessage = function (aMsg) {
    equal(aMsg, msgs[msgCount++], "Sending an unexpected message.");
  };
  conv.prepareForSending = function (aMsg) {
    ok(aMsg.QueryInterface(Ci.imIOutgoingMessage), "Wrong message type.");
    prepared = true;
    return aMsg.message.split("\n");
  };

  conv.sendMsg(msg);

  ok(prepared, "Message wasn't prepared for sending.");
  equal(msgCount, 3, "Not enough messages were sent.");
});

add_task(function test_removeMessage() {
  let didRemove = false;
  const conv = new Conversation();
  conv.addObserver({
    observe(subject, topic, data) {
      if (topic === "remove-text") {
        equal(data, "foo");
        didRemove = true;
      }
    },
  });

  conv.removeMessage("foo");
  ok(didRemove);
});

add_task(function test_sendTyping() {
  const roomStub = new Conversation();
  Services.prefs.setBoolPref("purple.conversations.im.send_typing", false);

  // Nothing happens until the conversation supports typing & the pref is enabled.
  let result = roomStub.sendTyping("lorem ipsum");
  ok(!roomStub._typingTimer, "Typing is disabled");
  equal(
    result,
    Ci.prplIConversation.NO_TYPING_LIMIT,
    "Default to no typing limit"
  );
  equal(roomStub.typingState, undefined, "Typing state is uninitialized");

  roomStub.supportTypingNotifications = true;
  result = roomStub.sendTyping("lorem ipsum");
  ok(!roomStub._typingTimer, "Typing is disabled via pref");
  equal(
    result,
    Ci.prplIConversation.NO_TYPING_LIMIT,
    "Default to no typing limit 2"
  );
  equal(roomStub.typingState, undefined, "Typing state is unmodified");

  Services.prefs.setBoolPref("purple.conversations.im.send_typing", true);

  // A message changes the state to typing.
  result = roomStub.sendTyping("lorem ipsum");
  ok(roomStub._typingTimer, "Typing is enabled and a timer is configured");
  equal(
    result,
    Ci.prplIConversation.NO_TYPING_LIMIT,
    "Default to no typing limit 3"
  );
  equal(roomStub.typingState, Ci.prplIConvIM.TYPING, "The user is typing");

  // Clearing the input resets to not typing.
  result = roomStub.sendTyping("");
  ok(!roomStub._typingTimer, "Typing timer is cancelled and reset");
  equal(
    result,
    Ci.prplIConversation.NO_TYPING_LIMIT,
    "Default to no typing limit 4"
  );
  equal(
    roomStub.typingState,
    Ci.prplIConvIM.NOT_TYPING,
    "The user is no longer typing"
  );

  roomStub.unInit();
});

add_task(function test_cancelTypingTimer() {
  const roomStub = {
    _typingTimer: setTimeout(() => {}, 10000), // eslint-disable-line mozilla/no-arbitrary-setTimeout
  };
  Conversation.prototype._cancelTypingTimer.call(roomStub);
  ok(!roomStub._typingTimer, "Typing timer is cancelled and reset");
});

add_task(function test_finishedComposing() {
  const roomStub = new Conversation();

  roomStub.finishedComposing();
  equal(roomStub.typingState, undefined, "Typing is disabled");

  roomStub.supportTypingNotifications = true;
  roomStub.finishedComposing();
  equal(
    roomStub.typingState,
    Ci.prplIConvIM.TYPED,
    "The user has left a message in the window"
  );
});
