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

#include <cstddef>
#include <string>

#include "ClkInfo.hh"
#include "Clock.hh"
#include "ContainerHelpers.hh"
#include "Debug.hh"
#include "Delay.hh"
#include "Graph.hh"
#include "GraphClass.hh"
#include "Liberty.hh"
#include "Network.hh"
#include "NetworkClass.hh"
#include "Path.hh"
#include "PathEnd.hh"
#include "Scene.hh"
#include "Sdc.hh"
#include "Search.hh"
#include "SearchClass.hh"
#include "SearchPred.hh"
#include "Tag.hh"
#include "TimingArc.hh"
#include "search/Crpr.hh"

namespace sta {

static void
minPulseWidth(const Path *path,
              const StaState *sta,
              // Return values.
              float &min_width,
              bool &exists);

CheckMinPulseWidths::CheckMinPulseWidths(StaState *sta) :
  heap_(0, MinPulseWidthSlackLess(sta)),
  sta_(sta)
{
}

void
CheckMinPulseWidths::clear()
{
  checks_.clear();
  heap_.clear();
}

MinPulseWidthCheckSeq &
CheckMinPulseWidths::check(const Net *net,
                           size_t max_count,
                           bool violators,
                           const SceneSeq &scenes)
{
  clear();
  if (!violators)
    heap_.setMaxSize(max_count);

  if (net)
    checkNet(net, violators, scenes);
  else
    checkAll(violators, scenes);

  if (violators)
    sort(checks_, MinPulseWidthSlackLess(sta_));
  else
    checks_ = heap_.extract();
  return checks_;
}

void
CheckMinPulseWidths::checkNet(const Net *net,
                              bool violators,
                              const SceneSeq &scenes)
{
  Graph *graph = sta_->graph();
  NetPinIterator *pin_iter = sta_->network()->pinIterator(net);
  while (pin_iter->hasNext()) {
    const Pin *pin = pin_iter->next();
    Vertex *vertex = graph->pinLoadVertex(pin);
    checkVertex(vertex, violators, scenes);
  }
  delete pin_iter;
}

void
CheckMinPulseWidths::checkAll(bool violators,
                              const SceneSeq &scenes)
{
  Graph *graph = sta_->graph();
  VertexIterator vertex_iter(graph);
  while (vertex_iter.hasNext()) {
    Vertex *vertex = vertex_iter.next();
    checkVertex(vertex, violators, scenes);
  }
}

void
CheckMinPulseWidths::checkVertex(Vertex *vertex,
                                 bool violators,
                                 const SceneSeq &scenes)
{
  Search *search = sta_->search();
  Debug *debug = sta_->debug();
  const MinMax *min_max = MinMax::max();
  SceneSet scene_set = Scene::sceneSet(scenes);
  VertexPathIterator path_iter(vertex, search);
  while (path_iter.hasNext()) {
    Path *path = path_iter.next();
    Vertex *path_vertex = path->vertex(sta_);
    const Mode *mode = path->mode(sta_);
    if (isClkEnd(path_vertex, mode)
        && path->isClock(search)
        && !path->tag(sta_)->clkInfo()->isGenClkSrcPath()
        && scene_set.contains(path->scene(sta_))
        && path->minMax(sta_) == min_max) {
      float min_width;
      bool exists;
      minPulseWidth(path, sta_, min_width, exists);
      if (exists) {
        MinPulseWidthCheck check(path);
        Path *close_path = check.closePath(sta_);
        // Don't bother visiting if nobody is home.
        if (close_path) {
          debugPrint(debug, "mpw", 2, "{} {} {}",
                     path_vertex->to_string(sta_),
                     path->transition(sta_) == RiseFall::rise() ? "(high)" : "(low)",
                     delayAsString(check.slack(sta_), sta_));
          if (violators) {
            if (delayLess(check.slack(sta_), 0.0, sta_))
              checks_.push_back(check);
          }
          else
            heap_.insert(check);
        }
      }
    }
  }
}

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

MinPulseWidthCheck::MinPulseWidthCheck() :
  open_path_(nullptr)
{
}

MinPulseWidthCheck::MinPulseWidthCheck(Path *open_path) :
  open_path_(open_path)
{
}

std::string
MinPulseWidthCheck::to_string(const StaState *sta)
{
  std::string result(sta->network()->pathName(pin(sta)));
  result += " ";
  result += (openTransition(sta) == RiseFall::rise()) ? "(high)" : "(low)";
  return result;
}

Pin *
MinPulseWidthCheck::pin(const StaState *sta) const
{
  return open_path_->pin(sta);
}

const RiseFall *
MinPulseWidthCheck::openTransition(const StaState *sta) const
{
  return open_path_->transition(sta);
}

Path *
MinPulseWidthCheck::closePath(const StaState *sta) const
{
  Scene *scene = open_path_->scene(sta);
  const MinMax *close_min_max = open_path_->tgtClkMinMax(sta);
  const RiseFall *open_rf = open_path_->transition(sta);
  const RiseFall *close_rf = open_rf->opposite();
  Tag *open_tag = open_path_->tag(sta);
  const ClkInfo *open_clk_info = open_tag->clkInfo();
  const ClkInfo close_clk_info(scene,
                               open_clk_info->clkEdge()->opposite(),
                               open_clk_info->clkSrc(),
                               open_clk_info->isPropagated(),
                               open_clk_info->genClkSrc(),
                               open_clk_info->isGenClkSrcPath(),
                               open_clk_info->pulseClkSense(),
                               delay_zero, 0.0, nullptr,
                               open_clk_info->minMax(),
                               open_clk_info->crprClkPath(sta),
                               sta);
  Tag close_tag(scene,
                0,
                close_rf,
                close_min_max,
                &close_clk_info,
                open_tag->isClock(),
                open_tag->inputDelay(),
                open_tag->isSegmentStart(),
                open_tag->states(),
                false);
  debugPrint(sta->debug(), "mpw", 3, " open  {}",
             open_tag->to_string(sta));
  debugPrint(sta->debug(), "mpw", 3, " close {}",
             close_tag.to_string(sta));
  VertexPathIterator close_iter(open_path_->vertex(sta), scene, close_min_max,
                                close_rf, sta);
  while (close_iter.hasNext()) {
    Path *close_path = close_iter.next();
    if (Tag::matchNoPathAp(close_path->tag(sta), &close_tag)) {
      debugPrint(sta->debug(), "mpw", 3, " match {}",
                 close_path->tag(sta)->to_string(sta));
      return close_path;
    }
  }
  return nullptr;
}

Arrival
MinPulseWidthCheck::openArrival(const StaState *) const
{
  return open_path_->arrival();
}

Arrival
MinPulseWidthCheck::closeArrival(const StaState *sta) const
{
  Path *close = closePath(sta);
  return close->arrival();
}

Arrival
MinPulseWidthCheck::openDelay(const StaState *sta) const
{
  return delayDiff(openArrival(sta), openClkEdge(sta)->time(), sta);
}

Arrival
MinPulseWidthCheck::closeDelay(const StaState *sta) const
{
  return delayDiff(closeArrival(sta), closeClkEdge(sta)->time(), sta);
}

const ClockEdge *
MinPulseWidthCheck::openClkEdge(const StaState *sta) const
{
  return open_path_->clkEdge(sta->search());
}

const ClockEdge *
MinPulseWidthCheck::closeClkEdge(const StaState *sta) const
{
  Tag *open_tag = open_path_->tag(sta);
  const ClkInfo *open_clk_info = open_tag->clkInfo();
  return open_clk_info->clkEdge()->opposite();
}

float
MinPulseWidthCheck::closeOffset(const StaState *sta) const
{
  const ClockEdge *open_clk_edge = openClkEdge(sta);
  const ClockEdge *close_clk_edge = closeClkEdge(sta);
  if (open_clk_edge->time() > close_clk_edge->time())
    return open_clk_edge->clock()->period();
  else
    return 0.0;
}

Arrival
MinPulseWidthCheck::width(const StaState *sta) const
{
  Arrival close_with_offset = delaySum(closeArrival(sta),
                                       closeOffset(sta),
                                       sta);
  Arrival minus_open = delayDiff(close_with_offset, open_path_->arrival(), sta);
  return delaySum(minus_open, checkCrpr(sta), sta);
}

float
MinPulseWidthCheck::minWidth(const StaState *sta) const
{
  float min_width;
  bool exists;
  minPulseWidth(open_path_, sta, min_width, exists);
  return min_width;
}

// Precedence:
//  set_min_pulse_width SDC command
//  SDF annotation
//  Liberty library
//    port min_pulse_width_low/high
//    min_pulse_width timing group
static void
minPulseWidth(const Path *path,
              const StaState *sta,
              // Return values.
              float &min_width,
              bool &exists)
{
  Pin *pin = path->pin(sta);
  const Clock *clk = path->clock(sta);
  const RiseFall *rf = path->transition(sta);
  const Sdc *sdc = path->sdc(sta);
  // set_min_pulse_width command.
  sdc->minPulseWidth(pin, clk, rf, min_width, exists);
  if (!exists) {
    const MinMax *min_max = path->minMax(sta);
    DcalcAPIndex dcalc_ap = path->dcalcAnalysisPtIndex(sta);
    Vertex *vertex = path->vertex(sta);
    Graph *graph = sta->graph();
    Edge *edge;
    TimingArc *arc;
    graph->minPulseWidthArc(vertex, rf, edge, arc);
    if (edge) {
      min_width = delayAsFloat(graph->arcDelay(edge, arc, dcalc_ap), min_max, sta);
      exists = true;
    }
  }
}

Crpr
MinPulseWidthCheck::checkCrpr(const StaState *sta) const
{
  CheckCrpr *check_crpr = sta->search()->checkCrpr();
  Path *close = closePath(sta);
  if (close)
    return check_crpr->checkCrpr(openPath(), close);
  else
    return 0.0;
}

Slack
MinPulseWidthCheck::slack(const StaState *sta) const
{
  return delayDiff(width(sta), minWidth(sta), sta);
}

Scene *
MinPulseWidthCheck::scene(const StaState *sta) const
{
  return open_path_->scene(sta);
}

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

MinPulseWidthSlackLess::MinPulseWidthSlackLess(const StaState *sta) :
  sta_(sta)
{
}

bool
MinPulseWidthSlackLess::operator()(const MinPulseWidthCheck &check1,
                                   const MinPulseWidthCheck &check2) const
{
  Slack slack1 = check1.slack(sta_);
  Slack slack2 = check2.slack(sta_);
  const Pin *pin1 = check1.pin(sta_);
  const Pin *pin2 = check2.pin(sta_);
  return delayLess(slack1, slack2, sta_)
    || (delayEqual(slack1, slack2, sta_)
        // Break ties for the sake of regression stability.
        && (sta_->network()->pinLess(pin1, pin2)
            || (pin1 == pin2
                && check1.openPath()->rfIndex(sta_)
                < check2.openPath()->rfIndex(sta_))));
}

} // namespace sta
