import gov.fnal.controls.acnet.AcnetError;
import gov.fnal.controls.daq.acquire.*;
import gov.fnal.controls.daq.context.CollectionContext;
import gov.fnal.controls.daq.datasource.*;
import gov.fnal.controls.daq.callback.*;
import gov.fnal.controls.daq.events.*;
import gov.fnal.controls.daq.items.*;
import gov.fnal.controls.daq.oac.*;

import java.util.*;

/*
   Example of reading and displaying SWIC display buffer.
   The data comes from the front-end as an array of
   216 (16 bit) shorts. The control system converts ("scales")
   this into an array of 216 doubles by the transformation:
    readings[i] = (double) ((10. * (double)raw[i]) / 32767.)
   (This transformation is specified in the definition
    of the device in the device database).
   In order to interpret the data properly, it
   is necessary to invert this transformation.
   Furthermore, the data is not simply an array of
   shorts, but a structure that contains some floats
   and (32 bit) longs as well. Hence it is necessary
   to combine some of the short elements to get
   the correct data. See the "readings" method below.
*/


public class SwicScaled implements ReadingArrayCallback {

// Offsets defining elements of the structure

// First part of the structure is a set of 8 blocks
// of calculation results.
    private static int CALCULATION_OFFSET = 0;
    private static int CALCULATION_BLOCK_COUNT = 8;
// Offsets within one of the 8 calculation blocks
// Flags for what was calculated; each a "short"
    private static int MEAN_FLAG_OFFSET = 0;
    private static int SIGMA_FLAG_OFFSET = 1;
    private static int INTENSITY_FLAG_OFFSET = 2;
    private static int FOM_FLAG_OFFSET = 3; // "Figure of merit"
    private static int STATUS_FLAG_OFFSET = 4;
// Actual calculation results
// These are 32 bit "floats" and have to be constructed
// from 2 of the returned array elements
    private static int MEAN_OFFSET = 5;
    private static int SIGMA_OFFSET = 7;
    private static int INTENSITY_OFFSET = 9;
    private static int FOM_OFFSET = 11;

    private static int CALCULATION_BLOCK_LENGTH = 13;

// The raw data
    private static int RAW_OFFSET = CALCULATION_BLOCK_LENGTH * CALCULATION_BLOCK_COUNT + CALCULATION_OFFSET;
    private static int RAW_LENGTH = 96;

// Flag indicating position of chamber (in/out/unknown/etc.)
    private static int POSITION_OFFSET = 200;
// Timestamp block
// Only the GPS timestamp is relevant for non-miniBoone SWICs
    private static int EVENT8FTIMESTAMP_OFFSET = 201;
// The first 2 words are combined to form a 32 bit seconds since 1/1/70
// The second 2 words are combined to form a ns offset to the above
    private static int GPSTIMESTAMP_OFFSET = EVENT8FTIMESTAMP_OFFSET + 4;
// Below not generally filled in except for miniBoone
    private static int SAMPLEEVENT_OFFSET = GPSTIMESTAMP_OFFSET + 4;
    private static int RESETEVENT_OFFSET = SAMPLEEVENT_OFFSET + 1;
    private static int N0CEVENT_OFFSET = RESETEVENT_OFFSET + 1;
    private static int MILLISLASTMI_OFFSET = N0CEVENT_OFFSET + 1;
    private static int N1DEVENTMIRESET_OFFSET = MILLISLASTMI_OFFSET + 1;
    private static int N1DEVENT00_OFFSET = N1DEVENTMIRESET_OFFSET + 1;

    private static long updateTime = (long) 200;

// A SWIC device in the fixed target line switchyard
    private static String[] devices = { "S:SF49DS" };
// The length of the structure is 432 bytes
    private static int[] lengths = { 432 };
    private DaqJob job;

    /**
     * Constructor for SwicScaled
     *
     * @param devices    Array of device names to read
     *
     **/
    public SwicScaled(String[] devices, int[] lengths) {
        initJob(devices, lengths);
    }

    /**
     * Set up the DaqJob to get data
     *
     * @param devices        Array of device names to read
     *
     */
    private void initJob(String[] devices, int[] lengths) {
        // This creates a connection to dse04with name "ReadingTry"
        DaqUser user = new DaqUser("ReadingTry", "dse04");

        AcceleratorDevicesItem item = new AcceleratorDevicesItem();
        for (int i = 0; i < devices.length; i++) {
        	AcceleratorDevice device = new AcceleratorDevice(devices[i], AcceleratorDevice.READING, lengths[i], 0);
		item.addDevice(device);
        }
        //
        // We want to get data from the Accelerator (vs database, etc.)
        //
        DataSource from = new AcceleratorSource();
        //
	DelegatingCallbackDisposition to = new DelegatingCallbackDisposition(this);
        //
        // Request only one reading
        //
        OnceImmediateEvent event = new OnceImmediateEvent();
//	DeltaTimeEvent event = new DeltaTimeEvent(updateTime);
//	ClockEvent event = new ClockEvent(0x35);
        DaqJobControl control = new DaqJobControl();

        //
        // Create the DaqJob with the above parameters
        // Data acquistion does not start until job.start() is called
        //
        job = new DaqJob(from, to, item, event, user, control);

    }

