/* Distributed under the Apache License, Version 2.0.
   See accompanying NOTICE file for details.*/

#include "circuit/SECircuit.h"

#define ZERO_APPROX 1e-10
#define OPEN_RESISTANCE 1e100

template<CIRCUIT_TEMPLATE>
SECircuit<CIRCUIT_TYPES>::SECircuit(const std::string& name, Logger* logger) : Loggable(logger), m_Name(name)
{
}

template<CIRCUIT_TEMPLATE>
SECircuit<CIRCUIT_TYPES>::~SECircuit()
{
  Clear();
}

template<CIRCUIT_TEMPLATE>
void SECircuit<CIRCUIT_TYPES>::Clear()
{
  m_ReferenceNodes.clear();
  for (auto& itr : m_TargetPathMap)
    delete itr.second;
  for (auto& itr : m_SourcePathMap)
    delete itr.second;
  for (auto& itr : m_ConnectedPathMap)
    delete itr.second;
  m_Nodes.clear();
  m_Paths.clear();
  m_TargetPathMap.clear();
  m_SourcePathMap.clear();
  m_ConnectedPathMap.clear();
  m_ValvePaths.clear();
  m_PolarizedElementPaths.clear();
}

template<CIRCUIT_TEMPLATE>
void SECircuit<CIRCUIT_TYPES>::Load(const CircuitBindType& src, SECircuit& dst, const std::map<std::string, NodeType*>& nodes, const std::map<std::string, PathType*>& paths)
{
  SECircuit::Serialize(src, dst, nodes, paths);
  dst.StateChange();
}
template<CIRCUIT_TEMPLATE>
void SECircuit<CIRCUIT_TYPES>::Serialize(const CircuitBindType& src, SECircuit& dst, const std::map<std::string, NodeType*>& nodes, const std::map<std::string, PathType*>& paths)
{// note: not clearing here as the derived class needs to clear and call this super class Load last to get the ref node hooked up
  dst.Clear();
  const cdm::CircuitData& srcC = src.circuit();
  dst.m_Name = srcC.name();
  for (int i=0; i<srcC.node_size(); i++)
  {
    const std::string name = srcC.node(i);
    auto idx = nodes.find(name);
    if (idx == nodes.end())
    {
      dst.Error(dst.m_Name + " could not find node " + name.c_str());
      return;
    }
    dst.AddNode(*idx->second);
  }
  for (int i = 0; i<srcC.path_size(); i++)
  {
    const std::string name = srcC.path(i);
    auto idx = paths.find(name);
    if (idx == paths.end())
    {
      dst.Error(dst.m_Name + " could not find path " + name.c_str());
      return;
    }
    dst.AddPath(*idx->second);
  }
  for (int i = 0; i<srcC.referencenode_size(); i++)
  {
    const std::string name = srcC.referencenode(i);
    auto idx = nodes.find(name);
    if (idx == nodes.end())
    {
      dst.Error(dst.m_Name + " could not find reference node " + name.c_str());
    }
    dst.AddReferenceNode(*idx->second);
  }
}
template<CIRCUIT_TEMPLATE>
CircuitBindType* SECircuit<CIRCUIT_TYPES>::Unload(const SECircuit& src)
{
  CircuitBindType* dst = new CircuitBindType();
  Serialize(src,*dst);
  return dst;
}
template<CIRCUIT_TEMPLATE>
void SECircuit<CIRCUIT_TYPES>::Serialize(const SECircuit& src, CircuitBindType& dst)
{
  cdm::CircuitData* dstC = dst.mutable_circuit();
  dstC->set_name(src.m_Name);
  if (src.HasReferenceNode())
  {
    for (NodeType* n : src.m_ReferenceNodes)
      dstC->add_referencenode(n->GetName());
  }    
  for (auto* n : src.m_Nodes)
    dstC->add_node(n->GetName());
  for (auto* p : src.m_Paths)
    dstC->add_path(p->GetName());
}

