package com.nanotec.example.nanolibexample;

import java.lang.Thread;

import com.nanotec.example.nanolibexample.NanolibHelper.NanolibException;
import com.nanotec.nanolib.DeviceHandle;
import com.nanotec.nanolib.OdIndex;
import com.nanotec.nanolib.OdIndexVector;
import com.nanotec.nanolib.ResultVoid;
import com.nanotec.nanolib.SampleData;
import com.nanotec.nanolib.SampleDataVector;
import com.nanotec.nanolib.SampledValueVector;
import com.nanotec.nanolib.SamplerConfiguration;
import com.nanotec.nanolib.SamplerMode;
import com.nanotec.nanolib.SamplerNotify;
import com.nanotec.nanolib.SamplerState;
import com.nanotec.nanolib.SamplerTrigger;
import com.nanotec.nanolib.SamplerTriggerCondition;

public class NanolibSamplerExample {

	/// <summary>
	/// Callback class derived from SamplerNotify.
	/// </summary>
	private class SamplerNotifyExample extends SamplerNotify // override
	{
		private boolean samplerRunning = false;
		private NanolibSamplerExample samplerExample = null;

		public SamplerNotifyExample(NanolibSamplerExample example) {
			this.samplerExample = example;
			this.samplerRunning = true;
		}

		public boolean isRunning() {
			return this.samplerRunning;
		}

		public void setInactive() {
			this.samplerRunning = false;
		}

		// Be aware that notifications are executed in the context of separate threads
		// other than thread that started the sampler.
		//
		// Be careful when calling Nanolib functionality here, as doing so may cause
		// this method
		// to be called recursively, potentially causing your application to deadlock.
		//
		// For the same reason, this method should not throw exceptions.

		/// <summary>
		/// Callback used by Sampler.
		/// </summary>
		/// <param name="lastError">Last error occured</param>
		/// <param name="samplerState">State of the sampler</param>
		/// <param name="sampleDatas">Sampled data</param>
		/// <param name="applicationData">Application data</param>
		/// <returns>None</returns>
		@Override
		public void notify(ResultVoid lastError, SamplerState samplerState, SampleDataVector sampleDatas,
				long applicationData) {
			if (!samplerRunning)
				throw new RuntimeException("Sampler not running");

			if (!sampleDatas.isEmpty())
				this.samplerExample.processSampledData(sampleDatas);

			if (samplerState == SamplerState.Failed) {
				try {
					samplerExample.handleSamplerFailed(lastError);
				} catch (Exception e) {
					// see comment above
				}
			}

			if ((samplerState != SamplerState.Ready) && (samplerState != SamplerState.Running)) {
				// It's now safe to destroy this notification object
				samplerRunning = false;
			}
		}
	}

	private NanolibHelper nanolibHelper;
	private DeviceHandle deviceHandle;
	private SamplerTriggerCondition triggerCondition = SamplerTriggerCondition.TC_GREATER;
	private OdIndex triggerAddress = new OdIndex(0x2400, (short) 0x01);
	private Object trackedAddresses[] = { new OdIndex(0x230F, (short) 0x00),	new OdIndex(0x4014, (short) 0x03) };
	private Integer triggerValue = Integer.valueOf(10);
	private Integer triggerValueInactive = triggerValue;
	private Integer triggerValueActive = triggerValue + 1;
	private Integer periodMilliseconds = Integer.valueOf(1000);
	private long lastIteration = Long.MAX_VALUE;
	private int sampleNumber = 0;
	private boolean headerPrinted = false;

	/// <summary>
	/// Class NanolibSamplerExample
	/// </summary>
	public NanolibSamplerExample(NanolibHelper nanolibHelper, DeviceHandle deviceHandle) {
		this.nanolibHelper = nanolibHelper;
		this.deviceHandle = deviceHandle;
	}

	/// <summary>
	/// Function process runs all examples without and with notification callback
	/// </summary>
	/// <returns>None</returns>
	public void process() throws NanolibException {
		processExamplesWithoutNotification();
		processExamplesWithNotification();
	}

