/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: HP80000.java
 * Written by David Hopkins.
 *
 * Copyright (c) 2003, Static Free Software. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.simulation.test;

public class HP80000 extends Equipment {

    public static int SYSTEM=0, SYSTEMCLOCK=1, DATAGEN1GHZ=2, DG1GHZ128K=3;

    private int selectedInst;

    /**
     * Creates a new instance of HP80000
     * @param name  
     */
    public HP80000(String name) {
        super(name);
        selectInstrument(HP80000.SYSTEM);
    }

    public void reset() {
        write("*CLS");
        write("*RST");
        try { Thread.sleep(300); } catch (InterruptedException e) { }
    }
    
    public void start() {
        write(":INST:SEL SYSTEMCLOCK");
        write(":TRIG:STAR");
        try { Thread.sleep(100); } catch (InterruptedException e) { }
    }
    
    public void stop() {
        write(":INST:SEL SYSTEMCLOCK");
        write(":TRIG:STOP");
        try { Thread.sleep(100); } catch (InterruptedException e) { }
    }
    
    public void pause() {
        write(":INST:SEL SYSTEMCLOCK");
        write(":TRIG:PAUS");
        try { Thread.sleep(100); } catch (InterruptedException e) { }
    }
    
    public void cont() {
        write(":INST:SEL SYSTEMCLOCK");
        write(":TRIG:CONT");
        try { Thread.sleep(100); } catch (InterruptedException e) { }
    }
    
    

    /**
     * Select an instrument. Most commands must be run after selecting
     * the proper instrument.
     * @param inst the instrument
     */
    public void selectInstrument(int inst) {

        write(":INST:SEL "+instToString(inst));
        selectedInst = inst;
    }

    private String instToString(int inst) {
        switch(inst) {
            case 0: return "System";
            case 1: return "SystemClock";
            case 2: return "DATAGEN1GHZ";
            case 3: return "DG1GHZ128K";
        }
        return "Unknown";
    }

    // ------------------------- System Clock -------------------------

    /**
     * Set the clock source to internal or external.
     * @param internal true for internal, false for external
     */
    public void setClockSource(boolean internal) {
        if (!isSelectedInstSystemClock()) return;
        if (internal)
            write(":CLOC:SOUR INT");
        else
            write(":CLOC:SOUR EXT");
    }

    /**
     * Set the internal system clock frequency. Default value is 1GHz.
     * The frequency will be set to the nearest available frequency setting.
     * The available range is 7.8125 MHz to 1 GHz.
     * @param freq the frequency
     * @param units the units: "M" or "KHz" or "MHz".
     */
    public void setClockFrequency(double freq, String units) {
        if (!isSelectedInstSystemClock()) return;
        if (!units.toLowerCase().endsWith("hz")) units = units + "HZ";
        write(":CLOC:INT:FREQ "+freq+" "+units);
    }


    // ----------------------- Managing Groups --------------------------

    public void printGroups() {
        if (!isSelectedInstDataGen()) return;
        write(":DIG:GRO:CAT?");
        String s = read(200);
        System.out.println("Groups: "+s);
    }

    /**
     * Define a group and assign channels to the group. The instrument must
     * be a DATAGEN1GHZ or DG1GHZ128K; the group name is whatever you choose,
     * and the slotsAndChannels argument sets which channels on which slots (modules)
     * are assigned to this group.
     * <P>
     * For example, the following strings all assign channels 0 to 3 on slot 0
     * and slot 2 to this group:
     * <pre>
     * Individual Logical Channels:
     * 0,1,2,3,8,9,10,11
     * Logical Channel Ranges:
     * 0:3,8:11
     * Module slot and channel:
     * 0(0:3),2(0:3)
     * </pre>
     *
     * @param groupName a new group name
     * @param slotsAndChannels the slots and channels
     */
    public void defineGroup(String groupName, String slotsAndChannels) {
        if (!isSelectedInstDataGen()) return;
        write(":DIG:GRO:DEF "+groupName+",(@"+slotsAndChannels+")");
    }