template<CIRCUIT_TEMPLATE>
void SECircuit<CIRCUIT_TYPES>::StateChange()
{
  // Cache target and source paths to save lots of time later
  for (auto& itr : m_TargetPathMap)
    delete itr.second;
  for (auto& itr : m_SourcePathMap)
    delete itr.second;
  for (auto& itr : m_ConnectedPathMap)
    delete itr.second;
  m_TargetPathMap.clear();
  m_SourcePathMap.clear();
  m_ConnectedPathMap.clear();
  m_ValvePaths.clear();
  m_PolarizedElementPaths.clear();

  for (PathType* p : m_Paths)
  {
    if (!p->HasValidElements())
    {
      ///\error Fatal: Invalid Path in the Circuit.
      m_ss << p->GetName() << " has invalid elements";
      Fatal(m_ss);
    }

    //There should never be a NextFlow value set on a path
    //Flow sources are defined using NextFlowSource
    /* if (p->HasNextFlux())
    {
    ///\error Fatal: Invalid Path in the Circuit.
    m_ss << p->GetName() << " has flux and will be ignored and overwritten. Use a flux source.";
    Warning(m_ss);
    }*/

    // Cache what paths are connected to what nodes
    NodeType* nTgt = &p->GetTargetNode();
    NodeType* nSrc = &p->GetSourceNode();
    std::vector<PathType*>* tgtPaths = m_TargetPathMap[nTgt];
    std::vector<PathType*>* srcPaths = m_SourcePathMap[nSrc];
    std::vector<PathType*>* conSrcPaths = m_ConnectedPathMap[nSrc];
    std::vector<PathType*>* conTgtPaths = m_ConnectedPathMap[nTgt];
    if (tgtPaths == nullptr)
    {
      tgtPaths = new std::vector<PathType*>();
      m_TargetPathMap[nTgt] = tgtPaths;
    }
    if (srcPaths == nullptr)
    {
      srcPaths = new std::vector<PathType*>();
      m_SourcePathMap[nSrc] = srcPaths;
    }
    if (conTgtPaths == nullptr)
    {
      conTgtPaths = new std::vector<PathType*>();
      m_ConnectedPathMap[nTgt] = conTgtPaths;
    }
    if (conSrcPaths == nullptr)
    {
      conSrcPaths = new std::vector<PathType*>();
      m_ConnectedPathMap[nSrc] = conSrcPaths;
    }

    //Make sure we didn't accidentally define something twice
    if (std::find(m_ConnectedPathMap[nSrc]->begin(), m_ConnectedPathMap[nSrc]->end(), p) != m_ConnectedPathMap[nSrc]->end() ||
      std::find(m_ConnectedPathMap[nTgt]->begin(), m_ConnectedPathMap[nTgt]->end(), p) != m_ConnectedPathMap[nTgt]->end())
    {
      m_ss << p->GetName() << " is defined multiple times.";
      Fatal(m_ss);
    }

    m_TargetPathMap[nTgt]->push_back(p);
    m_SourcePathMap[nSrc]->push_back(p);
    m_ConnectedPathMap[nSrc]->push_back(p);
    m_ConnectedPathMap[nTgt]->push_back(p);

    // Cache Valves
    if (p->HasNextValve())
      m_ValvePaths.push_back(p);
    if (p->HasNextPolarizedState())
      m_PolarizedElementPaths.push_back(p);
  }

  int jIdx = 0;
  for (NodeType* n : m_Nodes)
  {
    //There should never be a next pressure value set on a node
    //  //Initializing a compliance "charge" is done on the current pressure value
    //  //Pressure sources are defined on a path
    //  if (n->HasNextPotential() && n!=GetReferenceNode())
    //  {
    //    m_ss << "You cannot set a pressure value without using a path pressure source.  The NextPressure value will be ignored and overwritten for Node " << n->GetName();
    //    Warning(m_ss);
    //  }    
      if (!IsReferenceNode(*n))
      {
          n->SetCalculatorIndex(jIdx);
          jIdx++;
      }
      else
      {
          n->SetCalculatorIndex(-1);
      }
  }
  if ((m_ValvePaths.size()+m_PolarizedElementPaths.size()) > 64)
  {
    ///\error Fatal: There are too many assumed state options.  The Circuit solver can only handle up to 64 Diodes and Polar Elements in a single circuit (i.e. ~1.8e19 possible combinations).
    Fatal("There are too many assumed state options.  The Circuit solver can only handle up to 64 Diodes and Polar Elements in a single circuit (i.e. ~1.8e19 possible combinations).");
  }
}

template<CIRCUIT_TEMPLATE>
std::string SECircuit<CIRCUIT_TYPES>::GetName() const
{
  return m_Name;
}

template<CIRCUIT_TEMPLATE>
bool SECircuit<CIRCUIT_TYPES>::HasReferenceNode() const
{
  return !m_ReferenceNodes.empty();
}
template<CIRCUIT_TEMPLATE>
bool SECircuit<CIRCUIT_TYPES>::IsReferenceNode(NodeType& node) const
{
  for (NodeType* n : m_ReferenceNodes)
    if (n == &node)
      return true;
  return false;  
}
template<CIRCUIT_TEMPLATE>
const std::vector<NodeType*>& SECircuit<CIRCUIT_TYPES>::GetReferenceNodes() const
{
  return m_ReferenceNodes;
}
template<CIRCUIT_TEMPLATE>
void SECircuit<CIRCUIT_TYPES>::AddReferenceNode(NodeType& node)
{
  // Make sure we have it
  bool found = false;
  for (NodeType* n : m_Nodes)
  {
    if (n == &node)
    {
      found = true;
      break;
    }
  }
  if (!found)
    AddNode(node);  
  if(!Contains(m_ReferenceNodes,node))
    m_ReferenceNodes.push_back(&node);
}

