/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: IRSIM.java
 *
 * Copyright (c) 2010, 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.irsim;

import com.sun.electric.api.irsim.IAnalyzer;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.text.TextUtils;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.lib.LibFile;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.extract.ExtractedPBucket;
import com.sun.electric.tool.extract.RCPBucket;
import com.sun.electric.tool.extract.TransistorPBucket;
import com.sun.electric.tool.io.FileType;
import com.sun.electric.tool.io.output.IRSIM.IRSIMPreferences;
import com.sun.electric.tool.simulation.BusSample;
import com.sun.electric.tool.simulation.DigitalSample;
import com.sun.electric.tool.simulation.Engine;
import com.sun.electric.tool.simulation.MutableSignal;
import com.sun.electric.tool.simulation.Signal;
import com.sun.electric.tool.simulation.SignalCollection;
import com.sun.electric.tool.simulation.SimulationTool;
import com.sun.electric.tool.simulation.Stimuli;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.waveform.Panel;
import com.sun.electric.tool.user.waveform.WaveSignal;
import com.sun.electric.tool.user.waveform.WaveformWindow;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import javax.swing.ProgressMonitorInputStream;
import javax.swing.SwingUtilities;

/**
 * Class for interfacing to the IRSIM simulator by reflection.
 */
public class IRSIM implements Engine, IAnalyzer.GUI {

    /** initial size of simulation window: 10ns */
    private static final double DEFIRSIMTIMERANGE = 10.0E-9f;
    private static boolean IRSIMChecked = false;
    private static IAnalyzer instance;

    /**
     * Method to tell whether the IRSIM simulator is available.
     * IRSIM is packaged separately because it is from Stanford University.
     * This method dynamically figures out whether the IRSIM module is present by using reflection.
     * @return true if the IRSIM simulator is available.
     */
    public static boolean hasIRSIM() {
        return getInstance() != null;
    }

    /**
     * Method to find the IRSIM module.
     * @return the IRSIM module (null if no IRSIM).
     */
    private static IAnalyzer getInstance() {
        if (instance == null && !IRSIMChecked) {
			try {
		    	Class<?> clz = Class.forName("com.sun.electric.plugins.irsim.IAnalyzerImpl");
		    	if (clz != null) instance = (IAnalyzer)clz.newInstance();
			} catch (Exception e) {}

            if (instance == null) {
                TextUtils.recordMissingComponent("IRSIM");
            }
            IRSIMChecked = true;
        }
        return instance;
    }

