/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: SamplerControl.java
 * Written by Tom O'Neill, Sun Microsystems.
 *
 * Copyright (c) 2005, Oracle and/or its affiliates. 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;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;

/**
 * Main API for sampler programming and calibration. The code assumes that a
 * node controls a sampler if and only if it satisfies the following conditions:
 * <ul>
 * <li>It is an instance of <code>SubchainNode</code> (e.g, it is a
 * <code>ChainNode</code>)</li>
 * <li>The node includes a <tt>pin</tt> attribute</li>
 * <li>The node contains one each of <tt>calibrate</tt>,<tt>enable</tt>,
 * and <tt>enable_f</tt>
 * <code>Subchain</code> nodes. (Other nodes are
 * allowed as well.)</li>
 * <li>The <tt>calibrate</tt>,<tt>enable</tt>, and <tt>enable_f</tt>
 * nodes each contain a single scan chain element.
 * </ul>
 * If the pin attribute cannot be set in the XML file, it may be set using
 * <code>ChainControl.setSubchainPin()</code>.
 */
public class SamplerControl extends Logger {

    /** Object containing scan chain model/APIs */
    ChainControl control;

    /**
     * Specifies whether chain control elements must be set <tt>HI</tt> to
     * enable the corresponding on-chip signal.
     */
    private final int polarity;

    /** Mapping from scan chain paths to pin names */
    public final Map map;

    /**
     * For each sampler control node, the value of the scan chain element
     * required to enable the control setting
     */
    private final String[] controlEnable = new String[CONTROL_NODES.length];

    /**
     * For each sampler control node, the value of the scan chain element
     * required to disable the control setting
     */
    private final String[] controlDisable = new String[CONTROL_NODES.length];

    /** Name of calibrate sampler control node */
    public final static String[] CONTROL_NODES = { "calibrate", "enable",
            "enable_f" };

    /** index of calibrate control node */
    public final static int IND_CALIBRATE = 0;

    public final static int IND_ENABLE = 1;

    public final static int IND_ENABLE_F = 2;

    /**
     * <code>polarity</code> value when calibrate, enable, enable_f scan chain
     * elements are set <tt>HI</tt> to set these signals true on chip.
     */
    public final static int POLARITY_NORMAL = 0;

    /**
     * <code>polarity</code> value when calibrate, enable, enable_f scan chain
     * elements are set <tt>LO</tt> to set these signals true on chip.
     */
    public final static int POLARITY_INVERTED = 1;

    /**
     * Scan chain value required to enable a sampler control signal, depends on
     * value of polarity.
     */
    private final static String[] CONTROL_ENABLE = { "1", "0" };

    /**
     * Scan chain value required to disable a sampler control signal, depends on
     * value of polarity.
     */
    private final static String[] CONTROL_DISABLE = { "0", "1" };

    /**
     * Constructor. Identifies samplers in <code>control</code> using the
     * conditions described at top. For each sampler, sets <code>inBits</code>
     * necessary to disable it. Does not shift any data, since the rest of the
     * chip state may not be specified.
     * <p>
     * Currently <code>polarity</code> value may be
     * <code>POLARITY_NORMAL</code> or <code>POLARITY_INVERTED</code>.
     * 
     * @param control
     *            Object containing scan chain model/APIs
     * @param polarity
     *            whether sampler control signals come from non-inverting scan
     *            chain output
     */
    public SamplerControl(ChainControl control, int polarity) {
        super();
        this.control = control;
        if (polarity != POLARITY_NORMAL && polarity != POLARITY_INVERTED) {
            Infrastructure.fatal("Bad polarity value " + polarity
                    + ", only POLARITY_NORMAL and POLARITY_INVERTED are "
                    + " supported");
        }
        this.polarity = polarity;
        this.map = new java.util.HashMap();

        // Fill controlEnable array according to polarity
        java.util.Arrays.fill(controlEnable, CONTROL_ENABLE[polarity]);
        java.util.Arrays.fill(controlDisable, CONTROL_DISABLE[polarity]);

        // Get all nodes in the experimental system
        String[] paths = control.getDescendents("");
        for (int ind = 0; ind < paths.length; ind++) {
            if (isSampler(paths[ind])) {
                map.put(paths[ind], control.getSubchainPin(paths[ind]));
                preclear(paths[ind]);
            }
        }
    }

    /**
     * Prints some information about the sampler object, including which
     * samplers are on each pin.
     */
    public String toString() {
        StringBuffer buffy = new StringBuffer("SamplerControl, control="
                + control);
        buffy.append("\n  Samplers on each pin:");

        // Convert Collection of values to TreeSet for unique + ordered
        java.util.Set pins = new java.util.TreeSet(map.values());

        // For each pin, add which samplers are on it
        for (java.util.Iterator iter = pins.iterator(); iter.hasNext();) {
            String pin = (String) iter.next();
            buffy.append("\n    " + pin + ": ");
            String[] samplers = getSamplersOnPin(pin);
            for (int ind = 0; ind < samplers.length; ind++) {
                buffy.append(" " + samplers[ind]);
            }
        }
        buffy.append("\n");
        return buffy.toString();
    }