	/// <summary>
	/// Helper function to call all examples without notification
	/// </summary>
	/// <returns>None</returns>
	private void processExamplesWithoutNotification() throws NanolibException {
		processSamplerWithoutNotificationNormal();
		processSamplerWithoutNotificationRepetitive();
		processSamplerWithoutNotificationContinuous();
	}

	/// <summary>
	/// Helper function to call all examples with notification
	/// </summary>
	/// <returns>None</returns>
	private void processExamplesWithNotification() throws NanolibException {
		processSamplerWithNotificationNormal();
		processSamplerWithNotificationRepetitive();
		processSamplerWithNotificationContinuous();
	}

	/// <summary>
	/// Configures and starts sampling process in normal mode with notificaion
	/// </summary>
	/// <returns>None</returns>
	private void processSamplerWithNotificationNormal() throws NanolibException {
		System.out.println("\nSampler with notification in normal mode:");

		configure(SamplerMode.Normal);

		SamplerNotifyExample samplerNotify = new SamplerNotifyExample(this);

		start(samplerNotify, 0);
		while (samplerNotify.isRunning()) {
			try {
				Thread.sleep(periodMilliseconds);
			} catch (InterruptedException e) {
				samplerNotify.setInactive();
			}
		}
	}

	/// <summary>
	/// Configures and starts sampling process in repetitive mode with notificaion
	/// </summary>
	/// <returns>None</returns>
	private void processSamplerWithNotificationRepetitive() throws NanolibException {
		System.out.println("\nSampler with notification in repetative mode:");

		configure(SamplerMode.Repetitive);

		SamplerNotifyExample samplerNotify = new SamplerNotifyExample(this);

		start(samplerNotify, 0);
		SamplerState samplerState = SamplerState.Ready;

		// wait for sampler to start (running)
		do {
			try {
				Thread.sleep(Integer.valueOf(50));
				samplerState = getSamplerState();
			} catch (InterruptedException e) {
				samplerState = SamplerState.Failed;
			}
		} while ((samplerState != SamplerState.Running)
				&& (samplerState != SamplerState.Failed));

		// process sampler data
		while (samplerNotify.isRunning()) {
			try {
				Thread.sleep(periodMilliseconds);
			} catch (InterruptedException e) {
				samplerNotify.setInactive();
			}

			if (lastIteration >= 4) {
				// In repetative mode the sampler will continue to run until it is stopped or an error occurs
				nanolibHelper.stopSampler(deviceHandle);
				break;
			}
		}
	}

	/// <summary>
	/// Configures and starts sampling process in continuous mode with notificaion
	/// </summary>
	/// <returns>None</returns>
	private void processSamplerWithNotificationContinuous() throws NanolibException {
		System.out.println("\nSampler with notification in continuous mode:");

		configure(SamplerMode.Continuous);

		SamplerNotifyExample samplerNotify = new SamplerNotifyExample(this);

		start(samplerNotify, 0);
		try {
			Thread.sleep(periodMilliseconds * 10);
		} catch (InterruptedException e) {
			samplerNotify.setInactive();
		}
		// In continuous the sampler will continue to run until it is stopped or an error occurs
		nanolibHelper.stopSampler(deviceHandle);
	}

	/// <summary>
	/// Configures and starts sampling process in normal mode without notificaion
	/// </summary>>
	/// <returns>None</returns>
	private void processSamplerWithoutNotificationNormal() throws NanolibException {
		System.out.println("\nSampler without notification in normal mode:");

		configure(SamplerMode.Normal);

		start(null, 0);

		SamplerState samplerState = SamplerState.Ready;

		do {
			try {
				Thread.sleep(this.periodMilliseconds);
			} catch (InterruptedException e) {
				samplerState = SamplerState.Failed;
				break;
			}
			processSampledData(getSamplerData());
			samplerState = getSamplerState();

		} while ((samplerState == SamplerState.Ready)
				|| (samplerState == SamplerState.Running));

		// Process any remaining data
		processSampledData(getSamplerData());

		if (samplerState == SamplerState.Failed)
			handleSamplerFailed(null);
	}

