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

#include <cmath>
#include <cinttypes>
#include <unordered_map>
#include <vector>

#include "Debug.hh"
#include "Liberty.hh"
#include "Mode.hh"
#include "Network.hh"
#include "ParseBus.hh"
#include "PortDirection.hh"
#include "Power.hh"
#include "Sdc.hh"
#include "Sta.hh"
#include "VcdParse.hh"
#include "VerilogNamespace.hh"

namespace sta {

// Transition count and high time for duty cycle for a group of pins
// for one bit of vcd ID.
class VcdCount
{
public:
  double transitionCount() const { return transition_count_; }
  VcdTime highTime(VcdTime time_max) const;
  void incrCounts(VcdTime time,
                  char value);
  void incrCounts(VcdTime time,
                  int64_t value);
  void addPin(const Pin *pin);
  const PinSeq &pins() const { return pins_; }

private:
  PinSeq pins_;
  VcdTime prev_time_ = -1;
  char prev_value_ = '\0';
  VcdTime high_time_ = 0;
  double transition_count_ = 0;
};

void
VcdCount::addPin(const Pin *pin)
{
  pins_.push_back(pin);
}

void
VcdCount::incrCounts(VcdTime time,
                     char value)
{
  // Initial value does not coontribute to transitions or high time.
  if (prev_time_ != -1) {
    if (prev_value_ == '1')
      high_time_ += time - prev_time_;
    if (value != prev_value_)
      transition_count_ +=
          (value == 'X' || value == 'Z' || prev_value_ == 'X' || prev_value_ == 'Z')
          ? .5
          : 1.0;
  }
  prev_time_ = time;
  prev_value_ = value;
}

VcdTime
VcdCount::highTime(VcdTime time_max) const
{
  if (prev_value_ == '1')
    return high_time_ + time_max - prev_time_;
  else
    return high_time_;
}

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

// VcdCount[bit]
using VcdCounts = std::vector<VcdCount>;
// ID -> VcdCount[bit]
using VcdIdCountsMap = std::unordered_map<std::string, VcdCounts>;

class VcdCountReader : public VcdReader
{
public:
  VcdCountReader(std::string_view scope,
                 const Network *sdc_network,
                 Report *report,
                 Debug *debug);
  VcdTime timeMax() const { return time_max_; }
  VcdTime timeMin() const { return time_min_; }
  const VcdIdCountsMap &countMap() const { return vcd_count_map_; }
  double timeScale() const { return time_scale_; }

  // VcdParse callbacks.
  void setDate(std::string_view ) override {}
  void setComment(std::string_view ) override {}
  void setVersion(std::string_view ) override {}
  void setTimeUnit(std::string_view time_unit,
                   double time_unit_scale,
                   double time_scale) override;
  void setTimeMin(VcdTime time) override;
  void setTimeMax(VcdTime time) override;
  void varMinDeltaTime(VcdTime) override {}
  bool varIdValid(std::string_view id) override;
  void makeVar(const VcdScope &scope,
               std::string_view name,
               VcdVarType type,
               size_t width,
               std::string_view id) override;
  void varAppendValue(const std::string &id,
                      VcdTime time,
                      char value) override;
  void varAppendBusValue(const std::string &id,
                         VcdTime time,
                         std::string_view bus_value) override;

private:
  void addVarPin(std::string_view pin_name,
                 std::string_view id,
                 size_t width,
                 size_t bit_idx);

  const std::string scope_;

  double time_scale_ = 1.0;
  VcdTime time_min_ = 0;
  VcdTime time_max_ = 0;
  VcdIdCountsMap vcd_count_map_;

