/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: HsimModel.java
 * Written by Frankie Liu, Sun Microsystems.
 *
 * Copyright (c) 2008, 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.util.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.io.*;

public class HsimModel extends NanosimModel {

  protected String simFile;

  public HsimModel() {
    super();
  }

  /**
   * Set the voltage on node
   * @param node name of node
   * @param value voltage to set in volts
   */
  public void setNodeVoltage(String node, double value) {
      if (assuraRCXNetlist) {
          node = node.replaceAll("\\.", "/");
      } else if (starRCXTNetlist) {
          if (node.startsWith("x")) node = node.substring(1);
          node = node.replaceAll("\\.x?", "/");
      }
      node = node.replaceAll("\\[","?");
      node = node.replaceAll("\\]","?");
      String cmd = "fv "+node+" "+value;
      issueCommand(cmd);
      String result = getLastCommandOutput().toString();
      if (!result.startsWith("HSIM >"))
          throw new RuntimeException("command \""+cmd+"\" gave the following error: " + result);
  }

  /**
   * Releases (forcing) on voltage on node
   * @param node name of node
   */
  public void releaseNodeVoltage(String node){
      if (assuraRCXNetlist) {
	node = node.replaceAll("\\.", "/");
      } else if (starRCXTNetlist) {
          if (node.startsWith("x")) node = node.substring(1);
          node = node.replaceAll("\\.x?", "/");
      }
      node = node.replaceAll("\\[","?");
      node = node.replaceAll("\\]","?");
      String cmd = "rv "+node+"";
      issueCommand(cmd);
      String result = getLastCommandOutput().toString();
      if (!result.startsWith("HSIM >"))
          throw new RuntimeException("command \""+cmd+"\" gave the following error: " + result);
  }


  /**
   * Sets voltage on node to 0
   * @param node name of node
   */
  public void disableNode(String node) {
    setNodeVoltage(node,0);
    // waitNS(timeStep);
  }

  /**
   * Same a releaseNodeVoltage
   * @param node name of node
   */
  public void enableNode(String node) {
    releaseNodeVoltage(node);
    // waitNS(timeStep);
  }


  /**
   * Checks if simFile is from Assura RCX
   * @return true if no IOException
   */
  protected boolean checkRcx() {
    // check if simfile is from assura RCX
    // if it is, we will have to replace hierarchy delimiter '.' with '/'
    System.out.println(simFile);

    try {
      BufferedReader freader = new BufferedReader(new FileReader(simFile));
      // PROGRAM will be in the first 10 or so lines, so search up till line 20
      for (int i=0; i<20; i++) {
	String line = freader.readLine();
	if (line == null) break;
	if (line.matches("\\*  PROGRAM .*?assura.*")) {
            assuraRCXNetlist = true;
            System.out.println("Info: Running on Assura extracted netlist, will replace all '.' in net names with '/'");
            break;
        } else if (line.matches("\\*|PROGRAM .*?Star-RCXT.*")) {
            starRCXTNetlist = true;
            System.out.println("Info: Running on Star-RCXT extracted netlist, will replace all '.x' in net names with '/'");
            break;
	}
      }
    } catch (IOException e) {
      System.out.println(e.getMessage());
      return false;
    }
    return true;
  }

  boolean start_(String command, String simFile, int recordSim) {

    setPrompt("HSIM > ");
    this.simFile=simFile;

    if (!checkRcx()){
      return false;
    }

    // consider the -top option when the top-level goo is wrapped in a .subckt

    // max sim time (-t) is 90071s
    // -wait_lic?
    if (!startProcess(command+" -time 90000 "+simFile+" -o "+simFile, null, null, simFile+".run"))
      return false;
      
    // get value of vdd
    //vdd = getNodeVoltage("vdd");
    vdd = 0.9;
    if (DEBUG)
      System.out.println("Using VDD of "+vdd);
    timeStep = getSimTres();
    if (DEBUG)
      System.out.println("Using time step of "+timeStep+" ns");

    // initialize test devices
    for (Iterator it = logicSettables.iterator(); it.hasNext(); ) {
      NanosimLogicSettable ls = (NanosimLogicSettable)it.next();
      if (!ls.init()) {
	System.out.println("LogicSettable initialization failed, aborting.");
	return false;
      }
    }
    for (Iterator it = jtagTesters.iterator(); it.hasNext(); ) {
      JtagTester tester = (JtagTester)it.next();
      tester.reset();
    }
    return true;
  }

  protected static final Pattern patSimTime = Pattern.compile("tnow=(\\S+)ns");
  public static final double badtime = -1;

