declare var TICloudAgent;

namespace uniflash {

	'use strict';
	/*global TICloudAgent*/

	export interface IDSService {
		setProperties();
		getProperties();
		getTargetInfo();
		program(programs: uniflash.programs.Program[]): angular.IPromise<boolean>;
		verify(programs: uniflash.programs.Program[]): angular.IPromise<boolean>;
		flashOperation(opStr: string): angular.IPromise<boolean>;
		readMemory(addr: string, typeSize: string, length: number, format: string, optionObj): angular.IPromise<any>;
		exportMemoryAsCoff(ranges: uniflash.memory.Range[], filePath: string, optionObj): angular.IPromise<string>;
		exportMemoryAsBin(range: uniflash.memory.Range, valueSizeInBits: number, filePath: string, optionObj): angular.IPromise<any>;
		evaluate(expression: string);
	}

	export interface IDSProgramFile {
		filename: string;
		loadAddress?: number;
	}

	export class DS {
		static $inject = ['$q', '$rootScope', '$log',
			'ticAlerts', 'ufSession', 'ufPrograms',
			'ufPropertiesValue', 'ufAgentInstallDialog',
			'ufPreferencesSrv', 'ufContext', 'ufConsole',
			'ufPropertiesDB', 'ufResets', 'ufTargetConfig', 'x2js'];
		public agent;
		public dsImpl;
		public partNum;
		public modules = ['File', 'TargetSupport'];
		public progressEventHandler;
		public targetSupportProgressEvents;
		public dSHandlers;
		public cleanUpTargetSupportProgressEvents;
		public cleanUpDSHandlers;
		public cachedConfigure = null;
		public cachedConnectedCores: { [key: string]: any; } = {};//map
		public targetStateListeners: { [key: string]: { (any): any; }[] } = {};//map
		public dsLogPath = null;
		public dsLogName = null;
		public runTargetAfterProgramLoadDisabled = false;
		public _ufResets;
		public _q;

		private isJLink;

		constructor(
			private $q,
			private $rootScope,
			private $log,
			private ticAlerts,
			private ufSession,
			private ufPrograms,
			private ufPropertiesValue,
			private ufAgentInstallDialog,
			private ufPreferencesSrv,
			private ufContext,
			private ufConsole,
			private ufPropertiesDB,
			private ufResets,
			private ufTargetConfig,
			private x2js
		) {
			this._ufResets = ufResets;
			this._q = $q;
			//listen for session launch here and change dsImpl accordingly
			$rootScope.$on('session.configured', () => {
				this.partNum = this.ufPropertiesDB.getPartnum();
				this.$log.debug('ufDS, session.configured, partnum of current session = ' + this.partNum);
				//factory base on the partnum
				this.isJLink = (this.ufSession.data
					&& this.ufSession.data.connectionXMLFile
					&& (this.ufSession.data.connectionXMLFile.toLowerCase().indexOf('j-link') !== -1));

				if (DSLprfImpl.matches(this.partNum) && !this.isJLink && !this.isSerial()) {
					this.dsImpl = new DSLprfImpl(
						this,
						this.$q,
						this.$rootScope,
						this.$log,
						this.ticAlerts,
						this.ufSession,
						this.ufPrograms,
						this.ufPropertiesValue,
						this.ufAgentInstallDialog,
						this.ufPreferencesSrv,
						this.ufContext,
						this.ufTargetConfig,
						this.ufConsole,
						this.x2js);
				} else if (DSDualCoreC28Impl.matches(this.partNum) && !this.isSerial()) {
					this.dsImpl = new DSDualCoreC28Impl(
						this,
						this.$q,
						this.$rootScope,
						this.$log,
						this.ticAlerts,
						this.ufSession,
						this.ufPrograms,
						this.ufPropertiesValue,
						this.ufAgentInstallDialog,
						this.ufPreferencesSrv,
						this.ufContext,
						this.ufTargetConfig,
						this.ufConsole,
						this.x2js);
				} else {
					this.dsImpl = new DSDefaultImpl(
						this,
						this.$q,
						this.$rootScope,
						this.$log,
						this.ticAlerts,
						this.ufSession,
						this.ufPrograms,
						this.ufPropertiesValue,
						this.ufAgentInstallDialog,
						this.ufPreferencesSrv,
						this.ufContext,
						this.ufTargetConfig,
						this.ufConsole,
						this.x2js);
				}

				// disable auto run on serial connection or PGA devices
                this.runTargetAfterProgramLoadDisabled = (this.isSerial() || (this.partNum.toLowerCase().indexOf('pga9') !== -1));
			});
		}

		isConfigured() {
			return (this.cachedConfigure) ? true : false;
		}

		getUfPropertiesValue() {
			return this.ufPropertiesValue;
		}

		getCachedConnectedCores() {
			return this.cachedConnectedCores;
		}

		getContext() {
			return this.ufContext;
		}

		getConsole() {
			return this.ufConsole;
		}

		registerProgressEventHandler(handler) {
			this.progressEventHandler = handler;
		}

		registerDSHandler(handler) {
			this.dSHandlers = handler;
		}

		registerTargetSupportHandler(handler) {
			this.targetSupportProgressEvents = handler;
		}

		private setupCleanUpTargetSupportProgressEventsHandler(agent) {
			if (this.targetSupportProgressEvents) {
				this.cleanUpTargetSupportProgressEvents = this.targetSupportProgressEvents(agent);
			}
		}

		private setupCleanUpDSHandlers(agent) {
			if (this.dSHandlers) {
				this.cleanUpDSHandlers = this.dSHandlers(agent);
			}
		}

