/*
Copyright (C) 2014  spin83

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, visit https://www.gnu.org/licenses/.
*/

const { St, Shell, Meta, Atk, Clutter, GObject } = imports.gi;

const Main = imports.ui.main;
const Panel = imports.ui.panel;
const PopupMenu = imports.ui.popupMenu;
const PanelMenu = imports.ui.panelMenu;
const CtrlAltTab = imports.ui.ctrlAltTab;
const ExtensionSystem = imports.ui.extensionSystem;

const ExtensionUtils = imports.misc.extensionUtils;
const CE = ExtensionUtils.getCurrentExtension();
const MultiMonitors = CE.imports.extension;
const Convenience = CE.imports.convenience;
const MMCalendar = CE.imports.mmcalendar;

const SHOW_ACTIVITIES_ID = 'show-activities';
var SHOW_APP_MENU_ID = 'show-app-menu';
const SHOW_DATE_TIME_ID = 'show-date-time';
const AVAILABLE_INDICATORS_ID = 'available-indicators';
const TRANSFER_INDICATORS_ID = 'transfer-indicators';

var StatusIndicatorsController = class StatusIndicatorsController  {
	constructor() {
		this._transfered_indicators = [];
		this._settings = Convenience.getSettings();
		
        this._updatedSessionId = Main.sessionMode.connect('updated', this._updateSessionIndicators.bind(this));
        this._updateSessionIndicators();
        if (MultiMonitors.gnomeShellVersion()[1]==32) {
        	this._extensionStateChangedId = ExtensionSystem.connect('extension-state-changed', 
        										this._extensionStateChanged.bind(this));
        }
        else { 
        	this._extensionStateChangedId = Main.extensionManager.connect('extension-state-changed', 
        										this._extensionStateChanged.bind(this));
        }

        this._transferIndicatorsId = this._settings.connect('changed::'+TRANSFER_INDICATORS_ID,
																		this.transferIndicators.bind(this));
	}
	
	destroy() {
		this._settings.disconnect(this._transferIndicatorsId);
		if (MultiMonitors.gnomeShellVersion()[1]==32) {
			ExtensionSystem.disconnect(this._extensionStateChangedId);
		}
		else {
			Main.extensionManager.disconnect(this._extensionStateChangedId);
		}
		Main.sessionMode.disconnect(this._updatedSessionId);
		this._settings.set_strv(AVAILABLE_INDICATORS_ID, []);
		this._transferBack(this._transfered_indicators);
	}
	
	transferBack(panel) {
		let transfer_back = this._transfered_indicators.filter((element) => {
			return element.monitor==panel.monitorIndex;
		});
		
		this._transferBack(transfer_back, panel);
	}
	
	transferIndicators() {
		let boxs = ['_leftBox', '_centerBox', '_rightBox'];
    	let transfers = this._settings.get_value(TRANSFER_INDICATORS_ID).deep_unpack();
    	let show_app_menu = this._settings.get_value(SHOW_APP_MENU_ID);
    	
    	let transfer_back = this._transfered_indicators.filter((element) => {
    		return !transfers.hasOwnProperty(element.iname);
		});
    	
    	this._transferBack(transfer_back);
    	
		for(let iname in transfers) {
			if(transfers.hasOwnProperty(iname) && Main.panel.statusArea[iname]) {
				let monitor = transfers[iname];
				
				let indicator = Main.panel.statusArea[iname];
				let panel = this._findPanel(monitor);
				boxs.forEach((box) => {
					if(Main.panel[box].contains(indicator.container) && panel) {
						global.log('a '+box+ " > " + iname + " : "+ monitor);
						this._transfered_indicators.push({iname:iname, box:box, monitor:monitor});
						Main.panel[box].remove_child(indicator.container);
						if (show_app_menu && box === '_leftBox')
							panel[box].insert_child_at_index(indicator.container, 1);
						else
							panel[box].insert_child_at_index(indicator.container, 0);
					}
				});
			}
		}
	}
	
	_findPanel(monitor) {
		for (let i = 0; i < Main.mmPanel.length; i++) {
			if (Main.mmPanel[i].monitorIndex == monitor) {
				return Main.mmPanel[i];
			}
		}
		return null;
	}
	
	_transferBack(transfer_back, panel) {
    	transfer_back.forEach((element) => {
    		this._transfered_indicators.splice(this._transfered_indicators.indexOf(element));
			if(Main.panel.statusArea[element.iname]) {
				let indicator = Main.panel.statusArea[element.iname];
				if(!panel) {
					panel = this._findPanel(element.monitor);
				}
				if(panel[element.box].contains(indicator.container)) {
		    		global.log("r "+element.box+ " > " + element.iname + " : "+ element.monitor);
		    		panel[element.box].remove_child(indicator.container);
		    		if (element.box === '_leftBox')
		    			Main.panel[element.box].insert_child_at_index(indicator.container, 1);
		    		else
		    			Main.panel[element.box].insert_child_at_index(indicator.container, 0);
				}
			}
		});
	}
    
	_extensionStateChanged() {
		this._findAvailableIndicators();
        this.transferIndicators();
	}
	
	_updateSessionIndicators() {
        let session_indicators = [];
        session_indicators.push('MultiMonitorsAddOn');
        let sessionPanel = Main.sessionMode.panel;
        for (let sessionBox in sessionPanel){
        	sessionPanel[sessionBox].forEach((sesionIndicator) => {
        		session_indicators.push(sesionIndicator);
            });
        }
        this._session_indicators = session_indicators;
		this._available_indicators = [];
		
        this._findAvailableIndicators();
        this.transferIndicators();
	}
	
    _findAvailableIndicators() {
		let available_indicators = [];
		let statusArea = Main.panel.statusArea;
		for(let indicator in statusArea) {
			if(statusArea.hasOwnProperty(indicator) && this._session_indicators.indexOf(indicator)<0){
				available_indicators.push(indicator);
			}
		}
		if(available_indicators.length!=this._available_indicators.length) {
			this._available_indicators = available_indicators;
//			global.log(this._available_indicators);
			this._settings.set_strv(AVAILABLE_INDICATORS_ID, this._available_indicators);
		}
	}
};