  /**
   * Gets simulation time
   * @return simulation time in ns
   */
  public double getSimTime() {
    issueCommand("pt");
    String result = getLastCommandOutput().toString();
    if(!result.startsWith("tnow")){
      System.out.println(">> Error in getSimTime : no tnow");
      return badtime;
    }
    Matcher m = patSimTime.matcher(result);
    if (!m.find()){
      System.out.println(">> Error in getSimTime : no match");
      return badtime;
    }
    double tmp;
    try {
      tmp = Double.parseDouble(m.group(1));
    } catch(NumberFormatException e) {
      System.out.println(">> Error in getSimTime : bad number conversion");
      return badtime;
    }
    return tmp;
  }

  /**
   * Applies voltages and continues simualtion for a time
   * @param nanoseconds time to continue simuation
   * @param applyVoltages true if voltages are to be applied
   */
  public void waitNS(double nanoseconds, boolean applyVoltages) {

    if (applyVoltages)
      applyVoltages();

    double tmp = getSimTime();
    if (tmp!=badtime) {
      simTime = tmp;
    }

    // calculate stop time (time at which to set breakpoint)
    double stopTime = simTime + nanoseconds;

    // set break point
    issueCommand("stop -at "+stopTime); // in ns
    issueCommand("cont");
    simTime = stopTime;

  }


  /**
   * Get the simulation's time step
   * @return the simulation time step in nanoseconds
   */
  public double getSimTres() {
    return 0.01;    // default is 10ps
  }

  // ====================================================================
  // ====================================================================

  //                      Get/Set voltages

  // ====================================================================
  // ====================================================================

  /**
   * This is used to apply all voltages at the same time for the current time step.
   */
  public void applyVoltages() {
    // release all forced nodes
    releaseNodes(new ArrayList(nodesToSet.keySet()));

    // all nodes are released, now apply all the values
    for (Iterator it = nodesToSet.entrySet().iterator(); it.hasNext(); ) {
      Map.Entry entry = (Map.Entry)it.next();
      String node = (String)entry.getKey();
      Double voltage = (Double)entry.getValue();
      if (assuraRCXNetlist) {
	node = node.replaceAll("\\.", "/");
      } else if (starRCXTNetlist) {
          if (node.startsWith("x")) node = node.substring(1);
          node = node.replaceAll("\\.x?", "/");
      }
      setNodeVoltage(node,voltage.doubleValue());
    }
    nodesToSet.clear();
    // waitNS(timeStep, false);
  }
  /**
   * release the list of nodes (Strings)
   */
  public void releaseNodes(List nodes) {
    for (Iterator it = nodes.iterator(); it.hasNext(); ) {
      String node = (String)it.next();
      if (assuraRCXNetlist) {
	node = node.replaceAll("\\.", "/");
      } else if (starRCXTNetlist) {
          if (node.startsWith("x")) node = node.substring(1);
          node = node.replaceAll("\\.x?", "/");
      }
      releaseNodeVoltage(node);
    }
    // waitNS(timeStep, false);
  }

  // sample output:
  //
  // HSIM > nv out
  // Node out (2):
  // V=0.628472, dV/dt=0.00371528 V/ns, time=100.000000ns
  // Capacitance report
  // Constant node capacitance          : 10000 fF
  // Voltage-dependent node capacitance : 0 fF
  // Total node capacitance             : 10000 fF

  protected static final Pattern patNodeInfo[] = {
    Pattern.compile(".*?V=(\\S+), dV/dt=(\\S+) (\\S+), time=(\\S+)"),
    Pattern.compile(".*?Constant node capacitance\\s+: (\\S+) (\\S+)"),
    Pattern.compile(".*?Voltage-dependent node capacitance\\s+: (\\S+) (\\S+)"),
    Pattern.compile(".*?Total node capacitance\\s+: (\\S+) (\\S+)")
  };

