import com.proatech.bioera.external.ExternalChartContext;
import com.proatech.bioera.external.ExternalChartIf;
import com.proatech.bioera.external.ExternalElementContext;
import com.proatech.bioera.external.ExternalInit2If;
import com.proatech.bioera.external.ExternalInitIf;
import com.proatech.bioera.external.ExternalSinkIf;
import com.proatech.bioera.external.ExternalSourceIf;

/** 
 * This class is an example implementation of ExternalElement, it implements interfaces: ExternalSourceIf, ExternalSinkIf, ExternalInitIf.
 * 
 * 1. It creates an element with one input and output channel.
 * 2. It multiplies input signal by 2.
 *  
 */
public class Test implements ExternalSourceIf, ExternalInit2If, ExternalSinkIf, ExternalChartIf {
	// This buffer will hold data provided to this element
	// This example has only one channel, so there is just one buffer	
	private int scalarBuffer[]; // Must be implemented for Scalar element
	private float floatBuffer[];  // Must be implemented for Float element
	private double doubleBuffer[];  // Must be implemented for DoubleFloat element
	
	int available; // This variable will remember how many data is currently in our internal buffer

	////////////// Definition of ExternalSourceIf interface  /////////////////////////
	
	/**
	 *  If your signal has constant rate it should be set here.
	 *  If it is set to -1, , then the information is taken from the element connected to input (in the design)
	 */
	public float getOutputDataRate() {
		return -1;
	}
	
	/**
	 *  If your signal has constant maximum digital value then it should be set here.
	 *  If it is set to Integer.MAX_VALUE, then the information is taken from the element connected to input (in the design)
	 */
	public int getDigitalMax() {
		return Integer.MAX_VALUE;
	}

	/**
	 *  If your signal has constant maximum digital value then it should be set here.
	 *  If it is set to Integer.MIN_VALUE, then the information is taken from the element connected to input (in design)
	 */
	public int getDigitalMin() {
		return Integer.MIN_VALUE;
	}

	/**
	 *  Define number of output channels. The design has to be restarted if this value changes.
	 */  
	public int getOutputChannelCount() {
		return 1;
	}

	/**
	 *  If your signal has constant maximum physical value then it should be set here.
	 *  If it is set to Float.POSITIVE_INFINITY, then the information is taken from the element connected to input (in design)
	 */
	public float getPhysicalMax() {
		return Float.POSITIVE_INFINITY;
	}

	/**
	 *  If your signal has constant minimum physical value then it should be set here.
	 *  If it is set to Float.MIN_VALUE, then the information is taken from the element connected to input (in design)
	 */
	public float getPhysicalMin() {
		return Float.NEGATIVE_INFINITY;
	}

	/**
	 *  If your signal has a physical unit it should be set here.
	 *  If it is set to null, then the information is taken from the element connected to input (in design)
	 */
	public String getPhysicalUnit() {
		return null;
	}
	
	/**
	 *  You can set name of each channel. It will be visible on the element's pipe.  
	 *  If not set (returns null), then a default name will be assigned.
	 */
	public String getOutputChannelName(int channel) {
		return "Channel " + (channel+1);
	}

	/**
	 *  You can set name of each input channel. It will be visible on the element's pipe.  
	 *  If not set (returns null), then a default name will be assigned.
	 */
	public String getInputChannelName(int channel) {
		return "Input " + (channel+1);
	}

	/**
	 *  This method is described in the ExternalSourceIf interface
	 */
	public int processOutputData(int channel, int[] destinationBuffer) {
		// Just copy the data which has been already preprocessed in write() method
		System.arraycopy(scalarBuffer, 0, destinationBuffer, 0, available);  
		int ret = available;
		
		if (available > 0)
			drawNumberOnChart(destinationBuffer[0] * context.getPhysDigiRatio());		
		
		// Reset internal buffer
		available = 0;

		return ret;
	}

	/**
	 *  This method is described in the ExternalSourceIf interface
	 */
	public int processOutputData(int channel, float[] destinationBuffer) {
		// Just copy the data which has been already preprocessed in write() method
		System.arraycopy(floatBuffer, 0, destinationBuffer, 0, available);  
		int ret = available;

		if (available > 0)
			drawNumberOnChart(destinationBuffer[0]);		

		// Reset internal buffer
		available = 0;

		return ret;
	}
	
	/**
	 *  This method is described in the ExternalSourceIf interface
	 */
	public int processOutputData(int channel, double[] destinationBuffer) {
		// Just copy the data which has been already preprocessed in write() method
		System.arraycopy(scalarBuffer, 0, destinationBuffer, 0, available);
		int ret = available;
		
		if (available > 0)
			drawNumberOnChart((float) destinationBuffer[0]);		

		// Reset internal buffer
		available = 0;

		return ret;
	}

	/////////////////// Defiinition of the ExternalInitIf interface ////////////////////
	
	/**
	 *  Called one time when the element is created.
	 */
	public void init(ExternalElementContext context) throws java.io.IOException {
		this.context = context;
	}

