namespace uniflash {
	export class DSDefaultImpl {
		constructor(
			protected ds,
			protected $q,
			protected $rootScope,
			protected $log,
			protected ticAlerts,
			protected ufSession,
			protected ufPrograms,
			protected ufPropertiesValue,
			protected ufAgentInstallDialog,
			protected ufPreferencesSrv,
			protected ufContext,
			protected ufTargetConfig,
			protected ufConsole,
			protected x2js
		) {

		}

		configureDS(ccxmlContents, fileName) {
			if (this.ds.cachedConfigure) {
				return this.ds.cachedConfigure.finally(() => {
					this.ds.unregisterEventsHandlers();
					this.$log.debug('returning cached configure');
					this.ds.callHandler(this.ds.progressEventHandler, 'Initialization Complete...');
					this.ds.setupCleanUpTargetSupportProgressEventsHandler(this.ds.agent);
					this.ds.setupCleanUpDSHandlers(this.ds.agent);
				});
			}

			let ccxmlFilePath;
			return (() => {
				this.ds.cachedConfigure = this.ds.initWithModules(this.ds.modules)
					.then((newAgent) => {
						this.ds.agent = newAgent;
						this.ds.callHandler(this.ds.progressEventHandler, 'Initialization Complete...');
						return this.writeCCXML(ccxmlContents, fileName);
					})
					.then((newCcxmlFilePath) => {
						ccxmlFilePath = newCcxmlFilePath;
						this.ds.setupCleanUpTargetSupportProgressEventsHandler(this.ds.agent);
						// todo : temporary solution, eventually this need to be fixed on the agenthost side
						if (typeof process === 'undefined') {
							return this.ds.agent.TargetSupport.add(ccxmlFilePath);
						}
					})
					.then(() => {
						return this.ds.agent.getSubModule('DS');
					})
					.then((newDS) => {
						this.ds.agent.DS = newDS;
						this.ds.setupCleanUpDSHandlers(this.ds.agent);
						return newDS.configure(ccxmlFilePath);
					})
					.then((data) => {
						this.$log.debug('configured');
						return {
							ds: this.ds.agent.DS,
							ccxmlFilePath: ccxmlFilePath,
							configData: data
						};
					})
					.catch((e) => {
						this.ds.cachedConfigure = null;
						if (e instanceof ti_common.CancelError) {
							throw new Error(e.message);
						} else {
							this.$log.error('configure failed: ' + e.message);
							if (this.ds.agent.DS) {
								this.ds.agent.DS.close();
							}
							throw e;
						}
					});
				this.$log.debug('returning new configure');
				return this.ds.cachedConfigure;
			})();
		}

		configureLogging(enable: boolean, filename: string) {
			this.$rootScope.$emit('ds.opStarted');
			let fullPath, configObj;
			return this.configureDS(this.ufSession.getCCXML(), 'uniflash_temp.ccxml')
				.then((configureDSRetObj) => {
					configObj = configureDSRetObj;
					return (enable) ? this.writeLocalFile(filename, '') : null;
				})
				.then((writeFileResObj) => {
					fullPath = (writeFileResObj === null) ? null : writeFileResObj.path;
					return configObj.ds.logging.configure(enable, fullPath);
				})
				.then(() => {
					return fullPath;
				})
				.finally(() => {
					this.$rootScope.$emit('ds.opFinished');
				});
		}

		disconnectAllCores() {
			let disconnectPromise = this.$q.when();
			if (Object.keys(this.ds.cachedConnectedCores).length > 0) {
				let disconnectPromises = [];
				angular.forEach(this.ds.cachedConnectedCores, (value, key) => {
					if (value) {
						disconnectPromises.push(value.targetState.disconnect());
					}
				});
				disconnectPromise = this.$q.all(disconnectPromises);
			}
			return disconnectPromise
				.finally(() => {
					this.ds.cachedConnectedCores = {};
				});
		}

		deconfigureDS(dontCloseDS?: boolean) {
			if (this.ds.cachedConfigure) {
				return this.disconnectAllCores()
					.then(() => {
						this.$rootScope.$emit('ds.opStarted');
						this.ds.callHandler(this.ds.progressEventHandler, 'Deconfiguring...');
						this.$log.debug('cores disconnected: ' + JSON.stringify(this.ds.cachedConnectedCores));
						if (this.ds.agent && this.ds.agent.DS) {
							return this.ds.agent.DS.deConfigure();
						}
					})
					.then((result) => {
						this.$log.debug('deconfigured');
						this.ds.cachedConfigure = null;
						this.ds.unregisterEventsHandlers();
						return result;
					})
					.finally(() => {
						if (!dontCloseDS && this.ds.agent && this.ds.agent.DS) {
							this.ds.agent.DS.close();
						}
						this.$rootScope.$emit('ds.opFinished');
					});
			} else {
				return this.$q.when();
			}
		}

		findCorePath(dSConfigObj, coreName) {
			for (let i = 0; i < dSConfigObj.configData.cores.length; i++) {
				if (_.endsWith(dSConfigObj.configData.cores[i], coreName)) {
					return dSConfigObj.configData.cores[i];
				}
			}
		}

		findNonDbgCorePath(dSConfigObj, coreName) {
			for (let i = 0; i < dSConfigObj.configData.nonDebugCores.length; i++) {
				if (_.endsWith(dSConfigObj.configData.nonDebugCores[i], coreName)) {
					return dSConfigObj.configData.nonDebugCores[i];
				}
			}
		}