    /**
     * Delete the group on the instrument. Note that the group "ALL" will delete all groups
     * @param groupName which group to delete
     */
    public void deleteGroup(String groupName) {
        if (!isSelectedInstDataGen()) return;
        if (groupName.equals("ALL"))
            write(":DIG:GRO:DEL:ALL");
        else
            write(":DIG:GRO:DEL "+groupName);
    }

    /**
     * Select given group for currently selected instrument
     * (currently selected instrument must be DATAGEN1GHZ or DG1GHZ128K)
     * Note that the group "ALL" will select ALL groups. Also,
     * the group "NONE" will select no groups, and all subsequent group-oriented
     * commands will be disabled.
     * @param groupName which group
     */
    public void selectGroup(String groupName) {
        if (!isSelectedInstDataGen()) return;
        write(":DIG:GRO:SEL "+groupName);
    }

    // ------------------- Group Level Commands for Data Gen ----------------

    /**
     * Set the pattern cycling for the currently selected group. Note that
     * this does not set the actual pattern itself, only the number of bits
     * and cycling of the pattern. Note that start+length must be les than 16384.
     * @param startLength number of start bits at start, generated once, must be 0 or 4 or more
     * @param cycleLength number of repeated bits, must be 0 or multiple of 8
     * @param cycleTimes number of times to loop, 0 to 255, or -1 for infinite looping
     */
    public void setGroupPatternCycle(int startLength, int cycleLength, int cycleTimes) {
        if (!isSelectedInstDataGen()) return;

        String l = cycleTimes < 0 ? "INF" : String.valueOf(cycleTimes);
        if (startLength < 4 && startLength != 0) {
            System.out.println("Error: pattern cycle start value must be 0, or 4 or more");
            return;
        }
        if (cycleLength % 8 != 0) {
            System.out.println("Error: pattern cycle length value must be 0 or multiple of 8");
            return;
        }
        if (cycleTimes > 255) {
            System.out.println("Warning: pattern cycle loop value maximum is 255, dropping "+cycleTimes+" to 255");
            cycleTimes = 255;
        }
        write(":PATT:CYCL "+startLength+","+cycleLength+","+l);
        try { Thread.sleep(100); } catch (InterruptedException e) { }
    }

    /**
     * Set the data for the current group. The array size must equal the number of
     * channels in the group. The length of the binData Strings must equal the number
     * of bits in the sequence. The string format is binary, for example, "010010110".
     * @param binData the binary data sequences for each channel.
     */
    public void setGroupData(String [] binData) {
        if (!isSelectedInstDataGen()) return;
        if (binData.length == 0) return;

        int numChannels = binData.length;
        int datalen = binData[0].length();

        for (int i=0; i<binData.length; i++) {
            if (binData[i].length() != datalen) {
                System.out.println("Error: in setData, all arrays must be the same length");
                return;
            }
        }
        for (int i=0; i<datalen; i++) {
            StringBuffer cmd = new StringBuffer();
            cmd.append(":PATT:MOD ");
            cmd.append(i);
            cmd.append(",#B");
            for (int j=numChannels-1; j >= 0; j--) {
                cmd.append(binData[j].charAt(i));
            }
            write(cmd.toString());
            try { Thread.sleep(100); } catch (InterruptedException e) { }
            //System.out.println("wrote: "+cmd.toString());
        }
        write(":PATT:UPD");
        try { Thread.sleep(100); } catch (InterruptedException e) { }
    }

    /**
     * Print data for signal in a group. Unfortunately this only seems to
     * work if there is only one signal in a group
     * @param start
     * @param end
     * @param numSignals
     */
    public void printGroupData(int start, int end, int numSignals) {
        if (!isSelectedInstDataGen()) return;

        write(":FORM ASC,"+numSignals);
        write(":PATT:MOD? "+start+","+end);
        String s = read(400);
        System.out.println(s);
    }