var MultiMonitorsAppMenuButton  = (() => {
	let MultiMonitorsAppMenuButton = class MultiMonitorsAppMenuButton extends PanelMenu.Button {
	    _init(panel) {
	    	if (panel.monitorIndex==undefined)
	    		this._monitorIndex = Main.layoutManager.primaryIndex;
	    	else	
	    		this._monitorIndex = panel.monitorIndex;
	    	this._actionOnWorkspaceGroupNotifyId = 0;
	    	this._targetAppGroup = null;
	    	this._lastFocusedWindow = null;
	    	Panel.AppMenuButton.prototype._init.call(this, panel);

	    	this._windowEnteredMonitorId = global.display.connect('window-entered-monitor',
			                					this._windowEnteredMonitor.bind(this));
			this._windowLeftMonitorId = global.display.connect('window-left-monitor',
			                					this._windowLeftMonitor.bind(this));
	    }
	    
	    _windowEnteredMonitor (metaScreen, monitorIndex, metaWin) {
	        if (monitorIndex == this._monitorIndex) {
	        	switch(metaWin.get_window_type()){
	        	case Meta.WindowType.NORMAL:
	        	case Meta.WindowType.DIALOG:
	        	case Meta.WindowType.MODAL_DIALOG:
	        	case Meta.WindowType.SPLASHSCREEN:
	        		this._sync();
	        		break;
	        	}
	        }
	    }
	
	    _windowLeftMonitor (metaScreen, monitorIndex, metaWin) {
	        if (monitorIndex == this._monitorIndex) {
	        	switch(metaWin.get_window_type()){
	        	case Meta.WindowType.NORMAL:
	        	case Meta.WindowType.DIALOG:
	        	case Meta.WindowType.MODAL_DIALOG:
	        	case Meta.WindowType.SPLASHSCREEN:
	        		this._sync();
	        		break;
	        	}
	        }
	    }
	    
	    _findTargetApp() {
	    	
	        if (this._actionOnWorkspaceGroupNotifyId) {
	            this._targetAppGroup.disconnect(this._actionOnWorkspaceGroupNotifyId);
	            this._actionOnWorkspaceGroupNotifyId = 0;
	            this._targetAppGroup = null;
	        }
	        let groupWindow = false;
	        let groupFocus = false;
	
	        let workspaceManager = global.workspace_manager;
	        let workspace = workspaceManager.get_active_workspace();
	        let tracker = Shell.WindowTracker.get_default();
	        let focusedApp = tracker.focus_app;
	        if (focusedApp && focusedApp.is_on_workspace(workspace)){
	        	let windows = focusedApp.get_windows();
	        	for (let i = 0; i < windows.length; i++) {
	        		let win = windows[i];
	        		if (win.located_on_workspace(workspace)){
	        			if (win.get_monitor() == this._monitorIndex){
	        				if (win.has_focus()){
	        					this._lastFocusedWindow = win;
	//    	        			global.log(this._monitorIndex+": focus :"+win.get_title()+" : "+win.has_focus());
		        			return focusedApp;	
	        				}
	        				else
	        					groupWindow = true;
	        			}
	        			else {
	        				if(win.has_focus())
	        					groupFocus = true;
	        			}
	        			if (groupFocus && groupWindow) {
							if(focusedApp != this._targetApp){
	    					this._targetAppGroup = focusedApp;
	    					this._actionOnWorkspaceGroupNotifyId = this._targetAppGroup.connect('notify::action-group', 
	    																				this._sync.bind(this));
	//    				 	global.log(this._monitorIndex+": gConnect :"+win.get_title()+" : "+win.has_focus());
							}
	        				break;
	        			}
	        		}
	        	}
	        }
	
	        for (let i = 0; i < this._startingApps.length; i++)
	            if (this._startingApps[i].is_on_workspace(workspace)){
	//            	global.log(this._monitorIndex+": newAppFocus");
	                return this._startingApps[i];
	            }
	        
	        if (this._lastFocusedWindow && this._lastFocusedWindow.located_on_workspace(workspace) &&
	        											this._lastFocusedWindow.get_monitor() == this._monitorIndex){
	//			global.log(this._monitorIndex+": lastFocus :"+this._lastFocusedWindow.get_title());
				return tracker.get_window_app(this._lastFocusedWindow);
	        }
	
	        let windows = global.display.get_tab_list(Meta.TabList.NORMAL_ALL, workspace);
	
	        for (let i = 0; i < windows.length; i++) {
	        	if(windows[i].get_monitor() == this._monitorIndex){
	        		this._lastFocusedWindow = windows[i];
	//        		global.log(this._monitorIndex+": appFind :"+windows[i].get_title());
	    			return tracker.get_window_app(windows[i]);
	    		}
	        }
	
	        return null;
	    }
	    
	    _sync() {
	    	if (!this._switchWorkspaceNotifyId)
	    		return;
	    	Panel.AppMenuButton.prototype._sync.call(this);
	    }
	    
	    _onDestroy() {
	    	if (this._actionGroupNotifyId) {
	            this._targetApp.disconnect(this._actionGroupNotifyId);
	            this._actionGroupNotifyId = 0;
	        }

	        global.display.disconnect(this._windowEnteredMonitorId);
	        global.display.disconnect(this._windowLeftMonitorId);
	        
            if (this._busyNotifyId) {
                this._targetApp.disconnect(this._busyNotifyId);
                this._busyNotifyId = 0;
            }
            
            if (this.menu._windowsChangedId) {
                this.menu._app.disconnect(this.menu._windowsChangedId);
                this.menu._windowsChangedId = 0;
            }
            Panel.AppMenuButton.prototype._onDestroy.call(this);
		}
	};
	MultiMonitors.copyClass(Panel.AppMenuButton, MultiMonitorsAppMenuButton);
	return GObject.registerClass({Signals: {'changed': {}},}, MultiMonitorsAppMenuButton);
})();

