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

#include <cstdio>
#include <set>

#include "ContainerHelpers.hh"
#include "Network.hh"

namespace sta {

using HpinDrvrLoads = std::set<HpinDrvrLoad*, HpinDrvrLoadLess>;

static void
visitPinsAboveNet2(const Pin *hpin,
                   Net *above_net,
                   NetSet &visited,
                   HpinDrvrLoads &above_drvrs,
                   HpinDrvrLoads &above_loads,
                   PinSet *hpin_path,
                   const Network *network);
static void
visitPinsBelowNet2(const Pin *hpin,
                   Net *above_net,
                   Net *below_net,
                   NetSet &visited,
                   HpinDrvrLoads &below_drvrs,
                   HpinDrvrLoads &below_loads,
                   PinSet *hpin_path,
                   const Network *network);
static void
visitHpinDrvrLoads(HpinDrvrLoads &drvrs,
                   HpinDrvrLoads &loads,
                   HpinDrvrLoadVisitor *visitor);

void
visitHpinDrvrLoads(const Pin *pin,
                   const Network *network,
                   HpinDrvrLoadVisitor *visitor)
{
  NetSet visited(network);
  HpinDrvrLoads above_drvrs;
  HpinDrvrLoads above_loads;
  PinSet hpin_path(network);
  Net *above_net = network->net(pin);
  if (above_net) {
    visitPinsAboveNet2(pin, above_net, visited, 
                       above_drvrs, above_loads,
                       &hpin_path, network);
  }

  // Search down from hpin terminal.
  HpinDrvrLoads below_drvrs;
  HpinDrvrLoads below_loads;
  Term *term = network->term(pin);
  if (term) {
    Net *below_net = network->net(term);
    if (below_net)
      visitPinsBelowNet2(pin, above_net, below_net, visited,
                         below_drvrs, below_loads,
                         &hpin_path, network);
  }
  if (network->isHierarchical(pin)) {
    visitHpinDrvrLoads(above_drvrs, below_loads, visitor);
    visitHpinDrvrLoads(below_drvrs, above_loads, visitor);
  }
  else {
    if (network->isDriver(pin)) {
      HpinDrvrLoad drvr(const_cast<Pin*>(pin), nullptr, &hpin_path, nullptr);
      HpinDrvrLoads drvrs;
      drvrs.insert(&drvr);
      visitHpinDrvrLoads(drvrs, below_loads, visitor);
      visitHpinDrvrLoads(drvrs, above_loads, visitor);
    }
    // Bidirects are drivers and loads.
    if (network->isLoad(pin)) {
      HpinDrvrLoad load(nullptr, const_cast<Pin*>(pin), nullptr, &hpin_path);
      HpinDrvrLoads loads;
      loads.insert(&load);
      visitHpinDrvrLoads(below_drvrs, loads, visitor);
      visitHpinDrvrLoads(above_drvrs, loads, visitor);
    }
  }
  deleteContents(above_drvrs);
  deleteContents(above_loads);
  deleteContents(below_drvrs);
  deleteContents(below_loads);
}

static void
visitPinsAboveNet2(const Pin *hpin,
                   Net *above_net,
                   NetSet &visited,
                   HpinDrvrLoads &above_drvrs,
                   HpinDrvrLoads &above_loads,
                   PinSet *hpin_path,
                   const Network *network)
{
  visited.insert(above_net);
  // Visit above net pins.
  NetPinIterator *pin_iter = network->pinIterator(above_net);
  while (pin_iter->hasNext()) {
    const Pin *above_pin = pin_iter->next();
    if (above_pin != hpin) {
      if (network->isDriver(above_pin)) {
        HpinDrvrLoad *drvr = new HpinDrvrLoad(above_pin, nullptr,
                                              hpin_path, nullptr);
        above_drvrs.insert(drvr);
      }
      if (network->isLoad(above_pin)) {
        HpinDrvrLoad *load = new HpinDrvrLoad(nullptr, above_pin,
                                              nullptr, hpin_path);
        above_loads.insert(load);
      }
      Term *above_term = network->term(above_pin);
      if (above_term) {
        Net *above_net1 = network->net(above_term);
        if (above_net1 && !visited.contains(above_net1)) {
          hpin_path->insert(above_pin);
          visitPinsAboveNet2(above_pin, above_net1, visited,
                             above_drvrs, above_loads,
                             hpin_path, network);
          hpin_path->erase(above_pin);
        }
      }
    }
  }
  delete pin_iter;

  // Search up from net terminals.
  NetTermIterator *term_iter = network->termIterator(above_net);
  while (term_iter->hasNext()) {
    Term *term = term_iter->next();
    Pin *above_pin = network->pin(term);
    if (above_pin 
        && above_pin != hpin) {
      Net *above_net1 = network->net(above_pin);
      if (above_net1 && !visited.contains(above_net1)) {
        hpin_path->insert(above_pin);
        visitPinsAboveNet2(above_pin, above_net1, visited,
                           above_drvrs, above_loads,
                           hpin_path, network);
        hpin_path->erase(above_pin);
      }

      if (network->isDriver(above_pin)) {
        HpinDrvrLoad *drvr = new HpinDrvrLoad(above_pin, nullptr,
                                              hpin_path, nullptr);
        above_drvrs.insert(drvr);
      }
      if (network->isLoad(above_pin)) {
        HpinDrvrLoad *load = new HpinDrvrLoad(nullptr, above_pin,
                                              nullptr, hpin_path);
        above_loads.insert(load);
      }
    }
  }
  delete term_iter;
}

static void
visitPinsBelowNet2(const Pin *hpin,
                   Net *above_net,
                   Net *below_net,
                   NetSet &visited,
                   HpinDrvrLoads &below_drvrs,
                   HpinDrvrLoads &below_loads,
                   PinSet *hpin_path,
                   const Network *network)
{
  visited.insert(below_net);
  // Visit below net pins.
  NetPinIterator *pin_iter = network->pinIterator(below_net);
  while (pin_iter->hasNext()) {
    const Pin *below_pin = pin_iter->next();
    if (below_pin != hpin) {
      if (above_net && !visited.contains(above_net))
        visitPinsAboveNet2(below_pin, above_net,
                           visited, below_drvrs, below_loads,
                           hpin_path, network);
      if (network->isDriver(below_pin)) {
        HpinDrvrLoad *drvr = new HpinDrvrLoad(below_pin, nullptr,
                                              hpin_path, nullptr);
        below_drvrs.insert(drvr);
      }
      if (network->isLoad(below_pin)) {
        HpinDrvrLoad *load = new HpinDrvrLoad(nullptr, below_pin,
                                              nullptr, hpin_path);
        below_loads.insert(load);
      }
      if (network->isHierarchical(below_pin)) {
        Term *term = network->term(below_pin);
        if (term) {
          Net *below_net1 = network->net(term);
          if (below_net1 && !visited.contains(below_net1)) {
            hpin_path->insert(below_pin);
            visitPinsBelowNet2(below_pin, below_net, below_net1, visited,
                               below_drvrs, below_loads,
                               hpin_path, network);
            hpin_path->erase(below_pin);
          }
        }
      }
    }
  }
  delete pin_iter;

  // Search up from net terminals.
  NetTermIterator *term_iter = network->termIterator(below_net);
  while (term_iter->hasNext()) {
    Term *term = term_iter->next();
    Pin *above_pin = network->pin(term);
    if (above_pin
        && above_pin != hpin) {
      Net *above_net1 = network->net(above_pin);
      if (above_net1 && !visited.contains(above_net1)) {
        hpin_path->insert(above_pin);
        visitPinsAboveNet2(above_pin, above_net1, 
                           visited, below_drvrs, below_loads,
                           hpin_path, network);
        hpin_path->erase(above_pin);
      }
    }
  }
  delete term_iter;
}

static void
visitHpinDrvrLoads(HpinDrvrLoads &drvrs,
                   HpinDrvrLoads &loads,
                   HpinDrvrLoadVisitor *visitor)
{
  for (HpinDrvrLoad *drvr : drvrs) {
    for (HpinDrvrLoad *load : loads) {
      HpinDrvrLoad clone(drvr->drvr(),
                         load->load(),
                         drvr->hpinsFromDrvr(),
                         load->hpinsToLoad());
      visitor->visit(&clone);
    }
  }
}

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

HpinDrvrLoad::HpinDrvrLoad(const Pin *drvr,
                           const Pin *load,
                           PinSet *hpins_from_drvr,
                           PinSet *hpins_to_load) :
  drvr_(drvr),
  load_(load),
  hpins_from_drvr_(hpins_from_drvr ? new PinSet(*hpins_from_drvr) : nullptr),
  hpins_to_load_(hpins_to_load ? new PinSet(*hpins_to_load) : nullptr)
{
}

HpinDrvrLoad::HpinDrvrLoad(const Pin *drvr,
                           const Pin *load) :
  drvr_(drvr),
  load_(load)
{
}

HpinDrvrLoad::~HpinDrvrLoad()
{
  delete hpins_from_drvr_;
  delete hpins_to_load_;
}

void 
HpinDrvrLoad::report(const Network *network)
{
  Report *report = network->report();
  std::string line = sta::format("{} -> {}: ",
                                 drvr_ ? network->pathName(drvr_) : "-",
                                 load_ ? network->pathName(load_) : "-");
  for (const Pin *pin : *hpins_from_drvr_)
    line += sta::format("{} ", network->pathName(pin));
  line += "* ";
  for (const Pin *pin : *hpins_to_load_)
    line += sta::format("{} ", network->pathName(pin));
  report->report(line);
}

void 
HpinDrvrLoad::setDrvr(const Pin *drvr)
{
  drvr_ = drvr;
}

bool
HpinDrvrLoadLess::operator()(const HpinDrvrLoad *drvr_load1,
                             const HpinDrvrLoad *drvr_load2) const
{
  const Pin *load1 = drvr_load1->load();
  const Pin *load2 = drvr_load2->load();
  if (load1 == load2) {
    const Pin *drvr1 = drvr_load1->drvr();
    const Pin *drvr2 = drvr_load2->drvr();
    return drvr1 < drvr2;
  }
  else
    return load1 < load2;
}

} // namespace sta