	/// <summary>
	/// Configures and starts sampling process in repetitive mode without notificaion
	/// </summary>
	/// <returns>None</returns>
	private void processSamplerWithoutNotificationRepetitive() throws NanolibException {
		System.out.println("\nSampler without notification in repetative mode:");

		configure(SamplerMode.Repetitive);
		try {
			start(null, 0);
		} catch (Exception e) {
			handleSamplerFailed(null);
		}

		SamplerState samplerState = SamplerState.Ready;

		// wait for sampler to start (running)
		do {
			try {
				Thread.sleep(Integer.valueOf(50));
				samplerState = getSamplerState();
			} catch (InterruptedException e) {
				samplerState = SamplerState.Failed;
			}
		} while ((samplerState != SamplerState.Running)
				&& (samplerState != SamplerState.Failed));

		// process sampler data
		do {
			try {
				Thread.sleep(this.periodMilliseconds);
			} catch (InterruptedException e) {
				samplerState = SamplerState.Failed;
				break;
			}
			processSampledData(getSamplerData());

			if (this.lastIteration >= 4) {
				try {
					// In repetative mode the sampler will continue to run until it is stopped or an
					// error occurs
					nanolibHelper.stopSampler(deviceHandle);
					break;
				} catch (Exception e) {
					handleSamplerFailed(null);
				}
			}
			samplerState = getSamplerState();

		} while ((samplerState == SamplerState.Ready)
				|| (samplerState == SamplerState.Running));

		// Process any remaining data
		processSampledData(getSamplerData());

		if (samplerState == SamplerState.Failed)
			handleSamplerFailed(null);
	}

	/// <summary>
	/// Configures and starts sampling process in continuous mode without notificaion
	/// </summary>
	/// <returns>None</returns>
	private void processSamplerWithoutNotificationContinuous() throws NanolibException {
		System.out.println("\nSampler without notification in continuous mode:");

		configure(SamplerMode.Continuous);
		try {
			start(null, 0);
		} catch (Exception e) {
			handleSamplerFailed(null);
		}

		SamplerState samplerState = SamplerState.Ready;
		int maxCycles = 10;
		int cycles = 0;

		do {
			try {
				Thread.sleep(this.periodMilliseconds);
			} catch (InterruptedException e) {
				samplerState = SamplerState.Failed;
				break;
			}
			processSampledData(getSamplerData());

			if (++cycles == maxCycles) {
				try {
					// In continuous mode the sampler will continue to run until it is stopped or an
					// error occurs
					nanolibHelper.stopSampler(deviceHandle);
					break;
				} catch (Exception e) {
					handleSamplerFailed(null);
				}
			}

			samplerState = getSamplerState();

		} while ((samplerState == SamplerState.Ready)
				|| (samplerState == SamplerState.Running));

		// Process any remaining data
		processSampledData(getSamplerData());

		if (samplerState == SamplerState.Failed)
			handleSamplerFailed(null);
	}

	/// <summary>
	/// Configures the sampler
	/// </summary>
	/// <param name="mode">sampling mode to use</param>
	/// <returns>None</returns>
	private void configure(SamplerMode mode) throws NanolibException {

		SamplerConfiguration samplerConfiguration = new SamplerConfiguration();
		OdIndexVector odIndexVector = new OdIndexVector();
		for (Object object : this.trackedAddresses) {
			OdIndex odIndex = (OdIndex) object;
			odIndexVector.add(odIndex);
		}
		samplerConfiguration.setTrackedAddresses(odIndexVector); // "Up time" and "Temperature"
		SamplerTrigger samplerTriggerStart = new SamplerTrigger();

		samplerTriggerStart.setCondition(this.triggerCondition);
		samplerTriggerStart.setAddress(this.triggerAddress);
		samplerTriggerStart.setValue(triggerValue.intValue());

		// set start trigger
		samplerConfiguration.setStartTrigger(samplerTriggerStart);
		samplerConfiguration.setPeriodMilliseconds(this.periodMilliseconds);
		// in continuous mode, duration has to be zero
		if (mode == SamplerMode.Continuous) {
			samplerConfiguration.setDurationMilliseconds(0);
		} else {
			samplerConfiguration.setDurationMilliseconds(4000);
		}
		// Currrently this value is not used
		samplerConfiguration.setPreTriggerNumberOfSamples(0);
		samplerConfiguration.setMode(mode);
		samplerConfiguration.setUsingSoftwareImplementation((mode == SamplerMode.Continuous));

		nanolibHelper.configureSampler(deviceHandle, samplerConfiguration);
	}

