namespace uniflash.session {

	'use strict';

	class SavedSessionData {

		public static CURRENT_VERSION: string = 'uniflash_session_format_version_2';
		public version: string = SavedSessionData.CURRENT_VERSION;
		public ccxmlContent: string = null;
		public deviceXMLFile: string = null;
		public connectionXMLFile: string = null;
		public queryConfig: uniflash.target_config.QueryConfigResult = { 
			cores: [], 
			debuggableCores: [],
			nondebuggableCores: [],
			deviceXMLFile: '', 
			connectionXMLFile: ''
		};
		public userPreferences: {};

		constructor() { }
	}

	enum SessionState {
		RefreshStarted = 1,
		RefreshCompleted,
		LoadStarted,
		LoadCompleted,
		Unitialized,
		Initialized
	}

	export class SessionService {

		static browserSessionStorageKey: string = 'uniflash_temp_session';
		static sessionFileExt: string = '.uniflash';
		static sessionFileType: string = 'Sessions';
		static saveSessionDialogTitle: string = 'Enter your UniFlash session name';
		static openSessionDialogTitle: string = 'Load UniFlash session';
		private sessionFile: ti_common.file.File;
		private sessionName: string;
		private sessionDataStrAtStart: string;

		// service data
		// that needs to be saved and loaded as part of the session
		private data: SavedSessionData = new SavedSessionData();

		// helper class to manage actual sesison data storage
		private sessionDataStore: SessionDataStore;

		static $inject = ['$q', '$window', '$state', '$rootScope', '$log',
			'ticFileDialogs', 'ticFileFactory', 'ticAlerts', 'ticExecutionPlatform',
			'ufPropertiesDB', 'ufTargetConfig', 'ufPreferencesSrv', 'ufConsole'];

		constructor(
			private $q: angular.IQService,
			private $window: angular.IWindowService,
			private $state: angular.ui.IStateService,
			private $rootScope,
			private $log,
			private ticFileDialogs: any,
			private ticFileFactory: ti_common.file.IFileFactory,
			private ticAlerts: any,
			private ticExecutionPlatform: ti_common.IExecutionPlatform,
			private ufPropertiesDB: any,
			private ufTargetConfig: any,
			private ufPreferencesSrv: any,
			private ufConsole: any
		) {
			this.sessionDataStore = new SessionDataStore();
			// before leaving the page save data to a session storage 
			this.$window.onbeforeunload = this.saveToBrowserSessionStorage;
		}

		private openDialogIfNeeded(selections) {
			if (selections) {
				return this.$q.resolve(selections);
			} else {
				return this.ticFileDialogs.openFile(SessionService.openSessionDialogTitle, SessionService.sessionFileExt, SessionService.sessionFileType);
			}
		}

		private saveCurrentSessionToFile(sessionFile: ti_common.file.File): angular.IPromise<string> {
			let dataToSave = this.saveSession();
			return sessionFile.setContents(JSON.stringify(dataToSave));
		}

		private saveSession(): any {
			this.$log.debug('session service: saveSession() ');
			this.sessionDataStore.updateBeforeSave();
			this.$log.debug('session service: saveSession() 2');
			let dataToSave = this.sessionDataStore.getSessionDataAll();
			this.$log.debug('session service: saveSession() dataToSave: ' + JSON.stringify(dataToSave));
			// addtionally add our own data to be saved
			dataToSave.session = this.data;
			this.$log.debug('session service: saveSession() this.data: ' + JSON.stringify(this.data));
			return dataToSave;
		}

		registerSessionDataProvider(key: string, provider: ISessionDataProvider) {
			this.sessionDataStore.registerSessionDataProvider(key, provider);
			//when some provider is registered, update initial data again
			this.sessionDataStrAtStart = angular.toJson(this.saveSession());
		}

		private createNewSession(deviceXMLFile, connectionXMLFile, lp = false) {
			let ccxmlContent;
			return this.ufTargetConfig.createCCXML(deviceXMLFile, connectionXMLFile, lp)
				.then((newCCXMLContent: string) => {
					ccxmlContent = newCCXMLContent;
					return this.ufTargetConfig.queryConfig(ccxmlContent);
				})
				.then((queryConfig: uniflash.target_config.QueryConfigResult) => {
					// set our internal data;
					this.data.deviceXMLFile = deviceXMLFile;
					this.data.connectionXMLFile = connectionXMLFile;
					this.data.ccxmlContent = ccxmlContent;
					this.removeUnwantedCores(queryConfig.cores);
					this.data.queryConfig = queryConfig;
					// wipe out the stored session data
					this.sessionDataStore.setSessionDataAll({});
					// notify everyone a new session is has been loaded
					this.sessionDataStore.sessionLoaded();
					this.sessionName = this.data.deviceXMLFile;
					return this.ufTargetConfig.getDevice(deviceXMLFile);
				})
				.then((device: any) => {
					return this.ufPropertiesDB.getPropertiesData(this.data.queryConfig);
				})
				.finally(() => {
					this.sessionDataStrAtStart = angular.toJson(this.saveSession());
				});
		}


