import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.nio.channels.FileChannel;

import otherSupport.SettingsManager;

import binaryMatrixFile.BinaryMatrixFile;

import comedi.ComediOptions;
import comediJNI.Comedi;
import comediJNI.ComediDef;
import comediJNI.ComediJNIException;

/**
 * Port of cmd demo from Comedi
 * 
	 * Example of using commands - asynchronous input
	 * Part of Comedilib
	 *
	 * Copyright (c) 1999,2000,2001 David A. Schleef <ds@schleef.org>
	 *
	 * This file may be freely modified, distributed, and combined with
	 * other software, as long as proper attribution is given in the
	 * source code.

	 * An example for directly using Comedi commands.  Comedi commands
	 * are used for asynchronous acquisition, with the timing controlled
	 * by on-board timers or external events.
	 */
public class cmd {
	static {
		new SettingsManager("minerva", true);
	}

	public static final int BUFSZ = 10000;
	
	public static final int N_CHANS  = 256;
	public static int chanlist[] = new int[N_CHANS];
	public static ComediDef.Range range_info[] = new ComediDef.Range[N_CHANS];
	public static int maxdata[] = new int[N_CHANS];

	public static void main(String[] args) {
		
		long dev;
		ComediDef.Cmd cmd;
		int ret;
		
		ComediOptions options = new ComediOptions();

		//init_parsed_options(&options);
		//parse_options(&options, argc, argv);

		/* The following variables used in this demo
		 * can be modified by command line
		 * options.  When modifying this demo, you may want to
		 * change them here. */
		options.fileName = "/dev/comedi0";
		options.subdevice = 0;
		options.channel = 0;
		options.range = 0;
		options.aref = ComediDef.AREF_GROUND;
		options.n_chan = 4;
		options.n_scan = 1000;
		options.freq = 1000.0;
		options.physical = true;

		/*
		 * options.value;
		options.physical;
		options.verbose;
		*/
		
		/* open the device */		
		dev = Comedi.open(options.fileName);
		if(dev == 0)
			throw new ComediJNIException("comedi_open");
		
		// Print numbers for clipped inputs
		Comedi.set_global_oor_behavior(ComediDef.COMEDI_OOR_NUMBER);

		/* Set up channel list */
		for(int i = 0; i < options.n_chan; i++){
			chanlist[i] = ComediDef.CR_PACK(options.channel + i, options.range, options.aref);
			range_info[i] = new ComediDef.Range();
			Comedi.get_range(dev, options.subdevice, options.channel, options.range, range_info[i]);
			maxdata[i] = Comedi.get_maxdata(dev, options.subdevice, options.channel);
			System.out.println("chan " + i + 
					": chanlist = "+chanlist[i] + 
					"\tmin = " + range_info[i].min + 
					"\tmax = " + range_info[i].max + 
					"\tunit = " + range_info[i].unit + 
					"\tmaxdata = " + maxdata[i]);
		}

		/* prepare_cmd_lib() uses a Comedilib routine to find a
		 * good command for the device.  prepare_cmd() explicitly
		 * creates a command, which may not work for your device. */
		cmd = new ComediDef.Cmd();
		prepare_cmd_lib(dev, options.subdevice, options.n_scan, options.n_chan, (int)(1e9 / options.freq), cmd);
		//prepare_cmd(dev, options.subdevice, options.n_scan, options.n_chan, 1e9 / options.freq, cmd);

		System.out.println("command before testing:");
		cmd.dump();

		/* comedi_command_test() tests a command to see if the
		 * trigger sources and arguments are valid for the subdevice.
		 * If a trigger source is invalid, it will be logically ANDed
		 * with valid values (trigger sources are actually bitmasks),
		 * which may or may not result in a valid trigger source.
		 * If an argument is invalid, it will be adjusted to the
		 * nearest valid value.  In this way, for many commands, you
		 * can test it multiple times until it passes.  Typically,
		 * if you can't get a valid command in two tests, the original
		 * command wasn't specified very well. */
		ret = Comedi.command_test(dev, cmd);
		if(ret < 0)
			throw new ComediJNIException("comedi_command_test. If EIO then this subdevice doesn't support commands");

		System.err.println("first test returned "+ ret + " " + ComediDef.cmdtest_messages[ret]);
		cmd.dump();

		ret = Comedi.command_test(dev, cmd);
		if(ret < 0)
			throw new ComediJNIException("comedi_command_test");

		
		System.err.println("second test returned " + ret + " " + ComediDef.cmdtest_messages[ret]);
		if(ret!=0){
			cmd.dump();
			throw new RuntimeException("Error preparing command");
		}

		/* start the command */
		ret = Comedi.command(dev, cmd);
		if(ret < 0)
			throw new ComediJNIException("comedi_command");

		//cRead(dev, options);
		jRead(dev, options);
		
	}
	