    /**
     * Method to run the IRSIM simulator on a given cell, context or file.
     * Uses reflection to find the IRSIM simulator (if it exists).
     * @param cell the Cell to simulate.
     * @param context the context to the cell to simulate.
     * @param fileName the name of the file with the netlist.  If this is null, simulate the cell.
     * If this is not null, ignore the cell and simulate the file.
     */
    public static void runIRSIM(Cell cell, VarContext context, String fileName, IRSIMPreferences ip, boolean doNow) {
        try {
            URL parameterURL = null;
            String parameterFile = ip.parameterFile.trim();
            if (parameterFile.length() > 0) {
                File pf = new File(parameterFile);
                if (pf != null && pf.exists()) {
                    parameterURL = TextUtils.makeURLToFile(parameterFile);
                } else {
                    parameterURL = LibFile.getLibFile(parameterFile);
                }
                if (parameterURL == null) {
                    System.out.println("Cannot find parameter file: " + parameterFile);
                }
            }
            IAnalyzer ianalyzer = getInstance();
            final IRSIM irsim = new IRSIM();
            final IAnalyzer.EngineIRSIM analyzer = ianalyzer.createEngine(irsim,
                    ip.steppingModel,
                    parameterURL,
                    ip.irDebug,
                    SimulationTool.isIRSIMShowsCommands(),
                    SimulationTool.isIRSIMDelayedX());
            irsim.a = analyzer;
            synchronized (analyzer) {
                // Load network
                if (cell != null) {
                    System.out.println("Loading netlist for " + cell.noLibDescribe() + "...");
                } else {
                    System.out.println("Loading netlist for file " + fileName + "...");
                }

                // Load network
                if (fileName == null) {
                    // generate the components directly
                    List<Object> components = com.sun.electric.tool.io.output.IRSIM.getIRSIMComponents(cell, context, ip);
                    Technology layoutTech = Schematics.getDefaultSchematicTechnology();
                    double lengthOff = Schematics.getDefaultSchematicTechnology().getGateLengthSubtraction() / layoutTech.getScale();
                    // load the circuit from memory
                    for (Object obj : components) {
                        ExtractedPBucket pb = (ExtractedPBucket) obj;

                        if (pb instanceof TransistorPBucket) {
                            TransistorPBucket tb = (TransistorPBucket) pb;
                            analyzer.putTransistor(tb.gateName, tb.sourceName, tb.drainName,
                                    tb.getTransistorLength(lengthOff), tb.getTransistorWidth(),
                                    tb.getActiveArea(), tb.getActivePerim(),
                                    tb.ni.getAnchorCenterX(), tb.ni.getAnchorCenterY(),
                                    tb.getType() == 'n');
                        } else if (pb instanceof RCPBucket) {
                            RCPBucket rcb = (RCPBucket) pb;
                            switch (rcb.getType()) {
                                case 'r':
                                    analyzer.putResistor(rcb.net1, rcb.net2, rcb.rcValue);
                                    break;
                                case 'C':
                                    analyzer.putCapacitor(rcb.net1, rcb.net2, rcb.rcValue);
                                    break;
                            }
                        }
                    }
                } else {
                    // get a pointer to to the file with the network (.sim file)
                    URL fileURL = TextUtils.makeURLToFile(fileName);
                    if (irsim.inputSim(fileURL)) {
                        return;
                    }
                }
                analyzer.finishNetwork();

                irsim.sd.setEngine(irsim);

                irsim.sigCollection = Stimuli.newSignalCollection(irsim.sd, "SIGNALS");
                irsim.sd.setSeparatorChar('/');
                analyzer.convertStimuli();
                irsim.sd.setCell(cell);

                // make a waveform window
                if (doNow) {
                    WaveformWindow.showSimulationDataInNewWindow(irsim.sd);
                    irsim.ww = irsim.sd.getWaveformWindow();
                    analyzer.init();
                } else {
                    SwingUtilities.invokeLater(new Runnable() {

                        public void run() {
                            WaveformWindow.showSimulationDataInNewWindow(irsim.sd);
                            irsim.ww = irsim.sd.getWaveformWindow();
                            analyzer.init();
                        }
                    });
                }
            }
        } catch (Exception e) {
            System.out.println("Unable to run the IRSIM simulator");
            e.printStackTrace(System.out);
        }
    }
    private IAnalyzer.EngineIRSIM a;
    private WaveformWindow ww;
    /** the SignalCollection being displayed */
    private SignalCollection sigCollection;
    private Collection<GuiSignalImpl> guiSigCollection = new ArrayList<GuiSignalImpl>();
    private final Stimuli sd = new Stimuli();

    // Engine
    /**
     * Returns FileType of vectors file.
     */
    public FileType getVectorsFileType() {
        return FileType.IRSIMVECTOR;
    }

    /**
     * Returns current Stimuli.
     */
    public Stimuli getStimuli() {
        return sd;
    }

    /**
     * Method to reload the circuit data.
     */
    public void refresh() {
    }

    /**
     * Method to update the simulation (because some stimuli have changed).
     */
    public void update() {
        a.playVectors();
    }

    /**
     * Method to set the currently-selected signal high at the current time.
     */
    public void setSignalHigh() {
        List<Signal<?>> signals = ww.getHighlightedNetworkNames();
        if (signals.isEmpty()) {
            Job.getUserInterface().showErrorMessage("Must select a signal before setting it High",
                    "No Signals Selected");
            return;
        }
        for (Signal<?> sig : signals) {
            String signalName = sig.getFullName().replace('.', '/');
            a.newContolPoint(signalName, ww.getMainXPositionCursor(), IAnalyzer.LogicState.LOGIC_1);
        }
        if (SimulationTool.isBuiltInResimulateEach()) {
            a.playVectors();
        }
    }

    /**
     * Method to set the currently-selected signal low at the current time.
     */
    public void setSignalLow() {
        List<Signal<?>> signals = ww.getHighlightedNetworkNames();
        if (signals.isEmpty()) {
            Job.getUserInterface().showErrorMessage("Must select a signal before setting it Low",
                    "No Signals Selected");
            return;
        }
        for (Signal<?> sig : signals) {
            String signalName = sig.getFullName().replace('.', '/');
            a.newContolPoint(signalName, ww.getMainXPositionCursor(), IAnalyzer.LogicState.LOGIC_0);
        }
        if (SimulationTool.isBuiltInResimulateEach()) {
            a.playVectors();
        }
    }

