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

#include "FuncExpr.hh"
#include "Report.hh"
#include "StaConfig.hh"
#include "cudd.h"

namespace sta {

Bdd::Bdd(const StaState *sta) :
  StaState(sta),
  cudd_mgr_(Cudd_Init(0, 0, CUDD_UNIQUE_SLOTS, CUDD_CACHE_SLOTS, 0))
{
}

Bdd::~Bdd()
{
  Cudd_Quit(cudd_mgr_);
}

DdNode *
Bdd::funcBdd(const FuncExpr *expr)
{
  DdNode *left = nullptr;
  DdNode *right = nullptr;
  DdNode *result = nullptr;
  switch (expr->op()) {
  case FuncExpr::Op::port: {
    LibertyPort *port = expr->port();
    result = ensureNode(port);
    break;
  }
  case FuncExpr::Op::not_:
    left = funcBdd(expr->left());
    if (left)
      // NOLINTNEXTLINE(performance-no-int-to-ptr)
      result = Cudd_Not(left);
    break;
  case FuncExpr::Op::or_:
    left = funcBdd(expr->left());
    right = funcBdd(expr->right());
    if (left && right)
      result = Cudd_bddOr(cudd_mgr_, left, right);
    else if (left)
      result = left;
    else if (right)
      result = right;
    break;
  case FuncExpr::Op::and_:
    left = funcBdd(expr->left());
    right = funcBdd(expr->right());
    if (left && right)
      result = Cudd_bddAnd(cudd_mgr_, left, right);
    else if (left)
      result = left;
    else if (right)
      result = right;
    break;
  case FuncExpr::Op::xor_:
    left = funcBdd(expr->left());
    right = funcBdd(expr->right());
    if (left && right)
      result = Cudd_bddXor(cudd_mgr_, left, right);
    else if (left)
      result = left;
    else if (right)
      result = right;
    break;
  case FuncExpr::Op::one:
    result = Cudd_ReadOne(cudd_mgr_);
    break;
  case FuncExpr::Op::zero:
    result = Cudd_ReadLogicZero(cudd_mgr_);
    break;
  default:
    report_->critical(1440, "unknown function operator");
  }
  if (result)
    Cudd_Ref(result);
  if (left)
    Cudd_RecursiveDeref(cudd_mgr_, left);
  if (right)
    Cudd_RecursiveDeref(cudd_mgr_, right);
  return result;
}

DdNode *
Bdd::findNode(const LibertyPort *port)
{
  auto port_var = bdd_port_var_map_.find(port);
  if (port_var == bdd_port_var_map_.end())
    return nullptr;
  else
    return port_var->second;
}

DdNode *
Bdd::ensureNode(const LibertyPort *port)
{
  auto port_var = bdd_port_var_map_.find(port);
  DdNode *node = nullptr;
  if (port_var == bdd_port_var_map_.end()) {
    unsigned var_index = bdd_port_var_map_.size();
    node = Cudd_bddIthVar(cudd_mgr_, var_index);
    bdd_port_var_map_[port] = node;
    bdd_var_idx_port_map_[var_index] = port;
    Cudd_Ref(node);
  }
  else
    node = port_var->second;
  return node;
}

const LibertyPort *
Bdd::nodePort(DdNode *node)
{
  auto port_index = bdd_var_idx_port_map_.find(Cudd_NodeReadIndex(node));
  if (port_index == bdd_var_idx_port_map_.end())
    return nullptr;
  else
    return port_index->second;
}

const LibertyPort *
Bdd::varIndexPort(int var_index)
{
  auto index_port = bdd_var_idx_port_map_.find(var_index);
  if (index_port == bdd_var_idx_port_map_.end())
    return nullptr;
  else
    return index_port->second;
}

void
Bdd::clearVarMap()
{
  bdd_port_var_map_.clear();
  bdd_var_idx_port_map_.clear();
}

} // namespace sta
