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

import com.sun.electric.database.geometry.btree.CachingPageStorage;
import com.sun.electric.database.geometry.btree.CachingPageStorageWrapper;
import com.sun.electric.database.geometry.btree.FilePageStorage;
import com.sun.electric.database.geometry.btree.MemoryPageStorage;
import com.sun.electric.database.geometry.btree.OverflowPageStorage;
import com.sun.electric.database.geometry.btree.PageStorage;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.tool.user.waveform.WaveformWindow;
import com.sun.electric.util.TextUtils;

import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 *  This class represents a set of simulation *inputs* -- that is,
 *  "force" and "release" events -- in contrast to Signal, which
 *  represents simulation *outputs*.
 */
public class Stimuli
{
	// logic levels and signal strengths for digital signals
	public static final int LOGIC         =  03;
	public static final int LOGIC_LOW     =   0;
	public static final int LOGIC_X       =   1;
	public static final int LOGIC_HIGH    =   2;
	public static final int LOGIC_Z       =   3;
	public static final int STRENGTH      = 014;
	public static final int OFF_STRENGTH  =   0;
	public static final int NODE_STRENGTH =  04;
	public static final int GATE_STRENGTH = 010;
	public static final int VDD_STRENGTH  = 014;

	/** the WaveformWindow associated with this Stimuli */		private WaveformWindow ww;
	/** the simulation engine associated with this Stimuli */	private Engine engine;
	/** the cell attached to this Stimuli information */		private Cell cell;
	/** the disk file associated with this Stimuli */			private URL fileURL;
	/** the separator character that breaks names */			private char separatorChar;
	/** the SignalCollections in this Stimuli */				private Map<String,SignalCollection> scMap;
	/** the list of SignalCollections in this Stimuli */		private List<SignalCollection> scList;
	/** control points when signals are selected */				private Map<Signal<?>,Double[]> controlPointMap;
    /** Cached version of net delimiter */                      private String delim;
	/** for storing stimuli in a disk cache */					private CachingPageStorage ps;