    /**
     * Method to set the currently-selected signal undefined at the current time.
     */
    public void setSignalX() {
        List<Signal<?>> signals = ww.getHighlightedNetworkNames();
        if (signals.isEmpty()) {
            Job.getUserInterface().showErrorMessage("Must select a signal before setting it Undefined",
                    "No Signals Selected");
            return;
        }
        for (Signal<?> sig : signals) {
            String signalName = sig.getFullName().replace('.', '/');
            a.newContolPoint(signalName, ww.getMainXPositionCursor(), IAnalyzer.LogicState.LOGIC_X);
        }
        if (SimulationTool.isBuiltInResimulateEach()) {
            a.playVectors();
        }
    }

    /**
     * Method to set the currently-selected signal to have a clock with a given period.
     */
    public void setClock(double period) {
        System.out.println("IRSIM CANNOT HANDLE CLOCKS YET");
    }

    /**
     * Method to show information about the currently-selected signal.
     */
    public void showSignalInfo() {
        List<Signal<?>> signals = ww.getHighlightedNetworkNames();
        if (signals.isEmpty()) {
            Job.getUserInterface().showErrorMessage("Must select a signal before displaying it",
                    "No Signals Selected");
            return;
        }
        for (Signal<?> sig : signals) {
            a.showSignalInfo(findGuiSignal(sig));
        }
    }

    /**
     * Method to remove all stimuli from the currently-selected signal.
     */
    public void removeStimuliFromSignal() {
        List<Signal<?>> signals = ww.getHighlightedNetworkNames();
        if (signals.size() != 1) {
            Job.getUserInterface().showErrorMessage("Must select a single signal on which to clear stimuli",
                    "No Signals Selected");
            return;
        }
        Signal<?> sig = signals.get(0);
        sig.clearControlPoints();
        a.clearControlPoints(findGuiSignal(sig));
        if (SimulationTool.isBuiltInResimulateEach()) {
            a.playVectors();
        }
    }

    /**
     * Method to remove the selected stimuli.
     * @return true if stimuli were deleted.
     */
    public boolean removeSelectedStimuli() {
        boolean found = false;
        for (Iterator<Panel> it = ww.getPanels(); it.hasNext();) {
            Panel wp = it.next();
            for (WaveSignal ws : wp.getSignals()) {
                if (!ws.isHighlighted()) {
                    continue;
                }
                double[] selectedCPs = ws.getSelectedControlPoints();
                if (selectedCPs == null) {
                    continue;
                }
                for (int i = 0; i < selectedCPs.length; i++) {
                    if (a.clearControlPoint(findGuiSignal(ws.getSignal()), selectedCPs[i])) {
                        found = true;
                    }
                }
            }
        }
        if (!found) {
            System.out.println("There are no selected control points to remove");
            return false;
        }

        // resimulate if requested
        if (SimulationTool.isBuiltInResimulateEach()) {
            a.playVectors();
        }
        return true;
    }

    /**
     * Method to remove all stimuli from the simulation.
     */
    public void removeAllStimuli() {
        for (Iterator<Panel> it = ww.getPanels(); it.hasNext();) {
            Panel wp = it.next();
            for (WaveSignal ws : wp.getSignals()) {
                ws.getSignal().clearControlPoints();
            }
        }
        a.clearAllVectors();

        if (SimulationTool.isBuiltInResimulateEach()) {
            a.playVectors();
        }
    }

    /**
     * Method to save the current stimuli information to disk.
     * @param stimuliFile file to save stimuli information
     */
    public void saveStimuli(File stimuliFile) throws IOException {
        a.saveStimuli(stimuliFile);
    }

    /**
     * Method to restore the current stimuli information from URL.
     * @param stimuliURL path to stimuli information
     */
    public void restoreStimuli(URL stimuliURL) throws IOException {
        if (stimuliURL == null) {
            throw new NullPointerException();
        }
        Reader reader = new InputStreamReader(stimuliURL.openStream());
        try {

            // remove all vectors
            a.clearAllVectors();
            for (Iterator<Panel> it = ww.getPanels(); it.hasNext();) {
                Panel wp = it.next();
                for (WaveSignal ws : wp.getSignals()) {
                    ws.getSignal().clearControlPoints();
                }
            }
            a.restoreStimuli(reader);
        } finally {
            reader.close();
        }
    }

