/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Name.java
 * Written by Jonathan Gainsley.
 *
 * Copyright (c) 2005, 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;

import java.util.*;

public class NanosimBERT implements BERT {

    // The Set of Events from all modules to issue to the Nanosim
    // simulator when the BERT is run().
    private Set events;

    // The Nanosim simulator
    private NanosimModel model;

    // all modules in this BERT
    private List modules;

    /**
     * Create a new NanosimBERT.
     * @param model the nanosim model that this BERT will use
     */
    public NanosimBERT(NanosimModel model) {
        this.model = model;
        events = new TreeSet();
        modules = new ArrayList();
    }

    public DataModule createDataModule() {
        DataModule mod = new NanosimDataModule();
        modules.add(mod);
        return mod;
    }

    /**
     * Run the BERT and its configured modules for the specified
     * time in nanoseconds. This is a replacement for doing
     * start, wait(nanoseconds), stop.
     * @param nanoseconds
     */
    public void run(double nanoseconds) {
        // current nanosim simulation time
        double nsStartTime = model.getTimeNS();

        // get all events from all modules
        events = getAllEvents(nsStartTime, nsStartTime+nanoseconds);

        // run all events in order until stop time
        double nsCurrentTime = nsStartTime;
        for (Iterator it = events.iterator(); it.hasNext(); ) {
            Event e = (Event)it.next();
            if (e.eventTimeNS.doubleValue() <= nsCurrentTime) {
                // issue event
                e.doIt(model);
            } else {
                // advance current time, then do it
                double waitTime = e.eventTimeNS.doubleValue() - nsCurrentTime;
                model.waitNS(waitTime);
                nsCurrentTime = model.getTimeNS();
                e.doIt(model);
            }
        }
        // if any analyzers, print out captured data for reference
        for (Iterator it = modules.iterator(); it.hasNext(); ) {
            NanosimDataModule module = (NanosimDataModule)it.next();
            for (Iterator it2 = module.getTerminals(); it2.hasNext(); ) {
                NanosimTerminal terminal = (NanosimTerminal)it2.next();
                if (!terminal.isEnabled()) continue;
                if (terminal instanceof NanosimDataAnalyzer) {
                    NanosimDataAnalyzer ana = (NanosimDataAnalyzer)terminal;
                    ana.printCapturedData();
                }
            }
        }
    }

    /**
     * Same as {@link #run(double)}, but in minutes for
     * very long runs.
     * @param minutes number of minutes to run
     */
    public void runMinutes(double minutes) {
        System.out.println("The Nanosim BERT does not support run(minutes) because it would never finish.");
    }

    private Set getAllEvents(double nsStartTime, double nsEndTime) {
        Set set = new TreeSet();
        for (Iterator it = modules.iterator(); it.hasNext(); ) {
            NanosimDataModule module = (NanosimDataModule)it.next();
            for (Iterator it2 = module.getTerminals(); it2.hasNext(); ) {
                NanosimTerminal terminal = (NanosimTerminal)it2.next();
                if (!terminal.isEnabled()) continue;
                if (terminal instanceof NanosimDataAnalyzer) {
                    NanosimDataAnalyzer ana = (NanosimDataAnalyzer)terminal;
                    ana.clearCapturedData();
                }
                Set modevents = terminal.generateEvents(nsStartTime, nsEndTime);
                set.addAll(modevents);
            }
        }
        return set;
    }

    // ======================== Support Classes ==========================

    public static class NanosimDataModule implements DataModule {

        // period in nanoseconds (1/freq)
        private double period;
        private List terminals;

        public NanosimDataModule() {
            period = 1;
            terminals = new ArrayList();
        }

        public DataGenerator createDataGenerator() {
            NanosimDataGen gen = new NanosimDataGen(this);
            terminals.add(gen);
            return gen;
        }

        public DataAnalyzer createDataAnalyzer() {
            NanosimDataAnalyzer ana = new NanosimDataAnalyzer(this);
            terminals.add(ana);
            return ana;
        }

        public void setFrequency(double freqKHz) {
            period = 1/freqKHz * 1000000;
        }

        public double getFrequency() {
            return 1/period * 1000000;
        }

        /** Return the period in nanoseconds */
        public double getPeriod() {
            return period;
        }

        /**
         * Return iterator over Terminal objects
         * that are part of this module
         * @return Iterator over Terminals.
         */
        protected Iterator getTerminals() {
            return terminals.iterator();
        }
    }

    public static abstract class NanosimTerminal implements Terminal {

        private NanosimDataModule parent;
        private boolean enabled;

        protected NanosimTerminal(NanosimDataModule parent) {
            this.parent = parent;
            enabled = true;
        }

        /**
         * Return a Set of {@link NanosimBERT.Event} objects
         * that characterize the behavior of the Module
         * when the BERT runs (BERT starts at time 0).
         * @param stopTimeNS the time to the BERT will stop,
         * with the BERT start time being time zero.
         * @return a Set of Events
         */
        public abstract Set generateEvents(double startTimeNS, double stopTimeNS);

        /**
         * Get the parent Module
         * @return the parent Module
         */
        public DataModule getParentModule() { return parent; }

        public void setEnabled(boolean enable) {
            this.enabled = enable;
        }

        public boolean isEnabled() { return enabled; }
    }


    /**
     * An Event to send to the Nanosim Simulator,
     * the same as the BERT would transition a signal
     * and send that to the real chip. Nanosim modules
     * generate such events.
     */
    public static class Event implements Comparable {
        private final Double eventTimeNS;
        private final int type;
        private final String node;
        private final double voltage;
        private final int state;
        private final NanosimDataAnalyzer analyzer;

