namespace uniflash.programs {

	interface IProgramSerializable {
		file: ti_common.file.IFileSerializable,
		isBinary: boolean,
		index: number,
		isSelected: boolean,
		provider: string,
		loadAddress?: string
	}

	export class Program {
		constructor(
			public file: ti_common.file.File,
			public provider: uniflash.programs.ProgramProvider,
			public isBinary: boolean,
			public index: number,
			public isSelected: boolean = true,
			public loadAddress?: string
		) {

		}

		toJSON(): IProgramSerializable {
			return {
				file: this.file.toJSON(),
				isBinary: this.isBinary,
				index: this.index,
				isSelected: this.isSelected,
				provider: this.provider.type,
				loadAddress: this.loadAddress
			}
		}
	}

	'use strict';

	angular.module('uniflash').service('ufPrograms', programs);
	programs.$inject = ['ufSession', 'ufContext', 'ufIsBinary', 'ticFileFactory', 'ufProgramProviders'];

	interface Core {
		name: string,
		programs: Program[]
	}

	interface ICoreSerializable {
		name: string,
		programs: IProgramSerializable[]
	}

	function programs(ufSession: uniflash.session.SessionService,
		ufContext: uniflash.Context, ufIsBinary, ticFileFactory: ti_common.file.IFileFactory, ufProgramProviders) {

		let cores: { [name: string]: Core } = {};

		function createProgramFromJSON(program: IProgramSerializable) {
			return new Program(
				ticFileFactory.createFromJSON(program.file),
				ufProgramProviders[program.provider],
				program.isBinary,
				program.index,
				program.isSelected,
				program.loadAddress
			);
		}

		ufSession.registerSessionDataProvider('programs', {
			onSessionLoad: function (newData: ICoreSerializable[]) {

				cores = {}; // reset data
				// init cores
				_.each(ufSession.queryConfig().cores, (coreInfo) => {
					cores[coreInfo.name] = {
						name: coreInfo.name,
						programs: []
					};
				});

				if (newData) {
					// deserialize
					_.each(newData, (core: ICoreSerializable) => {
						if (cores[core.name]) {
							cores[core.name].programs = _.map(core.programs, (program: IProgramSerializable) => {
								return createProgramFromJSON(program);
							})
						} else {
							console.log("Warning! no core found while loading session data, core id = " + core.name);
						}
					});
				}

			},
			getSessionData: function () {
				var toJSON: ICoreSerializable[] = [];
				_.each(cores, function (core: Core) {
					let programs = _.map(core.programs, (program: Program) => {
						return program.toJSON();
					})

					toJSON.push(
						{
							name: core.name,
							programs: programs
						}
					)
				});

				return toJSON;
			}
		});

		function getCoreData() {
			return cores[ufContext.getCurrentCore().name];
		}

		function getAllGUICoresData() {
			return _.map(ufSession.queryConfig().cores, (coreInfo) => {
				return cores[coreInfo.name];
			});
		}

		function getPrograms() {
			return getCoreData().programs;
		}

		function getProgramsOnCore(coreName) {
			if (coreName && cores[coreName]) {
				return cores[coreName].programs;
			} else {
				return undefined;
			}
		}

		function getProgramsOnAllCores() {
			const programs = [];
			_.each(ufSession.queryConfig().cores, (coreInfo) => {
				programs.concat(cores[coreInfo.name].programs);
			});
			return programs;
		}

		// We need to track the position of the program in the list because it is sortable and supports duplicates
		// The sorting messes up the $index property of ng-repeat so when we try to remove using it we remove the
		// wrong item. Therefore removing via $index is not an option. We could remove by reference ( search and find index)
		// but that would only find the first instance. Thus we need to have each program uniquely identify itself via the index
		// and keep the index updated.
		function addProgram(file: ti_common.file.File, programProvider: uniflash.programs.ProgramProvider, order: number) {
			let programs = getPrograms();
			let index = programs.length;

			if (order !== undefined) {
				programs.push(new Program(file, programProvider, true, index, true, order.toString()));
			} else {
				programs.push(new Program(file, programProvider, ufIsBinary(file), index));
			}
		}

		function removeProgram(program) {
			let programs = getPrograms();
			programs.splice(program.index, 1);
			_.each(programs, function (program, index) {
				program.index = index;
			});
		}

		return {
			getPrograms: getPrograms,
			getAllGUICoresData: getAllGUICoresData,
			getProgramsOnCore: getProgramsOnCore,
			getProgramsOnAllCores: getProgramsOnAllCores,
			addProgram: addProgram,
			removeProgram: removeProgram
		};
	}

}