		getCore(coreName, dSConfigObj?, isNonDebug?) {
			let configObject;
			if (!dSConfigObj) {
				if (this.ds.cachedConfigure) {
					configObject = this.ds.cachedConfigure;
				} else {
					throw new Error('connectToCore: ds not configured.');
				}
			} else {
				configObject = dSConfigObj;
			}

			let corePath = isNonDebug ? this.findNonDbgCorePath(dSConfigObj, coreName) : this.findCorePath(dSConfigObj, coreName);
			return configObject.ds.getSubModule(corePath);
		}

		connectToCore(coreName, dSConfigObj?, isNonDebug?, options?) {
			let coreInfo = this.ds.cachedConnectedCores[coreName];
			if (!coreInfo) {
				let coreRet;
				return this.getCore(coreName, dSConfigObj, isNonDebug)
					.then((newCore) => {
						coreRet = newCore;

						// only need to set properties if the current context matches the connecting core
						if (_.includes(coreName, this.ds.getContext().getCurrentCore().name)) {
							coreRet.settings.set(this.ds.getUfPropertiesValue().getPropertiesValuesMap(this.ds.getContext().getCurrentCore().name));
						}

						if (this.ds.targetStateListeners[coreName]) {
							angular.forEach(this.ds.targetStateListeners[coreName], (listenerToAdd) => {
								coreRet.targetState.addListener('changed', listenerToAdd);
							});
						}

						if (options && (options.connect === false)) {
							// don't need to connect if connect option is false
							return;
						}

						this.ds.cachedConnectedCores[coreName] = coreRet;
						return coreRet.targetState.connect();
					})
					.then(() => {
						this.$log.debug('connect to core ' + coreName);
						return coreRet;
					})
					.catch(() => {
						this.$log.debug('connect err to ' + coreName);
						this.ds.cachedConnectedCores[coreName] = null;
					});
			} else {
				return this.$q.when(coreInfo);
			}
		}

		writeFileToBeFlashed(file: ti_common.file.File) {
			if (file.path !== file.name || file.isLocal) {
				return this.$q.resolve(file.path);
			}

			return file.getContents(ti_common.file.ContentType.Blob)
				.then((content: ti_common.file.Content) => {
					return TICloudAgent.Util.encodeAsBase64(content);
				}).then((encodedAsBase64) => {
					return this.ds.agent.File.write(file.name, encodedAsBase64);
				}).then((obj) => {
					return obj.path;
				}).then((programFileLocalPath) => {
					return programFileLocalPath.replace(/\\/g, '/');
				});
		}

		writeCCXML(ccxmlContents, filename) {
			return TICloudAgent.Util.encodeAsBase64(ccxmlContents)
				.then((encodedAsBase64) => {
					return this.ds.agent.File.write(filename, encodedAsBase64);
				}).then((obj) => {
					return obj.path;
				});
		}

		writeLocalFile(path, args) {
			return this.ds.agent.File.write(path, args);
		}

		// main operations are going through this block as it handles configure/connect/work/rest/disconnect etc with progress and console
		completeWorkflowAction(mainAction, coreName: string, optionObj?: any) {
			this.$rootScope.$emit('ds.opStarted');

			let curCore;
			return this.configureDS(this.ufSession.getCCXML(), 'uniflash_temp.ccxml')
				.then((configObj) => {
					return this.connectToCore(coreName, configObj, undefined, optionObj);
				})
				.then((core) => {
					if (optionObj && (optionObj.connect === false)) {
						// don't need to halt the target if connect option is false
						curCore = core;
						return;
					}

					if (core && mainAction) {
						curCore = core;
						return core.targetState.halt()
							.catch((e) => {
								if (e.message !== 'Target is not running') {
									this.$log.error('Halt target failed: ' + e.message);
									throw e;
								}
							});
					}
				})
				.then(() => {
					if (curCore) {
						return mainAction(curCore);
					}
				})
				.catch((error) => {
					if (error !== 'backdrop click' && !(error instanceof ti_common.CancelError)) {
						if (error.message === 'Unknown error') {
							return this.ticAlerts.showError('Error executing current operation. Please check the console for additional error information.');
						} else if (error.message.substring(0, 41) === 'Flash interface has already been created.') {
							return this.ticAlerts.showError(error.message.substring(0, 41) + ' Please restart the application (Desktop) or refresh your browser (Cloud) and try the operation again.');
						} else {
							return this.ticAlerts.showError(error.message);
						}
					}
				})
				.finally(() => {
					return this.$q((resolve) => {
						if (curCore) {
							resolve(curCore.settings.get([])
								.then((allSettings) => {
									this.ufPropertiesValue.updateFromDS(allSettings.nameValueMap, this.ufContext.getCurrentCore().name);
								}));
						} else {
							resolve();
						}
					})
						.then(() => {
							if (optionObj && (optionObj.connect === false)) {
								// don't need to run target if we are not connecting to the target (ie; NCOperation)
								return;
							}
							if (curCore && optionObj && optionObj.runAfterOp) {
								return curCore.targetState.run(1).catch((e) => {
									if (e) {
										this.$log.error('completeWorkflowAction curCore.targetState.run(1) ' + JSON.stringify(e));
									}
								});
							}
						})
						.then(() => {
							if (optionObj && (optionObj.connect === false || optionObj.disconnect === false)) {
								// don't need to disconnect if we did not connect (ie; NCOperation), or if we don't want to disconnect
								return;
							}
							if (this.ufPreferencesSrv.get('Auto Disconnect')) {
								return this.disconnectAllCores().catch((e) => {
									if (e) {
										this.$log.error('completeWorkflowAction this.disconnectAllCores() ' + JSON.stringify(e));
									}
								});
							}
						})
						.finally(() => {
							this.$rootScope.$emit('ds.opFinished');
						});
				});

		}
	}
}