    /**
     * Start the DaqJob that was created in initJob
     *
     */
    public void startJob() {
        try {
            job.start();
            job.waitForSetup();
            job.waitForCompletion();
        } catch (Exception e) {
            System.out.println("whoops, job.start caught: " + e.getMessage());
        }
    }

    /**
     *  Main program
     *
     *  @param argv                Arguments, ignored in this example
     *
     */
    public static void main(String[] argv) {
        // java SwicScaled
	if (argv.length > 0) {
	  devices[0] = argv[0];
	}
	System.out.println("Reading device " + devices[0]);
        SwicScaled test = new SwicScaled(devices, lengths);
        test.startJob();
        System.exit(0);
    }

//
// This method is called when readings are available
// The SWIC display buffer is a structure containing multiple data types.
// The ACNET communication protocol does not handle this well at all.
// In order to properly unpack the data, it is necessary to
// first invert the scaling transformation, then regroup/
// reinterpret the resulting unscaled array
//
    public void readings(WhatDaq device, int element, int error, Date timestamp, CollectionContext context, double[] readings){
         System.out.println ( device.getDeviceName() + " " + element + " " + error );
// Create array of unscaled values
	int[] unscaled = new int[readings.length];
	for (int ii = 0; ii < readings.length; ii++) {
// Undo scaling transformation done by the infrastructure
	  unscaled[ii] = (int) (readings[ii] * 32767./10.);
	}
// Calculation blocks
	for (int ii = 0; ii < CALCULATION_BLOCK_COUNT; ii++) {
// Flags indicating what was calculated and calculation status
	  int calc_offset = ii*CALCULATION_BLOCK_LENGTH;
	  int cmean = unscaled[calc_offset + MEAN_FLAG_OFFSET];
	  int csigma = unscaled[calc_offset + SIGMA_FLAG_OFFSET];
	  int cintensity = unscaled[calc_offset + INTENSITY_FLAG_OFFSET];
	  int cunsplit = unscaled[calc_offset + FOM_FLAG_OFFSET];
	  int status = unscaled[calc_offset + STATUS_FLAG_OFFSET];
	  System.out.println("Flags - mean: " + cmean + " sigma: " + csigma + " intens: " + cintensity + " unsplit: " + cunsplit + " status: " + status);
// Calculation results
// Two unscaled words must be combined, the result is a floating point number
	  int meanUpper = unscaled[calc_offset + MEAN_OFFSET];
	  int meanLower = unscaled[calc_offset + MEAN_OFFSET + 1];
	  float mean = Float.intBitsToFloat((meanUpper << 16) + (meanLower & 0xffff));
	  int sigmaUpper = unscaled[calc_offset + SIGMA_OFFSET];
	  int sigmaLower = unscaled[calc_offset + SIGMA_OFFSET + 1];
	  float sigma = Float.intBitsToFloat((sigmaUpper << 16) + (sigmaLower & 0xffff));
	  int intensityUpper = unscaled[calc_offset + INTENSITY_OFFSET];
	  int intensityLower = unscaled[calc_offset + INTENSITY_OFFSET + 1];
	  float intensity = Float.intBitsToFloat((intensityUpper << 16) + (intensityLower & 0xffff));
	  int unsplitUpper = unscaled[calc_offset + FOM_OFFSET];
	  int unsplitLower = unscaled[calc_offset + FOM_OFFSET + 1];
	  float unsplit = Float.intBitsToFloat((unsplitUpper << 16) + (unsplitLower & 0xffff));
	  System.out.println("Mean: " + mean + " sigma: " + sigma + " intensity: " + intensity + " unsplit: " + unsplit);
      }
// Print raw data
      for (int i = 0; i < RAW_LENGTH; i++) {
	int raw = (unscaled[RAW_OFFSET + i] & 0xff);
	System.out.println("Raw " + i + ": " + raw + " 0x" + Integer.toHexString(raw));
      }
// Print position
      int pos = unscaled[POSITION_OFFSET];
      System.out.println("Position flag: " + pos);
//
// Of the timestamp block, only the GPS timestamp is filled in except for miniBoone
// GPS time stamp
// The first two words are combined into a single word that is seconds since 1/1/70
      int gpsUpper = unscaled[GPSTIMESTAMP_OFFSET];
      int gpsLower = unscaled[GPSTIMESTAMP_OFFSET + 1];
      int gps = (gpsUpper << 16) + (gpsLower & 0xffff);
      System.out.println("GPS Hex: " + Integer.toHexString(gpsUpper) + " " + Integer.toHexString(gpsLower));
      System.out.println("GPS seconds: " + gps + " " + Integer.toHexString(gps));
// The second two words are combined into the number of ns offset from the above
      int gpsnsUpper = unscaled[GPSTIMESTAMP_OFFSET + 2];
      int gpsnsLower = unscaled[GPSTIMESTAMP_OFFSET + 3];
      int gpsns = (gpsnsUpper << 16) + (gpsnsLower & 0xffff);
      System.out.println("GPS nano seconds: " + gpsns );
      long gpsTime = ((long)gps) * 1000 + ((long) gpsns)/1000000;
      System.out.println("GPS date: " + (new Date(gpsTime)));
// Rest of stuff not relevant except for miniBoone
	  
    }

}