	/**
	 *  This is called only if ExternalInitIf is implemented (available in older versions)
	 *  This is not called if ExternalInit2If is implemented, and replaced with the init(ExternalElementContext)
	 */ 	
	public void init() throws java.io.IOException {
	}
	
	/**
	 *  Called whenever element is started.
	 */  
	public void start() throws java.io.IOException {
		scalarBuffer = new int[65536]; // allocate buffer large enough to keep incoming data
		floatBuffer = new float[65536]; // allocate buffer large enough to keep incoming data
		doubleBuffer = new double[65536]; // allocate buffer large enough to keep incoming data
		System.out.println("External element started");
	}

	/**
	 *  Called whenever element is stopped.
	 */  
	public void stop() throws java.io.IOException {
		System.out.println("External element stopped");
	}
	
	/**
	 *  Called whenever element is paused.
	 */  	
	public void pause() throws java.io.IOException {
		System.out.println("External element paused");
	}
	
	/**
	 *  Called whenever element is reinited.
	 */  	
	public void reinit() throws java.io.IOException {
		System.out.println("External element reinited");
	}
	
	/**
	 *  Called whenever element is resumed.
	 */  	
	public void resume() throws java.io.IOException {
		System.out.println("External element resumed");
	}
	
	/**
	 *  Called one time when element is removed from the design (or exit).
	 */  	
	public void destroy() {
		System.out.println("External element destroyed");
	}

	/**
	 *  Define number of input channels. The design has to be restarted if this value changes.
	 */  
	public int getInputChannelCount() {
		return 1;
	}

	/**
	 *  This method is called only in scalar ExternalElement when there is data delivered to input pipe
	 *  It must return the number of samples processed, or Integer.MAX_VALUE if all data should be dropped.
	 */  
	public int processInputData(int channel, int[] source, int count) {
		if (System.currentTimeMillis() > time) {
			System.out.println("Processing input data, " + count++ + " seconds since the start");
			time = System.currentTimeMillis() + 1000;
		}
		
		// Remember how many data we read
		available = count;

		// Read all available data into our internal buffer
		System.arraycopy(source, 0, scalarBuffer, 0, count);
		
		// Process the data ....
		// Multiply all samples by 2
		for (int i = 0; i < count; i++) {
			scalarBuffer[i] *= 2;
		}
		
		// This means that all data has been processed, it could also return the 'available' value.
		return Integer.MAX_VALUE;
	}
	
	/**
	 *  This method is called only in F_ExternalElement when there is data delivered to input pipe
	 *  It must return the number of samples processed, or Integer.MAX_VALUE if all data should be dropped.
	 */  
	public int processInputData(int channel, float[] source, int count) {
		// Remember how many data we read
		available = count;
		
		// Read all available data into our internal buffer
		System.arraycopy(source, 0, floatBuffer, 0, count);
		
		// Process the data ....
		// Multiply all samples by 2
		for (int i = 0; i < count; i++) {
			floatBuffer[i] *= 2;
		}
		
		// This means that all data has been processed, it could also return the 'available' value.
		return Integer.MAX_VALUE;
	}	

	long time = 0;
	int count = 0;
	public int processInputData(int channel, double[] source, int count) {
		// This means that all data has been processed, it could also return the 'available' value.
		return Integer.MAX_VALUE;
	}

	public void chartInit(ExternalChartContext chartContext) {
		this.chartContext = chartContext;		
	}

	public void chartReinit() {
		if (chartContext != null) {
			this.chartComponentWidth = chartContext.getComponentWidth();
			this.chartComponentHeight = chartContext.getComponentHeight();
		}
	}	

	private ExternalElementContext context;
	private ExternalChartContext chartContext;
	private int chartComponentWidth, chartComponentHeight;

	// Example method ONLY for Windows
	// Comment it out when developing for Android
	
	private void drawNumberOnChart(float value) {
		java.awt.Graphics2D gr = chartContext.getChartGraphics();
		gr.clearRect(0, 0, chartComponentWidth, chartComponentHeight);			
		gr.setColor(java.awt.Color.red);
		String text = Float.toString(value);
		int x = chartContext.getComponentWidth() / 2;
		int y = chartContext.getComponentHeight() / 2;
		gr.drawString(text, x, y);
		chartContext.requestRepaint();
	}

	// ----------------- End-of-code for Windows----------------------------	

	
	// Example method ONLY for Android
	// Uncomment it when developing for Android

//	private void drawNumberOnChart(float value) {
//		android.graphics.Canvas canvas = (android.graphics.Canvas) chartContext.getAndroidCanvas();
//		
//		canvas.drawColor(android.graphics.Color.WHITE);
//
//		android.graphics.Paint paint = new android.graphics.Paint();
//		paint.setColor(android.graphics.Color.RED);
//
//		String text = Float.toString(value);
//		int x = chartComponentWidth / 2;
//		int y = chartComponentHeight / 2;
//		canvas.drawText(text, x, y, paint);
//		chartContext.requestRepaint();
//	}

}