    /**
     * Set the signal format for the currently selected Group
     * Note that there is only one signal polarity setting for a module, so other
     * groups on the same module will also be affected.
     * @param NonReturntoZero true to set to non-return-to-zero, false to set return-to-zero
     */
    public void setSignalFormat(boolean NonReturntoZero) {
        if (!isSelectedInstDataGen()) return;
        if (NonReturntoZero)
            write(":SIGN:FORM NRZ");
        else
            write(":SIGN:FORM RZ");
    }

    /**
     * Set the signal polarity (normal or inverted) for all signals
     * in the currently selected Group.
     * @param normal true for normal, false for inverted
     */
    public void setGroupSignalPolarity(boolean normal) {
        setGroupSignalPolarity(-1, normal);
    }

    /**
     * Set the signal polarity (normal or inverted) for a signal
     * in the currently selected Group.
     * @param signal which signal in the group, or -1 for all signals
     * @param normal true for normal, false for inverted
     */
    public void setGroupSignalPolarity(int signal, boolean normal) {
        if (!isSelectedInstDataGen()) return;
        String s = signal < 0 ? "" : String.valueOf(signal);
        if (normal)
            write(":SIGN:POL"+s+" NORM");
        else
            write(":SIGN:POL"+s+" INV");
    }

    /**
     * Turn on or off the output state
     * (whether output is enabled) for all signals in the currently selected Group.
     * @param on true to enable, false to disable
     */
    public void setOutputState(boolean on) {
        setOutputState(-1, on);
    }

    /**
     * Turn on or off the output state
     * (whether output is enabled) for the signal in the currently selected Group.
     * Or, set signal to -1 to set the state for all signals in the group.
     * @param signal -1 for all signals, or which signal in the currently selected group.
     * @param on true to enable, false to disable
     */
    public void setOutputState(int signal, boolean on) {
        if (!isSelectedInstDataGen()) return;
        String s = signal < 0 ? "" : String.valueOf(signal);
        if (on)
            write(":OUTP"+s+" ON");
        else
            write(":OUTP"+s+" OFF");
    }

    /**
     * Set the output termination for all signals in the currently selected group
     * The state can be one of the following:
     * <pre>
     * 0: 50 ohms terminated to ground (GROUND)
     * 1: 50 ohms terminated to -2V (ECL)
     * 2: 50 ohms terminated to +3V (PECL)
     * 3: open circuit (OPEN)
     * </pre>
     * Note that the output state must be switched off to change this setting
     * @param state which termination to set
     */
    public void setOutputTermination(int state) {
        setOutputTermination(-1, state);
    }

    /**
     * Set the output termination for the signal in the currently selected group,
     * or all signals in the group if signal is -1.
     * The state can be one of the following:
     * <pre>
     * 0: 50 ohms terminated to ground (GROUND)
     * 1: 50 ohms terminated to -2V (ECL)
     * 2: 50 ohms terminated to +3V (PECL)
     * 3: open circuit (OPEN)
     * </pre>
     * Note that the output state must be switched off to change this setting
     * @param signal which signal in the group, or -1 for all signals in the group
     * @param state which termination to set
     */
    public void setOutputTermination(int signal, int state) {
        if (!isSelectedInstDataGen()) return;
        String s = signal < 0 ? "" : String.valueOf(signal);
        switch (state) {
            case 0: write(":OUTP"+s+":TERM GRO"); break;
            case 1: write(":OUTP"+s+":TERM ECL"); break;
            case 2: write(":OUTP"+s+":TERM PECL"); break;
            case 3: write(":OUTP"+s+":TERM OPEN"); break;
            default:
                System.out.println("State "+state+" is not a valid output termination state");
        }
    }