  /**
   * Gets the voltages on the nodes
   * @param nodes the nodes to query
   * @param returnState defunct, not used
   * @return a list of voltages, or null on error
   */
  protected List getNodeInfo(List nodes, boolean returnState) {

    // apply any outstanding voltages
    if (nodesToSet.size() > 0)
      applyVoltages();

    if (assuraRCXNetlist || starRCXTNetlist) {
      List nodesfixed = new ArrayList();
      for (Iterator it = nodes.iterator(); it.hasNext(); ) {
	String node = (String)it.next();
        if (assuraRCXNetlist) {
            node = node.replaceAll("\\.", "/");
        } else if (starRCXTNetlist) {
          if (node.startsWith("x")) node = node.substring(1);
          node = node.replaceAll("\\.x?", "/");
        }
	nodesfixed.add(node);
      }
      nodes = nodesfixed;
    }

    List nodeslc = new ArrayList();
    List voltages = new ArrayList();

    StringBuffer cmd = new StringBuffer();
    cmd.append("nv ");

    boolean error=false;
    for (Iterator it = nodes.iterator(); it.hasNext() && !error; ) {
      String node = (String)it.next();
      node = node.toLowerCase();
      nodeslc.add(node);

      String nodex = node;
      nodex = nodex.replaceAll("\\[","?");
      nodex = nodex.replaceAll("\\]","?");

      // HACK
      if (nodex.equals("vss")) {
          voltages.add(0);
      } else {
          cmd.append(nodex);
          issueCommand(cmd.toString());
          String result = getLastCommandOutput().toString();
          if (!result.startsWith("Node "+node)) {
              System.out.println("Error on getInfoNode: for node "+node+"; result:\n  "+result);
              error=true;
              continue;
          }

          Double voltage = new Double(0);
          Double dvdt    = new Double(0);
          Double time    = new Double(0);
          Double cap     = new Double(0);
          Double vcap    = new Double(0);
          Double tcap    = new Double(0);
          
          for (int patternIndex=0; patternIndex < patNodeInfo.length && !error; patternIndex++) {
              
              Matcher m = patNodeInfo[patternIndex].matcher(result);
              if (!m.find()){
                  System.out.println(">> Error on getInfoNode : for node "+node);
                  System.out.println(">> Pattern " + patternIndex +" : "+patNodeInfo[patternIndex]);
                  System.out.println(">> String : "+result);
                  error=true;
                  continue;
              }
              try {
                  switch (patternIndex) {
                      case 0:
                          voltage = Double.parseDouble(m.group(1));
                          dvdt    = Double.parseDouble(m.group(2));
                          // time    = Double.parseDouble(m.group(4));
                          break;
                      case 1:
                          cap     = Double.parseDouble(m.group(1));
                          break;
                      case 2:
                          vcap    = Double.parseDouble(m.group(1));
                          break;
                      case 3:
                          tcap    = Double.parseDouble(m.group(1));
                          break;
                      case 4:
                          break;
                      default:
                  }
              } catch (NumberFormatException e) {
                  System.out.println(">> Error on get_info_node: NumberFormatException for node "+node);
                  System.out.println(">> Pattern: "+patNodeInfo[patternIndex]);
                  System.out.println(">> String: "+result);
                  for(int ngroup=0;ngroup<=m.groupCount();ngroup++){
                      System.out.println(">> Group "+ngroup+" : "+m.group(ngroup));
                  }
                  error=true; continue;
              }
              result = result.substring(m.end());
              if (DEBUG) {
                  System.out.println(">> Pattern: "+patNodeInfo[patternIndex]);
                  System.out.println(">> "+result);
              }
          }

          if (DEBUG) {
              System.out.println("getInfoNode "+node+":");
              System.out.println(voltage);
              System.out.println(dvdt);
              System.out.println(time);
              System.out.println(cap);
              System.out.println(vcap);
              System.out.println(tcap);
          }
          
          voltages.add(returnState ? (voltage >= vdd/2 ? 1.0 : 0.0) : voltage);
      }
    }

    if (error)
      return null;
    else
      return voltages;
  }

  /**
   * Saves all nodes. Xref restartDatabase, see also .STORE/.RESTORE
   * @param filename 
   */
  public void saveDatabase(String filename) {
    issueCommand("savesim filename");
  }

  /**
   * Restore all nodes. Xref saveDatabase, see also .STORE/.RESTORE
   * @param filename 
   * @param timens some earlier time in nanoseconds, basically
   * one can restart many times, each output file will have .rs# suffix
   */
  public void restartDatabase(String filename,double timens) {
    issueCommand("restart "+timens+" "+filename);
  }

  /** Unit Test
   * This test requires the file sim.spi in your working dir
   * */
  public static void main(String[] args) {
    HsimModel nm = new HsimModel();
    // the example file sim.spi needs to be in your working dir
    nm.start("hsim64", "rc.sp", 0);
    nm.issueCommand("pt");
    nm.waitNS(2);
    nm.finish();
    
    /* rc.sp
* Title
.option post
vin vdd gnd 1
rin vdd out 10k
cout out gnd 10p
.ic v(out) 0
.tran 10p 1000n
.param HSIMSTOPAT=100n
*.stop at=100n
.print v(*)
.end
    */

  }
}