    /**
	 * Constructor to build a new Simulation Data object.
	 */
	public Stimuli()
	{
		separatorChar = '.';
		scMap = new HashMap<String,SignalCollection>();
		scList = new ArrayList<SignalCollection>();
		controlPointMap = new HashMap<Signal<?>,Double[]>();
		delim = " ";

		// build disk cache
		try
		{
			long highWaterMarkInBytes = 50 * 1024 * 1024;
			PageStorage fps = FilePageStorage.create();
			PageStorage ops = new OverflowPageStorage(new MemoryPageStorage(fps.getPageSize()), fps, highWaterMarkInBytes);
			ps = new CachingPageStorageWrapper(ops, 16 * 1024, false);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public CachingPageStorage getPageStorage() { return ps; }

	/**
	 * Free allocated resources before closing.
	 */
	public void finished()
	{
		controlPointMap.clear();
		scMap.clear();
		scList.clear();
//		ps.close();
//		ps = null;
		ww = null;
	}

	public void addSignalCollection(SignalCollection an)
	{
		scMap.put(an.getName(), an);
		scList.add(an);
	}

	/**
	 * Method to find a SignalCollection with a given name.
	 * @param title the stimulus name being queried.
	 * @return the SignalCollection with that name (null if not found).
	 */
	public SignalCollection findSignalCollection(String title)
	{
		SignalCollection an = scMap.get(title);
		return an;
	}

    public void setNetDelimiter(String d) { delim = d; }

    public String getNetDelimiter() { return delim; }

	public Iterator<SignalCollection> getSignalCollections() { return scList.iterator(); }

	/**
	 * Method to set the Cell associated with this simulation data.
	 * The associated Cell is the top-level cell in the hierarchy,
	 * and is usually the Cell that was used to generate the simulation input deck.
	 * @param cell the Cell associated with this simulation data.
	 */
	public void setCell(Cell cell) { this.cell = cell; }

	/**
	 * Method to return the Cell associated with this simulation data.
	 * The associated Cell is the top-level cell in the hierarchy,
	 * and is usually the Cell that was used to generate the simulation input deck.
	 * @return the Cell associated with this simulation data.
	 */
	public Cell getCell() { return cell; }

	/**
	 * Method to set the simulation Engine associated with this simulation data.
	 * This is only for data associated with built-in simulators (ALS and IRSIM).
	 * @param engine the simulation Engine associated with this simulation data.
	 */
	public void setEngine(Engine engine) { this.engine = engine; }

	/**
	 * Method to return the simulation Engine associated with this simulation data.
	 * This is only for data associated with built-in simulators (ALS and IRSIM).
	 * @return the simulation Engine associated with this simulation data.
	 */
	public Engine getEngine() { return engine; }

	public void setWaveformWindow(WaveformWindow ww) { this.ww = ww; }

	/**
	 * Method to return the separator character for names in this simulation.
	 * The separator character separates levels of hierarchy.  It is usually a "."
	 * @return the separator character for names in this simulation.
	 */
	public char getSeparatorChar() { return separatorChar; }

	/**
	 * Method to set the separator character for names in this simulation.
	 * The separator character separates levels of hierarchy.  It is usually a "."
	 * @param sep the separator character for names in this simulation.
	 */
	public void setSeparatorChar(char sep) { separatorChar = sep; }

	/**
	 * Method to set a URL to the file containing this simulation data.
	 * @param fileURL a URL to the file containing this simulation data.
	 */
	public void setFileURL(URL fileURL) { this.fileURL = fileURL; }

	/**
	 * Method to return a URL to the file containing this simulation data.
	 * @return a URL to the file containing this simulation data.
	 */
	public URL getFileURL() { return fileURL; }

	/**
	 * Method to return the WaveformWindow that displays this simulation data.
	 * @return the WaveformWindow that displays this simulation data.
	 */
	public WaveformWindow getWaveformWindow() { return ww; }

	/**
	 * Method to return an array of control points associated with a signal.
	 * Control points are places where the user has added stimuli to the signal (set a level or strength).
	 * These points can be selected for change of the stimuli.
	 * @param sig the signal in question.
	 * @return an array of times where there are control points.
	 * Null if no control points are defined.
	 */
	public Double [] getControlPoints(Signal<?> sig) { return controlPointMap.get(sig); }

	/**
	 * Method to clear the list of control points associated with a signal.
	 * Control points are places where the user has added stimuli to the signal (set a level or strength).
	 * These points can be selected for change of the stimuli.
	 * @param sig the signal to clear.
	 */
	public void clearControlPoints(Signal<?> sig) { controlPointMap.remove(sig); }

	/**
	 * Method to add a new control point to the list on a signal.
	 * Control points are places where the user has added stimuli to the signal (set a level or strength).
	 * These points can be selected for change of the stimuli.
	 * @param sig the signal in question.
	 * @param time the time of the new control point.
	 */
	public void addControlPoint(Signal<?> sig, double time)
	{
		Double [] controlPoints = controlPointMap.get(sig);
		if (controlPoints == null)
		{
			controlPoints = new Double[1];
			controlPoints[0] = Double.valueOf(time);
			controlPointMap.put(sig, controlPoints);
		} else
		{
			// see if it is in the list already
			for(int i=0; i<controlPoints.length; i++)
				if (controlPoints[i].doubleValue() == time) return;

			// extend the list
			Double [] newCP = new Double[controlPoints.length + 1];
			for(int i=0; i<controlPoints.length; i++)
				newCP[i] = controlPoints[i];
			newCP[controlPoints.length] = Double.valueOf(time);
			controlPointMap.put(sig, newCP);
		}
	}

	/**
	 * Method to remove control points the list on a signal.
	 * Control points are places where the user has added stimuli to the signal (set a level or strength).
	 * These points can be selected for change of the stimuli.
	 * @param sig the signal in question.
	 * @param time the time of the control point to delete.
	 */
	public void removeControlPoint(Signal<?> sig, double time)
	{
		Double [] controlPoints = controlPointMap.get(sig);
		if (controlPoints == null) return;

		// see if it is in the list already
		boolean found = false;
		for(int i=0; i<controlPoints.length; i++)
			if (controlPoints[i].doubleValue() == time) { found = true;   break; }
		if (!found) return;

		// shrink the list
		Double [] newCP = new Double[controlPoints.length - 1];
		int j = 0;
		for(int i=0; i<controlPoints.length; i++)
		{
			if (controlPoints[i].doubleValue() != time)
				newCP[j++] = controlPoints[i];
		}
		controlPointMap.put(sig, newCP);
	}

	/**
	 * Method to return the leftmost X coordinate of this Stimuli.
	 * This value may not be the same as the minimum-x of the bounds, because
	 * the data may not be monotonically increasing (may run backwards, for example).
	 * @return the leftmost X coordinate of this Stimuli.
	 */
	public double getMinTime()
	{
		double leftEdge = 0, rightEdge = 0;
		for(SignalCollection sc : scList)
		{
            for (Signal<?> sig : sc.getSignals())
            {
                if (leftEdge == rightEdge)
                {
                    leftEdge = sig.getMinTime();
                    rightEdge = sig.getMaxTime();
                } else
                {
                    if (leftEdge < rightEdge)
                    {
                        leftEdge = Math.min(leftEdge, sig.getMinTime());
                        rightEdge = Math.max(rightEdge, sig.getMaxTime());
                    } else
                    {
                        leftEdge = Math.max(leftEdge, sig.getMinTime());
                        rightEdge = Math.min(rightEdge, sig.getMaxTime());
                    }
                }
			}
		}
		return leftEdge;
	}

	/**
	 * Method to return the rightmost X coordinate of this Stimuli.
	 * This value may not be the same as the maximum-x of the bounds, because
	 * the data may not be monotonically increasing (may run backwards, for example).
	 * @return the rightmost X coordinate of this Stimuli.
	 */
	public double getMaxTime()
	{
		double leftEdge = 0, rightEdge = 0;
		for(SignalCollection sc : scList)
		{
            for (Signal<?> sig : sc.getSignals())
            {
                if (leftEdge == rightEdge)
                {
                    leftEdge = sig.getMinTime();
                    rightEdge = sig.getMaxTime();
                } else
                {
                    if (leftEdge < rightEdge)
                    {
                        leftEdge = Math.min(leftEdge, sig.getMinTime());
                        rightEdge = Math.max(rightEdge, sig.getMaxTime());
                    } else
                    {
                        leftEdge = Math.max(leftEdge, sig.getMinTime());
                        rightEdge = Math.min(rightEdge, sig.getMaxTime());
                    }
                }
			}
		}
		if (engine != null)
		{
			double minRange = engine.getMinTimeRange();
			if (rightEdge - leftEdge < minRange) rightEdge = leftEdge + minRange;
		}
		return rightEdge;
	}

	/**
	 * Method to tell whether this simulation data is analog or digital.
	 * @return true if this simulation data is analog.
	 */
	public boolean isAnalog()
	{
		for(SignalCollection sc : scList)
		{
            for (Signal<?> sig : sc.getSignals())
            {
            	if (!sig.isDigital()) return true;
            }
		}
		return false;
	}

	/**
	 * Method to convert a strength to an index value.
	 * The strengths are OFF_STRENGTH, NODE_STRENGTH, GATE_STRENGTH, and VDD_STRENGTH.
	 * The indices are integers that can be saved to disk.
	 * @param strength strength level.
	 * @return the index for that strength (0-based).
	 */
	public static int strengthToIndex(int strength) { return strength / 4; }

	/**
	 * Method to convert a strength index to a strength value.
	 * The strengths are OFF_STRENGTH, NODE_STRENGTH, GATE_STRENGTH, and VDD_STRENGTH.
	 * The indices of the strengths are integers that can be saved to disk.
	 * @param index a strength index (0-based).
	 * @return the equivalent strength.
	 */
	public static int indexToStrength(int index) { return index * 4; }

	/**
	 * Method to describe the level in a given state.
	 * A 'state' is a combination of a level and a strength.
	 * The levels are LOGIC_LOW, LOGIC_HIGH, LOGIC_X, and LOGIC_Z.
	 * @param state the given state.
	 * @return a description of the logic level in that state.
	 */
	public static String describeLevel(int state)
	{
		switch (state&Stimuli.LOGIC)
		{
			case Stimuli.LOGIC_LOW: return "low";
			case Stimuli.LOGIC_HIGH: return "high";
			case Stimuli.LOGIC_X: return "undefined";
			case Stimuli.LOGIC_Z: return "floating";
		}
		return "?";
	}

	/**
	 * Method to describe the level in a given state, with only 1 character.
	 * A 'state' is a combination of a level and a strength.
	 * The levels are LOGIC_LOW, LOGIC_HIGH, LOGIC_X, and LOGIC_Z.
	 * @param state the given state.
	 * @return a description of the logic level in that state.
	 */
	public static String describeLevelBriefly(int state)
	{
		switch (state&Stimuli.LOGIC)
		{
			case Stimuli.LOGIC_LOW: return "L";
			case Stimuli.LOGIC_HIGH: return "H";
			case Stimuli.LOGIC_X: return "X";
			case Stimuli.LOGIC_Z: return "Z";
		}
		return "?";
	}

	/**
	 * Method to convert a state representation (L, H, X, Z) to a state
	 * @param s1 character string that contains state value.
	 * @return the state value.
	 */
	public static int parseLevel(String s1)
	{
		if (s1.length() > 0)
		{
			switch (s1.charAt(0))
			{
				case 'L': case 'l': return Stimuli.LOGIC_LOW;
				case 'X': case 'x': return Stimuli.LOGIC_X;
				case 'H': case 'h': return Stimuli.LOGIC_HIGH;
				case 'Z': case 'z': return Stimuli.LOGIC_Z;
			}
		}
		return Stimuli.LOGIC_X;
	}

	/**
	 * Method to describe the strength in a given state.
	 * A 'state' is a combination of a level and a strength.
	 * The strengths are OFF_STRENGTH, NODE_STRENGTH, GATE_STRENGTH, and VDD_STRENGTH.
	 * @param strength the given strength.
	 * @return a description of the strength in that state.
	 */
	public static String describeStrength(int strength)
	{
		switch (strength&Stimuli.STRENGTH)
		{
			case Stimuli.OFF_STRENGTH: return "off";
			case Stimuli.NODE_STRENGTH: return "node";
			case Stimuli.GATE_STRENGTH: return "gate";
			case Stimuli.VDD_STRENGTH: return "power";
		}
		return "?";
	}

    public static SignalCollection newSignalCollection(Stimuli sd, final String title)
    {
    	SignalCollection sc = new SignalCollection(title);
		sd.addSignalCollection(sc);
        return sc;
    }

    /**
     * Method to find busses in a list of signals and create them.
     * @param signalList the list of signals.
     * @param sc the SignalCollection in which the signals reside.
     */
    public void makeBusSignals(List<Signal<?>> signalList, SignalCollection sc)
	{
		if (signalList == null) return;
        Collections.sort(signalList, new WaveformWindow.SignalsByName());
		List<Signal<?>> busSoFar = new ArrayList<Signal<?>>();
		for(Signal<?> sig : signalList)
		{
			String curSignalName = sig.getSignalName();
			int squarePos = curSignalName.indexOf('[');
			if (squarePos < 0)
			{
				makeBus(busSoFar, sc);
				continue;
			}
			boolean startNewBus = false;
			if (busSoFar.size() > 0)
			{
				String curBusName = curSignalName.substring(0, squarePos);
				int curIndex = TextUtils.atoi(curSignalName.substring(squarePos+1));
				String curScope = sig.getSignalContext();
				if (curScope == null) curScope = "";

				Signal<?> lastSig = busSoFar.get(busSoFar.size()-1);
				String lastSignalName = lastSig.getSignalName();
				squarePos = lastSignalName.indexOf('[');
				String lastBusName = lastSignalName.substring(0, squarePos);
				int lastIndex = TextUtils.atoi(lastSignalName.substring(squarePos+1));
				String lastScope = lastSig.getSignalContext();
				if (lastScope == null) lastScope = "";

				if (!lastBusName.equals(curBusName)) startNewBus = true; else
					if (!lastScope.equals(curScope)) startNewBus = true; else
						if (lastIndex+1 != curIndex) startNewBus = true;
			}
			if (startNewBus)
				makeBus(busSoFar, sc);
			busSoFar.add(sig);
		}
		makeBus(busSoFar, sc);
	}

	private void makeBus(List<Signal<?>> busSoFar, SignalCollection sc)
	{
		if (busSoFar.size() == 0) return;
		int width = busSoFar.size();
		Signal<DigitalSample>[] subsigs = new Signal[width];
		for(int i=0; i<width; i++)
            subsigs[i] = (Signal<DigitalSample>)busSoFar.get(i);

		// get first index
		String firstEntryName = subsigs[0].getSignalName();
		int firstSquarePos = firstEntryName.indexOf('[');
		int firstIndex = TextUtils.atoi(firstEntryName.substring(firstSquarePos+1));

		// get last index
		String lastEntryName = subsigs[width-1].getSignalName();
		int lastSquarePos = lastEntryName.indexOf('[');
		int lastIndex = TextUtils.atoi(lastEntryName.substring(lastSquarePos+1));

		// make the bus
		String busName = firstEntryName.substring(0, firstSquarePos) + "[" + firstIndex + ":" + lastIndex + "]";
		String scope = subsigs[0].getSignalContext();
        BusSample.createSignal(sc, this, busName, scope, true, subsigs);

        // reset the list
        busSoFar.clear();
	}

}