		private initPromise: angular.IPromise<void>;

		// start a new session if needed
		startIfNeeded(deviceXMLFile, connectionXMLFile, force, lp = false): angular.IPromise<void> {
			let currentState: SessionState = force ? SessionState.Unitialized : this.getState();
			if (!this.sessionName) { this.sessionName = deviceXMLFile; }
			switch (currentState) {
				case SessionState.RefreshStarted:
					this.updateState(SessionState.RefreshCompleted);
					let browserSessiontDataToLoad = this.$window.sessionStorage.getItem(SessionService.browserSessionStorageKey);
					this.initPromise = this.loadData(JSON.parse(browserSessiontDataToLoad)).catch((e) => {
						return this.ticAlerts.showError('Failed to load session data from browser storage : ' + e.message);
					});
					break;
				case SessionState.LoadStarted:
					this.initPromise = this.$q.resolve();
					this.updateState(SessionState.LoadCompleted);
				case SessionState.LoadCompleted:
				case SessionState.RefreshCompleted:
					this.updateState(SessionState.Initialized);
					break;
				default:
					this.initPromise = this.createNewSession(deviceXMLFile, connectionXMLFile, lp)
						.then(() => {
							this.updateState(SessionState.Initialized);
						});
					break;
			}

			return this.initPromise;
		}

		isG2DeviceSpecialHandling(deviceId) {
			let OSType = TICloudAgent.getOS();
			let isG2Device = /.*MSP.*G2.*/.test(deviceId);
			return (OSType !== 'win') && isG2Device;
		}

		private loadData(sessionData: any): angular.IPromise<void> {
			this.sessionDataStore.setSessionDataAll(sessionData);
			this.data = this.sessionDataStore.getSessionDataAll().session;

			return this.ufPropertiesDB.getPropertiesData(this.data.queryConfig)
				.then(() => {
					this.sessionDataStore.sessionLoaded();
					// session update the state
					this.$state.go('main.Program', {
						deviceXMLFile: this.data.deviceXMLFile,
						connectionXMLFile: this.data.connectionXMLFile
					}, {
							inherit: false,
							reload: true
						});
				});
		}

		newFromCCXML(deviceXMLFile: string, connectionXMLFile: string, ccxmlContent: string) {
			this.updateState(SessionState.LoadStarted);
			return this.ufTargetConfig.queryConfig(ccxmlContent)
				.then((queryConfig: uniflash.target_config.QueryConfigResult) => {
					// set our internal data;
					this.data.deviceXMLFile = deviceXMLFile;
					this.data.connectionXMLFile = connectionXMLFile;
					this.data.ccxmlContent = ccxmlContent;
					this.removeUnwantedCores(queryConfig.cores);
					this.data.queryConfig = queryConfig;
					// wipe out the stored session data
					this.sessionDataStore.setSessionDataAll({});
					// notify everyone a new session is has been loaded
					this.sessionDataStore.sessionLoaded();
					this.sessionName = this.data.deviceXMLFile;
					return this.ufTargetConfig.getDevice(deviceXMLFile);
				})
				.then((device: any) => {
					return this.ufPropertiesDB.getPropertiesData(this.data.queryConfig);
				})
				.finally(() => {
					this.sessionDataStrAtStart = angular.toJson(this.saveSession());
				});
		}

		load(selections) {
			this.$log.debug('session service: load()');
			let sessionFile;
			let confirmPromise = this.initPromise ? this.confirmLeave() : this.$q.resolve('No session to save');
			return confirmPromise.then(() => {
				return this.openDialogIfNeeded(selections).then((selections: ti_common.file.File[]) => {
					sessionFile = selections[0];
					return this.ufPreferencesSrv.pushRecentSessionFile(sessionFile);
				}).then(() => {
					return sessionFile.getContents();
				}).then((contents: string) => {
					return this.updateSessionDataIfNeeded(JSON.parse(contents));
				}).then((sessionData) => {
					if (!this.isG2DeviceSpecialHandling(sessionData.session.deviceXMLFile)) {
						this.sessionFile = sessionFile;
						this.updateState(SessionState.LoadStarted);
						this.sessionName = sessionFile.name.slice(0, -9);
						return this.loadData(sessionData);
					} else {
						throw { message: 'G2 Devices are only supported on Windows.' };
					}
				}).catch((e) => {
					if ((e !== 'No') && (e !== 'Cancel Clicked') && (e !== 'escape key press') && (e !== 'backdrop click')) {
						let msg = 'Load session failed : ' + e.message;
						return this.ticAlerts.showError(msg);
					}
				}).finally(() => {
					this.sessionDataStrAtStart = angular.toJson(this.saveSession());
				});
			});
		}

		private updateSessionDataIfNeeded(sessionData) {
			return this.$q.resolve(this.isOldSession(sessionData.session.version))
				.then((isNeedUpdate) => {
					if (isNeedUpdate) {
						return this.$q.resolve(this.ticExecutionPlatform.getPlatform() === ti_common.PlatformType.NW)
							.then((isNW) => {
								if (isNW) {
									return this.ticAlerts.showConfirm('Update Session File to Continue', 'Your session file is of an older version and needs to be updated to work with this version of the tool. Updating the session file may cause it to stop working on an older version of UniFlash.');
								}
							})
							.then(() => {
								return this.updateSessionData(sessionData);
							});
					} else {
						return sessionData;
					}
				});
		}

