class SimplePageHandler {
	constructor(pageRoot, databaseUpdate) {
		this.pageRoot = pageRoot;
		this.databaseUpdate = databaseUpdate;

		var navigation = new NavigationBar();
		//TODO: Move sidebar to here
		if ( $('.field-container').length > 0 ) {
			getFields();
		}
	}
}

class InputPageHandler extends SimplePageHandler {
	constructor(pageRoot, databaseUpdate) {
		super(pageRoot, databaseUpdate);

		var self = this;
		
		$('#hold-button').on('click', this.toggleHoldButton.bind(this));
		$('#ok-button').on('click', this.writeInput.bind(this, true));
		$('#cancel-button').on('click', function() { window.location = self.pageRoot });
		$('#apply-button').on('click', this.writeInput.bind(this, false));

		//Initialise JQuery-UI elements
		$('#dialog').dialog({ 
			autoOpen: false,
			modal: true,
			buttons: {
				Ok: function() {
					$(this).dialog("close");
				}
			},
			position: { my: 'top', at: 'top+150' }
		});

		this.isPopulated = this.populateFromParams();
	}
	
	/**
	 * Redirect to another page, while persisting current inputs for return
	 * @param {*} redirectLocation page to redirect to
	 */
	redirectInputs(redirectLocation) {
		var inputs = {};
		$('.database-input').each(function() {
			inputs[$(this).attr('id')] = $(this).val();
		});
		window.location.href = redirectLocation + "?previous=" + $('.content-wrapper').attr('data-subheading')
			+ "&params=" + JSON.stringify(inputs);
	}

	/**
	 * Repopulate inputs after returning from redirect
	 */
	populateFromParams() {
		var params = urlParam('params');
		if (params != null) {
			$.each(JSON.parse(params), function(key, value) {
				$('#' + key).val(value);
			});
			return true;
		}
		return false;
	}

	/**
	 * Freeze/Unfreeze fields on the page
	 */
	toggleHoldButton() {
		isPaused = !isPaused;
		
		if ($('#hold-button').html() == 'Release') {
			$('#hold-button').html('Hold');
		} else $('#hold-button').html('Release');
	}
	
	/**
	 * Validation for database inputs. Overwrite to add page specific validation.
	 * Returns a deferred object, to allow for validation which requires a database read.
	 */
	validateForm() {
		return $.Deferred().resolve(true);
	}
	
	/**
	 * Write inputs to database
	 * @param {boolean} isRedirect if true redirect to page root
	 */
	writeInput(isRedirect) {
		self = this;
		self.validateForm().done(function(isValid) {
			if (isValid) {
				writeToDatabase(getDatabaseInputs(), self.databaseUpdate).done(function() {
					if (isRedirect) {
						window.location = self.pageRoot;
					}
				});	
			}
		});
	}
}

class SelectInputHandler extends InputPageHandler {
	constructor(pageRoot, databaseUpdate, pageSection, boardArray) {
		super(pageRoot, databaseUpdate);

		this.boardArray = boardArray;
		this.canIntervalID = 0;
		this.pageSection = pageSection;

		$('#input-select').on('change', this.changeSelectedInput.bind(this));
		$('#input-type-select').on('change', this.changeSelectedInputSource.bind(this));
		$('#input-name').on('input', function() {
			var $select = $('#input-select option:selected');
			$select.html($select.html().split(' - ')[0] + ' - ' + $(this).val());
		});
		$('#nmea-config').on('click', this.redirectInputs.bind(this, "nmeaFilter.php"));
		$('#can-config').on('click', this.redirectInputs.bind(this, "canFilter.php"));
		$('#num-sources').on('change', this.populateAvailableInputs.bind(this));
		$('#nmea-instance').on('change', this.changeNMEAInstance.bind(this));

		this.populateAvailableSources().done(this.setCurrentInput.call(this));
	}

	/**
	 * NMEA instance copies to generic instance, to avoid conflicting writes
	 */
	changeNMEAInstance() {
		$('#instance').val( $('#nmea-instance option:selected').val() );
		this.updateFields();
	}

	/**
	 * Handler for Input select change, overwritten by select handler
	 */
	changeSelectedInput() {
		this.populateSelectedInput.call(this);
	}

