// OpenSTA, Static Timing Analyzer
// Copyright (c) 2019, Parallax Software, Inc.
// 
// This program 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.
// 
// This program 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 <https://www.gnu.org/licenses/>.

#include <string>
#include <iostream>
#include <fstream>
#include <regex>
#include "Machine.hh"
#include "Debug.hh"
#include "Error.hh"
#include "Report.hh"
#include "StringUtil.hh"
#include "FuncExpr.hh"
#include "Liberty.hh"
#include "TimingArc.hh"
#include "Network.hh"
#include "Graph.hh"
#include "Sdc.hh"
#include "DcalcAnalysisPt.hh"
#include "Parasitics.hh"
#include "PathAnalysisPt.hh"
#include "Path.hh"
#include "PathRef.hh"
#include "PathExpanded.hh"
#include "StaState.hh"
#include "WriteSpice.hh"

namespace sta {

using std::string;
using std::ofstream;
using std::ifstream;

typedef Vector<string> StringVector;
typedef Map<string, StringVector*> CellSpicePortNames;
typedef int Stage;
typedef Map<ParasiticNode*, int> ParasiticNodeMap;

void
split(const string &text,
      const string &delims,
      // Return values.
      StringVector &tokens);
void
streamPrint(ofstream &stream,
	    const char *fmt,
	    ...) __attribute__((format (printf, 2, 3)));
void
stringPrint(string &str,
	    const char *fmt,
	    ...) __attribute__((format (printf, 2, 3)));

////////////////////////////////////////////////////////////////

class WriteSpice : public StaState
{
public:
  WriteSpice(Path *path,
	     const char *spice_filename,
	     const char *subckts_filename,
	     const char *lib_subckts_filename,
	     const char *models_filename,
	     const StaState *sta);
  ~WriteSpice();
  void writeSpice();;

private:
  void writeHeader();
  void writeStageInstances();
  void writeInputSource();
  void writeStageSubckts();
  void writeInputStage(Stage stage);
  void writeMeasureStmts();
  void writeGateStage(Stage stage);
  void writeStageVoltageSources(LibertyCell *cell,
				StringVector *spice_port_names,
				const char *inst_name,
				LibertyPort *from_port,
				LibertyPort *drvr_port);
  void writeStageParasitics(Stage stage);
  void writeSubckts();
  void findPathCellnames(// Return values.
			 StringSet &path_cell_names);
  void recordSpicePortNames(const char *cell_name,
			    StringVector &tokens);
  float pgPortVoltage(const char *pg_port_name,
		      LibertyCell *cell);
  float pgPortVoltage(LibertyPgPort *pg_port);
  float maxTime();
  const char *nodeName(ParasiticNode *node);
  void initNodeMap(const char *net_name);

  // Stage "accessors".
  // Internally a stage index from stageFirst() to stageLast()
  // is turned into an index into path_expanded_.
  Stage stageFirst();
  Stage stageLast();
  string stageName(Stage stage);
  int stageGateInputPathIndex(Stage stage);
  int stageDrvrPathIndex(Stage stage);
  int stageLoadPathIndex(Stage stage);
  PathRef *stageGateInputPath(Stage stage);
  PathRef *stageDrvrPath(Stage stage);
  PathRef *stageLoadPath(Stage stage);
  TimingArc *stageGateArc(Stage stage);
  TimingArc *stageWireArc(Stage stage);
  Edge *stageGateEdge(Stage stage);
  Edge *stageWireEdge(Stage stage);
  Pin *stageInputPin(Stage stage);
  Pin *stageDrvrPin(Stage stage);
  Pin *stageLoadPin(Stage stage);
  const char *stageInputPinName(Stage stage);
  const char *stageDrvrPinName(Stage stage);
  const char *stageLoadPinName(Stage stage);

  Path *path_;
  const char *spice_filename_;
  const char *subckts_filename_;
  const char *lib_subckts_filename_;
  const char *models_filename_;

  ofstream spice_stream_;
  PathExpanded path_expanded_;
  CellSpicePortNames cell_spice_port_names_;
  ParasiticNodeMap node_map_;
  int next_node_index_;
  const char *net_name_;