template<CIRCUIT_TEMPLATE>
void SECircuit<CIRCUIT_TYPES>::AddNode(NodeType& node)
{
  if (!Contains(this->m_Nodes, node))
    this->m_Nodes.push_back(&node);
}
template<CIRCUIT_TEMPLATE>
bool SECircuit<CIRCUIT_TYPES>::HasNode(NodeType& node)
{
  return Contains(m_Nodes, node);
}
template<CIRCUIT_TEMPLATE>
bool SECircuit<CIRCUIT_TYPES>::HasNode(const std::string& name)
{
  return GetNode(name) != nullptr;
}
template<CIRCUIT_TEMPLATE>
NodeType* SECircuit<CIRCUIT_TYPES>::GetNode(const std::string& name)
{
  for (NodeType* n : m_Nodes)
  {
    if (n->GetName() == name)
      return n;
  }
  return nullptr;
}
template<CIRCUIT_TEMPLATE>
const NodeType* SECircuit<CIRCUIT_TYPES>::GetNode(const std::string& name) const
{
  for (NodeType* n : m_Nodes)
  {
    if (n->GetName() == name)
      return n;
  }
  return nullptr;
}
template<CIRCUIT_TEMPLATE>
const std::vector<NodeType*>& SECircuit<CIRCUIT_TYPES>::GetNodes() const
{
  return m_Nodes;
}
template<CIRCUIT_TEMPLATE>
void SECircuit<CIRCUIT_TYPES>::RemoveNode(const NodeType& node)
{
  size_t i = 0;
  for (NodeType* n : m_Nodes)
  {
    if (n == &node)
    {
      m_Nodes.erase(m_Nodes.begin() + i);
      return;
    }
    i++;
  }
}
template<CIRCUIT_TEMPLATE>
void SECircuit<CIRCUIT_TYPES>::RemoveNode(const std::string& name)
{
  size_t i = 0;
  for (NodeType* n : m_Nodes)
  { 
    if(n->GetName()==name)
    {
      m_Nodes.erase(m_Nodes.begin()+i);
      return;
    }
    i++;
  } 
}

template<CIRCUIT_TEMPLATE>
void SECircuit<CIRCUIT_TYPES>::AddPath(PathType& path)
{
  if (!Contains(m_Paths, path))
    m_Paths.push_back(&path);
}
template<CIRCUIT_TEMPLATE>
bool SECircuit<CIRCUIT_TYPES>::HasPath(PathType& path)
{
  return Contains(m_Paths, path);
}
template<CIRCUIT_TEMPLATE>
bool SECircuit<CIRCUIT_TYPES>::HasPath(const std::string& name)
{
  return GetPath(name) != nullptr;
}
template<CIRCUIT_TEMPLATE>
PathType* SECircuit<CIRCUIT_TYPES>::GetPath(const std::string& name)
{
  for (PathType* p : m_Paths)
  {
    if(p->GetName() == name)
      return p;
  }
  return nullptr;
}
template<CIRCUIT_TEMPLATE>
const PathType* SECircuit<CIRCUIT_TYPES>::GetPath(const std::string& name) const
{
  for (PathType* p : m_Paths)
  {
    if (p->GetName() == name)
      return p;
  }
  return nullptr;
}
template<CIRCUIT_TEMPLATE>
const std::vector<PathType*>& SECircuit<CIRCUIT_TYPES>::GetPaths() const
{
  return m_Paths;
}
template<CIRCUIT_TEMPLATE>
void SECircuit<CIRCUIT_TYPES>::RemovePath(const PathType& path)
{
  size_t i = 0;
  for (PathType* p : m_Paths)
  { 
    if(p==&path)
    {
      m_Paths.erase(m_Paths.begin()+i);
      return;
    }
    i++;
  } 
}
template<CIRCUIT_TEMPLATE>
void SECircuit<CIRCUIT_TYPES>::RemovePath(const std::string& name)
{
  size_t i = 0;
  for (PathType* p : m_Paths)
  {
    if (p->GetName() == name)
    {
      m_Paths.erase(m_Paths.begin() + i);
      return;
    }
    i++;
  }
}
template<CIRCUIT_TEMPLATE>
const std::vector<PathType*>& SECircuit<CIRCUIT_TYPES>::GetValvePaths()
{
  return m_ValvePaths;
}
template<CIRCUIT_TEMPLATE>
const std::vector<PathType*>& SECircuit<CIRCUIT_TYPES>::GetPolarizedElementPaths()
{
  return m_PolarizedElementPaths;
}

