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

#include "Graph.hh"
#include "GraphDelayCalc.hh"
#include "Liberty.hh"
#include "Network.hh"
#include "Scene.hh"
#include "Sdc.hh"
#include "TimingArc.hh"

namespace sta {

ParallelDelayCalc::ParallelDelayCalc(StaState *sta):
  DelayCalcBase(sta)
{
}

ArcDcalcResultSeq
ParallelDelayCalc::gateDelays(ArcDcalcArgSeq &dcalc_args,
                              const LoadPinIndexMap &load_pin_index_map,
                              const Scene *scene,
                              const MinMax *min_max)
{
  if (dcalc_args.size() == 1) {
    ArcDcalcArg &dcalc_arg = dcalc_args[0];
    ArcDcalcResult dcalc_result =  gateDelay(dcalc_arg.drvrPin(), dcalc_arg.arc(),
                                             dcalc_arg.inSlew(), dcalc_arg.loadCap(),
                                             dcalc_arg.parasitic(),
                                             load_pin_index_map,
                                             scene, min_max);
    ArcDcalcResultSeq dcalc_results;
    dcalc_results.push_back(dcalc_result);
    return dcalc_results;
  }
  return gateDelaysParallel(dcalc_args, load_pin_index_map, scene, min_max);
}

ArcDcalcResultSeq
ParallelDelayCalc::gateDelaysParallel(ArcDcalcArgSeq &dcalc_args,
                                      const LoadPinIndexMap &load_pin_index_map,
                                      const Scene *scene,
                                      const MinMax *min_max)
{
  size_t drvr_count = dcalc_args.size();
  ArcDcalcResultSeq dcalc_results(drvr_count);
  Slew slew_sum = 0.0;
  ArcDelay load_delay_sum = 0.0;
  std::vector<ArcDelay> intrinsic_delays(dcalc_args.size());
  std::vector<ArcDelay> load_delays(dcalc_args.size());
  for (size_t drvr_idx = 0; drvr_idx < drvr_count; drvr_idx++) {
    ArcDcalcArg &dcalc_arg = dcalc_args[drvr_idx];
    ArcDcalcResult &dcalc_result = dcalc_results[drvr_idx];
    const Pin *drvr_pin = dcalc_arg.drvrPin();
    const TimingArc *arc = dcalc_arg.arc();
    const Slew &in_slew = dcalc_arg.inSlew();

    ArcDcalcResult intrinsic_result = gateDelay(drvr_pin, arc, in_slew, 0.0, nullptr,
                                                load_pin_index_map, scene, min_max);
    ArcDelay intrinsic_delay = intrinsic_result.gateDelay();
    intrinsic_delays[drvr_idx] = intrinsic_result.gateDelay();

    ArcDcalcResult gate_result = gateDelay(drvr_pin, arc, in_slew, dcalc_arg.loadCap(),
                                           dcalc_arg.parasitic(),
                                           load_pin_index_map, scene, min_max);
    ArcDelay gate_delay = gate_result.gateDelay();
    Slew drvr_slew = gate_result.drvrSlew();
    ArcDelay load_delay = delayDiff(gate_delay, intrinsic_delay, this);
    load_delays[drvr_idx] = load_delay;

    if (!delayZero(load_delay, this))
      delayIncr(load_delay_sum, delayDiv(1.0, load_delay, this), this);
    if (!delayZero(drvr_slew, this))
      delayIncr(slew_sum, delayDiv(1.0, drvr_slew, this), this);

    dcalc_result.setLoadCount(load_pin_index_map.size());
    for (const auto &[load_pin, load_idx] : load_pin_index_map) {
      dcalc_result.setWireDelay(load_idx, gate_result.wireDelay(load_idx));
      dcalc_result.setLoadSlew(load_idx, gate_result.loadSlew(load_idx));
    }
  }

  ArcDelay gate_load_delay = delayZero(load_delay_sum, this)
    ? delay_zero
    : delayDiv(1.0, load_delay_sum, this);
  ArcDelay drvr_slew = delayZero(slew_sum, this)
    ? delay_zero
    : delayDiv(1.0, slew_sum, this);

  for (size_t drvr_idx = 0; drvr_idx < drvr_count; drvr_idx++) {
    ArcDcalcResult &dcalc_result = dcalc_results[drvr_idx];
    dcalc_result.setGateDelay(delaySum(intrinsic_delays[drvr_idx], gate_load_delay, this));
    dcalc_result.setDrvrSlew(drvr_slew);
  }
  return dcalc_results;
}

} // namespace sta
