// 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 "CheckTiming.hh"

#include "ClkNetwork.hh"
#include "ExceptionPath.hh"
#include "Format.hh"
#include "Genclks.hh"
#include "Graph.hh"
#include "GraphClass.hh"
#include "Levelize.hh"
#include "Mode.hh"
#include "Network.hh"
#include "NetworkClass.hh"
#include "NetworkCmp.hh"
#include "Path.hh"
#include "PortDelay.hh"
#include "PortDirection.hh"
#include "Sdc.hh"
#include "SdcClass.hh"
#include "Sim.hh"
#include "StaState.hh"
#include "TimingRole.hh"

namespace sta {

CheckTiming::CheckTiming(StaState *sta) :
  StaState(sta)
{
}

CheckTiming::~CheckTiming()
{
  deleteErrors();
}

void
CheckTiming::deleteErrors()
{
  for (CheckError *error : errors_) {
    delete error;
  }
}

void
CheckTiming::clear()
{
  deleteErrors();
  errors_.clear();
}

CheckErrorSeq &
CheckTiming::check(const Mode *mode,
                   bool no_input_delay,
                   bool no_output_delay,
                   bool reg_multiple_clks,
                   bool reg_no_clks,
                   bool unconstrained_endpoints,
                   bool loops,
                   bool generated_clks)
{
  clear();
  mode_ = mode;
  sdc_ = mode->sdc();
  sim_ = mode->sim();
  clk_network_ = mode->clkNetwork();
  
  if (no_input_delay)
    checkNoInputDelay();
  if (no_output_delay)
    checkNoOutputDelay();
  if (reg_multiple_clks || reg_no_clks)
    checkRegClks(reg_multiple_clks, reg_no_clks);
  if (unconstrained_endpoints)
    checkUnconstrainedEndpoints();
  if (loops)
    checkLoops();
  if (generated_clks)
    checkGeneratedClocks();
  return errors_;
}

// Make sure there is a set_input_delay for each input/bidirect.
void
CheckTiming::checkNoInputDelay()
{
  PinSet no_arrival(network_);
  Instance *top_inst = network_->topInstance();
  InstancePinIterator *pin_iter = network_->pinIterator(top_inst);
  while (pin_iter->hasNext()) {
    const Pin *pin = pin_iter->next();
    if (!sdc_->isClock(pin)) {
      PortDirection *dir = network_->direction(pin);
      if (dir->isAnyInput()
          && !sdc_->hasInputDelay(pin)
          && !sim_->isConstant(pin))
        no_arrival.insert(pin);
    }
  }
  delete pin_iter;
  pushPinErrors("Warning: There {} {} input port{} missing set_input_delay.",no_arrival);
}

void
CheckTiming::checkNoOutputDelay()
{
  PinSet no_departure(network_);
  checkNoOutputDelay(no_departure);
  pushPinErrors("Warning: There {} {} output port{} missing set_output_delay.",
                no_departure);
}

void
CheckTiming::checkNoOutputDelay(PinSet &no_departure)
{
  Instance *top_inst = network_->topInstance();
  InstancePinIterator *pin_iter = network_->pinIterator(top_inst);
  while (pin_iter->hasNext()) {
    const Pin *pin = pin_iter->next();
    PortDirection *dir = network_->direction(pin);
    if (dir->isAnyOutput()
        && !sdc_->hasOutputDelay(pin)
        && !sim_->isConstant(pin))
      no_departure.insert(pin);
  }
  delete pin_iter;
}

bool
CheckTiming::hasClkedCheck(Vertex *vertex)
{
  VertexInEdgeIterator edge_iter(vertex, graph_);
  while (edge_iter.hasNext()) {
    Edge *edge = edge_iter.next();
    if (edge->role() == TimingRole::setup()
        && clk_network_->isClock(edge->from(graph_)))
      return true;
  }
  return false;
}

void
CheckTiming::checkRegClks(bool reg_multiple_clks,
                          bool reg_no_clks)
{
  PinSet no_clk_pins(network_);
  PinSet multiple_clk_pins(network_);
  for (Vertex *vertex : graph_->regClkVertices()) {
    const Pin *pin = vertex->pin();
    const ClockSet *clks = clk_network_->clocks(pin);
    if (reg_no_clks && clks == nullptr)
      no_clk_pins.insert(pin);
    if (reg_multiple_clks && clks && clks->size() > 1)
      multiple_clk_pins.insert(pin);
  }
  pushPinErrors("Warning: There {} {} unclocked register/latch pin{}.",
                no_clk_pins);
  pushPinErrors("Warning: There {} {} register/latch pin{} with multiple clocks.",
                multiple_clk_pins);
}

static const char *
plurality(int n)
{
  return n == 1 ? "is" : "are";
}

static const char *
pluralSuffix(int n)
{
  return n == 1 ? "" : "s";
}

void
CheckTiming::checkLoops()
{
  // These may not need to be sorted because the graph roots are
  // sorted during levelization so the discovery should be consistent.
  GraphLoopSeq &loops = levelize_->loops();
  // Count the combinational loops.
  int loop_count = 0;
  for (GraphLoop *loop : loops) {
    if (loop->isCombinational())
      loop_count++;
  }
  if (loop_count > 0) {
    CheckError *error = new CheckError;
    error->push_back(sta::format("Warning: There {} {} combinational loop{} in the design.",
                                 plurality(loop_count),
                                 loop_count,
                                 pluralSuffix(loop_count)));

    for (GraphLoop *loop : loops) {
      if (loop->isCombinational()) {
        Edge *last_edge = nullptr;
        for (Edge *edge : *loop->edges()) {
          Pin *pin = edge->from(graph_)->pin();
          error->push_back(sdc_network_->pathName(pin));
          last_edge = edge;
        }
        if (last_edge) {
          error->emplace_back("| loop cut point");
          const Pin *pin = last_edge->to(graph_)->pin();
          error->push_back(sdc_network_->pathName(pin));

          // Separator between loops.
          error->emplace_back("--------------------------------");
        }
      }
    }
    errors_.push_back(error);
  }
}

void
CheckTiming::checkUnconstrainedEndpoints()
{
  PinSet unconstrained_ends(network_);
  checkUnconstrainedOutputs(unconstrained_ends);
  checkUnconstrainedSetups(unconstrained_ends);
  pushPinErrors("Warning: There {} {} unconstrained endpoint{}.",
                unconstrained_ends);
}

void
CheckTiming::checkUnconstrainedOutputs(PinSet &unconstrained_ends)
{
  Instance *top_inst = network_->topInstance();
  InstancePinIterator *pin_iter = network_->pinIterator(top_inst);
  while (pin_iter->hasNext()) {
    Pin *pin = pin_iter->next();
    PortDirection *dir = network_->direction(pin);
    Vertex *vertex = graph_->pinLoadVertex(pin);
    if (dir->isAnyOutput()
        && !sim_->isConstant(pin)
        && !((hasClkedDepature(pin)
              && hasClkedArrival(vertex))
             || hasMaxDelay(pin)))
      unconstrained_ends.insert(pin);
  }
  delete pin_iter;
}

bool
CheckTiming::hasClkedDepature(Pin *pin)
{
  OutputDelaySet *output_delays = sdc_->outputDelaysLeafPin(pin);
  if (output_delays) {
    for (OutputDelay *output_delay : *output_delays) {
      if (output_delay->clkEdge() != nullptr
          || output_delay->refPin() != nullptr)
        return true;
    }
  }
  return false;
}

// Check for max delay exception that ends at pin.
bool
CheckTiming::hasMaxDelay(Pin *pin)
{
  for (const ExceptionPath *exception : sdc_->exceptions()) {
    ExceptionTo *to = exception->to();
    if (exception->isPathDelay()
        && exception->minMax() == MinMaxAll::max()
        && to
        && to->hasPins()
        && to->pins()->contains(pin))
      return true;
  }
  return false;
}

void
CheckTiming::checkUnconstrainedSetups(PinSet &unconstrained_ends)
{
  VertexIterator vertex_iter(graph_);
  while (vertex_iter.hasNext()) {
    Vertex *vertex = vertex_iter.next();
    if (!sim_->isConstant(vertex)) {
      VertexInEdgeIterator edge_iter(vertex, graph_);
      while (edge_iter.hasNext()) {
        Edge *edge = edge_iter.next();
        if (edge->role() == TimingRole::setup()
            && (!clk_network_->isClock(edge->from(graph_))
                || !hasClkedArrival(edge->to(graph_)))) {
          unconstrained_ends.insert(vertex->pin());
          break;
        }
      }
    }
  }
}

bool
CheckTiming::hasClkedArrival(Vertex *vertex)
{
  VertexPathIterator path_iter(vertex, this);
  while (path_iter.hasNext()) {
    Path *path = path_iter.next();
    if (path->clock(this))
      return true;
  }
  return false;
}

void
CheckTiming::checkGeneratedClocks()
{
  ClockSet gen_clk_errors;
  for (auto clk : sdc_->clocks()) {
    if (clk->isGenerated()) {
      mode_->genclks()->checkMaster(clk, sdc_);
      bool found_clk = false;
      VertexSet src_vertices = makeVertexSet(this);
      clk->srcPinVertices(src_vertices, network_, graph_);
      for (Vertex *vertex : src_vertices) {
        if (clk_network_->isClock(vertex)) {
          found_clk = true;
          break;
        }
      }
      if (!found_clk)
        gen_clk_errors.insert(clk);
    }
  }
  pushClkErrors("Warning: There {} {} generated clock{} not connected to a clock source.",
                gen_clk_errors);
}

// Report the "msg" error for each pin in "pins".
void
CheckTiming::pushPinErrors(std::string_view msg,
                           PinSet &pins)
{
  if (!pins.empty()) {
    CheckError *error = new CheckError;
    error->push_back(sta::formatRuntime(msg,
                                        plurality(pins.size()),
                                        pins.size(),
                                        pluralSuffix(pins.size())));
    // Sort the error pins so the output is independent of the order
    // the the errors are discovered.
    PinSeq pins1 = sortByPathName(&pins, network_);
    for (const Pin *pin : pins1) {
      error->push_back(sdc_network_->pathName(pin));
    }
    errors_.push_back(error);
  }
}

void
CheckTiming::pushClkErrors(const char *msg,
                           ClockSet &clks)
{
  if (!clks.empty()) {
    CheckError *error = new CheckError;
    error->push_back(sta::formatRuntime(msg,
                                        plurality(clks.size()),
                                        clks.size(),
                                        pluralSuffix(clks.size())));
    // Sort the error clks so the output is independent of the order
    // the the errors are discovered.
    ClockSeq clks1 = sortByName(&clks);
    for (const Clock *clk : clks1) {
      error->push_back(clk->name());
    }
    errors_.push_back(error);
  }
}

} // namespace sta