//--------------------------------------------------------------------------------------------------
/// \brief
/// Returns all target paths for a node
///
/// \param  Circuit The circuit in question.
/// \param  node Circuit node
///
/// \return Vector of circuit paths
///
/// \details
/// Finds all paths where the input node is the target node. Returns a vector of the paths.
//--------------------------------------------------------------------------------------------------
template<CIRCUIT_TEMPLATE>
const std::vector<PathType*>* SECircuit<CIRCUIT_TYPES>::GetTargetPaths(const NodeType& node) const
{
  auto itr = m_TargetPathMap.find(&node);
  if (itr == m_TargetPathMap.end())
  {
    Error("Circuit does not have path information for node",node.GetName());
    return nullptr;
  }
  return itr->second;
}

//--------------------------------------------------------------------------------------------------
/// \brief
/// Returns all source paths for a node
///
/// \param  Circuit The circuit in question.
/// \param  node Circuit node
///
/// \return Vector of circuit paths
///
/// \details
/// Finds all paths where the input node is the source node. Returns a vector of the paths.
//--------------------------------------------------------------------------------------------------
template<CIRCUIT_TEMPLATE>
const std::vector<PathType*>* SECircuit<CIRCUIT_TYPES>::GetSourcePaths(const NodeType& node) const
{
  auto itr = m_SourcePathMap.find(&node);
  if (itr == m_SourcePathMap.end())
  {
    Error("Circuit does not have path information for node", node.GetName());
    return nullptr;
  }
  return itr->second;
}

//--------------------------------------------------------------------------------------------------
/// \brief
/// Returns all source paths for a node
///
/// \param  Circuit The circuit in question.
/// \param  node Circuit node
///
/// \return Vector of circuit paths
///
/// \details
/// Finds all paths where the input node is the source or target node. Returns a vector of the paths.
//--------------------------------------------------------------------------------------------------
template<CIRCUIT_TEMPLATE>
const std::vector<PathType*>* SECircuit<CIRCUIT_TYPES>::GetConnectedPaths(const NodeType& node) const
{
  auto itr = m_ConnectedPathMap.find(&node);
  if (itr == m_ConnectedPathMap.end())
  {
    Error("Circuit does not have path information for node", node.GetName());
    return nullptr;
  }
  return itr->second;
}

//--------------------------------------------------------------------------------------------------
/// \brief
/// Set all Current and Next values to the Baseline values for all Elements.
//--------------------------------------------------------------------------------------------------
template<CIRCUIT_TEMPLATE>
void SECircuit<CIRCUIT_TYPES>::SetNextAndCurrentFromBaselines()
{
  for(PathType* p : m_Paths)
  {
    if (p->HasResistanceBaseline())
    {
      p->GetResistance().Copy(p->GetResistanceBaseline());
      p->GetNextResistance().Copy(p->GetResistanceBaseline());
    }

    if (p->HasCapacitanceBaseline())
    {
      p->GetCapacitance().Copy(p->GetCapacitanceBaseline());
      p->GetNextCapacitance().Copy(p->GetCapacitanceBaseline());
    }

    if (p->HasInductanceBaseline())
    {
      p->GetInductance().Copy(p->GetInductanceBaseline());
      p->GetNextInductance().Copy(p->GetInductanceBaseline());
    }

    if (p->HasFluxSourceBaseline())
    {
      p->GetFluxSource().Copy(p->GetFluxSourceBaseline());
      p->GetNextFluxSource().Copy(p->GetFluxSourceBaseline());
    }

    if (p->HasPotentialSourceBaseline())
    {
      p->GetPotentialSource().Copy(p->GetPotentialSourceBaseline());
      p->GetNextPotentialSource().Copy(p->GetPotentialSourceBaseline());
    }

    if (p->HasSwitch())
      p->SetNextSwitch(p->GetSwitch());
    if (p->HasValve())
      p->SetNextValve(p->GetValve());
  }

  for (NodeType* n : m_Nodes)
  {
    if (n->HasQuantityBaseline())
    {
      n->GetQuantity().Copy(n->GetQuantityBaseline());
      n->GetNextQuantity().Copy(n->GetQuantityBaseline());
    }
  }
}