		private isOldSession(ver) {
			if (!ver) {
				return true;
			}
			//custom logic to determine which is newer, right now just compare the last digit in the version str
			let fileVerNum = parseInt(ver.substring(ver.lastIndexOf('_') + 1));
			let curVerNum = parseInt(SavedSessionData.CURRENT_VERSION.substring(SavedSessionData.CURRENT_VERSION.lastIndexOf('_') + 1));
			return curVerNum > fileVerNum;
		}

		private updateSessionData(data) {
			let ret = data;
			//TODO: all session file update logic goes here
			//update the structure of session data as needed
			return ret;
		}

		new() {
			this.$log.debug('session service: new()');
			this.saveToBrowserSessionStorage();
			this.$state.go('welcome');
		}

		getCCXML() {
			return this.data.ccxmlContent;
		}

		getDeviceXMLFile() {
			return this.data.deviceXMLFile;
		}

		getConnectionXMLFile() {
			return this.data.connectionXMLFile;
		}

		save(): angular.IPromise<string> {
			this.$log.debug('session service: save() 1');
			return this.ticFileDialogs.saveFile(SessionService.saveSessionDialogTitle, this.sessionName, SessionService.sessionFileExt)
				.then((newSessionFile: ti_common.file.File) => {
					this.sessionFile = newSessionFile;
					this.$log.debug('session service: save() sessionFile: ' + this.sessionFile);
					this.sessionName = newSessionFile.name.slice(0, -9);
					this.$log.debug('session service: save() sessionName: ' + this.sessionName);
					return this.ufPreferencesSrv.pushRecentSessionFile(this.sessionFile);
				})
				.then(() => {
					this.$log.debug('session service: save() 2');
					return this.saveCurrentSessionToFile(this.sessionFile);
				})
				.then(() => {
					this.$log.debug('session service: save() 3');
					this.sessionDataStrAtStart = angular.toJson(this.saveSession());
					this.$log.debug('session service: save() 4');
				})
				.catch((e) => {
					this.$log.debug('session service: save() Error in saving file: ' + e.message);
				});
		}

		saveToTempSession(): angular.IPromise<string> {
			let dataToSave = this.sessionDataStore.getSessionDataAll();
			if (dataToSave.session && dataToSave.session.ccxmlContent) {
				let fileNameWithExt = 'unsaved session' + SessionService.sessionFileExt;
				let file = this.ticFileFactory.create(fileNameWithExt);
				return this.saveCurrentSessionToFile(file);
			} else {
				return this.$q.when('');
			}
		}

		queryConfig(): uniflash.target_config.QueryConfigResult {
			return this.data.queryConfig;
		}

		confirmLeave(): angular.IPromise<string> {
			this.ufConsole.clearMessages();
			let oldData = this.sessionDataStrAtStart;
			let newData = angular.toJson(this.saveSession());

			if (oldData === newData) {
				return this.$q.resolve('');
			} else {
				return this.ticAlerts.showConfirm('Are you sure?', 'Unsaved session changes will be lost!').then(
					() => {
						this.initPromise = null;
						this.updateState(SessionState.Unitialized);
					}
				);
			}
		}

		private updateState(newState: SessionState) {
			this.$window.sessionStorage.setItem('state', JSON.stringify(newState));
			if (newState === SessionState.LoadCompleted || newState === SessionState.Initialized) {
				this.$rootScope.$emit('session.configured');
			}
		}

		private getState(): SessionState {
			let newState: SessionState = JSON.parse(this.$window.sessionStorage.getItem('state'));
			return newState ? newState : SessionState.Unitialized;
		}

		/* Used to facilatite saving state whent he page reloads*/
		private saveToBrowserSessionStorage = () => {
			let isMainPage = this.$state.current.name.split(".")[0] === "main";
			if (SessionState.Unitialized !== this.getState() && isMainPage) {
				let dataToSave = this.saveSession();
				this.updateState(SessionState.RefreshStarted);
				this.$window.sessionStorage.setItem(SessionService.browserSessionStorageKey, JSON.stringify(dataToSave));
			}
		};

		private removeUnwantedCores(coreList: uniflash.target_config.Core[]) {
			this.removeCore(coreList, 'C67');
			this.removeCore(coreList, 'CSETB');
		}

		private removeCore(coreList: uniflash.target_config.Core[], coreName: string) {
			let index = _.findIndex(coreList, function (core) {
				return (core.name.substring(0, coreName.length) === coreName);
			});
			if (index !== -1) { coreList.splice(index, 1); }
		}

		isSerial(): boolean {
			return (this.data.queryConfig.connectionXMLFile.indexOf('Serial_Connection') !== -1);
		}
	}


	angular.module('uniflash').service('ufSession', SessionService);

}
