
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2022 Matthias Koefferlein

  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 2 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, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/

#include "dbNetlistCompareUtils.h"
#include "dbNetlist.h"
#include "dbNetlistDeviceClasses.h"

#include "tlEnv.h"

namespace db
{

// --------------------------------------------------------------------------------------------------------------------
//  NetlistCompareGlobalOptions implementation

NetlistCompareGlobalOptions::NetlistCompareGlobalOptions ()
{
  m_is_initialized = false;
}

void
NetlistCompareGlobalOptions::ensure_initialized ()
{
  if (! m_is_initialized) {
    //  $KLAYOUT_NETLIST_COMPARE_DEBUG_NETCOMPARE
    debug_netcompare = tl::app_flag ("netlist-compare-debug-netcompare");
    //  $KLAYOUT_NETLIST_COMPARE_DEBUG_NETGRAPH
    debug_netgraph = tl::app_flag ("netlist-compare-debug-netgraph");
    m_is_initialized = true;
  }
}

NetlistCompareGlobalOptions *
NetlistCompareGlobalOptions::options ()
{
  //  TODO: thread safe?
  static NetlistCompareGlobalOptions s_options;
  s_options.ensure_initialized ();
  return &s_options;
}

// --------------------------------------------------------------------------------------------------------------------
//   Some utilities

std::string nl_compare_debug_indent (size_t depth)
{
  std::string s;
  for (size_t d = 0; d < depth; ++d) {
    s += "|  ";
  }
  return s;
}

// --------------------------------------------------------------------------------------------------------------------
//   Some functions

bool combined_case_sensitive (const db::Netlist *a, const db::Netlist *b)
{
  bool csa = a ? a->is_case_sensitive () : true;
  bool csb = b ? b->is_case_sensitive () : true;
  return csa && csb;
}

//  for comparing the net names also employ the pin name if one is given
const std::string &extended_net_name (const db::Net *n)
{
  if (! n->name ().empty ()) {
    return n->name ();
  } else if (n->begin_pins () != n->end_pins ()) {
    return n->begin_pins ()->pin ()->name ();
  } else {
    return n->name ();
  }
}

int name_compare (const db::Net *a, const db::Net *b)
{
  return db::Netlist::name_compare (combined_case_sensitive (a->netlist (), b->netlist ()), extended_net_name (a), extended_net_name (b));
}

bool net_names_are_different (const db::Net *a, const db::Net *b)
{
  if (! a || ! b || extended_net_name (a).empty () || extended_net_name (b).empty ()) {
    return false;
  } else {
    return name_compare (a, b) != 0;
  }
}

bool net_names_are_equal (const db::Net *a, const db::Net *b)
{
  if (! a || ! b || extended_net_name (a).empty () || extended_net_name (b).empty ()) {
    return false;
  } else {
    return name_compare (a, b) == 0;
  }
}

// --------------------------------------------------------------------------------------------------------------------
//  DeviceCompare implementation

bool
DeviceCompare::operator() (const std::pair<const db::Device *, size_t> &d1, const std::pair<const db::Device *, size_t> &d2) const
{
  if (d1.second != d2.second) {
    return d1.second < d2.second;
  }
  return db::DeviceClass::less (*d1.first, *d2.first);
}

bool
DeviceCompare::equals (const std::pair<const db::Device *, size_t> &d1, const std::pair<const db::Device *, size_t> &d2) const
{
  if (d1.second != d2.second) {
    return false;
  }
  return db::DeviceClass::equal (*d1.first, *d2.first);
}

// --------------------------------------------------------------------------------------------------------------------
//  SubCircuitCompare implementation

bool
SubCircuitCompare::operator() (const std::pair<const db::SubCircuit *, size_t> &sc1, const std::pair<const db::SubCircuit *, size_t> &sc2) const
{
  return sc1.second < sc2.second;
}

bool
SubCircuitCompare::equals (const std::pair<const db::SubCircuit *, size_t> &sc1, const std::pair<const db::SubCircuit *, size_t> &sc2) const
{
  return sc1.second == sc2.second;
}

// --------------------------------------------------------------------------------------------------------------------
//  CircuitPinMapper implementation

CircuitPinCategorizer::CircuitPinCategorizer ()
{
  //  .. nothing yet ..
}

void
CircuitPinCategorizer::map_pins (const db::Circuit *circuit, size_t pin1_id, size_t pin2_id)
{
  m_pin_map [circuit].same (pin1_id, pin2_id);
}

void
CircuitPinCategorizer::map_pins (const db::Circuit *circuit, const std::vector<size_t> &pin_ids)
{
  if (pin_ids.size () < 2) {
    return;
  }

  tl::equivalence_clusters<size_t> &pm = m_pin_map [circuit];
  for (size_t i = 1; i < pin_ids.size (); ++i) {
    pm.same (pin_ids [0], pin_ids [i]);
  }
}

size_t
CircuitPinCategorizer::is_mapped (const db::Circuit *circuit, size_t pin_id) const
{
  std::map<const db::Circuit *, tl::equivalence_clusters<size_t> >::const_iterator pm = m_pin_map.find (circuit);
  if (pm != m_pin_map.end ()) {
    return pm->second.has_attribute (pin_id);
  } else {
    return false;
  }
}

size_t
CircuitPinCategorizer::normalize_pin_id (const db::Circuit *circuit, size_t pin_id) const
{
  std::map<const db::Circuit *, tl::equivalence_clusters<size_t> >::const_iterator pm = m_pin_map.find (circuit);
  if (pm != m_pin_map.end ()) {
    size_t cluster_id = pm->second.cluster_id (pin_id);
    if (cluster_id > 0) {
      return (*pm->second.begin_cluster (cluster_id))->first;
    }
  }
  return pin_id;
}

// --------------------------------------------------------------------------------------------------------------------
//  CircuitMapper implementation

CircuitMapper::CircuitMapper ()
  : mp_other (0)
{
  //  .. nothing yet ..
}

void
CircuitMapper::map_pin (size_t this_pin, size_t other_pin)
{
  m_pin_map.insert (std::make_pair (this_pin, other_pin));
  m_rev_pin_map.insert (std::make_pair (other_pin, this_pin));
}

bool
CircuitMapper::has_other_pin_for_this_pin (size_t this_pin) const
{
  return m_pin_map.find (this_pin) != m_pin_map.end ();
}

bool
CircuitMapper::has_this_pin_for_other_pin (size_t other_pin) const
{
  return m_rev_pin_map.find (other_pin) != m_rev_pin_map.end ();
}

size_t
CircuitMapper::other_pin_from_this_pin (size_t this_pin) const
{
  std::map<size_t, size_t>::const_iterator i = m_pin_map.find (this_pin);
  tl_assert (i != m_pin_map.end ());
  return i->second;
}

size_t
CircuitMapper::this_pin_from_other_pin (size_t other_pin) const
{
  std::map<size_t, size_t>::const_iterator i = m_rev_pin_map.find (other_pin);
  tl_assert (i != m_rev_pin_map.end ());
  return i->second;
}

// --------------------------------------------------------------------------------------------------------------------
//  DeviceFilter implementation

DeviceFilter::DeviceFilter (double cap_threshold, double res_threshold)
  : m_cap_threshold (cap_threshold), m_res_threshold (res_threshold)
{
  //  .. nothing yet ..
}

bool
DeviceFilter::filter (const db::Device *device) const
{
  const db::DeviceClassResistor *res = dynamic_cast<const db::DeviceClassResistor *> (device->device_class ());
  const db::DeviceClassCapacitor *cap = dynamic_cast<const db::DeviceClassCapacitor *> (device->device_class ());

  if (res) {
    if (m_res_threshold > 0.0 && device->parameter_value (db::DeviceClassResistor::param_id_R) > m_res_threshold) {
      return false;
    }
  } else if (cap) {
    if (m_cap_threshold > 0.0 && device->parameter_value (db::DeviceClassCapacitor::param_id_C) < m_cap_threshold) {
      return false;
    }
  }

  return true;
}

// --------------------------------------------------------------------------------------------------------------------
//  generic_categorizer implementation

template <class Obj> generic_categorizer<Obj>::generic_categorizer (bool with_name)
  : m_next_cat (0), m_with_name (with_name), m_case_sensitive (true)
{
  //  .. nothing yet ..
}

template <class Obj>
void
generic_categorizer<Obj>::set_case_sensitive (bool f)
{
  m_case_sensitive = f;
}

template <class Obj>
void
generic_categorizer<Obj>::same (const Obj *ca, const Obj *cb)
{
  if (! ca && ! cb) {
    return;
  } else if (! ca) {
    same (cb, ca);
  } else if (! cb) {
    //  making a object same as null will make this device being ignored
    m_cat_by_ptr [ca] = 0;
    return;
  }

  //  reuse existing category if one is assigned already -> this allows associating
  //  multiple categories to other ones (A->C, B->C)
  typename std::map<const Obj *, size_t>::const_iterator cpa = m_cat_by_ptr.find (ca);
  typename std::map<const Obj *, size_t>::const_iterator cpb = m_cat_by_ptr.find (cb);

  if (cpa != m_cat_by_ptr.end () && cpb != m_cat_by_ptr.end ()) {

    if (cpa->second != cpb->second) {
      //  join categories (cat(B)->cat(A))
      for (typename std::map<const Obj *, size_t>::iterator cp = m_cat_by_ptr.begin (); cp != m_cat_by_ptr.end (); ++cp) {
        if (cp->second == cpb->second) {
          cp->second = cpa->second;
        }
      }
    }

  } else if (cpb != m_cat_by_ptr.end ()) {

    //  reuse cat(B) category
    m_cat_by_ptr.insert (std::make_pair (ca, cpb->second));

  } else if (cpa != m_cat_by_ptr.end ()) {

    //  reuse cat(A) category
    m_cat_by_ptr.insert (std::make_pair (cb, cpa->second));

  } else {

    //  new category
    ++m_next_cat;
    m_cat_by_ptr.insert (std::make_pair (ca, m_next_cat));
    m_cat_by_ptr.insert (std::make_pair (cb, m_next_cat));

  }
}

template <class Obj>
bool
generic_categorizer<Obj>::has_cat_for (const Obj *cls)
{
  return m_cat_by_ptr.find (cls) != m_cat_by_ptr.end ();
}

template <class Obj>
size_t
generic_categorizer<Obj>::cat_for (const Obj *cls)
{
  typename std::map<const Obj *, size_t>::const_iterator cp = m_cat_by_ptr.find (cls);
  if (cp != m_cat_by_ptr.end ()) {
    return cp->second;
  }

  if (m_with_name) {

    std::string cls_name = db::Netlist::normalize_name (m_case_sensitive, cls->name ());

    std::map<std::string, size_t>::const_iterator c = m_cat_by_name.find (cls_name);
    if (c != m_cat_by_name.end ()) {
      m_cat_by_ptr.insert (std::make_pair (cls, c->second));
      return c->second;
    } else {
      ++m_next_cat;
      m_cat_by_name.insert (std::make_pair (cls_name, m_next_cat));
      m_cat_by_ptr.insert (std::make_pair (cls, m_next_cat));
      return m_next_cat;
    }

  } else {

    ++m_next_cat;
    m_cat_by_ptr.insert (std::make_pair (cls, m_next_cat));
    return m_next_cat;

  }
}

//  explicit instantiations
template class DB_PUBLIC generic_categorizer<db::DeviceClass>;
template class DB_PUBLIC generic_categorizer<db::Circuit>;

// --------------------------------------------------------------------------------------------------------------------
//  DeviceCategorizer implementation

DeviceCategorizer::DeviceCategorizer ()
  : generic_categorizer<db::DeviceClass> ()
{
  //  .. nothing yet ..
}

void
DeviceCategorizer::same_class (const db::DeviceClass *ca, const db::DeviceClass *cb)
{
  generic_categorizer<db::DeviceClass>::same (ca, cb);
}

size_t
DeviceCategorizer::cat_for_device (const db::Device *device)
{
  const db::DeviceClass *cls = device->device_class ();
  if (! cls) {
    return 0;
  }

  return cat_for_device_class (cls);
}

void
DeviceCategorizer::clear_strict_device_categories ()
{
  m_strict_device_categories.clear ();
}

void
DeviceCategorizer::set_strict_device_category (size_t cat)
{
  m_strict_device_categories.insert (cat);
}

bool
DeviceCategorizer::is_strict_device_category (size_t cat) const
{
  return m_strict_device_categories.find (cat) != m_strict_device_categories.end ();
}

// --------------------------------------------------------------------------------------------------------------------
//  CircuitCategorizer implementation

CircuitCategorizer::CircuitCategorizer ()
  : generic_categorizer<db::Circuit> ()
{
  //  .. nothing yet ..
}

void
CircuitCategorizer::same_circuit (const db::Circuit *ca, const db::Circuit *cb)
{
  //  no arbitrary cross-pairing
  //  NOTE: many layout circuits are allowed for one schematic to account for layout alternatives.
  if (ca && has_cat_for (ca)) {
    throw tl::Exception (tl::to_string (tr ("Circuit is already paired with other circuit: ")) + ca->name ());
  }
  generic_categorizer<db::Circuit>::same (ca, cb);
}

size_t
CircuitCategorizer::cat_for_subcircuit (const db::SubCircuit *subcircuit)
{
  const db::Circuit *cr = subcircuit->circuit_ref ();
  if (! cr) {
    return 0;
  } else {
    return cat_for_circuit (cr);
  }
}

}