	/** Read using the standard C read() function (in JNI) and copy data back
	 * direct equiv of the cmd.c demo program  
	 * @param dev
	 * @param options */
	private static void cRead(long dev, ComediOptions options){
		int total=0;
		int subdev_flags;
		int raw;
		byte buf[] = new byte[BUFSZ];

		subdev_flags = Comedi.get_subdevice_flags(dev, options.subdevice);
		
		while(true){
			int bytesRead = Comedi.read(dev, buf, BUFSZ);
			if(bytesRead < 0){
				/* some error occurred */
				System.err.println(" read ");
				break;
			}else if(bytesRead == 0){
				/* reached stop condition */
				break;
			}else{
				int col = 0;
				int bytes_per_sample;
				total += bytesRead;
				if(options.verbose) System.err.println("read " + bytesRead + " " + total);
				
				ByteBuffer bBuf = ByteBuffer.wrap(buf);
				bBuf.order(ByteOrder.LITTLE_ENDIAN);
				
				boolean isShort = (subdev_flags & ComediDef.SDF_LSAMPL) == 0;
				int count = bytesRead / (isShort ? 2 : 4);
								
				for(int i = 0; i < count; i++){
					raw = isShort ? bBuf.getShort() : bBuf.getInt();
										
					print_datum(raw, col, options.physical);
					col++;
					if(col == options.n_chan){
						System.out.println();
						col=0;
					}
				}
			}
		}

	}
	

	/** Attempt to read the file descriptor directly in java using NIO */
	private static void jRead(long dev, ComediOptions options){
		int total=0;
		int subdev_flags;
		int raw;
		int bufferSize = 10000;
		
		subdev_flags = Comedi.get_subdevice_flags(dev, options.subdevice);
		
		FileDescriptor fd = Comedi.getFileDesc(dev);
		FileInputStream fin = new FileInputStream(fd);
		FileChannel chan = fin.getChannel();
				
		ByteBuffer bBuf = ByteBuffer.allocate(BUFSZ);
		bBuf.order(ByteOrder.LITTLE_ENDIAN);
			
		while(true){
			
			System.out.println("reading, buf is :" + bBuf);
			
			int bytesRead;
			try{
				bytesRead = chan.read(bBuf);
			}catch(IOException err){
				throw new RuntimeException(err);
			}
			System.out.println("done reading, buf is :" + bBuf + ", ret=" + bytesRead);
			
			if(bytesRead < 0){
				/* some error occurred */
				System.err.println(" read ");
				break;
			}else if(bytesRead == 0){
				/* reached stop condition */
				break;
			}else{
				int col = 0;
				total += bytesRead;
				if(options.verbose) System.err.println("read " + bytesRead + " " + total);
				
				bBuf.flip();
				
				boolean isShort = (subdev_flags & ComediDef.SDF_LSAMPL) == 0;
				int count = bytesRead / (isShort ? 2 : 4);
								
				for(int i = 0; i < count; i++){
					if(isShort){
						raw = bBuf.getShort();
						if(raw < 0) //fix short to int signed conversion so that to_phys() gets the same bytes
							raw += 65536;
					}else{						
						raw = bBuf.getInt();
					}
										
					print_datum(raw, col, options.physical);
					col++;
					if(col == options.n_chan){
						System.out.println();
						col=0;
					}
				}
				
				bBuf.flip();
			}
		}

	}