var MultiMonitorsActivitiesButton = (() => {
	let MultiMonitorsActivitiesButton = class MultiMonitorsActivitiesButton extends PanelMenu.Button {
	_init() {
	        super._init(0.0, null, true);
	        this.accessible_role = Atk.Role.TOGGLE_BUTTON;
	
	        this.name = 'mmPanelActivities';
	
	        /* Translators: If there is no suitable word for "Activities"
	           in your language, you can use the word for "Overview". */
	        this._label = new St.Label({ text: _("Activities"),
	                                     y_align: Clutter.ActorAlign.CENTER });
	        this.add_actor(this._label);
	
	        this.label_actor = this._label;
	
	        this.connect('captured-event', this._onCapturedEvent.bind(this));
	        this.connect_after('key-release-event', this._onKeyRelease.bind(this));
	
	        this._showingId = Main.overview.connect('showing', () => {
	            this.add_style_pseudo_class('overview');
	            this.add_accessible_state (Atk.StateType.CHECKED);
	        });
	        this._hidingId = Main.overview.connect('hiding', () => {
	            this.remove_style_pseudo_class('overview');
	            this.remove_accessible_state (Atk.StateType.CHECKED);
	        });
	        
	        this._xdndTimeOut = 0;
	    }
	    
	    _onDestroy() {
		    Main.overview.disconnect(this._showingId);
		    Main.overview.disconnect(this._hidingId);
		    super._onDestroy();
	    }
	}
	MultiMonitors.copyClass(Panel.ActivitiesButton, MultiMonitorsActivitiesButton);
	return GObject.registerClass(MultiMonitorsActivitiesButton);
})();