    /**
     * Checks if path to sampler scan chain node has correct format and
     * identifies an actual sampler.
     * 
     * @param path
     *            Path to the scan chain node for the sampler
     */
    void checkPath(String path) {
        if (map.containsKey(path) == false) {
            Infrastructure.fatal("Path " + path + " is not a recognized "
                    + "sampler" + "\nSee SamplerControl javadoc for how to "
                    + "identify sampler nodes");
        }
    }

    /**
     * Disable the sampler at the specified <code>SubchainNode</code> by
     * setting the <tt>calibrate</tt>,<tt>enable</tt>, and
     * <tt>enable_f</tt> scan chain elements according to the sampler
     * polarity.
     * 
     * @param path
     *            Path to the scan chain node for the sampler
     */
    public void clear(String path) {
        preclear(path);
        logSet("SamplerControl.clear(): clearing " + path);
        control.shift(control.getParentChain(path), false, true);
    }

    /**
     * Set the <code>inBits</code> values to disable the sampler at the
     * specified <code>SubchainNode</code>, but do not shift the data into
     * the chip. The <tt>calibrate</tt>,<tt>enable</tt>, and
     * <tt>enable_f</tt> scan chain elements are set according to the sampler
     * polarity.
     * 
     * @param path
     *            Path to the subchain node for the sampler
     */
    private void preclear(String path) {
        checkPath(path);

        for (int ind = 0; ind < CONTROL_NODES.length; ind++) {
            control.setInBits(path + "." + CONTROL_NODES[ind],
                    controlDisable[ind]);
        }
    }

    /**
     * Enable the sampler at the specified <code>SubchainNode</code> by
     * setting the <tt>enable</tt> and <tt>enable_f</tt> scan chain elements
     * according to the sampler polarity. Disables the <tt>calibrate</tt> scan
     * chain element.
     * <p>
     * If <code>enable</code> or <code>enable_f</code> is <tt>true</tt>,
     * all samplers on the same output pin are disabled before setting the
     * requested sampler. This is to prevent interference with the measurement.
     * 
     * @param path
     *            Path to the scan chain node for the sampler
     * @param enable
     *            Whether to enable the standard version of the sampler
     * @param enable_f
     *            Whether to enable the source-follower version of the sampler
     */
    public void setEnables(String path, boolean enable, boolean enable_f) {
        checkPath(path);

        if (enable || enable_f) {
            clearSiblings(path);
        }

        setOne(path, false, enable, enable_f);
    }

    /**
     * Enable or disable calibration for the sampler at the specified
     * <code>SubchainNode</code> by setting the <tt>calibrate</tt> scan
     * chain element according to the sampler polarity.
     * 
     * @param path
     *            Path to the scan chain node for the sampler
     * @param calibrate
     *            Whether to configure the sampler for calibration
     */
    public void setCalibrate(String path, boolean calibrate) {
        checkPath(path);

        setControl(path, IND_CALIBRATE, calibrate);
        logSet("SamplerControl.setCalibrate(): setting " + path
                + " to calibrate");
        control.shift(control.getParentChain(path), false, true);
    }

    /**
     * Clears all samplers on same pin as specified sampler.
     * 
     * @param path
     *            Path to the scan chain node for the sampler
     */
    private void clearSiblings(String path) {
        String targetPin = (String) map.get(path);
        String[] paths = getSamplersOnPin(targetPin);
        for (int ind = 0; ind < paths.length; ind++) {
            clear(paths[ind]);
        }
    }

    /**
     * Calibrates the specified sampler, writing the current versus voltage to
     * the provided file. Assumes that <code>setEnables()</code> has been used
     * to set at least one of <code>enable</code> and <code>enable_f</code>.
     * 
     * @param fileName
     *            File to write the calibration data to
     * @param path
     *            Path to the scan chain node for the sampler
     * @param ivspec
     *            Object specifying the IV curve to measure
     * @throws IOException
     */
    public void calibrate(String fileName, String path, AmpsVsVolts ivspec)
            throws IOException {
        PrintWriter file = new PrintWriter(new FileWriter(fileName));
        file.println("# calibration (voltage, current) for sampler at path "
                + path);
        setCalibrate(path, true);
        ivspec.measure(file);
        setCalibrate(path, false);
        file.close();
    }