  const Network *sdc_network_;
  Report *report_;
  Debug *debug_;
};

VcdCountReader::VcdCountReader(std::string_view scope,
                               const Network *sdc_network,
                               Report *report,
                               Debug *debug) :
  scope_(scope),
  sdc_network_(sdc_network),
  report_(report),
  debug_(debug)
{
}

void
VcdCountReader::setTimeUnit(std::string_view ,
                            double time_unit_scale,
                            double time_scale)
{
  time_scale_ = time_scale * time_unit_scale;
}

void
VcdCountReader::setTimeMin(VcdTime time)
{
  time_min_ = time;
}

void
VcdCountReader::setTimeMax(VcdTime time)
{
  time_max_ = time;
}

bool
VcdCountReader::varIdValid(std::string_view )
{
  return true;
}

void
VcdCountReader::makeVar(const VcdScope &scope,
                        std::string_view name,
                        VcdVarType type,
                        size_t width,
                        std::string_view id)
{
  if (type == VcdVarType::wire || type == VcdVarType::reg) {
    std::string path_name;
    bool first = true;
    for (std::string_view context : scope) {
      if (!first)
        path_name += '/';
      path_name += context;
      first = false;
    }
    size_t scope_length = scope_.size();
    // string::starts_with in c++20
    if (scope_length == 0 || path_name.substr(0, scope_length) == scope_) {
      path_name += '/';
      path_name += name;
      // Strip the scope from the name.
      std::string var_scoped = path_name.substr(scope_length + 1);
      if (width == 1) {
        std::string pin_name = netVerilogToSta(var_scoped);
        addVarPin(pin_name, id, width, 0);
      }
      else {
        bool is_bus, is_range, subscript_wild;
        std::string bus_name;
        int from, to;
        parseBusName(var_scoped, '[', ']', '\\', is_bus, is_range, bus_name,
                     from, to, subscript_wild);
        if (is_bus) {
          std::string sta_bus_name = netVerilogToSta(bus_name);
          int bit_idx = 0;
          if (to < from) {
            for (int bus_bit = to; bus_bit <= from; bus_bit++) {
              std::string pin_name = sta_bus_name;
              pin_name += '[';
              pin_name += std::to_string(bus_bit);
              pin_name += ']';
              addVarPin(pin_name, id, width, bit_idx);
              bit_idx++;
            }
          }
          else {
            for (int bus_bit = to; bus_bit >= from; bus_bit--) {
              std::string pin_name = sta_bus_name;
              pin_name += '[';
              pin_name += std::to_string(bus_bit);
              pin_name += ']';
              addVarPin(pin_name, id, width, bit_idx);
              bit_idx++;
            }
          }
        }
        else
          report_->warn(1451, "problem parsing bus {}.", var_scoped);
      }
    }
  }
}

void
VcdCountReader::addVarPin(std::string_view pin_name,
                          std::string_view id,
                          size_t width,
                          size_t bit_idx)
{
  const Pin *pin = sdc_network_->findPin(pin_name);
  LibertyPort *liberty_port = pin ? sdc_network_->libertyPort(pin) : nullptr;
  if (pin && !sdc_network_->isHierarchical(pin)
      && !sdc_network_->direction(pin)->isInternal()
      && !sdc_network_->direction(pin)->isPowerGround()
      && !(liberty_port && liberty_port->isPwrGnd())) {
    VcdCounts &vcd_counts = vcd_count_map_[std::string(id)];
    vcd_counts.resize(width);
    vcd_counts[bit_idx].addPin(pin);
    debugPrint(debug_, "read_vcd", 2, "id {} pin {}", id, pin_name);
  }
}

void
VcdCountReader::varAppendValue(const std::string &id,
                               VcdTime time,
                               char value)
{
  const auto &itr = vcd_count_map_.find(id);
  if (itr != vcd_count_map_.end()) {
    VcdCounts &vcd_counts = itr->second;
    if (debug_->check("read_vcd", 3)) {
      for (auto &vcd_count : vcd_counts) {
        for (const Pin *pin : vcd_count.pins()) {
          debugPrint(debug_, "read_vcd", 3, "{} time {} value {}",
                     sdc_network_->pathName(pin), time, value);
        }
      }
    }
    for (auto &vcd_count : vcd_counts) {
      vcd_count.incrCounts(time, value);
    }
  }
}

void
VcdCountReader::varAppendBusValue(const std::string &id,
                                  VcdTime time,
                                  std::string_view bus_value)
{
  const auto &itr = vcd_count_map_.find(id);
  if (itr != vcd_count_map_.end()) {
    VcdCounts &vcd_counts = itr->second;
    for (size_t bit_idx = 0; bit_idx < vcd_counts.size(); bit_idx++) {
      char bit_value;
      if (bus_value.size() == 1)
        bit_value = bus_value[0];
      else if (bit_idx < bus_value.size())
        bit_value = bus_value[bit_idx];
      else
        bit_value = '0';
      VcdCount &vcd_count = vcd_counts[bit_idx];
      vcd_count.incrCounts(time, bit_value);
      if (debug_->check("read_vcd", 3)) {
        for (const Pin *pin : vcd_count.pins()) {
          debugPrint(debug_, "read_vcd", 3, "{} time {} value {}",
                     sdc_network_->pathName(pin), time, bit_value);
        }
      }
    }
  }
}

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

class ReadVcdActivities : public StaState
{
public:
  ReadVcdActivities(std::string_view filename,
                    std::string_view scope,
                    const Sdc *sdc,
                    Sta *sta);
  void readActivities();

private:
  void setActivities();
  void checkClkPeriod(const Pin *pin,
                      double transition_count);

