import * as HyperdeckLib from 'hyperdeck-js-lib';

export class Mitti {
	hyperdeck: any;
	events: {
		connected?: () => void;
		connectionlost?: () => void;
		connecterror?: (message: string) => void;
	};

	connected: boolean;

	constructor(ip: string) {
		this.events = {};
		this.connected = false;

		// console.log(`Connecting to hyperdeck at ${ip}`);

		var _this = this;
		this.hyperdeck = new HyperdeckLib.Hyperdeck(ip);
		this.hyperdeck
			.onConnected()
			.then(function() {
				_this.hyperdeck.getNotifier().on('asynchronousEvent', function(response) {
					//console.log('Got an asynchronous event with code ' + response.code + '.');
				});

				_this.hyperdeck.getNotifier().on('connectionLost', function() {
					_this.connected = false;
					console.error('Connection lost.');
					if (_this.events.connectionlost != undefined) _this.events.connectionlost();
				});

				if (_this.events.connected != undefined) _this.events.connected();
			})
			.catch(function(error) {
				console.log(error);
				_this.connected = false;
				if (_this.events.connecterror != undefined)
					_this.events.connecterror('Failed to connect to hyperdeck.');
			});
	}

	on(event: 'connected' | 'connectionlost' | 'connecterror', callback: (...args: any[]) => void) {
		if (this.events[event] != undefined) throw new Error(`Event '${event}' already created`);
		else {
			this.events[event] = callback;
			if (event == 'connected' && this.connected == true) callback();
		}
	}

	raw(command: string) {
		return new Promise((resolve, reject) => {
			//console.log('Sending command: ' + command);
			this.hyperdeck
				.makeRequest(command)
				.then(function(response) {
					//console.log('Got response with code ' + response.code + '.');

					resolve(response);
				})
				.catch(function(errResponse) {
					if (!errResponse) {
						reject(new Error('The request failed because the hyperdeck connection was lost.'));
					} else {
						reject(new Error('The hyperdeck returned an error with status code ' + errResponse.code + '.'));
					}
				});
		});
	}

	getTransportInfo(): Promise<{
		status: 'playing' | 'stopped';
		speed: string;
		'slot id': string;
		'display timecode': string;
		'clip id': string;
		'single clip': string;
		'video format': string;
		'loop': string;
	}> {
		return new Promise((resolve, reject) => {
			//console.log('Getting transport info');
			this.hyperdeck
				.makeRequest('transport info')
				.then(function(response) {
					//console.log('Got response with code ' + response.code + '.');

					if (response.params != undefined) {
						resolve(response.params);
					} else {
						reject(new Error('Response contained no params'));
					}
				})
				.catch(function(errResponse) {
					if (!errResponse) {
						reject(new Error('The request failed because the hyperdeck connection was lost.'));
					} else {
						reject(new Error('The hyperdeck returned an error with status code ' + errResponse.code + '.'));
					}
				});
		});
	}

	getClipCount(): Promise<number> {
		return new Promise((resolve, reject) => {
			//console.log('Getting clips');
			this.hyperdeck
				.makeRequest('clips count')
				.then(function(response) {
					//console.log('Got response with code ' + response.code + '.');

					if (response.params != undefined) {
						if (response.params['clip count'] != undefined) {
							resolve(response.params['clip count']);
						} else {
							reject(new Error('Response contained no clip count data'));
						}
					} else {
						reject(new Error('Response contained no params data'));
					}
				})
				.catch(function(errResponse) {
					if (!errResponse) {
						reject(new Error('The request failed because the hyperdeck connection was lost.'));
					} else {
						reject(new Error('The hyperdeck returned an error with status code ' + errResponse.code + '.'));
					}
				});
		});
	}

	getClips(): Promise<{ [index: string]: string }> {
		return new Promise((resolve, reject) => {
			//console.log('Getting clips');
			this.hyperdeck
				.clipsGet()
				.then(function(response) {
					//console.log('Got response with code ' + response.code + '.');

					if (response.params != undefined) {
						if (response.params['clip count'] != undefined) delete response.params['clip count'];

						resolve(response.params);
					} else {
						reject(new Error('Response contained no clip data'));
					}
				})
				.catch(function(errResponse) {
					if (!errResponse) {
						reject(new Error('The request failed because the hyperdeck connection was lost.'));
					} else {
						reject(new Error('The hyperdeck returned an error with status code ' + errResponse.code + '.'));
					}
				});
		});
	}

	selectClip(index: number): Promise<true> {
		return new Promise((resolve, reject) => {
			//console.log('Selecting clip');
			this.hyperdeck
				.makeRequest(`goto: clip id: ${index}`)
				.then(function(response) {
					//console.log('Got response with code ' + response.code + '.');

					resolve(true);
				})
				.catch(function(errResponse) {
					if (!errResponse) {
						reject(new Error('The request failed because the hyperdeck connection was lost.'));
					} else {
						reject(new Error('The hyperdeck returned an error with status code ' + errResponse.code + '.'));
					}
				});
		});
	}

	play(): Promise<true> {
		return new Promise((resolve, reject) => {
			//console.log('Playing');
			this.hyperdeck
				.play()
				.then(function(response) {
					//console.log('Got response with code ' + response.code + '.');

					resolve(true);
				})
				.catch(function(errResponse) {
					if (!errResponse) {
						reject(new Error('The request failed because the hyperdeck connection was lost.'));
					} else {
						reject(new Error('The hyperdeck returned an error with status code ' + errResponse.code + '.'));
					}
				});
		});
	}

	stop(): Promise<true> {
		return new Promise((resolve, reject) => {
			//console.log('Stopping');
			this.hyperdeck
				.stop()
				.then(function(response) {
					//console.log('Got response with code ' + response.code + '.');

					resolve(true);
				})
				.catch(function(errResponse) {
					if (!errResponse) {
						reject(new Error('The request failed because the hyperdeck connection was lost.'));
					} else {
						reject(new Error('The hyperdeck returned an error with status code ' + errResponse.code + '.'));
					}
				});
		});
	}

	next(wrap: boolean): Promise<true> {
		return new Promise((resolve, reject) => {
			//console.log('Next clip');

			this.getTransportInfo()
				.then((transportInfo) => {
					var newClip = parseInt(transportInfo['clip id']) + 1;

					if (wrap == true) {
						this.getClipCount()
							.then((clipCount) => {
								if (newClip > clipCount) newClip = 1;
								this.selectClip(newClip).then(resolve).catch(reject);
							})
							.catch(reject);
					} else this.selectClip(newClip).then(resolve).catch(reject);
				})
				.catch(reject);
		});
	}

	previous(wrap: boolean): Promise<true> {
		return new Promise((resolve, reject) => {
			//console.log('Previous clip');

			this.getTransportInfo()
				.then((transportInfo) => {
					var newClip = parseInt(transportInfo['clip id']) - 1;

					if (wrap == true) {
						this.getClipCount()
							.then((clipCount) => {
								if (newClip < 1) newClip = clipCount;
								this.selectClip(newClip).then(resolve).catch(reject);
							})
							.catch(reject);
					} else this.selectClip(newClip).then(resolve).catch(reject);
				})
				.catch(reject);
		});
	}
}