	/// <summary>
	/// Starts the sampling process
	/// </summary>
	/// <param name="samplerNotifyExample">used for notify callback, will be null for no notification</param>
	/// <param name="applicationData">application spedific data</param>
	/// <returns>None</returns>
	private void start(SamplerNotifyExample samplerNotifyExample, long applicationData) throws NanolibException {

		this.lastIteration = Long.MAX_VALUE;
		this.sampleNumber = 0;
		this.headerPrinted = false;

		// Deactivate the start trigger
		nanolibHelper.writeNumber(deviceHandle, triggerValueInactive.intValue(), triggerAddress, 32);

		// Start the sampler
		try {
			nanolibHelper.startSampler(this.deviceHandle, samplerNotifyExample, applicationData);
		} catch (NanolibException e) {
			if (samplerNotifyExample != null) {
				samplerNotifyExample.setInactive();
			}
			throw e;
		}

		// Activate start trigger
		try {
			nanolibHelper.writeNumber(this.deviceHandle, this.triggerValueActive, this.triggerAddress, 32);
		} catch (NanolibException e) {
			nanolibHelper.stopSampler(deviceHandle);
			throw e;
		}
	}

	/// <summary>
	/// Process and output the sampled data to console 
	/// </summary>
	/// <param name="sampleDatas">sampled data from sampler process</param>
	/// <returns>None</returns>
	private void processSampledData(SampleDataVector sampleDatas) throws RuntimeException {

		final int numberOfTrackedAddresses = trackedAddresses.length;

		for (Object object : sampleDatas) {
			// should always be true
			if (object instanceof SampleData)
			{
				SampleData sampleData = (SampleData)object;
			
				final SampledValueVector sampledValues = sampleData.getSampledValues();
				final int numberOfSampledValues = sampledValues.size();

				if ((numberOfSampledValues % numberOfTrackedAddresses) != 0) {
					String errorMsg = "NanolibSamplerExample::processSampledData: Number of samples does not match number of tracked addresses!";
					throw new RuntimeException(errorMsg);
				}

				if (lastIteration != sampleData.getIterationNumber().longValue()) {
					sampleNumber = 0;
					lastIteration = sampleData.getIterationNumber().longValue();
				}

				// header 
				if (!headerPrinted) {

					System.out.println("----------------------------------------------------------------------");
					System.out.printf("%10s %10s %14s %8s %14s %8s\n", 
						"Iteration", "Sample", "[Up Time]", "Time", "[Temperature]", "Time");
					System.out.println("----------------------------------------------------------------------");
					headerPrinted = true;
				}

				// sampled values data
				for (int index = 0; index < numberOfSampledValues; index +=	numberOfTrackedAddresses) {
					
					System.out.printf("%10s %10s %14s %8s %14s %8s\n", lastIteration, sampleNumber, 
						sampledValues.get(index).getValue(), sampledValues.get(index).getCollectTimeMsec(), 
						sampledValues.get(index + 1).getValue(), sampledValues.get(index + 1).getCollectTimeMsec());
					++sampleNumber;
				}
			}
		}
	}

	/// <summary>
	/// Returns the current state of the sampler
	/// </summary>
	/// <returns>None</returns>
	private SamplerState getSamplerState() throws NanolibException {
		return nanolibHelper.getSamplerState(deviceHandle);
	}

	/// <summary>
	/// Returns the sampled data
	/// </summary>
	/// <returns>None</returns>
	private SampleDataVector getSamplerData() throws NanolibException {
		return nanolibHelper.getSamplerData(deviceHandle);
	}

	/// <summary>
	/// Outputs the last occured error
	/// </summary>
	/// <param name="lastError">the last error from calling function</param>
	/// <returns>None</returns>
	private void handleSamplerFailed(ResultVoid lastError) throws NanolibException {
		if (lastError == null) {
			if (this.getSamplerState() == SamplerState.Failed) {
				lastError = nanolibHelper.getSamplerLastError(deviceHandle);
			}
		}

		if (lastError.hasError()) {
			System.err.println("Sampler execution failed with error: " + lastError.getError());
		}
	}
}