    /**
     * Set the delay of a signal in the currently selected group.
     * The valid range is -2000 to +2000 ps
     * @param ps how many picoseconds to delay the data relative to the clock
     */
    public void setGroupDelay(int ps) {
        setGroupDelay(-1, ps);
    }

    /**
     * Set the delay of a signal in the currently selected group.
     * The valid range is -2000 to +2000 ps. Reset value is 0.
     * @param signal which signal in the currently selected group, -1 for all signals
     * @param ps how many picoseconds to delay the data relative to the clock
     */
    public void setGroupDelay(int signal, int ps) {
        if (!isSelectedInstDataGen()) return;
        String s = signal < 0 ? "" : String.valueOf(signal);
        if (ps < -2000 || ps > 2000) {
            System.out.println("Valid range for group delay is -2000ps to 2000ps: not "+ps);
            return;
        }
        write(":SOUR:PULS:DEL"+s+" "+ps+" PS");
    }

    /**
     * Set the high voltage level for all signals in the currently selected group
     * Reset value is +1V. Range depends on termination and Low value setting.
     * The difference between high and low is constrained such that:
     * <pre>
     * Term    Min and Max Magnitude of Voltage Swing
     * Ground  0.5V <= (High - Low) <= 2.5V
     * ECL     0.5V <= (High - Low) <= 2.5V
     * PECL    0.5V <= (High - Low) <= 2.5V
     * OPEN    1.0V <= (High - Low) <= 5.0V
     * </pre>
     * Absolute limits are:
     * <pre>
     * Term    Limits
     * Ground  -1.5V to 3.0V
     * ECL     -1.5V to 1.0V
     * PECL     0V   to 4.5V
     * OPEN    -2.5V to 4.5V
     * </pre>
     * Note that the output state must be switched off to change this setting
     * @param voltage the voltage for a high value
     */
    public void setGroupHighVoltageLevel(double voltage) {
        setGroupHighVoltageLevel(-1, voltage);
    }

    /**
     * Set the high voltage level for a signal in the currently selected group
     * Reset value is +1V. Range depends on termination and Low value setting.
     * The difference between high and low is constrained such that:
     * <pre>
     * Term    Min and Max Magnitude of Voltage Swing
     * Ground  0.5V <= (High - Low) <= 2.5V
     * ECL     0.5V <= (High - Low) <= 2.5V
     * PECL    0.5V <= (High - Low) <= 2.5V
     * OPEN    1.0V <= (High - Low) <= 5.0V
     * </pre>
     * Absolute limits are:
     * <pre>
     * Term    Limits
     * Ground  -1.5V to 3.0V
     * ECL     -1.5V to 1.0V
     * PECL     0V   to 4.5V
     * OPEN    -2.5V to 4.5V
     * </pre>
     * Note that the output state must be switched off to change this setting
     * @param signal which signal in the currently selected group, -1 for all signals
     * @param voltage the voltage for a high value
     */
    public void setGroupHighVoltageLevel(int signal, double voltage) {
        if (!isSelectedInstDataGen()) return;
        String s = signal < 0 ? "" : String.valueOf(signal);
        write(":SOUR:VOLT:LEV:IMM:HIGH"+s+" "+voltage+" V");
    }

    /**
     * Set the low voltage level for all signals in the currently selected group
     * Reset value is -1V. Range depends on termination and High value setting.
     * The difference between high and low is constrained such that:
     * <pre>
     * Term    Min and Max Magnitude of Voltage Swing
     * Ground  0.5V <= (High - Low) <= 2.5V
     * ECL     0.5V <= (High - Low) <= 2.5V
     * PECL    0.5V <= (High - Low) <= 2.5V
     * OPEN    1.0V <= (High - Low) <= 5.0V
     * </pre>
     * Absolute limits are:
     * <pre>
     * Term    Limits
     * Ground  -1.5V to 3.0V
     * ECL     -1.5V to 1.0V
     * PECL     0V   to 4.5V
     * OPEN    -2.5V to 4.5V
     * </pre>
     * Note that the output state must be switched off to change this setting
     * @param voltage the voltage for a high value
     */
    public void setGroupLowVoltageLevel(double voltage) {
        setGroupLowVoltageLevel(-1, voltage);
    }