    /**
     * Method to return the minimum amount of time to show in the waveform window.
     * @return the minimum amount of time to show in the waveform window.
     */
    public double getMinTimeRange() {
        return DEFIRSIMTIMERANGE;
    }

    // IAnalyzer.GUI
    public IAnalyzer.GuiSignal makeSignal(String name) {
        // make a signal for it
        int slashPos = name.lastIndexOf('/');
        MutableSignal<DigitalSample> sig =
                slashPos >= 0
                ? DigitalSample.createSignal(sigCollection, sd, name.substring(slashPos + 1), name.substring(0, slashPos))
                : DigitalSample.createSignal(sigCollection, sd, name, null);
        GuiSignalImpl sigImpl = new GuiSignalImpl(sig, null);
        guiSigCollection.add(sigImpl);
        return sigImpl;
    }

    public void createBus(String busName, IAnalyzer.GuiSignal... subsigs) {
        Signal<DigitalSample>[] sigArray = new Signal[subsigs.length];
        for (int i = 0; i < subsigs.length; i++) {
            IAnalyzer.GuiSignal guiSig = subsigs[i];
            Signal<DigitalSample> sigImpl = (Signal<DigitalSample>) ((GuiSignalImpl) guiSig).impl;
            sigArray[i] = sigImpl;
        }
        Signal<BusSample<DigitalSample>> busSignal = BusSample.createSignal(sigCollection, sd, busName, null, true, sigArray);
        GuiSignalImpl sigImpl = new GuiSignalImpl(busSignal, subsigs);
        guiSigCollection.add(sigImpl);
    }

    public void makeBusSignals(List<IAnalyzer.GuiSignal> sigList) {
        List<Signal<?>> sigListImpl = new ArrayList<Signal<?>>();
        HashMap<Signal<?>, GuiSignalImpl> sigToGui = new HashMap<Signal<?>, GuiSignalImpl>();
        for (IAnalyzer.GuiSignal sig : sigList) {
            Signal<DigitalSample> sigImpl = (Signal<DigitalSample>) ((GuiSignalImpl) sig).impl;
            sigListImpl.add(sigImpl);
            GuiSignalImpl old = sigToGui.put(sigImpl, (GuiSignalImpl) sig);
            if (old != null) {
                throw new IllegalArgumentException("Duplicate " + old);
            }
        }
        sd.makeBusSignals(sigListImpl, sigCollection);
        for (Signal<?> sig : sigCollection.getSignals()) {
            Signal<?>[] busMembers = sig.getBusMembers();
            if (busMembers == null) {
                continue;
            }
            IAnalyzer.GuiSignal[] memberArray = new IAnalyzer.GuiSignal[busMembers.length];
            for (int i = 0; i < busMembers.length; i++) {
                GuiSignalImpl ss = sigToGui.get(busMembers[i]);
                if (ss == null) {
                    throw new IllegalArgumentException();
                }
                memberArray[i] = ss;
            }
            guiSigCollection.add(new GuiSignalImpl(sig, memberArray));
        }
    }

    public Collection<IAnalyzer.GuiSignal> getSignals() {
        return Collections.<IAnalyzer.GuiSignal>unmodifiableCollection(guiSigCollection);
    }

    public void setMainXPositionCursor(double curTime) {
        if (SimulationTool.isBuiltInAutoAdvance()) {
            ww.setMainXPositionCursor(curTime + 10.0 / 1000000000.0);
        }
    }

    public void openPanel(Collection<IAnalyzer.GuiSignal> sigs) {
        for (IAnalyzer.GuiSignal guiSig : sigs) {
            Signal<?> sig = ((GuiSignalImpl) guiSig).impl;
            int height = User.getWaveformDigitalPanelHeight();
            Panel wp = new Panel(ww, height);
            wp.makeSelectedPanel(-1, -1);
            new WaveSignal(wp, sig);
        }
    }

    public void closePanels() {
        ww.clearHighlighting();
        List<Panel> allPanels = new ArrayList<Panel>();
        for (Iterator<Panel> it = ww.getPanels(); it.hasNext();) {
            allPanels.add(it.next());
        }
        for (Panel wp : allPanels) {
            wp.closePanel();
        }
    }