	/**
	 * Handler for Input Source select change
	 */
	changeSelectedInputSource() {
		// Changing source implicitly changes the instance
		var instance = 0;
		var selectedSource = $('#input-type-select option:selected').val();
		if ( $.inArray(selectedSource.split(':')[0], this.boardArray ) > -1 ) {
			instance = selectedSource.split(':')[1];
		} else if ( selectedSource == "NMEA0183" ) {
			instance = $('#nmea-instance').val();
		} else if ( selectedSource == "CanBus" ) {
			instance = $('#can-device').attr('data-address');
		}
		$('.field-container').filter('[data-sender="' + selectedSource.split(':')[0] + '"]').attr('data-instance', instance);
		$('#instance').val(instance);

		this.displayDefaultConfig.call(this);
		this.displayInputConfig.call(this);
		this.updateFields();
	}

	/**
	 * Set configuration item formatting to default
	 * i.e. non-context dependent items
	 */
	displayDefaultConfig() {
		$('[name="input-configuration"]').hide();
		// SoG margin changes depending on source, design issue?
		$('#sog-calibration').removeClass('top-wrapper');
		// Reset CANbus state variable
		clearInterval(this.canIntervalID);
		this.canIntervalID = 0;
	}

	/**
	 * Format configuration items conditional on current input source
	 */
	displayInputConfig() {
		var inputSource = $('#input-type-select option:selected').val();
		switch (inputSource) {
			case "CanBus":
				this.pollCanState.call(this);
				$('#mult-calibration').show();
				$('#input-channel').html('<h2>Can Device:</h2>').show();
				$('#can-device').show();
				$('#can-config').show();
				$('#source-select').show();
				break;
			case "NMEA0183":
				$('#mult-calibration').show();
				$('#input-channel').html('<h2>NMEA0183 Channel:</h2>');
				$('#nmea-instance').show();
				$('#nmea-config').show();
				$('#source-select').show();
				break;
			case "No Source":
				// SoG margin changes depending on source, design issue?
				$('#sog-calibration').addClass('top-wrapper');
				break;
			default:
				$('#pulse-calibration').show();
				break;
		}
	}

	/**
	 * Track CANbus state for comms changes
	 */
	pollCanState() {
		var canState = null;
		var self = this;
		self.canIntervalID = setInterval(function() {
			$.ajax({
				url: '\helpers.php',
				type: 'POST',
				data: {
					action: 'queryProcessor',
					query: 'FieldId=0x2801',
				},
				success: function(result) {
					var fieldMatchRegex = /(0x.*)=\s?(.*)\r/g;
					var match = fieldMatchRegex.exec(result);
					
					if ( $.isNumeric(canState) ) {
						if ( parseInt(match[2]) != canState ) {
							// Reload from database
							readSingle("CanSource", self.pageSection).done(function(canSource) {
								if ( $.inArray( $('#can-device').val(), canSource.split(';') ) == -1 ) {
									$('#can-device').css('background-color', '#eaa');
								} else {
									$('#can-device').css('background-color', '');
								}
							});
						}
					}
					canState = parseInt(match[2]);
				}
			});
		}, 2000);
	}

	/**
	 * Populate Input select with available inputs
	 * Called if number of available inputs changes
	 */
	populateAvailableInputs() {
		var readDatabase = [];
		var self = this;
		var sourceCount = $('#num-sources').val();

		// Clear inputs
		$('#input-select').find('option').remove();

		// Read new inputs
		for (var i = 0; i < sourceCount; i++) {
			(function(sourceNumber) {
				var def = new $.Deferred();
				readDatabase.push(def);

				var internalInputHandle = self.pageSection + "Source" + sourceNumber;
				readSingle(internalInputHandle, "Name").done(function(sourceName) {
					$('#input-select').append('<option value="' + internalInputHandle + '">'
						+ self.pageSection + (sourceNumber + 1) + ' - ' + sourceName +
						'</option>');
					def.resolve();
				})
			})(i);
		}

		// Once all inputs read, sort and set default
		$.when.apply($, readDatabase).then(function() {
			sortSelect( $('#input-select') );
			self.setCurrentInput.call(self);
		});
	}

	/**
	 * Populate Input Source select with available sources
	 */
	populateAvailableSources() {
		var def = $.Deferred();
		var self = this;

		// Get set of boards which are valid for this page
		$.when( readSection("Boards"), getServerMillis() ).done(function(section, milliseconds) {
			var section = JSON.parse(section[0]);
			var sectionKeys = Object.keys(section);

			var availableBoards = sectionKeys.filter(function(key) {
				// Keys have form <board name>:<board number>
				return ($.inArray( key.split(':')[0], self.boardArray.map(function(x) {
					return x.toLowerCase();
				})) > -1) && (milliseconds[0] - section[key] < 600000);
			});

			// Add board to select
			availableBoards.forEach(function(boardName) {
				boardName = toTitleCase(boardName);
				$('#input-type-select').append('<option value="' + boardName +
					'">' + boardName + '</option>');
			});

			def.resolve();
		});

		return def.promise();
	}