const MULTI_MONITOR_PANEL_ITEM_IMPLEMENTATIONS = {
    'activities': MultiMonitorsActivitiesButton,
    'appMenu': MultiMonitorsAppMenuButton,
    'dateMenu': MMCalendar.MultiMonitorsDateMenuButton,
};

var MultiMonitorsPanel = (() => {
	let MultiMonitorsPanel = class MultiMonitorsPanel extends St.Widget {
	    _init (monitorIndex, mmPanelBox) {
	    	this.monitorIndex = monitorIndex;

	    	super._init({ name: 'panel',
	            reactive: true });
	
			if (MultiMonitors.gnomeShellVersion()[1]==32) {
				this.actor = this;
			}
			this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
			
			this._sessionStyle = null;
			
			this.statusArea = {};
			
			this.menuManager = new PopupMenu.PopupMenuManager(this);
			
			this._leftBox = new St.BoxLayout({ name: 'panelLeft' });
			this.add_child(this._leftBox);
			this._centerBox = new St.BoxLayout({ name: 'panelCenter' });
			this.add_child(this._centerBox);
			this._rightBox = new St.BoxLayout({ name: 'panelRight' });
			this.add_child(this._rightBox);
			
			this._leftCorner = new Panel.PanelCorner(St.Side.LEFT);
			this.add_child(this._leftCorner.actor);
			
			this._rightCorner = new Panel.PanelCorner(St.Side.RIGHT);
			this.add_child(this._rightCorner.actor);
			
			this.connect('button-press-event', this._onButtonPress.bind(this));
			this.connect('touch-event', this._onButtonPress.bind(this));
			this.connect('key-press-event', this._onKeyPress.bind(this));
			
			this._showingId = Main.overview.connect('showing', () => {
			  this.add_style_pseudo_class('overview');
			});
			this._hidingId = Main.overview.connect('hiding', () => {
			  this.remove_style_pseudo_class('overview');
			});
			
			mmPanelBox.panelBox.add(this);
			Main.ctrlAltTabManager.addGroup(this, _("Top Bar"), 'focus-top-bar-symbolic',
			                              { sortGroup: CtrlAltTab.SortGroup.TOP });
			
			this._updatedId = Main.sessionMode.connect('updated', this._updatePanel.bind(this));
			
			this._workareasChangedId = global.display.connect('workareas-changed', () => { this.queue_relayout(); });
			
			this._updatePanel();
			
	        this._settings = Convenience.getSettings();
	        this._showActivitiesId = this._settings.connect('changed::'+SHOW_ACTIVITIES_ID,
	        													this._showActivities.bind(this));
	        this._showActivities();

	        this._showAppMenuId = this._settings.connect('changed::'+SHOW_APP_MENU_ID,
																this._showAppMenu.bind(this));
	        this._showAppMenu();
	        
	        this._showDateTimeId = this._settings.connect('changed::'+SHOW_DATE_TIME_ID,
																this._showDateTime.bind(this));
	        this._showDateTime();
	        
	        this.connect('destroy', this._onDestroy.bind(this));
		}
	    
	    _onDestroy() {
			global.display.disconnect(this._workareasChangedId);
		    Main.overview.disconnect(this._showingId);
		    Main.overview.disconnect(this._hidingId);

		    this._settings.disconnect(this._showActivitiesId);
		    this._settings.disconnect(this._showAppMenuId);
		    this._settings.disconnect(this._showDateTimeId);
		    
		    Main.ctrlAltTabManager.removeGroup(this);
		    
		    Main.sessionMode.disconnect(this._updatedId);
	    }
	    
	    _showActivities() {
	    	let name = 'activities';
	    	if (this._settings.get_boolean(SHOW_ACTIVITIES_ID)) {
	    		if (this.statusArea[name])
	    			this.statusArea[name].visible = true;
	    	}
	    	else {
	    		if (this.statusArea[name])
	    			this.statusArea[name].visible = false;
	    	}
		}
		
		_showDateTime() {
	    	let name = 'dateMenu';
	    	if (this._settings.get_boolean(SHOW_DATE_TIME_ID)) {
	    		if (this.statusArea[name])
	    			this.statusArea[name].visible = true;
	    	}
	    	else {
	    		if (this.statusArea[name])
	    			this.statusArea[name].visible = false;
	    	}
		}
		
		_showAppMenu() {
			let name = 'appMenu';
	    	if (this._settings.get_boolean(SHOW_APP_MENU_ID)) {
	    		if (!this.statusArea[name]) {
	    			let indicator = new MultiMonitorsAppMenuButton(this);
	    			this.statusArea[name] = indicator;
	    			let box = this._leftBox;
	    			this._addToPanelBox(name, indicator, box.get_n_children()+1, box);
	    		}
	    	}
	    	else {
	    		if (this.statusArea[name]) {
	    			let indicator = this.statusArea[name];
	    			this.menuManager.removeMenu(indicator.menu);
	    			indicator.destroy();
	    			delete this.statusArea[name];
	    		}
	    	}
		}

	    vfunc_get_preferred_width(forHeight) {
	    	if (Main.layoutManager.monitors.length>this.monitorIndex)
	    		return [0, Main.layoutManager.monitors[this.monitorIndex].width];
	    	
	        return [0,  0];
	    }
	    
	    _hideIndicators() {
	        for (let role in MULTI_MONITOR_PANEL_ITEM_IMPLEMENTATIONS) {
	            let indicator = this.statusArea[role];
	            if (!indicator)
	                continue;
	            
	            if (indicator.menu)
	                indicator.menu.close();
	            
	            indicator.container.hide();
	        }
	    }
	
	    _ensureIndicator(role) {
	        let indicator = this.statusArea[role];
	        if (indicator) {
	            indicator.container.show();
	            return null;
	        }
	        else {
	            let constructor = MULTI_MONITOR_PANEL_ITEM_IMPLEMENTATIONS[role];
	            if (!constructor) {
	                // This icon is not implemented (this is a bug)
	                return null;
	            }
	            indicator = new constructor(this);
	            this.statusArea[role] = indicator;
	        }
	        return indicator;
	    }
	};
	MultiMonitors.copyClass(Panel.Panel, MultiMonitorsPanel);
	return GObject.registerClass(MultiMonitorsPanel);
})();