  // Resistance to use to simulate a short circuit between spice nodes.
  static const float short_ckt_resistance_;
};

////////////////////////////////////////////////////////////////

class SubcktEndsMissing : public StaException
{
public:
  SubcktEndsMissing(const char *cell_name,
		    const char *subckt_filename);;
  const char *what() const throw();

protected:
  string what_;
};

SubcktEndsMissing::SubcktEndsMissing(const char *cell_name,
				     const char *subckt_filename)
{
  what_ = "Error: spice subckt for cell ";
  what_ += cell_name;
  what_ += " missing .ends in ";
  what_ += subckt_filename;
}

const char *
SubcktEndsMissing::what() const throw()
{
  return what_.c_str();
}

////////////////////////////////////////////////////////////////

void
writeSpice (Path *path,
	    const char *spice_filename,
	    const char *subckts_filename,
	    const char *lib_subckts_filename,
	    const char *models_filename,
	    StaState *sta)
{
  WriteSpice writer(path, spice_filename, subckts_filename,
		    lib_subckts_filename, models_filename, sta);
  writer.writeSpice();
}

const float WriteSpice::short_ckt_resistance_ = .0001;

WriteSpice::WriteSpice(Path *path,
		       const char *spice_filename,
		       const char *subckts_filename,
		       const char *lib_subckts_filename,
		       const char *models_filename,
		       const StaState *sta) :
  StaState(sta),
  path_(path),
  spice_filename_(spice_filename),
  subckts_filename_(subckts_filename),
  lib_subckts_filename_(lib_subckts_filename),
  models_filename_(models_filename),
  path_expanded_(sta),
  net_name_(NULL)
{
}

WriteSpice::~WriteSpice()
{
  cell_spice_port_names_.deleteContents();
}

void
WriteSpice::writeSpice()
{
  spice_stream_.open(spice_filename_);
  if (spice_stream_.is_open()) {
    path_expanded_.expand(path_, true);
    // Find subckt port names as a side-effect of writeSubckts.
    writeSubckts();
    writeHeader();
    writeStageInstances();
    writeInputSource();
    writeStageSubckts();
    streamPrint(spice_stream_, ".end\n");
    spice_stream_.close();
  }
  else
    throw FileNotWritable(spice_filename_);
}

void
WriteSpice::writeHeader()
{
  const MinMax *min_max = path_->minMax(this);
  const Pvt *pvt = sdc_->operatingConditions(min_max);
  if (pvt == NULL)
    pvt = network_->defaultLibertyLibrary()->defaultOperatingConditions();
  float temp = pvt->temperature();
  streamPrint(spice_stream_, ".temp %.1f\n", temp);
  streamPrint(spice_stream_, ".include \"%s\"\n", models_filename_);
  streamPrint(spice_stream_, ".include \"%s\"\n", subckts_filename_);

  float max_time = maxTime();
  float time_step = max_time / 1e+3;
  streamPrint(spice_stream_, ".tran %.3g %.3g\n\n",
	      time_step, max_time);
}

float
WriteSpice::maxTime()
{
  float end_slew = path_->slew(this);
  float max_time = (path_->arrival(this) + end_slew * 2) * 1.5;
  return max_time;
}

void
WriteSpice::writeStageInstances()
{
  streamPrint(spice_stream_, "*****************\n");
  streamPrint(spice_stream_, "* Stage instances\n");
  streamPrint(spice_stream_, "*****************\n\n");

  for (Stage stage = stageFirst(); stage <= stageLast(); stage++) {
    const char *stage_name = stageName(stage).c_str();
    if (stage == stageFirst())
      streamPrint(spice_stream_, "x%s %s %s %s\n",
		  stage_name,
		  stageDrvrPinName(stage),
		  stageLoadPinName(stage),
		  stage_name);
    else 
      streamPrint(spice_stream_, "x%s %s %s %s %s\n",
		  stage_name,
		  stageInputPinName(stage),
		  stageDrvrPinName(stage),
		  stageLoadPinName(stage),
		  stage_name);
  }
  streamPrint(spice_stream_, "\n");
}

float
WriteSpice::pgPortVoltage(const char *pg_port_name,
			  LibertyCell *cell)
{
  auto pg_port = cell->findPgPort(pg_port_name);
  return pgPortVoltage(pg_port);
}

float
WriteSpice::pgPortVoltage(LibertyPgPort *pg_port)
{
  auto cell = pg_port->cell();
  auto voltage_name = pg_port->voltageName();
  auto lib = cell->libertyLibrary();
  float voltage = lib->supplyVoltage(voltage_name);
  return voltage;
}

void
WriteSpice::writeInputSource()
{
  streamPrint(spice_stream_, "**************\n");
  streamPrint(spice_stream_, "* Input source\n");
  streamPrint(spice_stream_, "**************\n\n");

  Stage input_stage = stageFirst();
  streamPrint(spice_stream_, "v1 %s 0 pwl(\n",
	      stageDrvrPinName(input_stage));
  auto wire_arc = stageWireArc(input_stage);
  auto load_pin = stageLoadPin(input_stage);
  auto cell = network_->libertyCell(network_->instance(load_pin));
  auto load_port = network_->libertyPort(load_pin);
  const char *pg_gnd_port_name = load_port->relatedGroundPin();
  const char *pg_pwr_port_name = load_port->relatedPowerPin();
  auto gnd_volt = pgPortVoltage(pg_gnd_port_name, cell);
  auto pwr_volt = pgPortVoltage(pg_pwr_port_name, cell);
  float volt0, volt1;
  if (wire_arc->fromTrans()->asRiseFall() == TransRiseFall::rise()) {
    volt0 = gnd_volt;
    volt1 = pwr_volt;
  }
  else {
    volt0 = pwr_volt;
    volt1 = gnd_volt;
  }
  float time0 = .1e-9;
  float time1 = .2e-9;
  streamPrint(spice_stream_, "+%.3e %.3e\n", 0.0, volt0);
  streamPrint(spice_stream_, "+%.3e %.3e\n", time0, volt0);
  streamPrint(spice_stream_, "+%.3e %.3e\n", time1, volt1);
  streamPrint(spice_stream_, "+%.3e %.3e\n", maxTime(), volt1);
  streamPrint(spice_stream_, "+)\n\n");
}

void
WriteSpice::writeMeasureStmts()
{
  streamPrint(spice_stream_, "********************\n");
  streamPrint(spice_stream_, "* Measure statements\n");
  streamPrint(spice_stream_, "********************\n\n");
}

void
WriteSpice::writeStageSubckts()
{
  streamPrint(spice_stream_, "***************\n");
  streamPrint(spice_stream_, "* Stage subckts\n");
  streamPrint(spice_stream_, "***************\n\n");

  for (Stage stage = stageFirst(); stage <= stageLast(); stage++) {
    if (stage == stageFirst())
      writeInputStage(stage);
    else
      writeGateStage(stage);
  }
}

// Input port to first gate input.
void
WriteSpice::writeInputStage(Stage stage)
{
  // Input arc.
  // External driver not handled.
  auto drvr_pin_name = stageDrvrPinName(stage);
  auto load_pin_name = stageLoadPinName(stage);
  streamPrint(spice_stream_, ".subckt %s %s %s\n",
	      stageName(stage).c_str(),
	      drvr_pin_name,
	      load_pin_name);
  writeStageParasitics(stage);
  streamPrint(spice_stream_, ".ends\n\n");
}

// Gate and load parasitics.
void
WriteSpice::writeGateStage(Stage stage)
{
  auto input_pin = stageInputPin(stage);
  auto input_pin_name = stageInputPinName(stage);
  auto drvr_pin = stageDrvrPin(stage);
  auto drvr_pin_name = stageDrvrPinName(stage);
  auto load_pin_name = stageLoadPinName(stage);
  streamPrint(spice_stream_, ".subckt stage%d %s %s %s\n",
	      stage,
	      input_pin_name,
	      drvr_pin_name,
	      load_pin_name);
  Instance *inst = network_->instance(input_pin);
  const char *inst_name = network_->pathName(inst);
  LibertyCell *cell = network_->libertyCell(inst);
  const char *cell_name = cell->name();
  auto spice_port_names = cell_spice_port_names_[cell_name];

  // Instance subckt call.
  streamPrint(spice_stream_, "x%s", inst_name);
  StringVector::Iterator port_iter(spice_port_names);
  while (port_iter.hasNext()) {
    const char *subckt_port_name = port_iter.next().c_str();
    auto pin = network_->findPin(inst, subckt_port_name);
    auto pg_port = cell->findPgPort(subckt_port_name);
    const char *pin_name;
    if (pin) {
      pin_name = network_->pathName(pin);
      streamPrint(spice_stream_, " %s", pin_name);
    }
    else if (pg_port)
      streamPrint(spice_stream_, " %s/%s", inst_name, subckt_port_name);
  }
  streamPrint(spice_stream_, " %s\n", cell_name);

  writeStageVoltageSources(cell, spice_port_names,
			   inst_name,
			   network_->libertyPort(input_pin),
			   network_->libertyPort(drvr_pin));
  writeStageParasitics(stage);
  streamPrint(spice_stream_, ".ends\n\n");
}

typedef Map<LibertyPort*, LogicValue> LibertyPortLogicValues;

// Find the logic values for expression inputs to enable paths from_port.
void
sensitizationValues(FuncExpr *expr,
		    LibertyPort *from_port,
		    // Return values.
		    LibertyPortLogicValues &port_values)
{
  switch (expr->op()) {
  case FuncExpr::op_port: {
    break;
  }
  case FuncExpr::op_not: {
    sensitizationValues(expr->left(), from_port, port_values);
    break;
  }
  case FuncExpr::op_or: {
    FuncExpr *left = expr->left();
    FuncExpr *right = expr->right();
    if (left->port() == from_port
	&& right->op() == FuncExpr::op_port)
      port_values[right->port()] = logic_zero;
    else if (right->port() == from_port
	     && left->op() == FuncExpr::op_port)
      port_values[left->port()] = logic_zero;
    break;
  }
  case FuncExpr::op_and: {
    FuncExpr *left = expr->left();
    FuncExpr *right = expr->right();
    if (left->port() == from_port
	&& right->op() == FuncExpr::op_port)
      port_values[right->port()] = logic_one;
    else if (right->port() == from_port
	     && left->op() == FuncExpr::op_port)
      port_values[left->port()] = logic_one;
    break;
  }
  case FuncExpr::op_xor: {
    // Need to know timing arc sense to get this right.
    FuncExpr *left = expr->left();
    FuncExpr *right = expr->right();
    if (left->port() == from_port
	&& right->op() == FuncExpr::op_port)
      port_values[right->port()] = logic_zero;
    else if (right->port() == from_port
	     && left->op() == FuncExpr::op_port)
      port_values[left->port()] = logic_zero;
    break;
  }
  case FuncExpr::op_one:
  case FuncExpr::op_zero:
    break;
  }
}

// Power/ground and input voltage sources.
void
WriteSpice::writeStageVoltageSources(LibertyCell *cell,
				     StringVector *spice_port_names,
				     const char *inst_name,
				     LibertyPort *from_port,
				     LibertyPort *drvr_port)
{
  auto from_port_name = from_port->name();
  auto drvr_port_name = drvr_port->name();
  LibertyLibrary *lib = cell->libertyLibrary();
  LibertyPortLogicValues port_values;
  sensitizationValues(drvr_port->function(), from_port, port_values);
  int volt_source = 1;
  debugPrint1(debug_, "write_spice", 2, "subckt %s\n", cell->name());
  StringVector::Iterator port_iter(spice_port_names);
  while (port_iter.hasNext()) {
    auto subckt_port_name = port_iter.next().c_str();
    auto pg_port = cell->findPgPort(subckt_port_name);
    debugPrint2(debug_, "write_spice", 2, " port %s%s\n",
		subckt_port_name,
		pg_port ? " pwr/gnd" : "");
    if (pg_port) {
      auto voltage = pgPortVoltage(pg_port);
      streamPrint(spice_stream_, "v%d %s/%s 0 %.3f\n",
		  volt_source,
		  inst_name, subckt_port_name,
		  voltage);
      volt_source++;
    } else if (!(stringEq(subckt_port_name, from_port_name)
		 || stringEq(subckt_port_name, drvr_port_name))) {
      // Input voltage to sensitize path from gate input to output.
      LibertyPort *port = cell->findLibertyPort(subckt_port_name);
      if (port) {
	const char *pg_port_name = NULL;
	bool port_has_value;
	LogicValue port_value;
	port_values.findKey(port, port_value, port_has_value);
	if (port_has_value) {
	  switch (port_value) {
	  case logic_zero:
	    pg_port_name = port->relatedGroundPin();
	    break;
	  case logic_one:
	    pg_port_name = port->relatedPowerPin();
	    break;
	  default:
	    break;
	  }
	  if (pg_port_name) {
	    auto pg_port = cell->findPgPort(pg_port_name);
	    if (pg_port) {
	      auto voltage_name = pg_port->voltageName();
	      if (voltage_name) {
		float voltage = lib->supplyVoltage(voltage_name);
		streamPrint(spice_stream_, "v%d %s/%s 0 %.3f\n",
			    volt_source,
			    inst_name, subckt_port_name,
			    voltage);
		volt_source++;
	      }
	      else
	      report_->error("port %s %s voltage %s not found,\n",
			     subckt_port_name,
			     pg_port_name,
			     voltage_name);
	    }
	    else
	      report_->error("port %s %s not found,\n",
			     subckt_port_name,
			     pg_port_name);
	  }
	}
      }
    }
  }
}

typedef Set<ParasiticDevice*> ParasiticDeviceSet;
typedef Set<ParasiticNode*> ParasiticNodeSet;

void
findParasiticDevicesNodes(ParasiticNode *node,
			  Parasitics *parasitics,
			  // Return values.
			  ParasiticNodeSet &nodes,
			  ParasiticDeviceSet &devices)
{
  nodes.insert(node);
  auto device_iter = parasitics->deviceIterator(node);
  while (device_iter->hasNext()) {
    auto device = device_iter->next();
    if (!devices.hasKey(device)) {
      devices.insert(device);
      auto other_node = parasitics->otherNode(device, node);
      findParasiticDevicesNodes(other_node, parasitics, nodes, devices);
    }
  }
  delete device_iter;
}

void
WriteSpice::writeStageParasitics(Stage stage)
{
  auto drvr_path = stageDrvrPath(stage);
  auto drvr_pin = stageDrvrPin(stage);
  auto load_pin = stageLoadPin(stage);
  auto dcalc_ap = drvr_path->dcalcAnalysisPt(this);
  auto parasitic_ap = dcalc_ap->parasiticAnalysisPt();
  auto parasitic = parasitics_->findParasiticNetwork(drvr_pin, parasitic_ap);
  int resistor_index = 1;
  int cap_index = 1;
  if (parasitic) {
    Net *net = network_->net(drvr_pin);
    auto net_name =
      net ? network_->pathName(net) : network_->pathName(drvr_pin);
    initNodeMap(net_name);
    streamPrint(spice_stream_, "* Net %s\n", net_name);
    auto node = parasitics_->findNode(parasitic, drvr_pin);
    ParasiticNodeSet nodes;
    ParasiticDeviceSet devices;
    findParasiticDevicesNodes(node, parasitics_, nodes, devices);
    ParasiticDeviceSet::Iterator device_iter(devices);
    while (device_iter.hasNext()) {
      auto device = device_iter.next();
      auto resistance = parasitics_->value(device, parasitic_ap);
      if (parasitics_->isResistor(device)) {
	ParasiticNode *node1, *node2;
	parasitics_->resistorNodes(device, node1, node2);
	streamPrint(spice_stream_, "R%d %s %s %.3e\n",
		    resistor_index,
		    nodeName(node1),
		    nodeName(node2),
		    resistance);
	resistor_index++;
      }
      else if (parasitics_->isCouplingCap(device)) {
      }
    }
    ParasiticNodeSet::Iterator node_iter(nodes);
    while (node_iter.hasNext()) {
      auto node = node_iter.next();
      auto cap = parasitics_->nodeGndCap(node, parasitic_ap);
      streamPrint(spice_stream_, "C%d %s 0 %.3e\n",
		  cap_index,
		  nodeName(node),
		  cap);
      cap_index++;
    }
  }
  else
    streamPrint(spice_stream_, "R1 %s %s %.3e\n",
		network_->pathName(drvr_pin),
		network_->pathName(load_pin),
		short_ckt_resistance_);
}

void
WriteSpice::initNodeMap(const char *net_name)
{
  stringDelete(net_name_);
  node_map_.clear();
  next_node_index_ = 1;
  net_name_ = stringCopy(net_name);
}

const char *
WriteSpice::nodeName(ParasiticNode *node)
{
  auto pin = parasitics_->connectionPin(node);
  if (pin)
    return parasitics_->name(node);
  else {
    int node_index;
    bool node_index_exists;
    node_map_.findKey(node, node_index, node_index_exists);
    if (!node_index_exists) {
      node_index = next_node_index_++;
      node_map_[node] = node_index;
    }
    return stringPrintTmp(strlen(net_name_) + 10, "%s/%d",
			  net_name_, node_index);
  }
}

////////////////////////////////////////////////////////////////

// Copy the subckt definition from lib_subckts_filename for
// each cell in path to path_subckts_filename.
void
WriteSpice::writeSubckts()
{
  StringSet path_cell_names;
  findPathCellnames(path_cell_names);

  ifstream lib_subckts_stream(lib_subckts_filename_);
  if (lib_subckts_stream.is_open()) {
    ofstream subckts_stream(subckts_filename_);
    if (subckts_stream.is_open()) {
      string line;
      while (getline(lib_subckts_stream, line)) {
	// .subckt <cell_name> [args..]
	StringVector tokens;
	split(line, " \t", tokens);
	if (tokens.size() >= 2
	    && stringEqual(tokens[0].c_str(), ".subckt")) {
	  const char *cell_name = tokens[1].c_str();
	  if (path_cell_names.hasKey(cell_name)) {
	    subckts_stream << line << "\n";
	    bool found_ends = false;
	    while (getline(lib_subckts_stream, line)) {
	      subckts_stream << line << "\n";
	      if (stringEqual(line.c_str(), ".ends")) {
		subckts_stream << "\n";
		found_ends = true;
		break;
	      }
	    }
	    if (!found_ends)
	      throw SubcktEndsMissing(cell_name, lib_subckts_filename_);
	    path_cell_names.eraseKey(cell_name);
	  }
	  recordSpicePortNames(cell_name, tokens);
	}
      }
      subckts_stream.close();
      lib_subckts_stream.close();

      if (!path_cell_names.empty()) {
	StringSet::Iterator cell_iter(path_cell_names);
	report_->error("The following subkcts are missing from %s\n",
		       lib_subckts_filename_);
	while (cell_iter.hasNext()) {
	  const char *cell_name = cell_iter.next();
	  report_->printError(" %s\n", cell_name);
	}
      }
    }
    else {
      lib_subckts_stream.close();
      throw FileNotWritable(subckts_filename_);
    }
  }
  else
    throw FileNotReadable(lib_subckts_filename_);
}

void
WriteSpice::findPathCellnames(// Return values.
			      StringSet &path_cell_names)
{
  for (Stage stage = stageFirst(); stage <= stageLast(); stage++) {
    auto arc = stageGateArc(stage);
    if (arc) {
      LibertyCell *cell = arc->set()->libertyCell();
      if (cell) {
	debugPrint1(debug_, "write_spice", 2, "cell %s\n", cell->name());
	path_cell_names.insert(cell->name());
      }
    }
  }
}

void
WriteSpice::recordSpicePortNames(const char *cell_name,
				 StringVector &tokens)
{
  auto cell = network_->findLibertyCell(cell_name);
  auto spice_port_names = new StringVector;
  for (int i = 2; i < tokens.size(); i++) {
    const char *port_name = tokens[i].c_str();
    auto port = cell->findLibertyPort(port_name);
    auto pg_port = cell->findPgPort(port_name);
    if (port == NULL && pg_port == NULL)
      report_->error("subckt %s port %s has no corresponding liberty port or pg_port.\n",
		     cell_name, port_name);
    spice_port_names->push_back(port_name);
  }
  cell_spice_port_names_[cell_name] = spice_port_names;
}

////////////////////////////////////////////////////////////////

Stage
WriteSpice::stageFirst()
{
  return 1;
}

Stage
WriteSpice::stageLast()
{
  return (path_expanded_.size() + 1) / 2;
}

string
WriteSpice::stageName(Stage stage)
{
  string name;
  stringPrint(name, "stage%d", stage);
  return name;
}

int
WriteSpice::stageGateInputPathIndex(Stage stage)
{
  return stage * 2 - 3;
}

int
WriteSpice::stageDrvrPathIndex(Stage stage)
{
  return stage * 2 - 2;
}

int
WriteSpice::stageLoadPathIndex(Stage stage)
{
  return stage * 2 - 1;
}

PathRef *
WriteSpice::stageGateInputPath(Stage stage)
{
  int path_index = stageGateInputPathIndex(stage);
  return path_expanded_.path(path_index);
}

PathRef *
WriteSpice::stageDrvrPath(Stage stage)
{
  int path_index = stageDrvrPathIndex(stage);
  return path_expanded_.path(path_index);
}

PathRef *
WriteSpice::stageLoadPath(Stage stage)
{
  int path_index = stageLoadPathIndex(stage);
  return path_expanded_.path(path_index);
}

TimingArc *
WriteSpice::stageGateArc(Stage stage)
{
  int path_index = stageDrvrPathIndex(stage);
  if (path_index >= 0)
    return path_expanded_.prevArc(path_index);
  else
    return NULL;
}

TimingArc *
WriteSpice::stageWireArc(Stage stage)
{
  int path_index = stageLoadPathIndex(stage);
  return path_expanded_.prevArc(path_index);
}

Edge *
WriteSpice::stageGateEdge(Stage stage)
{
  PathRef *path = stageGateInputPath(stage);
  TimingArc *arc = stageGateArc(stage);
  return path->prevEdge(arc, this);
}

Edge *
WriteSpice::stageWireEdge(Stage stage)
{
  PathRef *path = stageLoadPath(stage);
  TimingArc *arc = stageWireArc(stage);
  return path->prevEdge(arc, this);
}

Pin *
WriteSpice::stageInputPin(Stage stage)
{
  PathRef *path = stageGateInputPath(stage);
  return path->pin(this);
}

Pin *
WriteSpice::stageDrvrPin(Stage stage)
{
  PathRef *path = stageDrvrPath(stage);
  return path->pin(this);
}

Pin *
WriteSpice::stageLoadPin(Stage stage)
{
  PathRef *path = stageLoadPath(stage);
  return path->pin(this);
}

const char *
WriteSpice::stageInputPinName(Stage stage)
{
  const Pin *pin = stageInputPin(stage);
  return network_->pathName(pin);
}

const char *
WriteSpice::stageDrvrPinName(Stage stage)
{
  Pin *pin = stageDrvrPin(stage);
  return network_->pathName(pin);
}

const char *
WriteSpice::stageLoadPinName(Stage stage)
{
  const Pin *pin = stageLoadPin(stage);
  return network_->pathName(pin);
}

////////////////////////////////////////////////////////////////

void
split(const string &text,
      const string &delims,
      // Return values.
      StringVector &tokens)
{
  auto start = text.find_first_not_of(delims);
  auto end = text.find_first_of(delims, start);
  while (end != string::npos) {
    tokens.push_back(text.substr(start, end - start));
    start = text.find_first_not_of(delims, end);
    end = text.find_first_of(delims, start);
  }
  if (start != string::npos)
    tokens.push_back(text.substr(start));
}

// fprintf for c++ streams.
// Yes, I hate formatted output to ostream THAT much.
void
streamPrint(ofstream &stream,
	    const char *fmt,
	    ...)
{
  va_list args;
  va_start(args, fmt);
  char *result;
  vasprintf(&result, fmt, args);
  stream << result;
  free(result);
  va_end(args);
}

// print for c++ strings.
void
stringPrint(string &str,
	    const char *fmt,
	    ...)
{
  va_list args;
  va_start(args, fmt);
  char *result;
  vasprintf(&result, fmt, args);
  str = result;
  free(result);
  va_end(args);
}

} // namespace