    public double getMaxPanelTime() {
        double maxPanelTime = Double.NEGATIVE_INFINITY;
        Iterator<Panel> it = ww.getPanels();
        if (it.hasNext()) {
            Panel wp = it.next();
            maxPanelTime = Math.max(maxPanelTime, wp.getMaxXAxis());
        }
        return maxPanelTime;
    }

    public void repaint() {
        ww.repaint();
    }

    /**
     * Returns canonical char for ignore-case comparison.
     * This is the same as Character.toLowerCase().
     * @param ch given char.
     * @return canonical char for the given char.
     */
    public char canonicChar(char ch) {
        return TextUtils.canonicChar(ch);
    }

    /**
     * Returns canonical string for ignore-case comparison.
     * FORALL String s1, s2: s1.equalsIgnoreCase(s2) == canonicString(s1).equals(canonicString(s2)
     * FORALL String s: canonicString(canonicString(s)).equals(canonicString(s))
     * @param s given String
     * @return canonical String
     * Simple "toLowerCase" is not sufficient.
     * For example ("\u0131").equalsIgnoreCase("i") , but Character.toLowerCase('\u0131') == '\u0131' .
     */
    public String canonicString(String s) {
        return TextUtils.canonicString(s);
    }

    /**
     * Method to parse the floating-point number in a string.
     * There is one reason to use this method instead of Double.parseDouble:
     * this method does not throw an exception if the number is invalid (or blank).
     * @param text the string with a number in it.
     * @return the numeric value.
     */
    public double atof(String text) {
        return TextUtils.atof(text);
    }

    /**
     * Method to parse the number in a string.
     * <P>
     * There are many reasons to use this method instead of Integer.parseInt...
     * <UL>
     * <LI>This method can handle any radix.
     *     If the number begins with "0", presume base 8.
     *     If the number begins with "0b", presume base 2.
     *     If the number begins with "0x", presume base 16.
     *     Otherwise presume base 10.
     * <LI>This method can handle numbers that affect the sign bit.
     *     If you give 0xFFFFFFFF to Integer.parseInt, you get a numberFormatPostFix exception.
     *     This method properly returns -1.
     * <LI>This method does not require that the entire string be part of the number.
     *     If there is extra text after the end, Integer.parseInt fails (for example "123xx").
     * <LI>This method does not throw an exception if the number is invalid (or blank).
     * </UL>
     * @param s the string with a number in it.
     * @return the numeric value.
     */
    public int atoi(String s) {
        return TextUtils.atoi(s);
    }

    /**
     * Method to convert a double to a string.
     * If the double has no precision past the decimal, none will be shown.
     * @param v the double value to format.
     * @return the string representation of the number.
     */
    public String formatDouble(double v) {
        return TextUtils.formatDouble(v);
    }

