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

#include <string_view>

#include "Graph.hh"
#include "GraphCmp.hh"
#include "Liberty.hh"
#include "Network.hh"
#include "Report.hh"
#include "Sdc.hh"
#include "StringUtil.hh"
#include "TimingArc.hh"
#include "TimingRole.hh"

namespace sta {

class ReportAnnotated : public StaState
{
public:
  ReportAnnotated(const Scene *scene,
                  bool report_cells,
                  bool report_nets,
                  bool report_in_ports,
                  bool report_out_ports,
                  int max_lines,
                  bool report_annotated,
                  bool report_unannotated,
                  bool constant_arcs,
                  StaState *sta);
  ReportAnnotated(const Scene *scene,
                  bool report_setup,
                  bool report_hold,
                  bool report_recovery,
                  bool report_removal,
                  bool report_nochange,
                  bool report_width,
                  bool report_period,
                  bool report_max_skew,
                  int max_lines,
                  bool report_annotated,
                  bool report_unannotated,
                  bool constant_arcs,
                  StaState *sta);
  void reportDelayAnnotation();
  void reportCheckAnnotation();

protected:
  enum class CountIndex {
    count_internal_net = TimingRole::index_max,
    count_input_net,
    count_output_net,
  };
  static const int count_index_max =
      static_cast<int>(CountIndex::count_output_net) + 1;
  static int count_delay;

  void init();
  void findCounts();
  void findPeriodCount(Pin *pin);
  void reportDelayCounts();
  void reportCheckCounts();
  void reportArcs();
  void reportArcs(const std::string &header,
                  bool report_annotated,
                  PinSet &pins);
  void reportArcs(Vertex *vertex,
                  bool report_annotated,
                  int &i);
  void reportPeriodArcs(const Pin *pin,
                        bool report_annotated,
                        int &i);
  void reportCount(std::string_view title,
                   int index,
                   int &total,
                   int &annotated_total);
  void reportCheckCount(const TimingRole *role,
                        int &total,
                        int &annotated_total);
  int roleIndex(const TimingRole *role,
                const Pin *from_pin,
                const Pin *to_pin);
  bool delayAnnotated(Edge *edge);

  const Scene *scene_;
  int max_lines_;
  bool report_annotated_;
  bool report_unannotated_;
  bool report_constant_arcs_;

