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

#include "DmpCeff.hh"
#include "GraphDelayCalc.hh"
#include "Liberty.hh"
#include "Network.hh"
#include "Parasitics.hh"
#include "PortDirection.hh"
#include "Sdc.hh"
#include "TableModel.hh"
#include "TimingArc.hh"

namespace sta {

// PiElmore parasitic delay calculator using Dartu/Menezes/Pileggi
// effective capacitance and elmore delay.
class DmpCeffElmoreDelayCalc : public DmpCeffDelayCalc
{
public:
  DmpCeffElmoreDelayCalc(StaState *sta);
  ArcDelayCalc *copy() override;
  std::string_view name() const override { return "dmp_ceff_elmore"; }
  ArcDcalcResult inputPortDelay(const Pin *port_pin,
                                float in_slew,
                                const RiseFall *rf,
                                const Parasitic *parasitic,
                                const LoadPinIndexMap &load_pin_index_map,
                                const Scene *scene,
                                const MinMax *min_max) override;

protected:
  void loadDelaySlew(const Pin *load_pin,
                     double drvr_slew,
                     const RiseFall *rf,
                     const LibertyLibrary *drvr_library,
                     const Parasitic *parasitic,
                     // Return values.
                     double &wire_delay,
                     double &load_slew) override;
};

ArcDelayCalc *
makeDmpCeffElmoreDelayCalc(StaState *sta)
{
  return new DmpCeffElmoreDelayCalc(sta);
}

DmpCeffElmoreDelayCalc::DmpCeffElmoreDelayCalc(StaState *sta) :
  DmpCeffDelayCalc(sta)
{
}

ArcDelayCalc *
DmpCeffElmoreDelayCalc::copy()
{
  return new DmpCeffElmoreDelayCalc(*this);
}

ArcDcalcResult
DmpCeffElmoreDelayCalc::inputPortDelay(const Pin *,
                                       float in_slew,
                                       const RiseFall *rf,
                                       const Parasitic *parasitic,
                                       const LoadPinIndexMap &load_pin_index_map,
                                       const Scene *scene,
                                       const MinMax *min_max)
{
  Parasitics *parasitics = scene->parasitics(min_max);
  ArcDcalcResult dcalc_result(load_pin_index_map.size());
  LibertyLibrary *drvr_library = network_->defaultLibertyLibrary();
  for (auto [load_pin, load_idx] : load_pin_index_map) {
    double wire_delay = 0.0;
    double load_slew = in_slew;
    bool elmore_exists = false;
    float elmore = 0.0;
    if (parasitic)
      parasitics->findElmore(parasitic, load_pin, elmore, elmore_exists);
    if (elmore_exists)
      // Input port with no external driver.
      dspfWireDelaySlew(load_pin, rf, in_slew, elmore, wire_delay, load_slew);
    thresholdAdjust(load_pin, drvr_library, rf, wire_delay, load_slew);
    dcalc_result.setWireDelay(load_idx, wire_delay);
    dcalc_result.setLoadSlew(load_idx, load_slew);
  }
  return dcalc_result;
}

void
DmpCeffElmoreDelayCalc::loadDelaySlew(const Pin *load_pin,
                                      double drvr_slew,
                                      const RiseFall *rf,
                                      const LibertyLibrary *drvr_library,
                                      const Parasitic *parasitic,
                                      // Return values.
                                      double &wire_delay,
                                      double &load_slew)
{
  wire_delay = 0.0;
  load_slew = drvr_slew;
  bool elmore_exists = false;
  float elmore = 0.0;
  if (parasitic)
    parasitics_->findElmore(parasitic, load_pin, elmore, elmore_exists);
  if (elmore_exists) {
    if (auto r = loadDelaySlewElmore(load_pin, elmore)) {
      wire_delay = r->first;
      load_slew = r->second;
    }
  }
  thresholdAdjust(load_pin, drvr_library, rf, wire_delay, load_slew);
}

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

// PiPoleResidue parasitic delay calculator using Dartu/Menezes/Pileggi
// effective capacitance and two poles/residues.
class DmpCeffTwoPoleDelayCalc : public DmpCeffDelayCalc
{
public:
  DmpCeffTwoPoleDelayCalc(StaState *sta);
  ArcDelayCalc *copy() override;
  std::string_view name() const override { return "dmp_ceff_two_pole"; }
  Parasitic *findParasitic(const Pin *drvr_pin,
                           const RiseFall *rf,
                           const Scene *scene,
                           const MinMax *min_max) override;
  ArcDcalcResult inputPortDelay(const Pin *port_pin,
                                float in_slew,
                                const RiseFall *rf,
                                const Parasitic *parasitic,
                                const LoadPinIndexMap &load_pin_index_map,
                                const Scene *scene,
                                const MinMax *min_max) override;
  ArcDcalcResult gateDelay(const Pin *drvr_pin,
                           const TimingArc *arc,
                           const Slew &in_slew,
                           float load_cap,
                           const Parasitic *parasitic,
                           const LoadPinIndexMap &load_pin_index_map,
                           const Scene *scene,
                           const MinMax *min_max) override;

protected:
  void loadDelaySlew(const Pin *load_pin,
                     double drvr_slew,
                     const RiseFall *rf,
                     const LibertyLibrary *drvr_library,
                     const Parasitic *parasitic,
                     // Return values.
                     double &wire_delay,
                     double &load_slew) override;

private:
  void loadDelay(double drvr_slew,
                 Parasitic *pole_residue,
                 double p1,
                 double k1,
                 double &wire_delay,
                 double &load_slew);
  float loadDelay(double vth,
                  double p1,
                  double p2,
                  double k1,
                  double k2,
                  double B,
                  double k1_p1_2,
                  double k2_p2_2,
                  double tt,
                  double y_tt);

