"use strict";
var EXPORTED_SYMBOLS = ["module"];
var module = function (application) {
var log = function (message) {
application.logger.debug("[Notifications Controller] " + message);
}
;
function NotificationsController() {
this._openedNotifications = {
};
this._observer = null;
this._closeTimer = null;
this._notificationIsConstructing = false;
this._notificationIsClosing = false;
this._notificationsAreMoving = false;
this._ignoreMouseEvents = false;
this.init();
}

const NOTIFICATIONS = {
ADD: "yb:notification:add",
REMOVE_QUEUED: "yb:notification:remove-queued"};
const GAP = {
AT_RIGHT: 10,
BETWEEN_NOTIFICATIONS: 15};
const SUBSCRIBERS_GROUP_ID = "notificationController";
const MAXIMUM_SIMULTANEOUS_NOTIFICATIONS_NUMBER = 4;
const DEFAULT_DISPLAY_TIME = 5;
const MINIMUM_DISPLAY_TIME = 1;
NotificationsController.prototype = {
constructor: NotificationsController,
init: function NotificationsController_init() {
log("init started.");
var controller = this;
this._observer = {
observe: function (subject, topic, data) {
var queue;
switch (topic) {
case NOTIFICATIONS.ADD:
log("Caught request to add notification.");
queue = controller._notificationsQueue;
let notification = JSON.parse(data);
queue.add(notification);
break;
case NOTIFICATIONS.REMOVE_QUEUED:
queue = controller._notificationsQueue;
let owner = data;
queue.removeAllNotificationsFrom(owner);
break;
default:
break;
}

}
};
var observerService = this._observerService;
this._TOPICS_LIST.forEach(function (topic) {
observerService.addObserver(controller._observer,topic,false);
}
);
var mailman = this._mailman;
mailman.addSubscriber({
subject: ["notification-added", "notification-window-created", "notification-window-closed"],
group: SUBSCRIBERS_GROUP_ID,
subscriber: {
receive: function ({subject: subject}) {
if ("notification-window-created" === subject)
{
controller._notificationIsConstructing = false;
}

controller.showNextNotification();
if (! controller.timerExists)
controller.startCloseTimer();
}
}});
mailman.addSubscriber({
subject: "notification-window-close-request",
group: SUBSCRIBERS_GROUP_ID,
subscriber: {
receive: function ({body: id}) {
controller.closeNotificationByUser(id);
}
}});
mailman.addSubscriber({
subject: ["windows-movement-started", "windows-movement-finished"],
group: SUBSCRIBERS_GROUP_ID,
subscriber: {
receive: function ({subject: subject}) {
controller._notificationsAreMoving = "windows-movement-started" === subject;
}
}});
mailman.addSubscriber({
subject: "open-settings",
group: SUBSCRIBERS_GROUP_ID,
subscriber: {
receive: function () {
controller.openSettings();
}
}});
mailman.addSubscriber({
subject: "send-to-owner",
group: SUBSCRIBERS_GROUP_ID,
subscriber: {
receive: function ({body: data}) {
controller.sendMessageToOwnerWidget(data);
}
}});
mailman.addSubscriber({
subject: "mouse-action",
group: SUBSCRIBERS_GROUP_ID,
subscriber: {
receive: function ({body: action}) {
if (! controller._ignoreMouseEvents)
{
controller.handleMouseAction(action);
}

}
}});
}
,
finalize: function NotificationsController_finalize() {
var controller = this;
this.destroyCloseTimer();
if (null !== this._observer)
{
let observerService = this._observerService;
this._TOPICS_LIST.forEach(function (topic) {
observerService.removeObserver(controller._observer,topic);
}
);
}

if (null !== this.__notificationsQueue)
{
this._notificationsQueue.finalize();
}

if (null !== this.__mailman)
{
this._mailman.removeSubscribersGroup(SUBSCRIBERS_GROUP_ID);
}

for(let id in this._openedNotifications) if (this._openedNotifications.hasOwnProperty(id))
{
let notification = this._openedNotifications[id];
notification.close();
}

}
,
openSettings: function NotificationsController_openSettings() {
const PLUGIN_ID = application.api.componentID;
application.api.Controls.openSettingsDialog(null,PLUGIN_ID,null);
}
,
sendMessageToOwnerWidget: function NotificationsController_sendMessageToOwnerWidget({from: notificationId, message: message}) {
var notification = this._openedNotifications[notificationId];
if ("undefined" == typeof notification)
{
return;
}

var topic = "yb:notification:message:" + notification.owner;
var data = JSON.stringify(message);
this._observerService.notifyObservers(null,topic,data);
}
,
showNextNotification: function NotificationsController_showNextNotification() {
var queue = this._notificationsQueue;
if (queue.isEmpty)
return;
if (this.newNotificationsAreBlocked)
{
log("New notifications are blocked.");
return;
}

var notification = queue.getNext();
this.showNotification(notification);
}
,
showNotification: function NotificationsController_showNotification(notification) {
log("Show new notification.");
var NotificationWindow = application.loadModule("NotificationWindow");
var screen = this._screen;
var position = {
x: screen.availWidth - NotificationWindow.DEFAULT_WIDTH - GAP.AT_RIGHT,
y: screen.availHeight - NotificationWindow.DEFAULT_HEIGHT};
var topNotification = this.topNotification;
if (null !== topNotification)
{
position.y = topNotification.position.y - NotificationWindow.DEFAULT_HEIGHT - GAP.BETWEEN_NOTIFICATIONS;
}

this._notificationIsConstructing = true;
var window = new NotificationWindow(notification, position);
window.__openTime = Date.now();
this._openedNotifications[window.id] = window;
}
,
closeNotificationByTimer: function NotificationsController_closeNotificationByTimer() {
log("Close notification by timer");
var notification = this.bottomNotification;
if (null === notification)
return;
var controller = this;
var notifications = this._openedNotifications;
var mailman = this._mailman;
mailman.send("windows-movement-started");
this._windowsJuggler.moveWindowsInDirection({
windows: [notification._window for each (notification in notifications)],
direction: "down",
distance: notification.height + GAP.BETWEEN_NOTIFICATIONS,
callback: function () {
controller.closeNotification(notification);
mailman.send("windows-movement-finished");
mailman.send("notification-window-closed");
}
});
}
,
closeNotificationByUser: function NotificationsController_closeNotificationByUser(id) {
log("Close notification by user");
var notification = this._openedNotifications[id];
var controller = this;
this._notificationIsClosing = true;
var closedNotificationPosition = notification.position;
this.closeNotification(notification);
var notificationsAboveClosed = this.getNotificationsAbovePoint(closedNotificationPosition);
var mailman = controller._mailman;
var callback = function () {
controller._notificationIsClosing = false;
controller._ignoreMouseEvents = false;
mailman.send("windows-movement-finished");
mailman.send("notification-window-closed");
}
;
if (0 === notificationsAboveClosed.length)
{
callback();
return;
}

controller._ignoreMouseEvents = true;
mailman.send("windows-movement-started");
this.fallNotificationsDown(notificationsAboveClosed,callback);
}
,
fallNotificationsDown: function NotificationsController_fallNotificationsDown(notifications, callback) {
var controller = this;
var windows = [notification._window for each (notification in notifications)];
var topFixedNotification = this.getTopFixedNotification(notifications);
var bottomMovingNotification = this._getBottomNotification(notifications);
const GROUND_LEVEL = this._screen.availHeight;
const START_MOVING_LEVEL = bottomMovingNotification.position.y + bottomMovingNotification.height;
this._windowsJuggler.fallDown(windows,function () {
var bottomBorderOfMovingGroup = bottomMovingNotification.position.y + bottomMovingNotification.height;
if (null !== topFixedNotification)
{
topFixedNotification = controller.getTopFixedNotification(notifications);
}

var level = GROUND_LEVEL;
if (null !== topFixedNotification)
{
level = topFixedNotification.position.y - GAP.BETWEEN_NOTIFICATIONS;
}

if (bottomBorderOfMovingGroup < level)
{
return {
stop: false};
}
 else
{
return {
stop: bottomBorderOfMovingGroup > level,
shift: {
y: level - START_MOVING_LEVEL}};
}

}
,callback);
}
,
closeNotification: function NotificationsController_closeNotification(notification) {
delete this._openedNotifications[notification.id];
notification.close();
}
,
handleMouseAction: function NotificationsController_handleMouseAction(action) {
switch (action) {
case "in":
if (this._notificationsAreMoving)
{
this._windowsJuggler.cancelWindowsMovement();
}

this.destroyCloseTimer();
break;
case "out":
this.startCloseTimer();
break;
default:
break;
}

}
,
getNotificationsAbovePoint: function NotificationsController_getNotificationsAbovePoint(point) {
var openedNotifications = this._openedNotifications;
var notificationsAbovePoint = [];
for(let id in openedNotifications) if (openedNotifications.hasOwnProperty(id))
{
let notification = openedNotifications[id];
let notificationBottomBorderPosition = notification.position.y + notification.height;
if (notificationBottomBorderPosition < point.y)
{
notificationsAbovePoint.push(notification);
}

}

return notificationsAbovePoint;
}
,
getTopFixedNotification: function NotificationsController_getTopFixedNotification(movingGroup) {
var openedNotifications = this._openedNotifications;
var topFixedNotification = null;
for(let id in openedNotifications) if (openedNotifications.hasOwnProperty(id))
{
let currentNotification = openedNotifications[id];
let isFixedNotification = - 1 === movingGroup.indexOf(currentNotification);
if (isFixedNotification)
{
if (null === topFixedNotification || topFixedNotification.position.y > currentNotification.position.y)
{
topFixedNotification = currentNotification;
}

}

}

return topFixedNotification;
}
,
startCloseTimer: function NotificationsController_startCloseTimer() {
this.destroyCloseTimer();
var notification = this.bottomNotification;
if (null === notification)
{
return;
}

var notificationIsAlreadyDisplayed = Date.now() - notification.__openTime;
var delay = DEFAULT_DISPLAY_TIME * 1000 - notificationIsAlreadyDisplayed;
if (delay < MINIMUM_DISPLAY_TIME * 1000)
{
delay = MINIMUM_DISPLAY_TIME * 1000;
}

var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
var controller = this;
timer.init({
observe: function () {
controller.closeNotificationByTimer();
controller.destroyCloseTimer();
}
},delay,timer.TYPE_ONE_SHOT);
this._closeTimer = timer;
}
,
destroyCloseTimer: function NotificationsController_destroyCloseTimer() {
if (this.timerExists)
{
this._closeTimer.cancel();
this._closeTimer = null;
}

}
,
get timerExists() {
return null !== this._closeTimer;
}
,
get newNotificationsAreBlocked() {
if (this.openedNotificationsNumber >= MAXIMUM_SIMULTANEOUS_NOTIFICATIONS_NUMBER)
{
return true;
}

if (this._notificationIsConstructing)
{
return true;
}

if (this._notificationsAreMoving)
{
return true;
}

if (this._notificationIsClosing)
{
return true;
}

return false;
}
,
get openedNotificationsNumber() {
return [notification for each (notification in this._openedNotifications)].length;
}
,
get topNotification() {
return this._getTopNotification(this._openedNotifications);
}
,
_getTopNotification: function NotificationsController__getTopNotification(notifications) {
var topNotification = null;
var minimumY = Infinity;
for(let id in notifications) if (notifications.hasOwnProperty(id))
{
let notification = notifications[id];
let positionY = notification.position.y;
if (positionY < minimumY)
{
minimumY = positionY;
topNotification = notification;
}

}

return topNotification;
}
,
get bottomNotification() {
return this._getBottomNotification(this._openedNotifications);
}
,
_getBottomNotification: function NotificationsController__getBottomNotification(notifications) {
var bottomNotification = null;
var maximumY = 0;
for(let id in notifications) if (notifications.hasOwnProperty(id))
{
let notification = notifications[id];
let positionY = notification.position.y;
if (positionY > maximumY)
{
maximumY = positionY;
bottomNotification = notification;
}

}

return bottomNotification;
}
,
__TOPICS_LIST: null,
get _TOPICS_LIST() {
if (null === this.__TOPICS_LIST)
{
this.__TOPICS_LIST = [notification for each (notification in NOTIFICATIONS)];
}

return this.__TOPICS_LIST;
}
,
__notificationsQueue: null,
get _notificationsQueue() {
if (null === this.__notificationsQueue)
{
let NotificationsQueue = application.loadModule("NotificationsQueue");
this.__notificationsQueue = new NotificationsQueue();
}

return this.__notificationsQueue;
}
,
__observerService: null,
get _observerService() {
if (null === this.__observerService)
{
this.__observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
}

return this.__observerService;
}
,
__mailman: null,
get _mailman() {
return this.__mailman ? this.__mailman : (this.__mailman = application.loadModule("Mailman"));
}
,
__windowMediator: null,
get _windowMediator() {
if (null === this.__windowMediator)
{
this.__windowMediator = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator);
}

return this.__windowMediator;
}
,
get _screen() {
var mainWindow = this._windowMediator.getMostRecentWindow("navigator:browser");
return mainWindow.screen;
}
,
__windowsJuggler: null,
get _windowsJuggler() {
return this.__windowsJuggler ? this.__windowsJuggler : (this.__windowsJuggler = application.loadModule("WindowsJuggler"));
}
};
return new NotificationsController();
}
;
