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

#include <cstddef>

#include "Clock.hh"
#include "ContainerHelpers.hh"
#include "Delay.hh"
#include "Graph.hh"
#include "GraphDelayCalc.hh"
#include "Liberty.hh"
#include "Mode.hh"
#include "Network.hh"
#include "NetworkClass.hh"
#include "Sdc.hh"
#include "SdcClass.hh"
#include "Search.hh"
#include "SearchPred.hh"

namespace sta {

CheckMinPeriods::CheckMinPeriods(StaState *sta) :
  heap_(0, MinPeriodSlackLess(sta)),
  sta_(sta)
{
}

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

MinPeriodCheckSeq &
CheckMinPeriods::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_, MinPeriodSlackLess(sta_));
  else
    checks_ = heap_.extract();
  return checks_;
}

void
CheckMinPeriods::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
CheckMinPeriods::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
CheckMinPeriods::checkVertex(Vertex *vertex,
                             bool violators,
                             const SceneSeq &scenes)
{
  MinPeriodCheck min_check = check(vertex, scenes);
  if (!min_check.isNull()) {
    if (violators) {
      if (delayLess(min_check.slack(sta_), 0.0, sta_))
        checks_.push_back(min_check);
    }
    else
      heap_.insert(min_check);
  }
}

MinPeriodCheck
CheckMinPeriods::check(Vertex *vertex,
                       const SceneSeq &scenes)
{
  Search *search = sta_->search();
  GraphDelayCalc *graph_dcalc = sta_->graphDelayCalc();
  MinPeriodCheck min_slack_check;
  Pin *pin = vertex->pin();
  for (const Scene *scene : scenes) {
    const Mode *mode = scene->mode();
    if (isClkEnd(vertex, mode)) {
      float min_period;
      bool exists;
      graph_dcalc->minPeriod(pin, scene, min_period, exists);
      if (exists) {
        const ClockSet clks = search->clocks(vertex, mode);
        for (Clock *clk : clks) {
          MinPeriodCheck check(pin, clk, scene);
          Slack slack = check.slack(sta_);
          if (min_slack_check.isNull()
              || delayLess(slack, min_slack_check.slack(sta_), sta_))
            min_slack_check = check;
        }
      }
    }
  }
  return min_slack_check;
}

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

MinPeriodCheck::MinPeriodCheck(Pin *pin,
                               Clock *clk,
                               const Scene *scene) :
  pin_(pin),
  clk_(clk),
  scene_(scene)
{
}

MinPeriodCheck::MinPeriodCheck() :
  pin_(nullptr),
  clk_(nullptr),
  scene_(nullptr)
{
}

float
MinPeriodCheck::period() const
{
  return clk_->period();
}

float
MinPeriodCheck::minPeriod(const StaState *sta) const
{
  GraphDelayCalc *graph_dcalc = sta->graphDelayCalc();
  float min_period;
  bool exists;
  graph_dcalc->minPeriod(pin_, scene_, min_period, exists);
  return min_period;
}

Slack
MinPeriodCheck::slack(const StaState *sta) const
{
  return clk_->period() - minPeriod(sta);
}

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

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

bool
MinPeriodSlackLess::operator()(const MinPeriodCheck &check1,
                               const MinPeriodCheck &check2) const
{
  Slack slack1 = check1.slack(sta_);
  Slack slack2 = check2.slack(sta_);
  const Pin *pin1 = check1.pin();
  const Pin *pin2 = check2.pin();
  return delayLess(slack1, slack2, sta_)
    // Break ties based on pin and clock names.
    || (delayEqual(slack1, slack2, sta_)
        && (sta_->network()->pinLess(pin1, pin2)
            || (pin1 == pin2
                && ClockNameLess()(check1.clk(),
                                   check2.clk()))));
}

} // namespace sta