  bool parasitic_is_pole_residue_{false};
  float vth_{0.0F};
  float vl_{0.0F};
  float vh_{0.0F};
  float slew_derate_{0.0F};
};

ArcDelayCalc *
makeDmpCeffTwoPoleDelayCalc(StaState *sta)
{
  return new DmpCeffTwoPoleDelayCalc(sta);
}

DmpCeffTwoPoleDelayCalc::DmpCeffTwoPoleDelayCalc(StaState *sta) :
  DmpCeffDelayCalc(sta)
{
}

ArcDelayCalc *
DmpCeffTwoPoleDelayCalc::copy()
{
  return new DmpCeffTwoPoleDelayCalc(*this);
}

Parasitic *
DmpCeffTwoPoleDelayCalc::findParasitic(const Pin *drvr_pin,
                                       const RiseFall *rf,
                                       const Scene *scene,
                                       const MinMax *min_max)
{
  Parasitic *parasitic = nullptr;
  const Sdc *sdc = scene->sdc();
  Parasitics *parasitics = scene->parasitics(min_max);
  if (parasitics == nullptr
      // set_load net has precedence over parasitics.
      || network_->direction(drvr_pin)->isInternal())
    return nullptr;
  // Prefer PiPoleResidue.
  parasitic = parasitics->findPiPoleResidue(drvr_pin, rf, min_max);
  if (parasitic)
    return parasitic;
  parasitic = parasitics->findPiElmore(drvr_pin, rf, min_max);
  if (parasitic)
    return parasitic;
  Parasitic *parasitic_network =
    parasitics->findParasiticNetwork(drvr_pin);
  if (parasitic_network) {
    parasitic = parasitics->reduceToPiPoleResidue2(parasitic_network, drvr_pin, rf,
                                                   scene, min_max);
    if (parasitic)
      return parasitic;
  }
  Wireload *wireload = sdc->wireload(min_max);
  if (wireload) {
    float pin_cap, wire_cap, fanout;
    bool has_wire_cap;
    graph_delay_calc_->netCaps(drvr_pin, rf, scene, min_max, pin_cap, wire_cap,
                               fanout, has_wire_cap);
    parasitic = parasitics->estimatePiElmore(drvr_pin, rf, wireload,
                                             fanout, pin_cap,
                                             scene, min_max);
  }
  return parasitic;
}

ArcDcalcResult
DmpCeffTwoPoleDelayCalc::inputPortDelay(const Pin *,
                                        float in_slew,
                                        const RiseFall *rf,
                                        const Parasitic *parasitic,
                                        const LoadPinIndexMap &load_pin_index_map,
                                        const Scene *scene,
                const MinMax *min_max)
{
  const Parasitics *parasitics = scene->parasitics(min_max);
  ArcDcalcResult dcalc_result(load_pin_index_map.size());
  double wire_delay = 0.0;
  double load_slew = in_slew;
  LibertyLibrary *drvr_library = network_->defaultLibertyLibrary();
  for (const auto [load_pin, load_idx] : load_pin_index_map) {
    if (parasitics->isPiPoleResidue(parasitic)) {
      const Parasitic *pole_residue = parasitics->findPoleResidue(parasitic, load_pin);
      if (pole_residue) {
        size_t pole_count = parasitics->poleResidueCount(pole_residue);
        if (pole_count >= 1) {
          ComplexFloat pole1, residue1;
          // Find the 1st (elmore) pole.
          parasitics->poleResidue(pole_residue, 0, pole1, residue1);
          if (pole1.imag() == 0.0
              && residue1.imag() == 0.0) {
            float p1 = pole1.real();
            float elmore = 1.0F / p1;
            dspfWireDelaySlew(load_pin, rf, in_slew, elmore, wire_delay, load_slew);
            thresholdAdjust(load_pin, drvr_library, rf, wire_delay, load_slew);
          }
        }
      }
    }
    dcalc_result.setWireDelay(load_idx, wire_delay);
    dcalc_result.setLoadSlew(load_idx, load_slew);
  }
  return dcalc_result;
}

ArcDcalcResult
DmpCeffTwoPoleDelayCalc::gateDelay(const Pin *drvr_pin,
                                   const TimingArc *arc,
                                   const Slew &in_slew,
                                   float load_cap,
                                   const Parasitic *parasitic,
                                   const LoadPinIndexMap &load_pin_index_map,
                                   const Scene *scene,
                                   const MinMax *min_max)
{
  parasitics_ = scene->parasitics(min_max);
  const LibertyLibrary *drvr_library = arc->to()->libertyLibrary();
  const RiseFall *rf = arc->toEdge()->asRiseFall();
  vth_ = drvr_library->outputThreshold(rf);
  vl_ = drvr_library->slewLowerThreshold(rf);
  vh_ = drvr_library->slewUpperThreshold(rf);
  slew_derate_ = drvr_library->slewDerateFromLibrary();
  return DmpCeffDelayCalc::gateDelay(drvr_pin, arc, in_slew, load_cap, parasitic,
                                     load_pin_index_map, scene, min_max) ;
}

void
DmpCeffTwoPoleDelayCalc::loadDelaySlew(const Pin *load_pin,
                                       double drvr_slew,
                                       const RiseFall *rf,
                                       const LibertyLibrary *drvr_library,
                                       const Parasitic *parasitic,
                                       // Return values.
                                       double &wire_delay,
                                       double &load_slew)
{
  parasitic_is_pole_residue_ = parasitics_->isPiPoleResidue(parasitic);
  // Should handle PiElmore parasitic.
  wire_delay = 0.0;
  load_slew = drvr_slew;
  Parasitic *pole_residue = nullptr;
  if (parasitic_is_pole_residue_)
    pole_residue = parasitics_->findPoleResidue(parasitic, load_pin);
  if (pole_residue) {
    size_t pole_count = parasitics_->poleResidueCount(pole_residue);
    if (pole_count >= 1) {
      ComplexFloat pole1, residue1;
      // Find the 1st (elmore) pole.
      parasitics_->poleResidue(pole_residue, 0, pole1, residue1);
      if (pole1.imag() == 0.0
          && residue1.imag() == 0.0) {
        float p1 = pole1.real();
        float k1 = residue1.real();
        if (pole_count >= 2)
          loadDelay(drvr_slew, pole_residue, p1, k1, wire_delay, load_slew);
        else {
          float elmore = 1.0F / p1;
          wire_delay = elmore;
          load_slew = drvr_slew;
        }
      }
    }
  }
  thresholdAdjust(load_pin, drvr_library, rf, wire_delay, load_slew);
}

void
DmpCeffTwoPoleDelayCalc::loadDelay(double drvr_slew,
                                   Parasitic *pole_residue,
                                   double p1,
                                   double k1,
                                   // Return values.
                                   double &wire_delay,
                                   double &load_slew)
{
  ComplexFloat pole2, residue2;
  parasitics_->poleResidue(pole_residue, 1, pole2, residue2);
  if (!delayZero(drvr_slew, this)
      && pole2.imag() == 0.0
      && residue2.imag() == 0.0) {
    double p2 = pole2.real();
    double k2 = residue2.real();
    double k1_p1_2 = k1 / (p1 * p1);
    double k2_p2_2 = k2 / (p2 * p2);
    double B = k1_p1_2 + k2_p2_2;
    // Convert tt to 0:1 range.
    float tt = delayAsFloat(drvr_slew) * slew_derate_ / (vh_ - vl_);
    double y_tt = (tt - B + k1_p1_2 * exp(-p1 * tt)
                   + k2_p2_2 * exp(-p2 * tt)) / tt;
    wire_delay = loadDelay(vth_, p1, p2, k1, k2, B, k1_p1_2, k2_p2_2, tt, y_tt)
      - tt * vth_;

    float tl = loadDelay(vl_, p1, p2, k1, k2, B, k1_p1_2, k2_p2_2, tt, y_tt);
    float th = loadDelay(vh_, p1, p2, k1, k2, B, k1_p1_2, k2_p2_2, tt, y_tt);
    load_slew = (th - tl) / slew_derate_;
  }
}

float
DmpCeffTwoPoleDelayCalc::loadDelay(double vth,
                                   double p1,
                                   double p2,
                                   double k1,
                                   double k2,
                                   double B,
                                   double k1_p1_2,
                                   double k2_p2_2,
                                   double tt,
                                   double y_tt)
{
  if (y_tt < vth) {
    // t1 > tt
    // Initial guess.
    double t1 = log(k1 * (exp(p1 * tt) - 1.0) / ((1.0 - vth) * p1 * p1 * tt))/p1;
    // Take one newton-raphson step.
    double exp_p1_t1 = exp(-p1 * t1);
    double exp_p2_t1 = exp(-p2 * t1);
    double exp_p1_t1_tt = exp(-p1 * (t1 - tt));
    double exp_p2_t1_tt = exp(-p2 * (t1 - tt));
    double y_t1 = (tt - k1_p1_2 * (exp_p1_t1_tt - exp_p1_t1)
                   - k2_p2_2 * (exp_p2_t1_tt - exp_p2_t1)) / tt;
    double yp_t1 = (k1 / p1 * (exp_p1_t1_tt - exp_p1_t1)
                    - k2 / p2 * (exp_p2_t1_tt - exp_p2_t1)) / tt;
    double delay = t1 - (y_t1 - vth) / yp_t1;
    return static_cast<float>(delay);
  }
  else {
    // t1 < tt
    // Initial guess based on y(tt).
    double t1 = vth * tt / y_tt;
    // Take one newton-raphson step.
    double exp_p1_t1 = exp(-p1 * t1);
    double exp_p2_t1 = exp(-p2 * t1);
    double y_t1 = (t1 - B + k1_p1_2 * exp_p1_t1
                   + k2_p2_2 * exp_p1_t1) / tt;
    double yp_t1 = (1 - k1 / p1 * exp_p1_t1
                    - k2 / p2 * exp_p2_t1) / tt;
    double delay = t1 - (y_t1 - vth) / yp_t1;
    return static_cast<float>(delay);
  }
}

} // namespace sta