    /**
     * Returns <tt>true</tt> if the node at the specified path controls a
     * sampler. See comments at top for the necessary conditions.
     * 
     * @param path
     *            path name to potential sampler control
     * @return whether specified node controls a sampler
     */
    private boolean isSampler(String path) {
        TestNode node = (TestNode) control.findNode(path);
        if (node.getChildCount() < 3 || (node instanceof SubchainNode == false))
            return false;

        int[] numInstances = getNumInstances(node);

        boolean oneOfEach = true, anyPresent = false;
        for (int ind = 0; ind < CONTROL_NODES.length; ind++) {
            if (numInstances[ind] != 1)
                oneOfEach = false;
            if (numInstances[ind] > 0)
                anyPresent = true;
        }

        if (oneOfEach) {
            if (control.getSubchainPin(path).length() <= 0) {
                System.err.println("*** SamplerControl warning: node " + path
                        + "\nappears to be a sampler, but does not have"
                        + " a valid pin name.  Please set the "
                        + "\n'pin' attribute in the XML file or use"
                        + " ChainControl.setSubchainPin()");
                return false;
            }

            return true;
        }

        if (anyPresent) {
            System.err.println("*** SamplerControl warning: node " + path
                    + "\nmay be a sampler that does not contain "
                    + "required nodes."
                    + "\nSamplers must contain exactly one each of '"
                    + CONTROL_NODES[0] + "', '" + CONTROL_NODES[1] + "', and '"
                    + CONTROL_NODES[2] + "'");
            return false;
        }

        return false;
    }

    /**
     * Return path strings of all samplers on specified pin
     * 
     * @param targetPin
     *            name of sampler current output pin
     * @return path strings of all samplers on <code>targetPin</code>
     */
    public String[] getSamplersOnPin(String targetPin) {
        java.util.List list = new java.util.ArrayList();
        for (java.util.Iterator iter = map.keySet().iterator(); iter.hasNext();) {
            String path = (String) iter.next();
            String pin = (String) map.get(path);
            if (pin.equals(targetPin)) {
                list.add(path);
            }
        }
        String[] paths = new String[list.size()];
        for (int ind = 0; ind < paths.length; ind++) {
            paths[ind] = (String) list.get(ind);
        }
        return paths;
    }

    /**
     * Enable or disable the specified sampler control
     * 
     * @param path
     *            Path to sampler
     * @param ind
     *            Index of control in CONTROL_NODES array
     * @param enable
     *            Whether to enable the control
     */
    private void setControl(String path, int ind, boolean enable) {
        if (enable) {
            control.setInBits(path + "." + CONTROL_NODES[ind],
                    controlEnable[ind]);
        } else {
            control.setInBits(path + "." + CONTROL_NODES[ind],
                    controlDisable[ind]);
        }
    }

    /**
     * Enable the sampler at the specified <code>SubchainNode</code> by
     * setting the <tt>calibrate</tt>,<tt>enable</tt>, and
     * <tt>enable_f</tt> scan chain elements according to the sampler
     * polarity. Does not affect any other samplers.
     * 
     * @param path
     *            Path to the scan chain node for the sampler
     * @param calibrate
     *            Whether to configure the sampler for calibration
     * @param enable
     *            Whether to enable the standard version of the sampler
     * @param enable_f
     *            Whether to enable the source-follower version of the sampler
     */
    private void setOne(String path, boolean calibrate, boolean enable,
            boolean enable_f) {
        checkPath(path);
        if (calibrate && !enable && !enable_f) {
            System.out.println("WARNING: Odd setting for sampler at " + path
                    + ": calibrate true, but enable and enable_f false.");
        }

        setControl(path, IND_CALIBRATE, calibrate);
        setControl(path, IND_ENABLE, enable);
        setControl(path, IND_ENABLE_F, enable_f);

        logSet("SamplerControl.setOne(): setting " + path);
        control.shift(control.getParentChain(path), false, true);
    }

    /**
     * Count number of children that are named after each of the required nodes
     * 
     * @param node
     * @return number of children that have each of the required names
     */
    private int[] getNumInstances(TestNode node) {
        int numInstances[] = new int[CONTROL_NODES.length];
        for (int kidIndex = 0; kidIndex < node.getChildCount(); kidIndex++) {
            TestNode kid = (TestNode) node.getChildAt(kidIndex);
            String kidName = kid.getName();
            for (int ind = 0; ind < CONTROL_NODES.length; ind++) {
                if (kidName.equals(CONTROL_NODES[ind])) {
                    String kidPath = kid.getPathString(1);
                    int length = control.getLength(kidPath);
                    if (length == 1) {
                        numInstances[ind] += 1;
                    } else {
                        System.err.println("*** SamplerControl warning: node "
                                + kidPath + " has length " + length
                                + ", should have length 1");
                    }
                }
            }
        }
        return numInstances;
    }

    /** Unit test */
    public static void main(String[] args) {
        ChainControl control = new ChainControl("heater.xml");

        control.setSubchainPin("heater.SW_expC.transmit.sample_cT", "frog");
        control.setSubchainPin("heater.NW_expC.transmit.sample_cT", "frog");
        control.setSubchainPin("heater.SE_expC.transmit.sample_cT", "toad");
        control.setSubchainPin("heater.NE_expC.transmit.sample_cT", "bar");

        SamplerControl samplers = new SamplerControl(control, POLARITY_INVERTED);
    }

}