	/**
	 * This prepares a command in a pretty generic way.  We ask the
	 * library to create a stock command that supports periodic
	 * sampling of data, then modify the parts we want. */
	private static int prepare_cmd_lib(long dev, int subdevice, int n_scan, int n_chan, int scan_period_nanosec, ComediDef.Cmd cmd){
		int ret;

		/* This comedilib function will get us a generic timed
		 * command for a particular board.  If it returns -1,
		 * that's bad. */
		ret = Comedi.get_cmd_generic_timed(dev, subdevice, cmd, n_chan, scan_period_nanosec);
		if(ret<0)
			throw new ComediJNIException("comedi_get_cmd_generic_timed failed");

		/* Modify parts of the command */
		cmd.chanlist = chanlist;
		cmd.chanlist_len = n_chan;
		if(cmd.stop_src == ComediDef.TRIG_COUNT) 
			cmd.stop_arg = n_scan;

		return 0;
	}
	
	
	/*
	 * Set up a command by hand.  This will not work on some devices.
	 * There is no single command that will work on all devices.
	 */
	private static int prepare_cmd(long dev, int subdevice, int n_scan, int n_chan, int period_nanosec, ComediDef.Cmd cmd){
		
		/* the subdevice that the command is sent to */
		cmd.subdev =	subdevice;

		/* flags */
		cmd.flags = 0;

		/* Wake up at the end of every scan */
		//cmd->flags |= TRIG_WAKE_EOS;

		/* Use a real-time interrupt, if available */
		//cmd->flags |= TRIG_RT;

		/* each event requires a trigger, which is specified
		   by a source and an argument.  For example, to specify
		   an external digital line 3 as a source, you would use
		   src=TRIG_EXT and arg=3. */

		/* The start of acquisition is controlled by start_src.
		 * TRIG_NOW:     The start_src event occurs start_arg nanoseconds
		 *               after comedi_command() is called.  Currently,
		 *               only start_arg=0 is supported.
		 * TRIG_FOLLOW:  (For an output device.)  The start_src event occurs
		 *               when data is written to the buffer.
		 * TRIG_EXT:     start event occurs when an external trigger
		 *               signal occurs, e.g., a rising edge of a digital
		 *               line.  start_arg chooses the particular digital
		 *               line.
		 * TRIG_INT:     start event occurs on a Comedi internal signal,
		 *               which is typically caused by an INSN_TRIG
		 *               instruction.
		 */
		cmd.start_src =	ComediDef.TRIG_NOW;
		cmd.start_arg =	0;

		/* The timing of the beginning of each scan is controlled by
		 * scan_begin.
		 * TRIG_TIMER:   scan_begin events occur periodically.
		 *               The time between scan_begin events is
		 *               convert_arg nanoseconds.
		 * TRIG_EXT:     scan_begin events occur when an external trigger
		 *               signal occurs, e.g., a rising edge of a digital
		 *               line.  scan_begin_arg chooses the particular digital
		 *               line.
		 * TRIG_FOLLOW:  scan_begin events occur immediately after a scan_end
		 *               event occurs.
		 * The scan_begin_arg that we use here may not be supported exactly
		 * by the device, but it will be adjusted to the nearest supported
		 * value by comedi_command_test(). */
		cmd.scan_begin_src =	ComediDef.TRIG_TIMER;
		cmd.scan_begin_arg = period_nanosec;		/* in ns */

		/* The timing between each sample in a scan is controlled by convert.
		 * TRIG_TIMER:   Conversion events occur periodically.
		 *               The time between convert events is
		 *               convert_arg nanoseconds.
		 * TRIG_EXT:     Conversion events occur when an external trigger
		 *               signal occurs, e.g., a rising edge of a digital
		 *               line.  convert_arg chooses the particular digital
		 *               line.
		 * TRIG_NOW:     All conversion events in a scan occur simultaneously.
		 * Even though it is invalid, we specify 1 ns here.  It will be
		 * adjusted later to a valid value by comedi_command_test() */
		cmd.convert_src =	ComediDef.TRIG_TIMER;
		cmd.convert_arg =	1;		/* in ns */

		/* The end of each scan is almost always specified using
		 * TRIG_COUNT, with the argument being the same as the
		 * number of channels in the chanlist.  You could probably
		 * find a device that allows something else, but it would
		 * be strange. */
		cmd.scan_end_src =	ComediDef.TRIG_COUNT;
		cmd.scan_end_arg =	n_chan;		/* number of channels */

		/* The end of acquisition is controlled by stop_src and
		 * stop_arg.
		 * TRIG_COUNT:  stop acquisition after stop_arg scans.
		 * TRIG_NONE:   continuous acquisition, until stopped using
		 *              comedi_cancel()
		 * */
		cmd.stop_src =		ComediDef.TRIG_COUNT;
		cmd.stop_arg =		n_scan;

		/* the channel list determined which channels are sampled.
		   In general, chanlist_len is the same as scan_end_arg.  Most
		   boards require this.  */
		cmd.chanlist =		chanlist;
		cmd.chanlist_len =	n_chan;

		return 0;
	}

	private static void print_datum(int raw, int channel_index, boolean physical) {
		double physical_value;
		if(physical) {
			physical_value = Comedi.to_phys(raw, range_info[channel_index], maxdata[channel_index]);
			System.out.print(physical_value + " "); //was formatted"%#8.6g "
		}else {
			System.out.print(raw + " ");
		}
	}
	
}