    /**
     * Set the low voltage level for a signal in the currently selected group
     * Reset value is -1V. Range depends on termination and High value setting.
     * The difference between high and low is constrained such that:
     * <pre>
     * Term    Min and Max Magnitude of Voltage Swing
     * Ground  0.5V <= (High - Low) <= 2.5V
     * ECL     0.5V <= (High - Low) <= 2.5V
     * PECL    0.5V <= (High - Low) <= 2.5V
     * OPEN    1.0V <= (High - Low) <= 5.0V
     * </pre>
     * Absolute limits are:
     * <pre>
     * Term    Limits
     * Ground  -1.5V to 3.0V
     * ECL     -1.5V to 1.0V
     * PECL     0V   to 4.5V
     * OPEN    -2.5V to 4.5V
     * </pre>
     * Note that the output state must be switched off to change this setting
     * @param signal which signal in the currently selected group, -1 for all signals
     * @param voltage the voltage for a high value
     */
    public void setGroupLowVoltageLevel(int signal, double voltage) {
        if (!isSelectedInstDataGen()) return;
        String s = signal < 0 ? "" : String.valueOf(signal);
        write(":SOUR:VOLT:LEV:IMM:LOW"+s+" "+voltage+" V");
    }

    //------------------------------------------------------------------------------

//    private void printResponse() {
//        String s = read(200);
//        System.out.println(s);
//    }

//    private boolean isSelectedInstSystem() {
//        if (selectedInst != HP80000.SYSTEM) {
//            System.out.println("Error, issuing SYSTEM related command when currently " +
//                    "selected instrument is not SYSTEM");
//            return false;
//        }
//        return true;
//    }

    private boolean isSelectedInstSystemClock() {
        if (selectedInst != HP80000.SYSTEMCLOCK) {
            System.out.println("Error, issuing SYSTEMCLOCK related command when currently " +
                    "selected instrument is not SYSTEMCLOCK");
            return false;
        }
        return true;
    }

    private boolean isSelectedInstDataGen() {
        if (selectedInst != HP80000.DATAGEN1GHZ && selectedInst != HP80000.DG1GHZ128K) {
            System.out.println("Error, issuing DATAGEN1GHZ/DG1GHZ128K related command when currently " +
                    "selected instrument is not DATAGEN1GHZ/DG1GHZ128K");
            return false;
        }
        return true;
    }

//    private void printErrorQueue() {
//        write(":SYST:ERR?");
//        String s = read(200);
//        System.out.println(s);
//    }

    // ------------------------------- Main ---------------------------------

    public static void main(String args[]) {
        // which gpib controller (ethernet-to-gpib box) the HP80000 is connected to
        Infrastructure.gpibControllers = new int[]{2};
        // create instrument based on assigned name in ibconf program on electron
        HP80000 datagen = new HP80000("HP80000");

        datagen.reset();
        datagen.selectInstrument(HP80000.DG1GHZ128K);
        datagen.deleteGroup("ALL");
        datagen.defineGroup("foo", "0(2:3)");
        datagen.printGroups();
        datagen.selectGroup("foo");
        datagen.setGroupPatternCycle(4, 8, 1);
        String [] data = new String [] { "000100110111",
                                         "100011001000" };
        datagen.printGroupData(0, 12, 2);
        datagen.setGroupData(data);
		datagen.deleteGroup("ALL");
		datagen.defineGroup("clk", "0(2)");
        datagen.printGroups();
        datagen.selectGroup("clk");
        datagen.setGroupPatternCycle(0, 16, -1);
		datagen.setGroupData(new String [] { "0000111111110000" });
        datagen.printGroupData(0, 15, 1);
        System.out.println("done");
    }
    
}