  int edge_count_[count_index_max];
  int edge_annotated_count_[count_index_max];
  int edge_constant_count_[count_index_max];
  int edge_constant_annotated_count_[count_index_max];
  bool report_role_[count_index_max];
  PinSet unannotated_pins_;
  PinSet annotated_pins_;
};

int ReportAnnotated::count_delay;

void
reportAnnotatedDelay(const Scene *scene,
                     bool report_cells,
                     bool report_nets,
                     bool report_in_ports,
                     bool report_out_ports,
                     int max_lines,
                     bool report_annotated,
                     bool report_unannotated,
                     bool report_constant_arcs,
                     StaState *sta)
{
  ReportAnnotated report(scene, report_cells, report_nets, report_in_ports,
                         report_out_ports, max_lines, report_annotated,
                         report_unannotated, report_constant_arcs, sta);
  report.reportDelayAnnotation();
}

ReportAnnotated::ReportAnnotated(const Scene *scene,
                                 bool report_cells,
                                 bool report_nets,
                                 bool report_in_ports,
                                 bool report_out_ports,
                                 int max_lines,
                                 bool report_annotated,
                                 bool report_unannotated,
                                 bool report_constant_arcs,
                                 StaState *sta) :
  StaState(sta),
  scene_(scene),
  max_lines_(max_lines),
  report_annotated_(report_annotated),
  report_unannotated_(report_unannotated),
  report_constant_arcs_(report_constant_arcs),
  unannotated_pins_(sta->network()),
  annotated_pins_(sta->network())
{
  init();
  report_role_[TimingRole::sdfIopath()->index()] = report_cells;
  report_role_[static_cast<int>(CountIndex::count_internal_net)] = report_nets;
  report_role_[static_cast<int>(CountIndex::count_input_net)] = report_in_ports;
  report_role_[static_cast<int>(CountIndex::count_output_net)] = report_out_ports;
}

void
ReportAnnotated::reportDelayAnnotation()
{
  findCounts();
  reportDelayCounts();
  reportArcs();
}

void
ReportAnnotated::reportDelayCounts()
{
  report_->report(
      "                                                          Not   ");
  report_->report(
      "Delay type                        Total    Annotated   Annotated");
  report_->report(
      "----------------------------------------------------------------");

  int total = 0;
  int annotated_total = 0;
  reportCount("cell arcs", count_delay, total, annotated_total);
  reportCount("internal net arcs", static_cast<int>(CountIndex::count_internal_net),
              total, annotated_total);
  reportCount("net arcs from primary inputs",
              static_cast<int>(CountIndex::count_input_net), total, annotated_total);
  reportCount("net arcs to primary outputs",
              static_cast<int>(CountIndex::count_output_net), total,
              annotated_total);
  report_->report(
      "----------------------------------------------------------------");
  report_->report("{:<28} {:10}  {:10}  {:10}", " ", total, annotated_total,
                  total - annotated_total);
}

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

void
reportAnnotatedCheck(const Scene *scene,
                     bool report_setup,
                     bool report_hold,
                     bool report_recovery,
                     bool report_removal,
                     bool report_nochange,
                     bool report_width,
                     bool report_period,
                     bool report_max_skew,
                     int max_lines,
                     bool report_annotated,
                     bool report_unannotated,
                     bool report_constant_arcs,
                     StaState *sta)

{
  ReportAnnotated report(scene, report_setup, report_hold, report_recovery,
                         report_removal, report_nochange, report_width,
                         report_period, report_max_skew, max_lines, report_annotated,
                         report_unannotated, report_constant_arcs, sta);
  report.reportCheckAnnotation();
}

ReportAnnotated::ReportAnnotated(const Scene *scene,
                                 bool report_setup,
                                 bool report_hold,
                                 bool report_recovery,
                                 bool report_removal,
                                 bool report_nochange,
                                 bool report_width,
                                 bool report_period,
                                 bool report_max_skew,
                                 int max_lines,
                                 bool report_annotated,
                                 bool report_unannotated,
                                 bool report_constant_arcs,
                                 StaState *sta) :
  StaState(sta),
  scene_(scene),
  max_lines_(max_lines),
  report_annotated_(report_annotated),
  report_unannotated_(report_unannotated),
  report_constant_arcs_(report_constant_arcs),
  unannotated_pins_(sta->network()),
  annotated_pins_(sta->network())
{
  init();
  report_role_[TimingRole::setup()->index()] = report_setup;
  report_role_[TimingRole::hold()->index()] = report_hold;
  report_role_[TimingRole::recovery()->index()] = report_recovery;
  report_role_[TimingRole::removal()->index()] = report_removal;
  report_role_[TimingRole::nochange()->index()] = report_nochange;
  report_role_[TimingRole::width()->index()] = report_width;
  report_role_[TimingRole::period()->index()] = report_period;
  report_role_[TimingRole::skew()->index()] = report_max_skew;
}

void
ReportAnnotated::reportCheckAnnotation()
{
  findCounts();
  reportCheckCounts();
  reportArcs();
}

void
ReportAnnotated::reportCheckCounts()
{
  report_->report(
      "                                                          Not   ");
  report_->report(
      "Check type                        Total    Annotated   Annotated");
  report_->report(
      "----------------------------------------------------------------");

  int total = 0;
  int annotated_total = 0;
  reportCheckCount(TimingRole::setup(), total, annotated_total);
  reportCheckCount(TimingRole::hold(), total, annotated_total);
  reportCheckCount(TimingRole::recovery(), total, annotated_total);
  reportCheckCount(TimingRole::removal(), total, annotated_total);
  reportCheckCount(TimingRole::nochange(), total, annotated_total);
  reportCheckCount(TimingRole::width(), total, annotated_total);
  reportCheckCount(TimingRole::period(), total, annotated_total);
  reportCheckCount(TimingRole::skew(), total, annotated_total);

  report_->report(
      "----------------------------------------------------------------");
  report_->report("{:<28} {:10}  {:10}  {:10}", " ", total, annotated_total,
                  total - annotated_total);
}

void
ReportAnnotated::reportCheckCount(const TimingRole *role,
                                  int &total,
                                  int &annotated_total)
{
  int index = role->index();
  if (edge_count_[index] > 0) {
    std::string title = sta::format("cell {} arcs", role->to_string());
    reportCount(title, index, total, annotated_total);
  }
}

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

void
ReportAnnotated::init()
{
  count_delay = TimingRole::sdfIopath()->index();
  for (int i = 0; i < count_index_max; i++) {
    edge_count_[i] = 0;
    edge_annotated_count_[i] = 0;
    edge_constant_count_[i] = 0;
    edge_constant_annotated_count_[i] = 0;
    report_role_[i] = false;
  }
}

void
ReportAnnotated::findCounts()
{
  const Sdc *sdc = scene_->sdc();
  VertexIterator vertex_iter(graph_);
  while (vertex_iter.hasNext()) {
    Vertex *from_vertex = vertex_iter.next();
    Pin *from_pin = from_vertex->pin();
    LogicValue from_logic_value;
    bool from_logic_value_exists;
    sdc->logicValue(from_pin, from_logic_value, from_logic_value_exists);
    VertexOutEdgeIterator edge_iter(from_vertex, graph_);
    while (edge_iter.hasNext()) {
      Edge *edge = edge_iter.next();
      const TimingRole *role = edge->role();
      Vertex *to_vertex = edge->to(graph_);
      Pin *to_pin = to_vertex->pin();
      int index = roleIndex(role, from_pin, to_pin);
      LogicValue to_logic_value;
      bool to_logic_value_exists;
      sdc->logicValue(to_pin, to_logic_value, to_logic_value_exists);

      edge_count_[index]++;

      if (from_logic_value_exists || to_logic_value_exists)
        edge_constant_count_[index]++;
      if (report_role_[index]) {
        if (delayAnnotated(edge)) {
          edge_annotated_count_[index]++;
          if (from_logic_value_exists || to_logic_value_exists)
            edge_constant_annotated_count_[index]++;
          if (report_annotated_)
            annotated_pins_.insert(from_pin);
        }
        else {
          if (report_unannotated_)
            unannotated_pins_.insert(from_pin);
        }
      }
    }
    findPeriodCount(from_pin);
  }
}

bool
ReportAnnotated::delayAnnotated(Edge *edge)
{
  TimingArcSet *arc_set = edge->timingArcSet();
  for (TimingArc *arc : arc_set->arcs()) {
    for (const MinMax *min_max : MinMax::range()) {
      DcalcAPIndex ap_index = scene_->dcalcAnalysisPtIndex(min_max);
      if (!graph_->arcDelayAnnotated(edge, arc, ap_index))
        return false;
    }
  }
  return true;
}

int
ReportAnnotated::roleIndex(const TimingRole *role,
                           const Pin *from_pin,
                           const Pin *to_pin)
{
  if (role == TimingRole::wire()) {
    if (network_->isTopLevelPort(from_pin))
      return static_cast<int>(CountIndex::count_input_net);
    else if (network_->isTopLevelPort(to_pin))
      return static_cast<int>(CountIndex::count_output_net);
    else
      return static_cast<int>(CountIndex::count_internal_net);
  }
  else if (role->sdfRole() == TimingRole::sdfIopath())
    return count_delay;
  else {
    if (role->isTimingCheck()
        && (role == TimingRole::latchSetup() || role == TimingRole::latchHold()))
      role = role->genericRole();
    return role->index();
  }
}

// Width and period checks are not edges in the graph so
// they require special handling.
void
ReportAnnotated::findPeriodCount(Pin *pin)
{
  LibertyPort *port = network_->libertyPort(pin);
  if (port) {
    DcalcAPIndex ap_index = 0;
    float value;
    bool exists, annotated;
    int period_index = TimingRole::period()->index();
    if (report_role_[period_index]) {
      port->minPeriod(value, exists);
      if (exists) {
        edge_count_[period_index]++;
        graph_->periodCheckAnnotation(pin, ap_index, value, annotated);
        if (annotated) {
          edge_annotated_count_[period_index]++;
          if (report_annotated_)
            annotated_pins_.insert(pin);
        }
        else {
          if (report_unannotated_)
            unannotated_pins_.insert(pin);
        }
      }
    }
  }
}

void
ReportAnnotated::reportCount(std::string_view title,
                             int index,
                             int &total,
                             int &annotated_total)
{
  if (report_role_[index]) {
    int count = edge_count_[index];
    int annotated_count = edge_annotated_count_[index];
    report_->report("{:<28} {:10}  {:10}  {:10}", title, count, annotated_count,
                    count - annotated_count);
    if (report_constant_arcs_) {
      int const_count = edge_constant_count_[index];
      int const_annotated_count = edge_constant_annotated_count_[index];
      report_->report("{:<28} {:10}  {:10}  {:10}", "constant arcs", "",
                      const_annotated_count, const_count - const_annotated_count);
    }
    total += count;
    annotated_total += annotated_count;
  }
}

void
ReportAnnotated::reportArcs()
{
  if (report_annotated_)
    reportArcs("Annotated Arcs", true, annotated_pins_);
  if (report_unannotated_)
    reportArcs("Unannotated Arcs", false, unannotated_pins_);
}

void
ReportAnnotated::reportArcs(const std::string &header,
                            bool report_annotated,
                            PinSet &pins)
{
  report_->reportBlankLine();
  report_->reportLine(header);
  PinSeq pins1 = sortByPathName(&pins, network_);
  int i = 0;
  for (const Pin *pin : pins1) {
    Vertex *vertex, *bidirect_drvr_vertex;
    graph_->pinVertices(pin, vertex, bidirect_drvr_vertex);
    reportArcs(vertex, report_annotated, i);
    if (bidirect_drvr_vertex)
      reportArcs(bidirect_drvr_vertex, report_annotated, i);
    reportPeriodArcs(pin, report_annotated, i);
    if (max_lines_ != 0 && i > max_lines_)
      break;
  }
}

void
ReportAnnotated::reportArcs(Vertex *vertex,
                            bool report_annotated,
                            int &i)
{
  const Pin *from_pin = vertex->pin();
  VertexOutEdgeIterator edge_iter(vertex, graph_);
  while (edge_iter.hasNext() && (max_lines_ == 0 || i < max_lines_)) {
    Edge *edge = edge_iter.next();
    const TimingRole *role = edge->role();
    const Pin *to_pin = edge->to(graph_)->pin();
    if (delayAnnotated(edge) == report_annotated
        && report_role_[roleIndex(role, from_pin, to_pin)]) {
      std::string_view role_name;
      if (role->isTimingCheck())
        role_name = role->to_string();
      else if (role->isWire()) {
        if (network_->isTopLevelPort(from_pin))
          role_name = "primary input net";
        else if (network_->isTopLevelPort(to_pin))
          role_name = "primary output net";
        else
          role_name = "internal net";
      }
      else
        role_name = "delay";
      const std::string &cond = edge->timingArcSet()->sdfCond();
      report_->report(" {:<18} {} -> {} {}", role_name, network_->pathName(from_pin),
                      network_->pathName(to_pin), cond);
      i++;
    }
  }
}

void
ReportAnnotated::reportPeriodArcs(const Pin *pin,
                                  bool report_annotated,
                                  int &i)
{
  LibertyPort *port = network_->libertyPort(pin);
  if (port) {
    DcalcAPIndex ap_index = 0;
    int period_index = TimingRole::period()->index();
    if (report_role_[period_index] && (max_lines_ == 0 || i < max_lines_)) {
      float value;
      bool exists, annotated;
      port->minPeriod(value, exists);
      if (exists) {
        edge_count_[period_index]++;
        graph_->periodCheckAnnotation(pin, ap_index, value, annotated);
        if (annotated == report_annotated) {
          report_->report(" {:<18} {}", "period", network_->pathName(pin));
          i++;
        }
      }
    }
  }
}

}  // namespace sta