	/**
	 * Populate context-dependent inputs
	 * @param {*} configKeyValue object containing key/value pairs
	 */
	populateInputConfig(configKeyValue) {
		this.displayDefaultConfig.call(this);

		$('.context-dependent').attr('data-section', $('#input-select option:selected').val());
		$('.context-dependent').each(function() {
			if ( $(this).prop('tagName').toLowerCase() == "input") {
				switch ( $(this).attr('type') ) {
					case "checkbox":
						$(this).prop('checked', configKeyValue[$(this).attr('data-key').toLowerCase()]);
						break;
					case "radio":
						$(this).prop('checked', configKeyValue[$(this).attr('data-key').toLowerCase()] == $(this).val());
						break;
					default:
						$(this).val(configKeyValue[$(this).attr('data-key').toLowerCase()]);
						break;
				}
			} else if ( $(this).prop('tagName').toLowerCase() == "select") {
				$(this).find('option[value="' + configKeyValue[$(this).attr('data-key').toLowerCase()] + '"]')
					.prop('selected', true);
			} else {
				$(this).val(configKeyValue[$(this).attr('data-key').toLowerCase()]);
			}
		});

		this.displayInputConfig.call(this);
		$('#input-type-select').change();
		this.updateFields();
	}

	/**
	 * Set all configuration parameters for selected input
	 */
	populateSelectedInput() {
		self = this;

		self.readCurrentInput().done(function(activeInputConfig) {
			self.populateInputConfig.call(self, activeInputConfig);
		});
	}

	/**
	 * Read all key, value pairs for the current input
	 * returns object containing these pairs
	 */
	readCurrentInput() {
		var currentInput = $('#input-select option:selected').val();
		var def = $.Deferred();

		readSection(currentInput).done(function(InputConfig) {
			def.resolve(JSON.parse(InputConfig));
		});

		return def.promise();
	}

	/**
	 * Read current input from database. Called once on page load.
	 */
	setCurrentInput() {
		if (!this.isPopulated) {
			readSingle(this.pageSection, 'Default').done(function(currentSource) {
				$('#input-select option[value="' + currentSource + '"]').prop('selected', true);
				$('#input-select').change();
			});
		} else {
			this.isPopulated = false;
		}
	}

	/**
	 * Update context-dependent fields
	 */
	updateFields() {
		$('.field-container').filter('[data-fixed="false"]').attr('data-sender', 
			$('#input-type-select option:selected').val());
		$('.field-container').filter('[data-fixed="false"]').attr('data-instance',
			$('#instance').val());
		$(document).trigger('fields-changed');
	}
}

class SelectSwitchingInputHandler extends SelectInputHandler {
	constructor(pageRoot, databaseUpdate, pageSection, boardArray) {
		super(pageRoot, databaseUpdate, pageSection, boardArray);

		var self = this;

		self.SwitchingIntervalID = null;
		self.SwitchingIntervalTime = 1000;

		readSingle(self.pageSection, "Switching").done(function(isSwitching) {
			if (isSwitching == "true" || isSwitching == 1) {
				self.SwitchingIntervalID = setInterval(self.pollActiveSource.bind(self), self.SwitchingIntervalTime);
			}
		});
	}

	/**
	 * Event handler for input select, with auto-switching
	 */
	changeSelectedInput() {
		var inputVal = $('#input-select option:selected').val();
		switch (inputVal) {
			case "Auto":
				this.initialiseSwitching.call(this);
				break;
			default:
				//Re-enable inputs
				$('input').prop('disabled', false);
				this.populateSelectedInput.call(this);
				break;
		}
	}

	/**
	 * Additionally hide source selection and active input display
	 */
	displayDefaultConfig() {
		super.displayDefaultConfig();

		$('#name-container').hide();
		$('#switching-wrapper').hide();
		$('#source-wrapper').hide();
	}

	/**
	 * Format configuration items conditional on current input source
	 */
	displayInputConfig() {
		if ($('#input-select option:selected').val() == "Auto") {
			$('#switching-wrapper').show();

			var inputSource = $('#active-input-source').val();
			switch (inputSource) {
				case "CanBus":
					$('#mult-calibration').show();
					break;
				case "NMEA0183":
					$('#mult-calibration').show();
					break;
				case "No Source":
					// SoG margin changes depending on source, design issue?
					$('#sog-calibration').addClass('top-wrapper');
					break;
				default:
					$('#pulse-calibration').show();
					break;
			}
		} else {
			$('#name-container').show();
			$('#source-wrapper').show();
			super.displayInputConfig();
		}
	}