        // Note that order is defined by type values, set should be last,
        // because a set and a get to the same node at the same time will return the old value,
        // not the new 'set' value
        private static final int TYPE_CAPTURENODE = 1;
        private static final int TYPE_CAPTUREANDCHECKNODE = 2;
        private static final int TYPE_SETNODE = 3;

        /**
         * Create a new Event to set the node to the value
         * at the specified time
         * @param eventTimeNS the time the event will occur
         * @param node the node to set
         * @param voltage the voltage to set the node to
         * @return the Event
         */
        protected static Event createSetNodeEvent(double eventTimeNS, String node,
                                                  double voltage) {
            // args state, analyzer are not used
            return new Event(eventTimeNS, TYPE_SETNODE, node, 0, voltage, null);
        }

        /**
         * Create a new Event to capture the node's value at the specified time.
         * @param eventTimeNS the time
         * @param node which node
         * @param analyzer the analyzer to report the capture to
         * @return the Event
         */
        protected static Event createCaptureNodeEvent(double eventTimeNS, String node,
                                                      NanosimDataAnalyzer analyzer) {
            // args state, voltage are not used
            return new Event(eventTimeNS, TYPE_CAPTURENODE, node, 0, 0, analyzer);
        }
        
        /**
         * Create a new Event to check the node for the given
         * value at the specified time. This also captures the data in the given analyzer.
         * @param eventTimeNS
         * @param node
         * @param expectedState
         * @param analyzer the analyzer to report the capture to
         * @return the Event
         */
        protected static Event createCaptureAndCheckNodeEvent(double eventTimeNS, String node,
                                                    int expectedState, NanosimDataAnalyzer analyzer) {
            // arg voltage is not used
            return new Event(eventTimeNS, TYPE_CAPTUREANDCHECKNODE, node, expectedState, 0, analyzer);
        }

        private Event(double eventTimeNS, int type, String node, int state, double voltage,
                      NanosimDataAnalyzer capturer) {
            this.eventTimeNS = Double.valueOf(eventTimeNS);
            this.type = type;
            this.node = node;
            this.state = state;
            this.voltage = voltage;
            this.analyzer = capturer;
        }

        private void doIt(NanosimModel model) {
            switch(type) {
                case TYPE_SETNODE: {
                    model.setNodeVoltage(node, voltage);
                    return;
                }
                case TYPE_CAPTURENODE: {
                    captureNode(model);
                    return;
                }
                case TYPE_CAPTUREANDCHECKNODE: {
                    int curState = captureNode(model);
                    if (curState != state) {
                        System.out.println("Check Node from BERT Data Analyzer on pin '"+
                                analyzer.getPinName()+"' failed @ sim time="+eventTimeNS+
                                "ns, expected "+state+" but was "+curState);
                        analyzer.logError();
                    }
                    return;
                }
            }
        }

        private int captureNode(NanosimModel model) {
            double curState = model.getNodeVoltage(node);
            int state = analyzer.voltageToState(curState);
            if (state == -1) {
                System.out.println("Error: analyzer captured node on pin "+analyzer.getPinName()+
                        " of voltage "+curState+" at time "+eventTimeNS+"ns, which is not a valid low or high value.");
                state = 0;
            }
            boolean b = state == 1 ? true : false;
            analyzer.captureBit(b);
            return state;
        }

        public int compareTo(Object o) {
            // check time first
            Event otherEvent = (Event)o;
            int compare = eventTimeNS.compareTo(otherEvent.eventTimeNS);
            if (compare == 0) {
                // if times are equal, check event type
                Integer me = Integer.valueOf(type);
                Integer other = Integer.valueOf(otherEvent.type);
                compare = me.compareTo(other);
                if (compare == 0) {
                    // if time and event type are equal, check pin name
                    return node.compareTo(otherEvent.node);
                }
                return compare;
            }
            return compare;
        }

        public String toString() {
            StringBuffer buf = new StringBuffer();
            buf.append("Event @ ");
            buf.append(eventTimeNS+"ns: ");
            switch(type) {
                case TYPE_SETNODE: {
                    buf.append("set node "+node+" to "+state);
                    break;
                }
                case TYPE_CAPTURENODE: {
                    buf.append("capture node "+node);
                    break;
                }
                case TYPE_CAPTUREANDCHECKNODE: {
                    buf.append("capture node "+node+", check it is "+state);
                    break;
                }
            }
            return buf.toString();
        }

        public boolean equals(Object o) {
            if (eventTimeNS.equals(o))
                return true;
            return false;
        }
    }

    /** Unit Test */
    public static void main(String args[]) {
        NanosimModel model = new NanosimModel();
        BERT bert = new NanosimBERT(model);

        DataModule module = bert.createDataModule();
        module.setFrequency(1000000);

        DataGenerator gen = module.createDataGenerator();
        gen.setDelay(0.0);
        gen.addPinName("testpin");
        gen.addPinName("testpin2");
        gen.setPattern(new BitVector("1011101", ""), new BitVector("110100100", ""));

        DataAnalyzer ana = module.createDataAnalyzer();
        ana.setDelay(0, 0.1);
        ana.setPinName("testpinOut");
        ana.measureBER(gen);

        double time = 30;
        System.out.println("Events, time "+time+"ns:");
        printEvents(((NanosimBERT)bert).getAllEvents(0, time));

        gen.setDelay(0.5);
        ana.setDelay(1, 0.5);

        System.out.println("-----------------------------------");
        System.out.println("Events, time "+time+"ns: with delay 0.5 gen, 1.5 ana");
        printEvents(((NanosimBERT)bert).getAllEvents(0, time));
    }

    static void printEvents(Set set) {
        for (Iterator it = set.iterator(); it.hasNext(); ) {
            NanosimBERT.Event e = (NanosimBERT.Event)it.next();
            System.out.println(e);
        }
    }
}