		unregisterEventsHandlers() {
			if (this.cleanUpTargetSupportProgressEvents) {
				this.cleanUpTargetSupportProgressEvents();
			}

			if (this.cleanUpDSHandlers) {
				this.cleanUpDSHandlers();
			}

			if (this.progressEventHandler) {
				this.progressEventHandler = null;
			}
		}

		private callHandler(handler, ...handlerArgs) {
			if (handler) {
				return handler(handlerArgs);
			}
		}

		addTargetStateListener(coreName: string, listener) {
			if (coreName) {
				this.$log.debug('addTargetStateListener on ' + coreName);
				if (!this.targetStateListeners[coreName]) {
					this.targetStateListeners[coreName] = [];
				}

				this.targetStateListeners[coreName].push(listener);
			}
		}


		removeTargetStateListener(coreName: string, listener) {
			if (coreName && this.cachedConnectedCores[coreName]) {
				this.$log.debug('removeTargetStateListener on ' + coreName);
				this.cachedConnectedCores[coreName].targetState.removeListener('changed', listener);
			}


			let listenerArray = this.targetStateListeners[coreName];
			if (listenerArray && listenerArray.length > 0) {
				for (let i = 0; i < listenerArray.length; i++) {
					if (listenerArray[i] === listener) {
						listenerArray.splice(i, 1);
						break;
					}
				}
			}
		}

		writeFiles(programOBJs: uniflash.programs.Program[]): angular.IPromise<IDSProgramFile[]> {
			// store functions to download program files to disk
			let writeFileFuncs = [];
			// store ds program file format objects to be based to the ds api
			let dsProgramFileObjs: IDSProgramFile[] = [];
			_.each(programOBJs, (programObj) => {
				writeFileFuncs.push(() => {
					return this.dsImpl.writeFileToBeFlashed(programObj.file)
						.then((programFileLocalPath) => {
							if (programObj.isBinary) {
								dsProgramFileObjs.push({
									filename: programFileLocalPath,
									loadAddress: this.mapLoadAddress(programObj)
								});
							} else {
								dsProgramFileObjs.push({ filename: programFileLocalPath });
							}
						});
				});
			});

			return writeFileFuncs.reduce((soFar, f) => {
				return soFar.then(f);
			}, this.$q.resolve())
				.then(() => {
					return dsProgramFileObjs;
				});
		}

		// cloud Agent Init with modules
		// should init agent + specified modules
		// should be moved to TICloudAgent
		private initWithModules(moduleNames) {
			let moduleInitPromises = [];
			let agent;

			this.callHandler(this.progressEventHandler, 'Initializing...');

			return TICloudAgent.Init()
				.catch((error) => {
					if (error instanceof Array) {
						// cloud agent installation error
						let deviceXML = this.ufSession.getDeviceXMLFile();
						let connectionXML = this.ufSession.getConnectionXMLFile();
						let params = {
							errors: error,
							deviceID: deviceXML,
							connectionID: connectionXML
						};
						// try to handle the installation issues
						return TICloudAgent.Install.getInstallWizard(params)
							.then((installWizardObj) => {
								return this.ufAgentInstallDialog.show(installWizardObj);
							});
					} else {
						// we can't handle it here, so keep it going up
						throw error;
					}
				})
				.then((newAgent) => {
					agent = newAgent;
					_.each(moduleNames, (moduleName) => {
						moduleInitPromises
							.push(agent.getSubModule(moduleName)
								.then((module) => {
									agent[moduleName] = module;
								}));
					});
				})
				.then(() => {
					return this.$q.all(moduleInitPromises);
				})
				.then(() => {
					return agent;
				});
		}

		mapLoadAddress(programObj) {
			if (programObj.isBinary && !programObj.loadAddress) {
				return 0;
			} else if (programObj.loadAddress) {
				return parseInt(programObj.loadAddress, 16);
			} else {
				return undefined;
			}
		}

		getDSCore(core: string) {
			let ret;
			if (DSLprfImpl.matches(this.partNum) && !this.isJLink) {
				ret = new LPRFCore(core, this);
			} else {
				ret = new DSCore(core, this);
			}
			return ret;
        }

        configureLogging(enable: boolean, filename: string) {
            this.dsLogName = filename;
            return this.dsImpl.configureLogging(enable, filename)
                .then((ret) => {
                    this.dsLogPath = enable ? ret : null;
                    return ret;
                });
        }

        getLoggingPath() {
            return this.dsLogPath;
        }

        getLoggingName() {
            return this.dsLogName;
        }

        isSerial() {
            return this.ufSession.isSerial();
        }

		// get the 'run target after program load' value for the current target
		getRunTargetAfterProgramLoad() {
			if (this.isSerial() || (this.partNum.toLowerCase().indexOf('pga9') !== -1)) {
				return false;
			} else if ( this.isC2000(this.partNum.toLowerCase()) ) {
				return this.ufPreferencesSrv.get('runTargetAfterProgramLoadC2000');
			} else {
				return this.ufPreferencesSrv.get('runTargetAfterProgramLoad');
			}
		}

		// toggle the current setting of 'run target after program load'
		toggleRunTargetAfterProgramLoad() {
			const id = this.isC2000(this.partNum.toLowerCase()) ? 'runTargetAfterProgramLoadC2000' : 'runTargetAfterProgramLoad';
			return this.ufPreferencesSrv.set(id, !this.ufPreferencesSrv.get(id));
		}

        private isC2000(partnum:string) {
            return ( (partnum.indexOf('tms320f28') !== -1) ||
                     (partnum.indexOf('tms320c28') !== -1) ||
                     (partnum.indexOf('tms320r28') !== -1) ||
                     (partnum.indexOf('f28plc') !== -1) ||
                     (partnum.indexOf('f28m3') !== -1) );
        }
    }

    angular.module('uniflash').service('ufDS', DS);
}