	/**
	 * Write switching status to database
	 */
	enableSwitching() {
		//Show loading box
		$('body').append('<section class="loading-dialogue"> \
			<div>Loading..</div> \
		</section>');

		writeSingle(this.pageSection, "Switching", true);

		if (this.SwitchingIntervalID == null) {
			this.SwitchingIntervalID = setInterval(this.pollActiveSource.bind(this), this.SwitchingIntervalTime);
		}
	}

	/**
	 * Begin auto-switching
	 */
	initialiseSwitching() {
		//Hide all context-dependent configuration
		this.displayDefaultConfig.call(this);
		$('#switching-button').show();
		// SoG margin changes depending on source, design issue?
		$('#sog-calibration').addClass('top-wrapper');

		//Disable inputs whilst swithing is active
		$('input').prop('disabled', true);

		//Show switching inputs
		$('#switching-wrapper').show();
	}

	/**
	 * Query processor for active source, react if changed
	 */
	pollActiveSource() {
		var self = this;

		$.ajax({
			type: 'POST',
			url: '/helpers.php',
			data: {
				action: 'queryProcessor',
				query: 'FieldId=0x1206'
			},
			success: function(result) {
				var fieldMatchRegex = /(0x.*)=\s?(.*)\r/g;
				var match = fieldMatchRegex.exec(result);
				var activeInputNumber = match[2];	// Input the processor is using

				if ( $.isNumeric(activeInputNumber) ) {
					var activeInput = self.pageSection + "Source" + activeInputNumber;

					if ( $('.loading-dialogue').is(':visible') ) {
						setTimeout(function() {
							$('.loading-dialogue').remove();
						}, 1000);
					}

					if ( $('#active-input').attr('data-input') != activeInputNumber) {
						$('#active-input').attr('data-input', activeInputNumber);
						$('#active-input').val($('#input-select option[value="' + activeInput + '"]').text());

						readSection(activeInput, 1).done(function(inputConfig) {
							var inputKeyVal = JSON.parse(inputConfig);
							$('#active-input-source').val(inputKeyVal.source);
							self.populateInputConfig(inputKeyVal);
						});
					}
				}
			}
		});
	}

	/**
	 * @override Enable/disable switching controls based on number of inputs
	 */
	populateAvailableInputs() {
		super.populateAvailableInputs();

		if ( $('#num-sources').val() > 1 ) {
			$('#switching').removeClass('disabled');
			$('#input-select').prepend(
				'<option value="Auto">Auto Switching</option>'
			);
		} else {
			$('#switching').addClass('disabled');
			this.stopSwitching.call(this);
		}
	}

	/**
	 * End auto-switching
	 */
	stopSwitching() {
		clearInterval(this.SwitchingIntervalID);
		this.SwitchingIntervalID = null;

		// Write to database
		writeSingle(this.pageSection, "Switching", false);

		//Re-enable inputs
		$('input').prop('disabled', false);

		// Reset active input
		$('#active-input').attr('data-input', -1).val('');
		$('#active-input-source').val('');
	}
}

class SpeedInputHandler extends SelectSwitchingInputHandler {
	constructor(pageRoot, databaseUpdate, pageSection, boardArray) {
		super(pageRoot, databaseUpdate, pageSection, boardArray);
	}

	changeSelectedInput() {
		if ( $('#use-gps').is(':checked') ) {
			//Just show default, data will be populated if unchecked
			this.displayDefaultConfig();
			// SoG margin changes depending on source, design issue?
			$('#sog-calibration').addClass('top-wrapper');
		} else {
			super.changeSelectedInput();
		}
	}

	/**
	 * Don't disable SoG as BSPD
	 */
	initialiseSwitching() {
		super.initialiseSwitching();

		$('#use-gps').prop('disabled', false);
	}

	/**
	 * @override Change data-field of NMEA Calc Speed
	 */
	updateFields() {
		var inputNumber = -1;
		if ( $('#input-select option:selected').val() == 'Auto' ) {
			inputNumber = $('#active-input').attr('data-input');
		} else {
			inputNumber = $('#input-select option:selected').val().slice(-1);
		}
		$('#NMEACalcSpeed').attr('data-field', "0x26" + ++inputNumber + "1");

		super.updateFields();
	}
}