// OpenSTA, Static Timing Analyzer
// Copyright (c) 2026, 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/>.
// 
// The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software.
// 
// Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 
// This notice may not be removed or altered from any source distribution.

#include "WriteSdc.hh"

#include <algorithm>
#include <ctime>
#include <set>
#include <string>
#include <string_view>
#include <vector>

#include "ClockGroups.hh"
#include "ClockInsertion.hh"
#include "ClockLatency.hh"
#include "ContainerHelpers.hh"
#include "DataCheck.hh"
#include "DeratingFactors.hh"
#include "DisabledPorts.hh"
#include "Error.hh"
#include "ExceptionPath.hh"
#include "Format.hh"
#include "Fuzzy.hh"
#include "Graph.hh"
#include "GraphCmp.hh"
#include "InputDrive.hh"
#include "Liberty.hh"
#include "Network.hh"
#include "NetworkCmp.hh"
#include "PortDelay.hh"
#include "PortDirection.hh"
#include "PortExtCap.hh"
#include "Report.hh"
#include "RiseFallValues.hh"
#include "Scene.hh"
#include "Sdc.hh"
#include "StaState.hh"
#include "Transition.hh"
#include "Units.hh"
#include "Variables.hh"
#include "Wireload.hh"
#include "WriteSdcPvt.hh"
#include "Zlib.hh"