    /**
     * Load a .sim file into memory.
     *
     * A .sim file consists of a series of lines, each of which begins with a key letter.
     * The key letter beginning a line determines how the remainder of the line is interpreted.
     * The following are the list of key letters understood.
     *
     *   | units: s tech: tech format: MIT|LBL|SU
     *     If present, this must be the first line in the .sim file.
     *     It identifies the technology of this circuit as tech and gives a scale factor for units of linear dimension as s.
     *     All linear dimensions appearing in the .sim file are multiplied by s to give centimicrons.
     *     The format field signifies the sim variant. Electric only recognizes SU format.
     *   type g s d l w x y g=gattrs s=sattrs d=dattrs
     *     Defines a transistor of type type. Currently, type may be e or d for NMOS, or p or n for CMOS.
     *     The name of the node to which the gate, source, and drain of the transistor are connected are given by g, s, and d respectively.
     *     The length and width of the transistor are l and w. The next two tokens, x and y, are optional.
     *     If present, they give the location of a point inside the gate region of the transistor.
     *     The last three tokens are the attribute lists for the transistor gate, source, and drain.
     *     If no attributes are present for a particular terminal, the corresponding attribute list may be absent
     *     (i.e, there may be no g= field at all).
     *     The attribute lists gattrs, etc. are comma-separated lists of labels.
     *     The label names should not include any spaces, although some tools can accept label names with
     *     spaces if they are enclosed in double quotes. In version 6.4.5 and later the default format
     *     produced by ext2sim is SU. In this format the attribute of the gate starting with S_ is the substrate node of the fet.
     *     The attributes of the gate, and source and substrate starting with A_, P_ are the area and perimeter
     *     (summed for that node only once) of the source and drain respectively. This addition to the format is backwards compatible.
     *   C n1 n2 cap
     *     Defines a capacitor between nodes n1 and n2. The value of the capacitor is cap femtofarads.
     *     NOTE: since many analysis tools compute transistor gate capacitance themselves from the
     *     transistor's area and perimeter, the capacitance between a node and substrate (GND!)
     *     normally does not include the capacitance from transistor gates connected to that node.
     *     If the .sim file was produced by ext2sim(1), check the technology file that was used to
     *     produce the original .ext files to see whether transistor gate capacitance is included or excluded;
     *     see "Magic Maintainer's Manual 2 - The Technology File for details.
     *   R node res
     *     Defines the lumped resistance of node node to be res ohms.
     *   r node1 node2 res
     *     Defines an explicit resistor between nodes node1 and node2 of resistance res ohms.
     *   N node darea dperim parea pperim marea mperim
     *     As an alternative to computed capacitances, some tools expect the total perimeter and area
     *     of the polysilicon, diffusion, and metal in each node to be reported in the .sim file.
     *     The N construct associates diffusion area darea (in square centimicrons) and diffusion
     *     perimeter dperim (in centimicrons) with node node, polysilicon area parea and perimeter pperim,
     *     and metal area marea and perimeter mperim. This construct is technology dependent and obsolete.
     *   = node1 node2
     *     Each node in a .sim file is named implicitly by having it appear in a transistor definition.
     *     All node names appearing in a .sim file are assumed to be distinct.
     *     Some tools, such as esim(1), recognize aliases for node names.
     *     The = construct allows the name node2 to be defined as an alias for the name node1.
     *     Aliases defined by means of this construct may not appear anywhere else in the .sim file.
     */
    private boolean inputSim(URL simFileURL) {
        // read the file
        String fileName = simFileURL.getFile();
        try {
            URLConnection urlCon = simFileURL.openConnection();
//			String contentLength = urlCon.getHeaderField("content-length");
//			long fileLength = -1;
//			try {
//				fileLength = Long.parseLong(contentLength);
//			} catch (Exception e) {}
//			long readSoFar = 0;
            InputStream inputStream = urlCon.getInputStream();
            InputStreamReader is = new InputStreamReader(new ProgressMonitorInputStream(null, "import " + fileName, inputStream));
            int numErrors = a.inputSim(is, fileName);
            inputStream.close();
            System.out.println("Loaded circuit, lambda=" + a.getLambda() + "u");
            return numErrors > 0;
        } catch (IOException e) {
            System.out.println("Error reading file");
            return false;
        }
    }

    private IAnalyzer.GuiSignal findGuiSignal(Signal<?> sig) {
        for (GuiSignalImpl guiSignal : guiSigCollection) {
            if (guiSignal.impl == sig) {
                return guiSignal;
            }
        }
        return null;
    }

    static class GuiSignalImpl implements IAnalyzer.GuiSignal {

        final Signal<?> impl;
        private IAnalyzer.GuiSignal[] busMembers;

        private GuiSignalImpl(Signal<?> impl, IAnalyzer.GuiSignal[] busMembers) {
            this.impl = impl;
            this.busMembers = busMembers;
        }

        public void updateHistory(double[] time, IAnalyzer.LogicState[] value) {
        }

        public String getFullName() {
            return impl.getFullName();
        }

        public String getSignalName() {
            return impl.getSignalName();
        }

        public IAnalyzer.GuiSignal[] getBusMembers() {
            return busMembers;
        }

        public void addControlPoint(double time) {
            impl.addControlPoint(time);
        }

        public void removeControlPoint(double time) {
            impl.removeControlPoint(time);
        }

        public void addSample(double t, IAnalyzer.LogicState v) {
            DigitalSample.Value value;
            switch (v) {
                case LOGIC_1:
                    value = DigitalSample.Value.HIGH;
                    break;
                case LOGIC_0:
                    value = DigitalSample.Value.LOW;
                    break;
                case LOGIC_X:
                    value = DigitalSample.Value.X;
                    break;
                default:
                    throw new AssertionError();
            }
            MutableSignal<DigitalSample> mutable = (MutableSignal<DigitalSample>) impl;
            if (mutable.getSample(t) == null) {
                mutable.addSample(t, DigitalSample.getSample(value, DigitalSample.Strength.LARGE_CAPACITANCE));
            }

        }
    }
}