  const std::string filename_;

  std::set<const Pin *> annotated_pins_;
  VcdCountReader vcd_reader_;
  VcdParse vcd_parse_;
  const Sdc *sdc_;
  Power *power_;

  static constexpr double sim_clk_period_tolerance_ = .1;
};

void
readVcdActivities(std::string_view filename,
                  std::string_view scope,
                  std::string_view mode_name,
                  Sta *sta)
{
  const Mode *mode = sta->findMode(mode_name);
  const Sdc *sdc = mode->sdc();
  ReadVcdActivities reader(filename, scope, sdc, sta);
  reader.readActivities();
}

ReadVcdActivities::ReadVcdActivities(std::string_view filename,
                                     std::string_view scope,
                                     const Sdc *sdc,
                                     Sta *sta) :
  StaState(sta),
  filename_(filename),
  vcd_reader_(scope,
              sdc_network_,
              report_,
              debug_),
  vcd_parse_(report_,
             debug_),
  sdc_(sdc),
  power_(sta->power())
{
}

void
ReadVcdActivities::readActivities()
{
  const ClockSeq &clks = sdc_->clocks();
  if (clks.empty())
    report_->error(820, "No clocks have been defined.");

  vcd_parse_.read(filename_.c_str(), &vcd_reader_);

  if (vcd_reader_.timeMax() > 0)
    setActivities();
  else
    report_->warn(1450, "VCD max time is zero.");
  report_->report("Annotated {} pin activities.", annotated_pins_.size());
}

void
ReadVcdActivities::setActivities()
{
  VcdTime time_min = vcd_reader_.timeMin();
  VcdTime time_max = vcd_reader_.timeMax();
  VcdTime time_delta = time_max - time_min;
  double time_scale = vcd_reader_.timeScale();
  for (auto &[id, vcd_counts] : vcd_reader_.countMap()) {
    for (const VcdCount &vcd_count : vcd_counts) {
      double transition_count = vcd_count.transitionCount();
      VcdTime high_time = vcd_count.highTime(time_max);
      float duty = static_cast<double>(high_time) / time_delta;
      float density = transition_count / (time_delta * time_scale);
      if (debug_->check("read_vcd", 1)) {
        for (const Pin *pin : vcd_count.pins()) {
          debugPrint(debug_, "read_vcd", 1,
                     "{} transitions {:.1f} activity {:.2f} duty {:.2f}",
                     sdc_network_->pathName(pin), transition_count, density, duty);
        }
      }
      for (const Pin *pin : vcd_count.pins()) {
        power_->setUserActivity(pin, density, duty, PwrActivityOrigin::vcd);
        if (sdc_->isLeafPinClock(pin))
          checkClkPeriod(pin, transition_count);
        annotated_pins_.insert(pin);
      }
    }
  }
}

void
ReadVcdActivities::checkClkPeriod(const Pin *pin,
                                  double transition_count)
{
  ClockSet *clks = sdc_->findLeafPinClocks(pin);
  if (clks) {
    VcdTime time_max = vcd_reader_.timeMax();
    VcdTime time_min = vcd_reader_.timeMin();
    double time_scale = vcd_reader_.timeScale();
    double sim_period =
        (time_max - time_min) * time_scale / (transition_count / 2.0);
    for (Clock *clk : *clks) {
      if (transition_count == 0)
        report_->warn(1453, "clock {} pin {} has no vcd transitions.", clk->name(),
                      sdc_network_->pathName(pin));
      else {
        double clk_period = clk->period();
        if (std::abs((clk_period - sim_period) / clk_period)
            > sim_clk_period_tolerance_)
          // Warn if sim clock period differs from SDC by more than 10%.
          report_->warn(1452,
                        "clock {} vcd period {} differs from SDC clock period {}",
                        clk->name(), delayAsString(sim_period, this),
                        delayAsString(clk_period, this));
      }
    }
  }
}

}  // namespace sta