namespace sta {

using ClockSenseSet = std::set<ClockSense*>;
using ClockSenseSeq = std::vector<ClockSense*>;

static std::string_view
transRiseFallFlag(const RiseFall *rf);
static std::string_view
transRiseFallFlag(const RiseFallBoth *rf);
static std::string_view
minMaxFlag(const MinMaxAll *min_max);
static std::string_view
minMaxFlag(const MinMax *min_max);
static std::string_view
earlyLateFlag(const MinMax *early_late);
static std::string_view
setupHoldFlag(const MinMax *min_max);
static std::string_view
timingDerateTypeKeyword(TimingDerateType type);

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

class WriteSdcObject
{
public:
  WriteSdcObject() = default;
  virtual ~WriteSdcObject() = default;
  virtual void write() const = 0;
};

class WriteGetPort : public WriteSdcObject
{
public:
  WriteGetPort(const Port *port,
               const WriteSdc *writer);
  void write() const override;

private:
  const Port *port_;
  const WriteSdc *writer_;
};

WriteGetPort::WriteGetPort(const Port *port,
                           const WriteSdc *writer) :
  port_(port),
  writer_(writer)
{
}

void
WriteGetPort::write() const
{
  writer_->writeGetPort(port_);
}

class WriteGetPinAndClkKey : public WriteSdcObject
{
public:
  WriteGetPinAndClkKey(const Pin *pin,
                       bool map_hpin_to_drvr,
                       const Clock *clk,
                       const WriteSdc *writer);
  void write() const override;

private:
  const Pin *pin_;
  bool map_hpin_to_drvr_;
  const Clock *clk_;
  const WriteSdc *writer_;
};

WriteGetPinAndClkKey::WriteGetPinAndClkKey(const Pin *pin,
                                           bool map_hpin_to_drvr,
                                           const Clock *clk,
                                           const WriteSdc *writer) :
  pin_(pin),
  map_hpin_to_drvr_(map_hpin_to_drvr),
  clk_(clk),
  writer_(writer)
{
}

void
WriteGetPinAndClkKey::write() const
{
  writer_->writeClockKey(clk_);
  sta::print(writer_->stream(), " ");
  writer_->writeGetPin(pin_, map_hpin_to_drvr_);
}

class WriteGetPin : public WriteSdcObject
{
public:
  WriteGetPin(const Pin *pin,
              bool map_hpin_to_drvr,
              const WriteSdc *writer);
  void write() const override;

private:
  const Pin *pin_;
  bool map_hpin_to_drvr_;
  const WriteSdc *writer_;
};

WriteGetPin::WriteGetPin(const Pin *pin,
                         bool map_hpin_to_drvr,
                         const WriteSdc *writer) :
  pin_(pin),
  map_hpin_to_drvr_(map_hpin_to_drvr),
  writer_(writer)
{
}

void
WriteGetPin::write() const
{
  writer_->writeGetPin(pin_, map_hpin_to_drvr_);
}

class WriteGetNet : public WriteSdcObject
{
public:
  WriteGetNet(const Net *net,
              const WriteSdc *writer);
  void write() const override;

private:
  const Net *net_;
  const WriteSdc *writer_;
};

WriteGetNet::WriteGetNet(const Net *net,
                         const WriteSdc *writer) :
  net_(net),
  writer_(writer)
{
}

void
WriteGetNet::write() const
{
  writer_->writeGetNet(net_);
}

class WriteGetInstance : public WriteSdcObject
{
public:
  WriteGetInstance(const Instance *inst,
                   const WriteSdc *writer);
  void write() const override;

private:
  const Instance *inst_;
  const WriteSdc *writer_;
};

WriteGetInstance::WriteGetInstance(const Instance *inst,
                                   const WriteSdc *writer) :
  inst_(inst),
  writer_(writer)
{
}

void
WriteGetInstance::write() const
{
  writer_->writeGetInstance(inst_);
}

class WriteGetLibCell : public WriteSdcObject
{
public:
  WriteGetLibCell(const LibertyCell *cell,
                  const WriteSdc *writer);
  void write() const override;

private:
  const LibertyCell *cell_;
  const WriteSdc *writer_;
};

WriteGetLibCell::WriteGetLibCell(const LibertyCell *cell,
                                 const WriteSdc *writer) :
  cell_(cell),
  writer_(writer)
{
}

void
WriteGetLibCell::write() const
{
  writer_->writeGetLibCell(cell_);
}

class WriteGetClock : public WriteSdcObject
{
public:
  WriteGetClock(const Clock *clk,
                const WriteSdc *writer);
  void write() const override;

private:
  const Clock *clk_;
  const WriteSdc *writer_;
};

WriteGetClock::WriteGetClock(const Clock *clk,
                             const WriteSdc *writer) :
  clk_(clk),
  writer_(writer)
{
}

void
WriteGetClock::write() const
{
  writer_->writeGetClock(clk_);
}

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

void
writeSdc(const Sdc *sdc,
         Instance *instance,
         std::string_view filename,
         std::string_view creator,
         bool map_hpins,
         bool native,
         int digits,
         bool gzip,
         bool no_timestamp)
{
  WriteSdc writer(sdc, instance, creator, map_hpins, native,
                  digits, no_timestamp);
  writer.write(filename, gzip);
}

WriteSdc::WriteSdc(const Sdc *sdc,
                   Instance *instance,
                   std::string_view creator,
                   bool map_hpins,
                   bool native,
                   int digits,
                   bool no_timestamp) :
  StaState(sdc),
  sdc_(sdc),
  instance_(instance),
  creator_(creator),
  map_hpins_(map_hpins),
  native_(native),
  digits_(digits),
  no_timestamp_(no_timestamp),
  top_instance_(instance == sdc_network_->topInstance()),
  instance_name_length_(sdc_network_->pathName(instance).size()),
  cell_(sdc_network_->cell(instance))
{
}

void
WriteSdc::write(std::string_view filename,
                bool gzip)
{
  openFile(filename, gzip);
  writeHeader();
  writeTiming();
  writeEnvironment();
  writeDesignRules();
  writeVariables();
  closeFile();
}

void
WriteSdc::openFile(std::string_view filename,
                   bool gzip)
{
  std::string filename_str(filename);
  stream_ = gzopen(filename_str.c_str(), gzip ? "wb" : "wT");
  if (stream_ == nullptr)
    throw FileNotWritable(filename);
}

void
WriteSdc::closeFile()
{
  gzclose(stream_);
}

void
WriteSdc::writeHeader() const
{
  writeCommentSeparator();
  sta::print(stream_, "# Created by {}\n", creator_);
  if (!no_timestamp_) {
    time_t now;
    time(&now);
    std::string time_str(ctime(&now));
    if (!time_str.empty() && time_str.back() == '\n')
      time_str.pop_back();
    sta::print(stream_, "# {}\n", time_str);
  }
  writeCommentSeparator();

  sta::print(stream_, "current_design {}\n", sdc_network_->name(cell_));
}

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

void
WriteSdc::writeTiming() const
{
  writeCommentSection("Timing Constraints");
  writeClocks();
  writePropagatedClkPins();
  writeClockUncertaintyPins();
  writeClockLatencies();
  writeClockInsertions();
  writeInterClockUncertainties();
  writeClockSenses();
  writeClockGroups();
  writeInputDelays();
  writeOutputDelays();
  writeDisables();
  writeExceptions();
  writeDataChecks();
}

void
WriteSdc::writeClocks() const
{
  // Write clocks in the order they were defined because generated
  // clocks depend on master clocks having been previously defined.
  for (const auto clk : sdc_->clocks_) {
    if (clk->isGenerated())
      writeGeneratedClock(clk);
    else
      writeClock(clk);
    writeClockSlews(clk);
    writeClockUncertainty(clk);
    if (clk->isPropagated()) {
      sta::print(stream_, "set_propagated_clock ");
      writeGetClock(clk);
      sta::print(stream_, "\n");
    }
  }
}

void
WriteSdc::writeClock(Clock *clk) const
{
  sta::print(stream_, "create_clock -name {}",
             clk->name());
  if (clk->addToPins())
    sta::print(stream_, " -add");
  sta::print(stream_, " -period ");
  float period = clk->period();
  writeTime(period);
  const FloatSeq &waveform = clk->waveform();
  if (!(waveform.size() == 2
        && waveform[0] == 0.0
        && fuzzyEqual(waveform[1], period / 2.0))) {
    sta::print(stream_, " -waveform ");
    writeFloatSeq(waveform, scaleTime(1.0));
  }
  writeCmdComment(clk);
  sta::print(stream_, " ");
  writeClockPins(clk);
  sta::print(stream_, "\n");
}

void
WriteSdc::writeGeneratedClock(Clock *clk) const
{
  sta::print(stream_, "create_generated_clock -name {}",
             clk->name());
  if (clk->addToPins())
    sta::print(stream_, " -add");
  sta::print(stream_, " -source ");
  writeGetPin(clk->srcPin(), true);
  Clock *master = clk->masterClk();
  if (master && !clk->masterClkInfered()) {
    sta::print(stream_, " -master_clock ");
    writeGetClock(master);
  }
  if (clk->combinational())
    sta::print(stream_, " -combinational");
  int divide_by = clk->divideBy();
  if (divide_by != 0)
    sta::print(stream_, " -divide_by {}", divide_by);
  int multiply_by = clk->multiplyBy();
  if (multiply_by != 0)
    sta::print(stream_, " -multiply_by {}", multiply_by);
  float duty_cycle = clk->dutyCycle();
  if (duty_cycle != 0.0) {
    sta::print(stream_, " -duty_cycle ");
    writeFloat(duty_cycle);
  }
  if (clk->invert())
    sta::print(stream_, " -invert");
  const IntSeq &edges = clk->edges();
  if (!edges.empty()) {
    sta::print(stream_, " -edges ");
    writeIntSeq(edges);
    const FloatSeq &edge_shifts = clk->edgeShifts();
    if (!edge_shifts.empty()) {
      sta::print(stream_, " -edge_shift ");
      writeFloatSeq(edge_shifts, scaleTime(1.0));
    }
  }
  writeCmdComment(clk);
  sta::print(stream_, " ");
  writeClockPins(clk);
  sta::print(stream_, "\n");
}

void
WriteSdc::writeClockPins(const Clock *clk) const
{
  const PinSet &pins = clk->pins();
  if (!pins.empty()) {
    if (pins.size() > 1)
      sta::print(stream_, "\\\n    ");
    writeGetPins(&pins, true);
  }
}

void
WriteSdc::writeClockSlews(const Clock *clk) const
{
  WriteGetClock write_clk(clk, this);
  const RiseFallMinMax slews = clk->slews();
  if (slews.hasValue())
    writeRiseFallMinMaxTimeCmd("set_clock_transition", &slews, write_clk);
}

void
WriteSdc::writeClockUncertainty(const Clock *clk) const
{
  float setup;
  bool setup_exists;
  clk->uncertainty(SetupHold::max(), setup, setup_exists);
  float hold;
  bool hold_exists;
  clk->uncertainty(SetupHold::min(), hold, hold_exists);
  if (setup_exists && hold_exists && setup == hold)
    writeClockUncertainty(clk, "", setup);
  else {
    if (setup_exists)
      writeClockUncertainty(clk, "-setup ", setup);
    if (hold_exists)
      writeClockUncertainty(clk, "-hold ", hold);
  }
}

void
WriteSdc::writeClockUncertainty(const Clock *clk,
                                std::string_view setup_hold,
                                float value) const
{
  sta::print(stream_, "set_clock_uncertainty {}", setup_hold);
  writeTime(value);
  sta::print(stream_, " {}\n", clk->name());
}

void
WriteSdc::writeClockUncertaintyPins() const
{
  for (const auto [pin, uncertainties] : sdc_->pin_clk_uncertainty_map_)
    writeClockUncertaintyPin(pin, uncertainties);
}

void
WriteSdc::writeClockUncertaintyPin(const Pin *pin,
                                   ClockUncertainties *uncertainties)
  const
{
  float setup;
  bool setup_exists;
  uncertainties->value(SetupHold::max(), setup, setup_exists);
  float hold;
  bool hold_exists;
  uncertainties->value(SetupHold::min(), hold, hold_exists);
  if (setup_exists && hold_exists && setup == hold)
    writeClockUncertaintyPin(pin, "",  setup);
  else {
    if (setup_exists)
      writeClockUncertaintyPin(pin, "-setup ",  setup);
    if (hold_exists)
      writeClockUncertaintyPin(pin, "-hold ",  hold);
  }
}
void
WriteSdc::writeClockUncertaintyPin(const Pin *pin,
                                   std::string_view setup_hold,
                                   float value) const
{
  sta::print(stream_, "set_clock_uncertainty {}", setup_hold);
  writeTime(value);
  sta::print(stream_, " ");
  writeGetPin(pin, true);
  sta::print(stream_, "\n");
}

void
WriteSdc::writeClockLatencies() const
{
  for (ClockLatency *latency : *sdc_->clockLatencies()) {
    const Pin *pin = latency->pin();
    const Clock *clk = latency->clock();
    if (pin && clk) {
      WriteGetPinAndClkKey write_pin(pin, true, clk, this);
      writeRiseFallMinMaxTimeCmd("set_clock_latency", latency->delays(),
                                 write_pin);
    }
    else if (pin) {
      WriteGetPin write_pin(pin, true, this);
      writeRiseFallMinMaxTimeCmd("set_clock_latency", latency->delays(),
                                 write_pin);
    }
    else if (clk) {
      WriteGetClock write_clk(clk, this);
      writeRiseFallMinMaxTimeCmd("set_clock_latency", latency->delays(),
                                 write_clk);
    }
  }
}

void
WriteSdc::writeClockInsertions() const
{
  for (ClockInsertion *insert : sdc_->clockInsertions()) {
    const Pin *pin = insert->pin();
    const Clock *clk = insert->clock();
    if (pin && clk) {
      WriteGetPinAndClkKey write_pin_clk(pin, true, clk, this);
      writeClockInsertion(insert, write_pin_clk);
    }
    else if (pin) {
      WriteGetPin write_pin(pin, true, this);
      writeClockInsertion(insert, write_pin);
    }
    else if (clk) {
      WriteGetClock write_clk(clk, this);
      writeClockInsertion(insert, write_clk);
    }
  }
}

void
WriteSdc::writeClockInsertion(ClockInsertion *insert,
                              WriteSdcObject &write_obj) const
{
  RiseFallMinMax *early_values = insert->delays(EarlyLate::early());
  RiseFallMinMax *late_values = insert->delays(EarlyLate::late());
  if (early_values->equal(late_values))
    writeRiseFallMinMaxTimeCmd("set_clock_latency -source",
                               late_values, write_obj);
  else {
    writeRiseFallMinMaxTimeCmd("set_clock_latency -source -early",
                               early_values, write_obj);
    writeRiseFallMinMaxTimeCmd("set_clock_latency -source -late",
                               late_values, write_obj);
  }
}

void
WriteSdc::writePropagatedClkPins() const
{
  for (const Pin *pin : sdc_->propagated_clk_pins_) {
    sta::print(stream_, "set_propagated_clock ");
    writeGetPin(pin, true);
    sta::print(stream_, "\n");
  }
}

void
WriteSdc::writeInterClockUncertainties() const
{
  for (InterClockUncertainty *uncertainty : sdc_->inter_clk_uncertainties_)
    writeInterClockUncertainty(uncertainty);
}

void
WriteSdc::
writeInterClockUncertainty(InterClockUncertainty *uncertainty) const
{
  const Clock *src_clk = uncertainty->src();
  const Clock *tgt_clk = uncertainty->target();
  const RiseFallMinMax *src_rise =
    uncertainty->uncertainties(RiseFall::rise());
  const RiseFallMinMax *src_fall =
    uncertainty->uncertainties(RiseFall::fall());
  float value;
  if (src_rise->equal(src_fall)
      && src_rise->isOneValue(value)) {
    sta::print(stream_, "set_clock_uncertainty -from ");
    writeGetClock(src_clk);
    sta::print(stream_, " -to ");
    writeGetClock(tgt_clk);
    sta::print(stream_, " ");
    writeTime(value);
    sta::print(stream_, "\n");
  }
  else {
    for (auto src_rf : RiseFall::range()) {
      for (auto tgt_rf : RiseFall::range()) {
        for (auto setup_hold : SetupHold::range()) {
          float value;
          bool exists;
          sdc_->clockUncertainty(src_clk, src_rf, tgt_clk, tgt_rf,
                                         setup_hold, value, exists);
          if (exists) {
            sta::print(stream_, "set_clock_uncertainty -{}_from ",
                      src_rf == RiseFall::rise() ? "rise" : "fall");
            writeGetClock(uncertainty->src());
            sta::print(stream_, " -{}_to ",
                      tgt_rf == RiseFall::rise() ? "rise" : "fall");
            writeGetClock(uncertainty->target());
            sta::print(stream_, " {} ",
                      setupHoldFlag(setup_hold));
            writeTime(value);
            sta::print(stream_, "\n");
          }
        }
      }
    }
  }
}

void
WriteSdc::writeInputDelays() const
{
  // Sort arrivals by pin and clock name.
  PortDelaySeq delays;
  for (InputDelay *input_delay : sdc_->inputDelays())
    delays.push_back(input_delay);

  PortDelayLess port_delay_less(sdc_network_);
  sort(delays, port_delay_less);

  for (PortDelay *input_delay : delays)
    writePortDelay(input_delay, true, "set_input_delay");
}

void
WriteSdc::writeOutputDelays() const
{
  // Sort departures by pin and clock name.
  PortDelaySeq delays;
  for (OutputDelay *output_delay : sdc_->outputDelays())
    delays.push_back(output_delay);

  sort(delays, PortDelayLess(sdc_network_));

  for (PortDelay *output_delay : delays) 
    writePortDelay(output_delay, false, "set_output_delay");
}

void
WriteSdc::writePortDelay(PortDelay *port_delay,
                         bool is_input_delay,
                         std::string_view sdc_cmd) const
{
  RiseFallMinMax *delays = port_delay->delays();
  float rise_min, rise_max, fall_min, fall_max;
  bool rise_min_exists, rise_max_exists, fall_min_exists, fall_max_exists;
  delays->value(RiseFall::rise(), MinMax::min(),
                rise_min, rise_min_exists);
  delays->value(RiseFall::rise(), MinMax::max(),
                rise_max, rise_max_exists);
  delays->value(RiseFall::fall(), MinMax::min(),
                fall_min, fall_min_exists);
  delays->value(RiseFall::fall(), MinMax::max(),
                fall_max, fall_max_exists);
  // Try to compress the four port delays.
  if (rise_min_exists
      && rise_max_exists
      && fall_min_exists
      && fall_max_exists
      && rise_max == rise_min
      && fall_min == rise_min
      && fall_max == rise_min)
    writePortDelay(port_delay, is_input_delay, rise_min,
                   RiseFallBoth::riseFall(), MinMaxAll::all(), sdc_cmd);
  else if (rise_min_exists
           && rise_max_exists
           && rise_max == rise_min
           && fall_min_exists
           && fall_max_exists
           && fall_min == fall_max) {
    writePortDelay(port_delay, is_input_delay, rise_min,
                   RiseFallBoth::rise(), MinMaxAll::all(), sdc_cmd);
    writePortDelay(port_delay, is_input_delay, fall_min,
                   RiseFallBoth::fall(), MinMaxAll::all(), sdc_cmd);
  }
  else if (rise_min_exists
           && fall_min_exists
           && rise_min == fall_min
           && rise_max_exists
           && fall_max_exists
           && rise_max == fall_max) {
    writePortDelay(port_delay, is_input_delay, rise_min,
                   RiseFallBoth::riseFall(), MinMaxAll::min(), sdc_cmd);
    writePortDelay(port_delay, is_input_delay, rise_max,
                   RiseFallBoth::riseFall(), MinMaxAll::max(), sdc_cmd);
  }
  else {
    if (rise_min_exists)
      writePortDelay(port_delay, is_input_delay, rise_min,
                     RiseFallBoth::rise(), MinMaxAll::min(), sdc_cmd);
    if (rise_max_exists)
      writePortDelay(port_delay, is_input_delay, rise_max,
                     RiseFallBoth::rise(), MinMaxAll::max(), sdc_cmd);
    if (fall_min_exists)
      writePortDelay(port_delay, is_input_delay, fall_min,
                     RiseFallBoth::fall(), MinMaxAll::min(), sdc_cmd);
    if (fall_max_exists)
      writePortDelay(port_delay, is_input_delay, fall_max,
                     RiseFallBoth::fall(), MinMaxAll::max(), sdc_cmd);
  }
}

void
WriteSdc::writePortDelay(PortDelay *port_delay,
                         bool is_input_delay,
                         float delay,
                         const RiseFallBoth *rf,
                         const MinMaxAll *min_max,
                         std::string_view sdc_cmd) const
{
  sta::print(stream_, "{} ", sdc_cmd);
  writeTime(delay);
  const ClockEdge *clk_edge = port_delay->clkEdge();
  if (clk_edge) {
    writeClockKey(clk_edge->clock());
    if (clk_edge->transition() == RiseFall::fall())
      sta::print(stream_, " -clock_fall");
  }
  sta::print(stream_, "{}{} -add_delay ",
             transRiseFallFlag(rf),
             minMaxFlag(min_max));
  const Pin *ref_pin = port_delay->refPin();
  if (ref_pin) {
    sta::print(stream_, "-reference_pin ");
    writeGetPin(ref_pin, true);
    sta::print(stream_, " ");
  }
  writeGetPin(port_delay->pin(), is_input_delay);
  sta::print(stream_, "\n");
}

class PinClockPairNameLess
{
public:
  PinClockPairNameLess(const Network *network);
  bool operator()(const PinClockPair &pin_clk1,
                  const PinClockPair &pin_clk2) const;

private:
  PinPathNameLess pin_less_;
};

PinClockPairNameLess::PinClockPairNameLess(const Network *network) :
  pin_less_(network)
{
}

bool
PinClockPairNameLess::operator()(const PinClockPair &pin_clk1,
                                 const PinClockPair &pin_clk2) const
{
  const Pin *pin1 = pin_clk1.first;
  const Pin *pin2 = pin_clk2.first;
  const Clock *clk1 = pin_clk1.second;
  const Clock *clk2 = pin_clk2.second;
  return pin_less_(pin1, pin2)
    || (pin1 == pin2
        && ((clk1 == nullptr && clk2)
            || (clk1 && clk2
                && clk1->index() < clk2->index())));
}

void
WriteSdc::writeClockSenses() const
{
  std::vector<PinClockPair> pin_clks;
  for (const auto& [pin_clk, sense] : sdc_->clk_sense_map_)
    pin_clks.push_back(pin_clk);

  // Sort by pin/clk pair so regressions results are stable.
  sort(pin_clks, PinClockPairNameLess(sdc_network_));
  
  for (auto pin_clk : pin_clks) {
    ClockSense sense;
    bool exists;
    findKeyValue(sdc_->clk_sense_map_, pin_clk, sense, exists);
    if (exists)
      writeClockSense(pin_clk, sense);
  }
}

void
WriteSdc::writeClockSense(PinClockPair &pin_clk,
                          ClockSense sense) const
{
  std::string_view flag;
  if (sense == ClockSense::positive)
    flag = "-positive";
  else if (sense == ClockSense::negative)
    flag = "-negative";
  else if (sense == ClockSense::stop)
    flag = "-stop_propagation";
  sta::print(stream_, "set_sense -type clock {} ", flag);
  const Clock *clk = pin_clk.second;
  if (clk) {
    sta::print(stream_, "-clock ");
    writeGetClock(clk);
    sta::print(stream_, " ");
  }
  writeGetPin(pin_clk.first, true);
  sta::print(stream_, "\n");
}

class ClockGroupLess
{
public:
  bool operator()(const ClockGroup *clk_group1,
                  const ClockGroup *clk_group2) const;
};


bool
ClockGroupLess::operator()(const ClockGroup *clk_group1,
                           const ClockGroup *clk_group2) const
{
  size_t size1 = clk_group1->size();
  size_t size2 = clk_group2->size();
  if (size1 < size2)
    return true;
  else if (size1 > size2)
    return false;
  else {
    ClockSeq clks1;
    for (Clock *clk1 : *clk_group1)
      clks1.push_back(clk1);
    sort(clks1, ClockNameLess());

    ClockSeq clks2;
    for (Clock *clk2 : *clk_group2)
      clks2.push_back(clk2);
    sort(clks2, ClockNameLess());

    ClockSeq::iterator clk_iter3 = clks1.begin();
    ClockSeq::iterator clk_iter4 = clks2.begin();
    while (clk_iter3 != clks1.end()
           && clk_iter4 != clks2.end()) {
      Clock *clk1 = *clk_iter3++;
      Clock *clk2 = *clk_iter4++;
      int cmp = clk1->name().compare(clk2->name());
      if (cmp < 0)
        return true;
      else if (cmp > 0)
        return false;
    }
    return false;
  }
}

void
WriteSdc::writeClockGroups() const
{
  for (const auto &[name, clk_groups] : sdc_->clk_groups_name_map_)
    writeClockGroups(clk_groups);
}

void
WriteSdc::writeClockGroups(ClockGroups *clk_groups) const
{
  sta::print(stream_, "set_clock_groups -name {} ", clk_groups->name());
  if (clk_groups->logicallyExclusive())
    sta::print(stream_, "-logically_exclusive \\\n");
  else if (clk_groups->physicallyExclusive())
    sta::print(stream_, "-physically_exclusive \\\n");
  else if (clk_groups->asynchronous())
    sta::print(stream_, "-asynchronous \\\n");
  if (clk_groups->allowPaths())
    sta::print(stream_, "-allow_paths \\\n");
  std::vector<ClockGroup*> groups;
  for (ClockGroup *clk_group : *clk_groups->groups())
    groups.push_back(clk_group);
  sort(groups, ClockGroupLess());
  bool first = true;
  for (ClockGroup *clk_group : groups) {
    if (!first)
      sta::print(stream_, "\\\n");
    sta::print(stream_, " -group ");
    writeGetClocks(clk_group);
    first = false;
  }
  writeCmdComment(clk_groups);
  sta::print(stream_, "\n");
}

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

void
WriteSdc::writeDisables() const
{
  writeDisabledCells();
  writeDisabledPorts();
  writeDisabledLibPorts();
  writeDisabledInstances();
  writeDisabledPins();
  writeDisabledEdges();
}

void
WriteSdc::writeDisabledCells() const
{
  DisabledCellPortsSeq disables = sortByName(sdc_->disabledCellPorts());
  for (const DisabledCellPorts *disable : disables) {
    const LibertyCell *cell = disable->cell();
    if (disable->all()) {
      sta::print(stream_, "set_disable_timing ");
      writeGetLibCell(cell);
      sta::print(stream_, "\n");
    }
    if (disable->fromTo()) {
      LibertyPortPairSeq from_tos = sortByName(disable->fromTo());
      for (const LibertyPortPair &from_to : from_tos) {
        const LibertyPort *from = from_to.first;
        const LibertyPort *to = from_to.second;
        sta::print(stream_, "set_disable_timing -from {{{}}} -to {{{}}} ",
                   from->name(),
                   to->name());
        writeGetLibCell(cell);
        sta::print(stream_, "\n");
      }
    }
    if (disable->from()) {
      LibertyPortSeq from = sortByName(disable->from());
      for (const LibertyPort *from_port : from) {
        sta::print(stream_, "set_disable_timing -from {{{}}} ",
                   from_port->name());
        writeGetLibCell(cell);
        sta::print(stream_, "\n");
      }
    }
    if (disable->to()) {
      LibertyPortSeq to = sortByName(disable->to());
      for (const LibertyPort *to_port : to) {
        sta::print(stream_, "set_disable_timing -to {{{}}} ",
                   to_port->name());
        writeGetLibCell(cell);
        sta::print(stream_, "\n");
      }
    }
    if (disable->timingArcSets()) {
      // The only syntax to disable timing arc sets disables all of the
      // cell's timing arc sets.
      sta::print(stream_, "set_disable_timing ");
      writeGetTimingArcsOfOjbects(cell);
      sta::print(stream_, "\n");
    }
  }
}

void
WriteSdc::writeDisabledPorts() const
{
  const PortSeq ports = sortByName(sdc_->disabledPorts(), sdc_network_);
  for (const Port *port : ports) {
    sta::print(stream_, "set_disable_timing ");
    writeGetPort(port);
    sta::print(stream_, "\n");
  }
}

void
WriteSdc::writeDisabledLibPorts() const
{
  LibertyPortSeq ports = sortByName(sdc_->disabledLibPorts());
  for (LibertyPort *port : ports) {
    sta::print(stream_, "set_disable_timing ");
    writeGetLibPin(port);
    sta::print(stream_, "\n");
  }
}

void
WriteSdc::writeDisabledInstances() const
{
  DisabledInstancePortsSeq disables = sortByPathName(sdc_->disabledInstancePorts(),
                                                     sdc_network_);
  for (DisabledInstancePorts *disable : disables) {
    Instance *inst = disable->instance();
    if (disable->all()) {
      sta::print(stream_, "set_disable_timing ");
      writeGetInstance(inst);
      sta::print(stream_, "\n");
    }
    else if (disable->fromTo()) {
      LibertyPortPairSeq from_tos = sortByName(disable->fromTo());
      for (LibertyPortPair &from_to : from_tos) {
        const LibertyPort *from_port = from_to.first;
        const LibertyPort *to_port = from_to.second;
        sta::print(stream_, "set_disable_timing -from {{{}}} -to {{{}}} ",
                   from_port->name(),
                   to_port->name());
        writeGetInstance(inst);
        sta::print(stream_, "\n");
      }
    }
    if (disable->from()) {
      LibertyPortSeq from = sortByName(disable->from());
      for (const LibertyPort *from_port : from) {
        sta::print(stream_, "set_disable_timing -from {{{}}} ",
                   from_port->name());
        writeGetInstance(inst);
        sta::print(stream_, "\n");
      }
    }
    if (disable->to()) {
      LibertyPortSeq to = sortByName(disable->to());
      for (const LibertyPort *to_port : to) {
        sta::print(stream_, "set_disable_timing -to {{{}}} ",
                   to_port->name());
        writeGetInstance(inst);
        sta::print(stream_, "\n");
      }
    }
  }
}

void
WriteSdc::writeDisabledPins() const
{
  PinSeq pins = sortByPathName(sdc_->disabledPins(), sdc_network_);
  for (const Pin *pin : pins) {
    sta::print(stream_, "set_disable_timing ");
    writeGetPin(pin, false);
    sta::print(stream_, "\n");
  }
}

void
WriteSdc::writeDisabledEdges() const
{
  EdgeSeq edges;
  for (Edge *edge : *sdc_->disabledEdges())
    edges.push_back(edge);
  sortEdges(&edges, sdc_network_, graph_);
  for (Edge *edge : edges) {
    EdgeSet matches;
    findMatchingEdges(edge, matches);
    if (matches.size() == 1)
      writeDisabledEdge(edge);
    else if (edgeSenseIsUnique(edge, matches))
      writeDisabledEdgeSense(edge);
  }
}

void
WriteSdc::findMatchingEdges(Edge *edge,
                            EdgeSet &matches) const
{
  Vertex *from_vertex = edge->from(graph_);
  Vertex *to_vertex = edge->to(graph_);
  Pin *to_pin = to_vertex->pin();
  VertexOutEdgeIterator edge_iter(from_vertex, graph_);
  while (edge_iter.hasNext()) {
    Edge *out_edge = edge_iter.next();
    if (out_edge->to(graph_)->pin() == to_pin)
      matches.insert(out_edge);
  }
}

bool
WriteSdc::edgeSenseIsUnique(Edge *edge,
                            EdgeSet &matches) const
{
  for (Edge *match : matches) {
    if (match != edge
        && match->sense() == edge->sense())
      return false;
  }
  return true;
}

void
WriteSdc::writeDisabledEdge(Edge *edge) const
{
  sta::print(stream_, "set_disable_timing ");
  writeGetTimingArcs(edge);
  sta::print(stream_, "\n");
}

void
WriteSdc::writeDisabledEdgeSense(Edge *edge) const
{
  sta::print(stream_, "set_disable_timing ");
  std::string filter = sta::format("sense == {}", to_string(edge->sense()));
  writeGetTimingArcs(edge, filter);
  sta::print(stream_, "\n");
}

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

void
WriteSdc::writeExceptions() const
{
  ExceptionPathSeq exceptions;
  for (ExceptionPath *exception : sdc_->exceptions())
    exceptions.push_back(exception);
  sort(exceptions, ExceptionPathLess(network_));
  for (ExceptionPath *exception : exceptions) {
    if (!exception->isFilter()
        && !exception->isLoop())
      writeException(exception);
  }
}

void
WriteSdc::writeException(ExceptionPath *exception) const
{
  writeExceptionCmd(exception);
  if (exception->from())
    writeExceptionFrom(exception->from());
  if (exception->thrus()) {
    for (ExceptionThru *thru : *exception->thrus())
      writeExceptionThru(thru);
  }
  if (exception->to())
    writeExceptionTo(exception->to());
  writeExceptionValue(exception);
  writeCmdComment(exception);
  sta::print(stream_, "\n");
}

void
WriteSdc::writeExceptionCmd(ExceptionPath *exception) const
{
  if (exception->isFalse()) {
    sta::print(stream_, "set_false_path");
    writeSetupHoldFlag(exception->minMax());
  }
  else if (exception->isMultiCycle()) {
    sta::print(stream_, "set_multicycle_path");
    const MinMaxAll *min_max = exception->minMax();
    writeSetupHoldFlag(min_max);
    if (min_max == MinMaxAll::min()) {
      // For hold MCPs default is -start.
      if (exception->useEndClk())
        sta::print(stream_, " -end");
    }
    else {
      // For setup MCPs default is -end.
      if (!exception->useEndClk())
        sta::print(stream_, " -start");
    }
  }
  else if (exception->isPathDelay()) {
    if (exception->minMax() == MinMaxAll::max())
      sta::print(stream_, "set_max_delay");
    else
      sta::print(stream_, "set_min_delay");
    if (exception->ignoreClkLatency())
      sta::print(stream_, " -ignore_clock_latency");
  }
  else if (exception->isGroupPath()) {
    if (exception->isDefault())
      sta::print(stream_, "group_path -default");
    else
      sta::print(stream_, "group_path -name {}", exception->name());
  }
  else
    report_->critical(1620, "unknown exception type");
}

void
WriteSdc::writeExceptionValue(ExceptionPath *exception) const
{
  if (exception->isMultiCycle())
    sta::print(stream_, " {}",
               exception->pathMultiplier());
  else if (exception->isPathDelay()) {
    sta::print(stream_, " ");
    writeTime(exception->delay());
  }
}

void
WriteSdc::writeExceptionFrom(ExceptionFrom *from) const
{
  writeExceptionFromTo(from, "from", true);
}

void
WriteSdc::writeExceptionTo(ExceptionTo *to) const
{
  const RiseFallBoth *end_rf = to->endTransition();
  if (end_rf != RiseFallBoth::riseFall())
    sta::print(stream_, "{} ", transRiseFallFlag(end_rf));
  if (to->hasObjects())
    writeExceptionFromTo(to, "to", false);
}

void
WriteSdc::writeExceptionFromTo(ExceptionFromTo *from_to,
                               std::string_view from_to_key,
                               bool map_hpin_to_drvr) const
{
  const RiseFallBoth *rf = from_to->transition();
  std::string_view rf_prefix = "-";
  if (rf == RiseFallBoth::rise())
    rf_prefix = "-rise_";
  else if (rf == RiseFallBoth::fall())
    rf_prefix = "-fall_";
  sta::print(stream_, "\\\n    {}{} ", rf_prefix, from_to_key);
  bool multi_objs =
    ((from_to->pins() ? from_to->pins()->size() : 0)
     + (from_to->clks() ? from_to->clks()->size() : 0)
     + (from_to->instances() ? from_to->instances()->size() : 0)) > 1;
  if (multi_objs)
    sta::print(stream_, "[list ");
  bool first = true;
  if (from_to->pins()) {
    PinSeq pins = sortByPathName(from_to->pins(), sdc_network_);
    for (const Pin *pin : pins) {
      if (multi_objs && !first)
        sta::print(stream_, "\\\n           ");
      writeGetPin(pin, map_hpin_to_drvr);
      first = false;
    }
  }
  if (from_to->clks())
    writeGetClocks(from_to->clks(), multi_objs, first);
  if (from_to->instances()) {
    InstanceSeq insts = sortByPathName(from_to->instances(), sdc_network_);
    for (const Instance *inst : insts) {
      if (multi_objs && !first)
        sta::print(stream_, "\\\n           ");
      writeGetInstance(inst);
      first = false;
    }
  }
  if (multi_objs)
    sta::print(stream_, "]");
}

void
WriteSdc::writeExceptionThru(ExceptionThru *thru) const
{
  const RiseFallBoth *rf = thru->transition();
  std::string_view rf_prefix = "-";
  if (rf == RiseFallBoth::rise())
    rf_prefix = "-rise_";
  else if (rf == RiseFallBoth::fall())
    rf_prefix = "-fall_";
  sta::print(stream_, "\\\n    {}through ", rf_prefix);
  PinSeq pins;
  mapThruHpins(thru, pins);
  bool multi_objs =
    (pins.size()
     + (thru->nets() ? thru->nets()->size() : 0)
     + (thru->instances() ? thru->instances()->size() : 0)) > 1;
  if (multi_objs)
    sta::print(stream_, "[list ");
  bool first = true;
  sort(pins, PinPathNameLess(network_));
  for (const Pin *pin : pins) {
    if (multi_objs && !first)
      sta::print(stream_, "\\\n           ");
    writeGetPin(pin);
    first = false;
  }

  if (thru->nets()) {
    NetSeq nets = sortByPathName(thru->nets(), sdc_network_);
    for (const Net *net : nets) {
      if (multi_objs && !first)
        sta::print(stream_, "\\\n           ");
      writeGetNet(net);
      first = false;
    }
  }
  if (thru->instances()) {
    InstanceSeq insts = sortByPathName(thru->instances(), sdc_network_);
    for (const Instance *inst : insts) {
      if (multi_objs && !first)
        sta::print(stream_, "\\\n           ");
      writeGetInstance(inst);
      first = false;
    }
  }
  if (multi_objs)
    sta::print(stream_, "]");
}

void
WriteSdc::mapThruHpins(ExceptionThru *thru,
                       PinSeq &pins) const
{
  if (thru->pins()) {
    for (const Pin *pin : *thru->pins()) {
      // Map hierarical pins to load pins outside of outputs or inside of inputs.
      if (network_->isHierarchical(pin)) {
        Instance *hinst = network_->instance(pin);
        bool hpin_is_output = network_->direction(pin)->isAnyOutput();
        PinConnectedPinIterator *cpin_iter = network_->connectedPinIterator(pin);
        while (cpin_iter->hasNext()) {
          const Pin *cpin = cpin_iter->next();
          if (network_->isLoad(cpin)
              && ((hpin_is_output
                   && !network_->isInside(network_->instance(cpin), hinst))
                  || (!hpin_is_output
                      && network_->isInside(network_->instance(cpin), hinst))))
            pins.push_back(cpin);
        }
        delete cpin_iter;
      }
      else
        pins.push_back(pin);
    }
  }
}

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

void
WriteSdc::writeDataChecks() const
{
  std::vector<DataCheck*> checks;
  for (const auto [pin, checks1] : sdc_->data_checks_to_map_) {
    for (DataCheck *check : *checks1)
      checks.push_back(check);
  }
  sort(checks, DataCheckLess(sdc_network_));
  for (DataCheck *check : checks)
    writeDataCheck(check);
}

void
WriteSdc::writeDataCheck(DataCheck *check) const
{
  for (auto setup_hold : SetupHold::range()) {
    float margin;
    bool one_value;
    check->marginIsOneValue(setup_hold, margin, one_value);
    if (one_value)
      writeDataCheck(check, RiseFallBoth::riseFall(),
                     RiseFallBoth::riseFall(), setup_hold, margin);
    else {
      for (auto from_rf : RiseFall::range()) {
        for (auto to_rf : RiseFall::range()) {
          float margin;
          bool margin_exists;
          check->margin(from_rf, to_rf, setup_hold, margin, margin_exists);
          if (margin_exists) {
            writeDataCheck(check, from_rf->asRiseFallBoth(),
                           to_rf->asRiseFallBoth(), setup_hold, margin);
          }
        }
      }
    }
  }
}

void
WriteSdc::writeDataCheck(DataCheck *check,
                         const RiseFallBoth *from_rf,
                         const RiseFallBoth *to_rf,
                         const SetupHold *setup_hold,
                         float margin) const
{
  std::string_view from_key = "-from";
  if (from_rf == RiseFallBoth::rise())
    from_key = "-rise_from";
  else if (from_rf == RiseFallBoth::fall())
    from_key = "-fall_from";
  sta::print(stream_, "set_data_check {} ", from_key);
  writeGetPin(check->from(), true);
  std::string_view to_key = "-to";
  if (to_rf == RiseFallBoth::rise())
    to_key = "-rise_to";
  else if (to_rf == RiseFallBoth::fall())
    to_key = "-fall_to";
  sta::print(stream_, " {} ", to_key);
  writeGetPin(check->to(), false);
  sta::print(stream_, "{} ",
             setupHoldFlag(setup_hold));
  writeTime(margin);
  sta::print(stream_, "\n");
}

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

void
WriteSdc::writeEnvironment() const
{
  writeCommentSection("Environment");
  writeOperatingConditions();
  writeWireload();
  writePortLoads();
  writeNetLoads();
  writeDriveResistances();
  writeDrivingCells();
  writeInputTransitions();
  writeNetResistances();
  writeConstants();
  writeCaseAnalysis();
  writeDeratings();
  writeVoltages();
}

void
WriteSdc::writeOperatingConditions() const
{
  OperatingConditions *cond = sdc_->operatingConditions(MinMax::max());
  if (cond)
    sta::print(stream_, "set_operating_conditions {}\n", cond->name());
}

void
WriteSdc::writeWireload() const
{
  WireloadMode wireload_mode = sdc_->wireloadMode();
  if (wireload_mode != WireloadMode::unknown)
    sta::print(stream_, "set_wire_load_mode \"{}\"\n",
               wireloadModeString(wireload_mode));
}

void
WriteSdc::writeNetLoads() const
{
  for (const auto [net, caps] : sdc_->net_wire_cap_map_) {
    float min_cap, max_cap;
    bool min_exists, max_exists;
    caps.value(MinMax::min(), min_cap, min_exists);
    caps.value(MinMax::max(), max_cap, max_exists);
    if (min_exists && max_exists
        && min_cap == max_cap)
      writeNetLoad(net, MinMaxAll::all(), min_cap);
    else {
      if (min_exists)
        writeNetLoad(net, MinMaxAll::min(), min_cap);
      if (max_exists)
        writeNetLoad(net, MinMaxAll::max(), max_cap);
    }
  }
}

void
WriteSdc::writeNetLoad(const Net *net,
                       const MinMaxAll *min_max,
                       float cap) const
{
  sta::print(stream_, "set_load ");
  sta::print(stream_, "{} ", minMaxFlag(min_max));
  writeCapacitance(cap);
  sta::print(stream_, " ");
  writeGetNet(net);
  sta::print(stream_, "\n");
}

void
WriteSdc::writePortLoads() const
{
  CellPortBitIterator *port_iter = sdc_network_->portBitIterator(cell_);
  while (port_iter->hasNext()) {
    Port *port = port_iter->next();
    writePortLoads(port);
  }
  delete port_iter;
}

void
WriteSdc::writePortLoads(const Port *port) const
{
  const PortExtCap *ext_cap = sdc_->portExtCap(port);
  if (ext_cap) {
    WriteGetPort write_port(port, this);
    writeRiseFallMinMaxCapCmd("set_load -pin_load",
                              ext_cap->pinCap(),
                              write_port);
    writeRiseFallMinMaxCapCmd("set_load -wire_load",
                              ext_cap->wireCap(),
                              write_port);
    writeMinMaxIntValuesCmd("set_port_fanout_number",
                            ext_cap->fanout(), write_port);
  }
}

void
WriteSdc::writeDriveResistances() const
{
  CellPortBitIterator *port_iter = sdc_network_->portBitIterator(cell_);
  while (port_iter->hasNext()) {
    Port *port = port_iter->next();
    InputDrive *drive = sdc_->findInputDrive(port);
    if (drive) {
      for (auto rf : RiseFall::range()) {
        if (drive->driveResistanceMinMaxEqual(rf)) {
          float res;
          bool exists;
          drive->driveResistance(rf, MinMax::max(), res, exists);
          sta::print(stream_, "set_drive {} ",
                    transRiseFallFlag(rf));
          writeResistance(res);
          sta::print(stream_, " ");
          writeGetPort(port);
          sta::print(stream_, "\n");
        }
        else {
          for (auto min_max : MinMax::range()) {
            float res;
            bool exists;
            drive->driveResistance(rf, min_max, res, exists);
            if (exists) {
              sta::print(stream_, "set_drive {} {} ",
                         transRiseFallFlag(rf),
                         minMaxFlag(min_max));
              writeResistance(res);
              sta::print(stream_, " ");
              writeGetPort(port);
              sta::print(stream_, "\n");
            }
          }
        }
      }
    }
  }
  delete port_iter;
}

void
WriteSdc::writeDrivingCells() const
{
  CellPortBitIterator *port_iter = sdc_network_->portBitIterator(cell_);
  while (port_iter->hasNext()) {
    Port *port = port_iter->next();
    InputDrive *drive = sdc_->findInputDrive(port);
    if (drive) {
      InputDriveCell *drive_rise_min = drive->driveCell(RiseFall::rise(),
                                                        MinMax::min());
      InputDriveCell *drive_rise_max = drive->driveCell(RiseFall::rise(),
                                                        MinMax::max());
      InputDriveCell *drive_fall_min = drive->driveCell(RiseFall::fall(),
                                                        MinMax::min());
      InputDriveCell *drive_fall_max = drive->driveCell(RiseFall::fall(),
                                                        MinMax::max());
      if (drive_rise_min
          && drive_rise_max
          && drive_fall_min
          && drive_fall_max
          && drive_rise_min->equal(drive_rise_max)
          && drive_rise_min->equal(drive_fall_min)
          && drive_rise_min->equal(drive_fall_max))
        // Only write one set_driving_cell if possible.
        writeDrivingCell(port, drive_rise_min, nullptr, nullptr);
      else {
        if (drive_rise_min
            && drive_rise_max
            && drive_rise_min->equal(drive_rise_max))
          writeDrivingCell(port, drive_rise_min, RiseFall::rise(), nullptr);
        else {
          if (drive_rise_min)
            writeDrivingCell(port, drive_rise_min, RiseFall::rise(),
                             MinMax::min());
          if (drive_rise_max)
            writeDrivingCell(port, drive_rise_max, RiseFall::rise(),
                             MinMax::max());
        }
        if (drive_fall_min
            && drive_fall_max
            && drive_fall_min->equal(drive_fall_max))
          writeDrivingCell(port, drive_fall_min, RiseFall::fall(), nullptr);
        else {
          if (drive_fall_min)
            writeDrivingCell(port, drive_fall_min, RiseFall::fall(),
                             MinMax::min());
          if (drive_fall_max)
            writeDrivingCell(port, drive_fall_max, RiseFall::fall(),
                             MinMax::max());
        }
      }
    }
  }
  delete port_iter;
}

void
WriteSdc::writeDrivingCell(Port *port,
                           InputDriveCell *drive_cell,
                           const RiseFall *rf,
                           const MinMax *min_max) const
{
  const LibertyCell *cell = drive_cell->cell();
  const LibertyPort *from_port = drive_cell->fromPort();
  const LibertyPort *to_port = drive_cell->toPort();
  const DriveCellSlews &from_slews = drive_cell->fromSlews();
  const LibertyLibrary *lib = drive_cell->library();
  sta::print(stream_, "set_driving_cell");
  if (rf)
    sta::print(stream_, " {}", transRiseFallFlag(rf));
  if (min_max)
    sta::print(stream_, " {}", minMaxFlag(min_max));
  // Only write -library if it was specified in the sdc.
  if (lib)
    sta::print(stream_, " -library {}", lib->name());
  sta::print(stream_, " -lib_cell {}", cell->name());
  if (from_port)
    sta::print(stream_, " -from_pin {{{}}}",
               from_port->name());
  sta::print(stream_,
             " -pin {{{}}} -input_transition_rise ",
             to_port->name());
  writeTime(from_slews[RiseFall::riseIndex()]);
  sta::print(stream_, " -input_transition_fall ");
  writeTime(from_slews[RiseFall::fallIndex()]);
  sta::print(stream_, " ");
  writeGetPort(port);
  sta::print(stream_, "\n");
}

void
WriteSdc::writeInputTransitions() const
{
  CellPortBitIterator *port_iter = sdc_network_->portBitIterator(cell_);
  while (port_iter->hasNext()) {
    Port *port = port_iter->next();
    InputDrive *drive = sdc_->findInputDrive(port);
    if (drive) {
      const RiseFallMinMax *slews = drive->slews();
      WriteGetPort write_port(port, this);
      writeRiseFallMinMaxTimeCmd("set_input_transition", slews, write_port);
    }
  }
  delete port_iter;
}

void
WriteSdc::writeNetResistances() const
{
  NetSeq nets;
  for (const auto [net, res] : sdc_->netResistances())
    nets.push_back(net);
  sort(nets, NetPathNameLess(sdc_network_));
  for (const Net *net : nets) {
    float min_res, max_res;
    bool min_exists, max_exists;
    sdc_->resistance(net, MinMax::min(), min_res, min_exists);
    sdc_->resistance(net, MinMax::max(), max_res, max_exists);
    if (min_exists && max_exists
        && min_res == max_res)
      writeNetResistance(net, MinMaxAll::all(), min_res);
    else {
      if (min_exists)
        writeNetResistance(net, MinMaxAll::min(), min_res);
      if (max_exists)
        writeNetResistance(net, MinMaxAll::max(), max_res);
    }
  }
}

void
WriteSdc::writeNetResistance(const Net *net,
                             const MinMaxAll *min_max,
                             float res) const
{
  sta::print(stream_, "set_resistance ");
  writeResistance(res);
  sta::print(stream_, "{} ", minMaxFlag(min_max));
  writeGetNet(net);
  sta::print(stream_, "\n");
}

void
WriteSdc::writeConstants() const
{
  PinSeq pins;
  sortedLogicValuePins(sdc_->logicValues(), pins);
  for (const Pin *pin : pins)
    writeConstant(pin);
}

void
WriteSdc::writeConstant(const Pin *pin) const
{
  std::string_view cmd = setConstantCmd(pin);
  sta::print(stream_, "{} ", cmd);
  writeGetPin(pin, false);
  sta::print(stream_, "\n");
}

std::string_view
WriteSdc::setConstantCmd(const Pin *pin) const
{
  LogicValue value;
  bool exists;
  sdc_->logicValue(pin, value, exists);
  switch (value) {
  case LogicValue::zero:
    return "set_logic_zero";
  case LogicValue::one:
    return "set_logic_one";
  case LogicValue::unknown:
    return "set_logic_dc";
  case LogicValue::rise:
  case LogicValue::fall:
  default:
    report_->critical(1621, "illegal set_logic value");
    return {};
  }
}

void
WriteSdc::writeCaseAnalysis() const
{
  PinSeq pins;
  sortedLogicValuePins(sdc_->caseLogicValues(), pins);
  for (const Pin *pin : pins)
    writeCaseAnalysis(pin);
}


void
WriteSdc::writeCaseAnalysis(const Pin *pin) const
{
  std::string_view value_str = caseAnalysisValueStr(pin);
  sta::print(stream_, "set_case_analysis {} ", value_str);
  writeGetPin(pin, false);
  sta::print(stream_, "\n");
}

std::string_view
WriteSdc::caseAnalysisValueStr(const Pin *pin) const
{
  LogicValue value;
  bool exists;
  sdc_->caseLogicValue(pin, value, exists);
  switch (value) {
  case LogicValue::zero:
    return "0";
  case LogicValue::one:
    return "1";
  case LogicValue::rise:
    return "rising";
  case LogicValue::fall:
    return "falling";
  case LogicValue::unknown:
  default:
    report_->critical(1622, "invalid set_case_analysis value");
    return {};
  }
}

void
WriteSdc::sortedLogicValuePins(const LogicValueMap &value_map,
                               PinSeq &pins) const
{
  for (const auto [pin, value] : value_map)
    pins.push_back(pin);
  // Sort pins.
  sort(pins, PinPathNameLess(sdc_network_));
}

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

void
WriteSdc::writeDeratings() const
{
  DeratingFactorsGlobal *factors = sdc_->derating_factors_;
  if (factors)
    writeDerating(factors);

  for (const auto [net, factors] : sdc_->net_derating_factors_) {
    WriteGetNet write_net(net, this);
    for (auto early_late : EarlyLate::range()) {
      writeDerating(factors, TimingDerateType::net_delay, early_late,
                    &write_net);
    }
  }

  for (const auto [inst, factors] : sdc_->inst_derating_factors_) {
    WriteGetInstance write_inst(inst, this);
    writeDerating(factors, &write_inst);
  }

  for (const auto [cell, factors] : sdc_->cell_derating_factors_) {
    WriteGetLibCell write_cell(cell, this);
    writeDerating(factors, &write_cell);
  }
}

void
WriteSdc::writeDerating(DeratingFactorsGlobal *factors) const
{
  for (auto early_late : EarlyLate::range()) {
    bool delay_is_one_value, check_is_one_value, net_is_one_value;
    float delay_value, check_value, net_value;
    factors->factors(TimingDerateType::cell_delay)->isOneValue(early_late,
                                                           delay_is_one_value,
                                                           delay_value);
    factors->factors(TimingDerateType::net_delay)->isOneValue(early_late,
                                                              net_is_one_value,
                                                              net_value);
    DeratingFactors *cell_check_factors =
      factors->factors(TimingDerateType::cell_check);
    cell_check_factors->isOneValue(early_late, check_is_one_value, check_value);
    if (delay_is_one_value
        && net_is_one_value
        && delay_value == net_value
        && (!cell_check_factors->hasValue()
            || (check_is_one_value && check_value == 1.0))) {
      if (delay_value != 1.0) {
        sta::print(stream_, "set_timing_derate {} ", earlyLateFlag(early_late));
        writeFloat(delay_value);
        sta::print(stream_, "\n");
      }
    }
    else {
      for (size_t type_index = 0;
           type_index < timing_derate_type_count;
           type_index++) {
        TimingDerateType type = static_cast<TimingDerateType>(type_index);
        DeratingFactors *type_factors = factors->factors(type);
        writeDerating(type_factors, type, early_late, nullptr);
      }
    }
  }
}

void
WriteSdc::writeDerating(DeratingFactorsCell *factors,
                        WriteSdcObject *write_obj) const
{
  for (auto early_late : EarlyLate::range()) {
    DeratingFactors *delay_factors=factors->factors(TimingDerateCellType::cell_delay);
    writeDerating(delay_factors, TimingDerateType::cell_delay, early_late, write_obj);
    DeratingFactors *check_factors=factors->factors(TimingDerateCellType::cell_check);
    writeDerating(check_factors, TimingDerateType::cell_check, early_late, write_obj);
  }
}

void
WriteSdc::writeDerating(DeratingFactors *factors,
                        TimingDerateType type,
                        const MinMax *early_late,
                        WriteSdcObject *write_obj) const
{
  std::string_view type_key = timingDerateTypeKeyword(type);
  bool is_one_value;
  float value;
  factors->isOneValue(early_late, is_one_value, value);
  if (is_one_value) {
    if (value != 1.0) {
      sta::print(stream_, "set_timing_derate {} {} ",
                 type_key,
                 earlyLateFlag(early_late));
      writeFloat(value);
      if (write_obj) {
        sta::print(stream_, " ");
        write_obj->write();
      }
      sta::print(stream_, "\n");
    }
  }
  else {
    for (size_t clk_data_index = 0;
         clk_data_index < path_clk_or_data_count;
         clk_data_index++) {
      PathClkOrData clk_data = static_cast<PathClkOrData>(clk_data_index);
      static constexpr std::string_view clk_data_keys[] = {"-clock", "-data"};
      std::string_view clk_data_key = clk_data_keys[clk_data_index];
      factors->isOneValue(clk_data, early_late, is_one_value, value);
      if (is_one_value) {
        if (value != 1.0) {
          sta::print(stream_, "set_timing_derate {} {} {} ",
                     type_key,
                     earlyLateFlag(early_late),
                     clk_data_key);
          writeFloat(value);
          if (write_obj) {
            sta::print(stream_, " ");
            write_obj->write();
          }
          sta::print(stream_, "\n");
        }
      }
      else {
        for (auto rf : RiseFall::range()) {
          float factor;
          bool exists;
          factors->factor(clk_data, rf, early_late, factor, exists);
          if (exists) {
            sta::print(stream_, "set_timing_derate {} {} {} {} ",
                       type_key,
                       clk_data_key,
                       transRiseFallFlag(rf),
                       earlyLateFlag(early_late));
            writeFloat(factor);
            if (write_obj) {
              sta::print(stream_, " ");
              write_obj->write();
            }
            sta::print(stream_, "\n");
          }
        }
      }
    }
  }
}

static std::string_view
timingDerateTypeKeyword(TimingDerateType type)
{
  static constexpr std::string_view type_keys[] = {
    "-cell_delay", "-cell_check", "-net_delay"};
  return type_keys[static_cast<int>(type)];
}

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

void
WriteSdc::writeVoltages() const
{
  float voltage_max, voltage_min;
  bool exists_max, exists_min;
  sdc_->voltage(MinMax::max(), voltage_max, exists_max);
  if (exists_max) {
    sdc_->voltage(MinMax::min(), voltage_min, exists_min);
    if (exists_min)
      sta::print(stream_, "set_voltage -min {:.3f} {:.3f}\n",
                 voltage_min,
                 voltage_max);
    else
      sta::print(stream_, "set_voltage {:.3f}\n", voltage_max);
  }

  for (const auto& [net, volts] : sdc_->net_voltage_map_) {
    volts.value(MinMax::max(), voltage_max, exists_max);
    if (exists_max) {
      volts.value(MinMax::min(), voltage_min, exists_min);
      if (exists_min)
        sta::print(stream_, "set_voltage -object_list {} -min {:.3f} {:.3f}\n",
                   sdc_network_->pathName(net),
                   voltage_min,
                   voltage_max);
      else
        sta::print(stream_, "set_voltage -object_list {} {:.3f}\n",
                   sdc_network_->pathName(net),
                   voltage_max);
    }
  }
}

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

void
WriteSdc::writeDesignRules() const
{
  writeCommentSection("Design Rules");
  writeMinPulseWidths();
  writeLatchBorowLimits();
  writeSlewLimits();
  writeCapLimits();
  writeFanoutLimits();
  writeMaxArea();
}

void
WriteSdc::writeMinPulseWidths() const
{
  for (const auto [pin, min_widths] : sdc_->pin_min_pulse_width_map_) {
    WriteGetPin write_obj(pin, false, this);
    writeMinPulseWidths(min_widths, write_obj);
  }

  for (const auto [inst, min_widths] : sdc_->inst_min_pulse_width_map_) {
    WriteGetInstance write_obj(inst, this);
    writeMinPulseWidths(min_widths, write_obj);
  }

  for (const auto [clk, min_widths] : sdc_->clk_min_pulse_width_map_) {
    WriteGetClock write_obj(clk, this);
    writeMinPulseWidths(min_widths, write_obj);
  }
}

void
WriteSdc::writeMinPulseWidths(RiseFallValues *min_widths,
                              WriteSdcObject &write_obj) const
{
  bool hi_exists, low_exists;
  float hi, low;
  min_widths->value(RiseFall::rise(), hi, hi_exists);
  min_widths->value(RiseFall::fall(), low, low_exists);
  if (hi_exists && low_exists
      && hi == low)
    writeMinPulseWidth("", hi, write_obj);
  else {
    if (hi_exists)
      writeMinPulseWidth("-high ", hi, write_obj);
    if (low_exists)
      writeMinPulseWidth("-low ", low, write_obj);
  }
}

void
WriteSdc::writeMinPulseWidth(std::string_view hi_low,
                             float value,
                             WriteSdcObject &write_obj) const
{
  sta::print(stream_, "set_min_pulse_width {}", hi_low);
  writeTime(value);
  sta::print(stream_, " ");
  write_obj.write();
  sta::print(stream_, "\n");
}

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

void
WriteSdc::writeLatchBorowLimits() const
{
  for (const auto [pin, limit] : sdc_->pin_latch_borrow_limit_map_) {
    sta::print(stream_, "set_max_time_borrow ");
    writeTime(limit);
    sta::print(stream_, " ");
    writeGetPin(pin, false);
    sta::print(stream_, "\n");
  }

  for (const auto [inst, limit] : sdc_->inst_latch_borrow_limit_map_) {
    sta::print(stream_, "set_max_time_borrow ");
    writeTime(limit);
    sta::print(stream_, " ");
    writeGetInstance(inst);
    sta::print(stream_, "\n");
  }

  for (const auto [clk, limit] : sdc_->clk_latch_borrow_limit_map_) {
    sta::print(stream_, "set_max_time_borrow ");
    writeTime(limit);
    sta::print(stream_, " ");
    writeGetClock(clk);
    sta::print(stream_, "\n");
  }
}

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

void
WriteSdc::writeSlewLimits() const
{
  const MinMax *min_max = MinMax::max();
  float slew;
  bool exists;
  sdc_->slewLimit(cell_, min_max, slew, exists);
  if (exists) {
    sta::print(stream_, "set_max_transition ");
    writeTime(slew);
    sta::print(stream_, " [current_design]\n");
  }

  CellPortBitIterator *port_iter = sdc_network_->portBitIterator(cell_);
  while (port_iter->hasNext()) {
    Port *port = port_iter->next();
    sdc_->slewLimit(port, min_max, slew, exists);
    if (exists) {
      sta::print(stream_, "set_max_transition ");
      writeTime(slew);
      sta::print(stream_, " ");
      writeGetPort(port);
      sta::print(stream_, "\n");
    }
  }
  delete port_iter;

  writeClkSlewLimits();
}

void
WriteSdc::writeClkSlewLimits() const
{
  const MinMax *min_max = MinMax::max();
  ClockSeq clks = sdc_->sortedClocks();
  for (const Clock *clk : clks) {
    float rise_clk_limit, fall_clk_limit, rise_data_limit, fall_data_limit;
    bool rise_clk_exists, fall_clk_exists, rise_data_exists, fall_data_exists;
    clk->slewLimit(RiseFall::rise(), PathClkOrData::clk, min_max,
                   rise_clk_limit, rise_clk_exists);
    clk->slewLimit(RiseFall::fall(), PathClkOrData::clk, min_max,
                   fall_clk_limit, fall_clk_exists);
    clk->slewLimit(RiseFall::rise(), PathClkOrData::data, min_max,
                   rise_data_limit, rise_data_exists);
    clk->slewLimit(RiseFall::fall(), PathClkOrData::data, min_max,
                   fall_data_limit, fall_data_exists);
    if (rise_clk_exists && fall_clk_exists
        && rise_data_exists && fall_data_exists
        && fall_clk_limit == rise_clk_limit
        && rise_data_limit == rise_clk_limit
        && fall_data_limit == rise_clk_limit)
      writeClkSlewLimit("", "", clk, rise_clk_limit);
    else {
      if (rise_clk_exists && fall_clk_exists
          && fall_clk_limit == rise_clk_limit)
        writeClkSlewLimit("-clock_path ", "", clk, rise_clk_limit);
      else {
        if (rise_clk_exists)
          writeClkSlewLimit("-clock_path ", "-rise ", clk, rise_clk_limit);
        if (fall_clk_exists)
          writeClkSlewLimit("-clock_path ", "-fall ", clk, fall_clk_limit);
      }
      if (rise_data_exists && fall_data_exists
          && fall_data_limit == rise_data_limit)
        writeClkSlewLimit("-data_path ", "", clk, rise_data_limit);
      else {
        if (rise_data_exists)
          writeClkSlewLimit("-data_path ", "-rise ", clk, rise_data_limit);
        if (fall_data_exists) {
          writeClkSlewLimit("-data_path ", "-fall ", clk, fall_data_limit);
        }
      }
    }
  }
}

void
WriteSdc::writeClkSlewLimit(std::string_view clk_data,
                            std::string_view rise_fall,
                            const Clock *clk,
                            float limit) const
{
  sta::print(stream_, "set_max_transition {}{}", clk_data, rise_fall);
  writeTime(limit);
  sta::print(stream_, " ");
  writeGetClock(clk);
  sta::print(stream_, "\n");
}

void
WriteSdc::writeCapLimits() const
{
  writeCapLimits(MinMax::min(), "set_min_capacitance");
  writeCapLimits(MinMax::max(), "set_max_capacitance");
}

void
WriteSdc::writeCapLimits(const MinMax *min_max,
                         std::string_view cmd) const
{
  float cap;
  bool exists;
  sdc_->capacitanceLimit(cell_, min_max, cap, exists);
  if (exists) {
    sta::print(stream_, "{} ", cmd);
    writeCapacitance(cap);
    sta::print(stream_, " [current_design]\n");
  }

  for (const auto [port, limits] : sdc_->port_cap_limit_map_) {
    float cap;
    bool exists;
    limits.value(min_max, cap, exists);
    if (exists) {
      sta::print(stream_, "{} ", cmd);
      writeCapacitance(cap);
      sta::print(stream_, " ");
      writeGetPort(port);
      sta::print(stream_, "\n");
    }
  }

  for (const auto [pin, limits] : sdc_->pin_cap_limit_map_) {
    float cap;
    bool exists;
    limits.value(min_max, cap, exists);
    if (exists) {
      sta::print(stream_, "{} ", cmd);
      writeCapacitance(cap);
      sta::print(stream_, " ");
      writeGetPin(pin, false);
      sta::print(stream_, "\n");
    }
  }
}

void
WriteSdc::writeMaxArea() const
{
  float max_area = sdc_->maxArea();
  if (max_area > 0.0) {
    sta::print(stream_, "set_max_area ");
    writeFloat(max_area);
    sta::print(stream_, "\n");
  }
}

void
WriteSdc::writeFanoutLimits() const
{
  writeFanoutLimits(MinMax::min(), "set_min_fanout");
  writeFanoutLimits(MinMax::max(), "set_max_fanout");
}

void
WriteSdc::writeFanoutLimits(const MinMax *min_max,
                            std::string_view cmd) const
{
  float fanout;
  bool exists;
  sdc_->fanoutLimit(cell_, min_max, fanout, exists);
  if (exists) {
    sta::print(stream_, "{} ", cmd);
    writeFloat(fanout);
    sta::print(stream_, " [current_design]\n");
  }
  else {
    CellPortBitIterator *port_iter = sdc_network_->portBitIterator(cell_);
    while (port_iter->hasNext()) {
      Port *port = port_iter->next();
      sdc_->fanoutLimit(port, min_max, fanout, exists);
      if (exists) {
        sta::print(stream_, "{} ", cmd);
        writeFloat(fanout);
        sta::print(stream_, " ");
        writeGetPort(port);
        sta::print(stream_, "\n");
      }
    }
    delete port_iter;
  }
}

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

void
WriteSdc::writeVariables() const
{
  if (variables_->propagateAllClocks()) {
    if (native_)
      sta::print(stream_, "set sta_propagate_all_clocks 1\n");
    else
      sta::print(stream_, "set timing_all_clocks_propagated true\n");
  }
  if (variables_->presetClrArcsEnabled()) {
    if (native_)
      sta::print(stream_, "set sta_preset_clear_arcs_enabled 1\n");
    else
      sta::print(stream_, "set timing_enable_preset_clear_arcs true\n");
  }
}

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

void
WriteSdc::writeGetTimingArcsOfOjbects(const LibertyCell *cell) const
{
  sta::print(stream_, "[{} -of_objects ", getTimingArcsCmd());
  writeGetLibCell(cell);
  sta::print(stream_, "]");
}

void
WriteSdc::writeGetTimingArcs(Edge *edge,
                             std::string_view filter) const
{
  sta::print(stream_, "[{} -from ", getTimingArcsCmd());
  Vertex *from_vertex = edge->from(graph_);
  writeGetPin(from_vertex->pin(), true);
  sta::print(stream_, " -to ");
  Vertex *to_vertex = edge->to(graph_);
  writeGetPin(to_vertex->pin(), false);
  if (!filter.empty())
    sta::print(stream_, " -filter {{{}}}", filter);
  sta::print(stream_, "]");
}

std::string_view
WriteSdc::getTimingArcsCmd() const
{
  return native_ ? "get_timing_edges" : "get_timing_arcs";
}

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

void
WriteSdc::writeGetLibCell(const LibertyCell *cell) const
{
  sta::print(stream_, "[get_lib_cells {{{}/{}}}]",
          cell->libertyLibrary()->name(),
          cell->name());
}

void
WriteSdc::writeGetLibPin(const LibertyPort *port) const
{
  LibertyCell *cell = port->libertyCell();
  LibertyLibrary *lib = cell->libertyLibrary();
  sta::print(stream_, "[get_lib_pins {{{}/{}/{}}}]",
             lib->name(),
             cell->name(),
             port->name());
}

void
WriteSdc::writeGetClocks(ClockSet *clks) const
{
  bool first = true;
  bool multiple = clks->size() > 1;
  if (multiple)
    sta::print(stream_, "[list ");
  writeGetClocks(clks, multiple, first);
  if (multiple)
    sta::print(stream_, "]");
}

void
WriteSdc::writeGetClocks(ClockSet *clks,
                         bool multiple,
                         bool &first) const
{
  ClockSeq clks1 = sortByName(clks);
  for (const Clock *clk : clks1) {
    if (multiple && !first)
      sta::print(stream_, "\\\n           ");
    writeGetClock(clk);
    first = false;
  }
}

void
WriteSdc::writeGetClock(const Clock *clk) const
{
  sta::print(stream_, "[get_clocks {{{}}}]",
             clk->name());
}

void
WriteSdc::writeGetPort(const Port *port) const
{
  sta::print(stream_, "[get_ports {{{}}}]", sdc_network_->name(port));
}

void
WriteSdc::writeGetPins(const PinSet *pins,
                       bool map_hpin_to_drvr) const
{
  if (map_hpins_) {
    PinSet leaf_pins(network_);;
    for (const Pin *pin : *pins) {
      if (network_->isHierarchical(pin)) {
        if (map_hpin_to_drvr)
          findLeafDriverPins(const_cast<Pin*>(pin), network_, &leaf_pins);
        else
          findLeafLoadPins(const_cast<Pin*>(pin), network_, &leaf_pins);
      }
      else
        leaf_pins.insert(pin);
    }
    PinSeq pins1 = sortByPathName(&leaf_pins, sdc_network_);
    writeGetPins1(&pins1);
  }
  else {
    PinSeq pins1 = sortByPathName(pins, sdc_network_);
    writeGetPins1(&pins1);
  }
}

void
WriteSdc::writeGetPins1(PinSeq *pins) const
{
  bool multiple = pins->size() > 1;
  if (multiple)
    sta::print(stream_, "[list ");
  bool first = true;
  for (const Pin *pin : *pins) {
    if (multiple && !first)
      sta::print(stream_, "\\\n          ");
    writeGetPin(pin);
    first = false;
  }
  if (multiple)
    sta::print(stream_, "]");
}

void
WriteSdc::writeGetPin(const Pin *pin) const
{
  if (sdc_network_->instance(pin) == instance_)
    sta::print(stream_, "[get_ports {{{}}}]", sdc_network_->portName(pin));
  else
    sta::print(stream_, "[get_pins {{{}}}]", pathName(pin));
}

void
WriteSdc::writeGetPin(const Pin *pin,
                      bool map_hpin_to_drvr) const
{
  if (map_hpins_ && network_->isHierarchical(pin)) {
    PinSet pins(network_);
    pins.insert(const_cast<Pin*>(pin));
    writeGetPins(&pins, map_hpin_to_drvr);
  }
  else
    writeGetPin(pin);
}

void
WriteSdc::writeGetNet(const Net *net) const
{
  sta::print(stream_, "[get_nets {{{}}}]", pathName(net));
}

void
WriteSdc::writeGetInstance(const Instance *inst) const
{
  sta::print(stream_, "[get_cells {{{}}}]", pathName(inst));
}

std::string
WriteSdc::pathName(const Pin *pin) const
{
  std::string pin_path = sdc_network_->pathName(pin);
  if (top_instance_)
    return pin_path;
  else
    return pin_path.substr(instance_name_length_ + 1);
}

std::string
WriteSdc::pathName(const Net *net) const
{
  std::string net_path = sdc_network_->pathName(net);
  if (top_instance_)
    return net_path;
  else
    return net_path.substr(instance_name_length_ + 1);
}

std::string
WriteSdc::pathName(const Instance *inst) const
{
  std::string inst_path = sdc_network_->pathName(inst);
  if (top_instance_)
    return inst_path;
  else
    return inst_path.substr(instance_name_length_ + 1);
}

void
WriteSdc::writeCommentSection(std::string_view line) const
{
  writeCommentSeparator();
  sta::print(stream_, "# {}\n", line);
  writeCommentSeparator();
}

void
WriteSdc::writeCommentSeparator() const
{
  sta::print(stream_, "###############################################################################\n");
}

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

void
WriteSdc::writeRiseFallMinMaxTimeCmd(std::string_view sdc_cmd,
                                     const RiseFallMinMax *values,
                                     WriteSdcObject &write_object) const
{
  writeRiseFallMinMaxCmd(sdc_cmd, values, units_->timeUnit()->scale(),
                         write_object);
}

void
WriteSdc::writeRiseFallMinMaxCapCmd(std::string_view sdc_cmd,
                                    const RiseFallMinMax *values,
                                    WriteSdcObject &write_object) const
{
  writeRiseFallMinMaxCmd(sdc_cmd, values, units_->capacitanceUnit()->scale(),
                         write_object);
}

void
WriteSdc::writeRiseFallMinMaxCmd(std::string_view sdc_cmd,
                                 const RiseFallMinMax *values,
                                 float scale,
                                 WriteSdcObject &write_object) const
{
  float fall_min, fall_max, rise_min, rise_max;
  bool fall_min_exists, fall_max_exists, rise_min_exists, rise_max_exists;
  values->value(RiseFall::fall(), MinMax::min(),
                fall_min, fall_min_exists);
  values->value(RiseFall::fall(), MinMax::max(),
                fall_max, fall_max_exists);
  values->value(RiseFall::rise(), MinMax::min(),
                rise_min, rise_min_exists);
  values->value(RiseFall::rise(), MinMax::max(),
                rise_max, rise_max_exists);
  if (fall_min_exists && fall_max_exists
      && rise_min_exists && rise_max_exists) {
    if (fall_min == rise_min
        && rise_max == rise_min
        && fall_max == rise_min) {
      // rise/fall/min/max match.
      writeRiseFallMinMaxCmd(sdc_cmd, rise_min, scale,
                             RiseFallBoth::riseFall(), MinMaxAll::all(),
                             write_object);
    }
    else if (rise_min == fall_min
             && rise_max == fall_max) {
      // rise/fall match.
      writeRiseFallMinMaxCmd(sdc_cmd, rise_min, scale,
                             RiseFallBoth::riseFall(), MinMaxAll::min(),
                             write_object);
      writeRiseFallMinMaxCmd(sdc_cmd, rise_max, scale,
                             RiseFallBoth::riseFall(), MinMaxAll::max(),
                             write_object);
    }
    else if (rise_min == rise_max
             && fall_min == fall_max) {
      // min/max match.
      writeRiseFallMinMaxCmd(sdc_cmd, rise_min, scale,
                             RiseFallBoth::rise(), MinMaxAll::all(),
                             write_object);
      writeRiseFallMinMaxCmd(sdc_cmd, fall_min, scale,
                             RiseFallBoth::fall(), MinMaxAll::all(),
                             write_object);
    }
  }
  else {
    if (rise_min_exists)
      writeRiseFallMinMaxCmd(sdc_cmd, rise_min, scale,
                             RiseFallBoth::rise(), MinMaxAll::min(),
                             write_object);
    if (rise_max_exists)
      writeRiseFallMinMaxCmd(sdc_cmd, rise_max, scale,
                             RiseFallBoth::rise(), MinMaxAll::max(),
                             write_object);
    if (fall_min_exists)
      writeRiseFallMinMaxCmd(sdc_cmd, fall_min, scale,
                             RiseFallBoth::fall(), MinMaxAll::min(),
                             write_object);
    if (fall_max_exists)
      writeRiseFallMinMaxCmd(sdc_cmd, fall_max, scale,
                             RiseFallBoth::fall(), MinMaxAll::max(),
                             write_object);
  }
}

void
WriteSdc::writeRiseFallMinMaxCmd(std::string_view sdc_cmd,
                                 float value,
                                 float scale,
                                 const RiseFallBoth *rf,
                                 const MinMaxAll *min_max,
                                 WriteSdcObject &write_object) const
{
  sta::print(stream_, "{}{}{} ",
             sdc_cmd,
             transRiseFallFlag(rf),
             minMaxFlag(min_max));
  writeFloat(value / scale);
  sta::print(stream_, " ");
  write_object.write();
  sta::print(stream_, "\n");
}

void
WriteSdc::writeClockKey(const Clock *clk) const
{
  sta::print(stream_, " -clock ");
  writeGetClock(clk);
}

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

void
WriteSdc::writeMinMaxFloatValuesCmd(std::string_view sdc_cmd,
                                    const MinMaxFloatValues *values,
                                    float scale,
                                    WriteSdcObject &write_object) const
{
  float min, max;
  bool min_exists, max_exists;
  values->value(MinMax::min(), min, min_exists);
  values->value(MinMax::max(), max, max_exists);
  if (min_exists && max_exists
      && min == max) {
    // min/max match.
    writeMinMaxFloatCmd(sdc_cmd, min, scale, MinMaxAll::all(), write_object);
  }
  else {
    if (min_exists)
      writeMinMaxFloatCmd(sdc_cmd, min, scale, MinMaxAll::min(), write_object);
    if (max_exists)
      writeMinMaxFloatCmd(sdc_cmd, max, scale, MinMaxAll::max(), write_object);
  }
}

void
WriteSdc::writeMinMaxFloatCmd(std::string_view sdc_cmd,
                              float value,
                              float scale,
                              const MinMaxAll *min_max,
                              WriteSdcObject &write_object) const
{
  sta::print(stream_, "{}{} ",
             sdc_cmd,
             minMaxFlag(min_max));
  writeFloat(value / scale);
  sta::print(stream_, " ");
  write_object.write();
  sta::print(stream_, "\n");
}

void
WriteSdc::writeMinMaxIntValuesCmd(std::string_view sdc_cmd,
                                  const MinMaxIntValues *values,
                                  WriteSdcObject &write_object) const
{
  int min, max;
  bool min_exists, max_exists;
  values->value(MinMax::min(), min, min_exists);
  values->value(MinMax::max(), max, max_exists);
  if (min_exists && max_exists
      && min == max) {
    // min/max match.
    writeMinMaxIntCmd(sdc_cmd, min, MinMaxAll::all(), write_object);
  }
  else {
    if (min_exists)
      writeMinMaxIntCmd(sdc_cmd, min, MinMaxAll::min(), write_object);
    if (max_exists)
      writeMinMaxIntCmd(sdc_cmd, max, MinMaxAll::max(), write_object);
  }
}

void
WriteSdc::writeMinMaxIntCmd(std::string_view sdc_cmd,
                            int value,
                            const MinMaxAll *min_max,
                            WriteSdcObject &write_object) const
{
  sta::print(stream_, "{}{} ",
             sdc_cmd,
             minMaxFlag(min_max));
  sta::print(stream_, "{} ", value);
  write_object.write();
  sta::print(stream_, "\n");
}

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

float
WriteSdc::scaleTime(float time) const
{
  return time / units_->timeUnit()->scale();
}

float
WriteSdc::scaleCapacitance(float cap) const
{
  return cap / units_->capacitanceUnit()->scale();
}

float
WriteSdc::scaleResistance(float res) const
{
  return res / units_->resistanceUnit()->scale();
}

void
WriteSdc::writeFloat(float value) const
{
  sta::print(stream_, "{}", sta::formatRuntime("{:.{}f}", value, digits_));
}

void
WriteSdc::writeTime(float time) const
{
  sta::print(stream_, "{}", sta::formatRuntime("{:.{}f}", scaleTime(time), digits_));
}

void
WriteSdc::writeCapacitance(float cap) const
{
  sta::print(stream_, "{}", sta::formatRuntime("{:.{}f}", scaleCapacitance(cap), digits_));
}

void
WriteSdc::writeResistance(float res) const
{
  sta::print(stream_, "{}", sta::formatRuntime("{:.{}f}", scaleResistance(res), digits_));
}

void
WriteSdc::writeFloatSeq(const FloatSeq &floats,
                        float scale) const
{
  sta::print(stream_, "{{");
  bool first = true;
  for (float flt : floats) {
    if (!first)
      sta::print(stream_, " ");
    writeFloat(flt * scale);
    first = false;
  }
  sta::print(stream_, "}}");
}

void
WriteSdc::writeIntSeq(const IntSeq &ints) const
{
  sta::print(stream_, "{{");
  bool first = true;
  for (int i : ints) {
    if (!first)
      sta::print(stream_, " ");
    sta::print(stream_, "{}", i);
    first = false;
  }
  sta::print(stream_, "}}");
}


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

static std::string_view
transRiseFallFlag(const RiseFall *rf)
{
  return (rf == RiseFall::rise()) ? "-rise" : "-fall";
}

static std::string_view
transRiseFallFlag(const RiseFallBoth *rf)
{
  if (rf == RiseFallBoth::rise())
    return " -rise";
  else if (rf == RiseFallBoth::fall())
    return " -fall";
  else
    return "";
}

static std::string_view
minMaxFlag(const MinMaxAll *min_max)
{
  if (min_max == MinMaxAll::min())
    return " -min";
  else if (min_max == MinMaxAll::max())
    return " -max";
  else
    return "";
}

static std::string_view
minMaxFlag(const MinMax *min_max)
{
  return (min_max == MinMax::min()) ? " -min" : " -max";
}

static std::string_view
earlyLateFlag(const MinMax *early_late)
{
  return (early_late == MinMax::min()) ? "-early" : "-late";
}

void
WriteSdc::writeSetupHoldFlag(const MinMaxAll *min_max) const
{
  if (min_max == MinMaxAll::min())
    sta::print(stream_,  " -hold");
  else if (min_max == MinMaxAll::max())
    sta::print(stream_,  " -setup");
}

static std::string_view
setupHoldFlag(const MinMax *min_max)
{
  return (min_max == MinMax::min()) ?  " -hold" : " -setup";
}

void
WriteSdc::writeCmdComment(SdcCmdComment *cmd) const
{
  const std::string &comment = cmd->comment();
  if (!comment.empty())
    sta::print(stream_, " -comment {{{}}}", comment);
}

} // namespace sta
