/***************************************************************************
 *   Copyright (C) 2012 by Santiago González                               *
 *                                                                         *
 ***( see copyright.txt file at root folder )*******************************/

#include <math.h>

#include "e-node.h"
#include "node.h"
#include "pin.h"
#include "e-pin.h"
#include "connector.h"
#include "e-element.h"
#include "circmatrix.h"
#include "simulator.h"

eNode::eNode( QString id )
{
    m_id = id; 
    m_nodeNum = -1; 

    m_voltChEl     = nullptr; 
    m_nonLinEl     = nullptr; 
    m_firstAdmit   = nullptr; 
    m_firstSingAdm = nullptr; 
    m_firstCurrent = nullptr; 
    m_nodeAdmit    = nullptr; 

    if( !id.isEmpty() ) Simulator::self()->addToEnodeList( this ); 
}
eNode::~eNode()
{
    clearElmList( m_voltChEl ); 
    clearElmList( m_nonLinEl ); 
    clearConnList( m_firstAdmit ); 
    clearConnList( m_firstSingAdm ); 
    clearConnList( m_firstCurrent ); 
    clearConnList( m_nodeAdmit ); 
}

void eNode::initialize()
{
    m_voltChanged  = true;  // Used for wire animation
    m_single       = false; 
    m_changed      = false; 
    m_currChanged  = false; 
    m_admitChanged = false; 
    m_nodeGroup = -1; 
    nextCH = nullptr; 
    m_volt = 0; 

    clearElmList( m_voltChEl ); 
    m_voltChEl = nullptr; 

    clearElmList( m_nonLinEl ); 
    m_nonLinEl = nullptr; 

    clearConnList( m_firstAdmit ); 
    m_firstAdmit = nullptr; 

    clearConnList( m_firstSingAdm ); 
    m_firstSingAdm = nullptr; 

    clearConnList( m_firstCurrent ); 
    m_firstCurrent = nullptr; 

    clearConnList( m_nodeAdmit ); 
    m_nodeAdmit = nullptr; 

    m_nodeList.clear(); 
}

void eNode::addConnection( ePin* epin, eNode* node )
{
    if( node == this ) return; // Be sure msg doesn't come from this node

    Connection* first = m_firstAdmit;  // Create list of connections
    while( first ){
        if( first->epin == epin ) return;  // Connection already in the list
        first = first->next; 
    }
    Connection* conn = new Connection( epin, node ); 
    conn->next = m_firstAdmit;   // Prepend
    m_firstAdmit = conn; 

    first = m_nodeAdmit;             // Create list of admitances to nodes (reusing Connection class)
    while( first ){
        if( first->node == node ) return;  // Node already in the list
        first = first->next; 
    }
    conn = new Connection( epin, node ); 
    conn->next = m_nodeAdmit;   // Prepend
    m_nodeAdmit = conn; 

    if( !m_nodeList.contains( conn->nodeNum ) ) m_nodeList.append( conn->nodeNum );  // Used by CircMatrix
}

void eNode::stampAdmitance( ePin* epin, double admit ) // Be sure msg doesn't come from this node
{
    Connection* conn = m_firstAdmit; 
    while( conn ){
        if( conn->epin == epin ) { conn->value = admit;  break;  } // Connection found
        conn = conn->next; 
    }
    m_admitChanged = true; 
    changed(); 
}

void eNode::addSingAdm( ePin* epin, eNode* node, double admit )
{
    Connection* conn = new Connection( epin, node ); 
    conn->next = m_firstSingAdm;   // Prepend
    m_firstSingAdm = conn; 
    conn->value = admit; 

    conn = nullptr; 
    Connection* first = m_nodeAdmit;    // Create list of admitances to nodes (reusing Connection class)
    while( first ){
        if( first->node == node )  { conn = first;  break;  }// Node already in the list
        first = first->next; 
    }
    if( !conn ){
        conn = new Connection( epin, node ); 
        conn->next = m_nodeAdmit;   // Prepend
        m_nodeAdmit = conn; 
    }
    if( !m_nodeList.contains( conn->nodeNum ) ) m_nodeList.append( conn->nodeNum );  // Used by CircMatrix
    m_admitChanged = true; 
    changed(); 
}

void eNode::stampSingAdm( ePin* epin, double admit )
{
    Connection* conn = m_firstSingAdm; 
    while( conn ){
        if( conn->epin == epin ) { conn->value = admit;  break;  } // Connection found
        conn = conn->next; 
    }
    m_admitChanged = true; 
    changed(); 
}

void eNode::createCurrent( ePin* epin )
{
    Connection* first = m_firstCurrent; 

    while( first ){
        if( first->epin == epin ) return;  // Element already in the list
        first = first->next; 
    }
    Connection* conn = new Connection( epin ); 
    conn->next = m_firstCurrent;   // Prepend
    m_firstCurrent = conn; 
}

void eNode::stampCurrent( ePin* epin, double current ) // Be sure msg doesn't come from this node
{
    Connection* conn = m_firstCurrent; 
    while( conn ){
        if( conn->epin == epin ) { conn->value = current;  break;  } // Connection found
        conn = conn->next; 
    }
    m_currChanged = true; 
    changed(); 
}

void eNode::changed()
{
    if( m_changed ) return; 
    m_changed = true; 
    Simulator::self()->addToChangedNodes( this ); 
}

void eNode::stampMatrix()
{
    if( m_nodeNum < 0 ) return; 
    m_changed = false; 

    if( m_admitChanged )
    {
        m_totalAdmit = 0; 

        if( m_single ){
            Connection* conn = m_firstAdmit; 
            while( conn ){ m_totalAdmit += conn->value;  conn = conn->next;  } // Calculate total admitance
        }else{
            Connection* na = m_nodeAdmit; 
            while( na ){ na->value = 0;  na = na->next;  } // Clear nodeAdmit

            Connection* conn = m_firstAdmit;  // Full Admitances
            while( conn ){
                double adm = conn->value; 
                int  enode = conn->nodeNum; 

                if( enode >= 0 ){        // Calculate admitances to nodes
                    na = m_nodeAdmit; 
                    while( na ){
                        if( na->nodeNum == enode ){ na->value += adm;  break;  }
                        na = na->next; 
                    }
                }
                m_totalAdmit += adm;     // Calculate total admitance
                conn = conn->next; 
            }
            CircMatrix::self()->stampDiagonal( m_nodeGroup, m_nodeNum, m_totalAdmit );  // Stamp diagonal

            conn = m_firstSingAdm;       // Single admitance values
            while( conn ){
                double adm = conn->value; 
                int  enode = conn->nodeNum; 

                if( enode >= 0 ){        // Add sinle admitance to node
                    na = m_nodeAdmit; 
                    while( na ){
                        if( na->nodeNum == enode ){ na->value += adm;  break;  }
                        na = na->next; 
                    }
                }
                conn = conn->next; 
            }
            na = m_nodeAdmit; 
            while( na ){                  // Stamp non diagonal
                int    enode = na->nodeNum; 
                double admit = na->value; 
                if( enode >= 0 ) CircMatrix::self()->stampMatrix( m_nodeNum, enode, -admit ); 
                na = na->next; 
            }
        }
        m_admitChanged = false; 
    }
    if( m_currChanged ){
        m_totalCurr  = 0; 

        Connection* conn = m_firstCurrent; 
        while( conn ){ m_totalCurr += conn->value;  conn = conn->next;  } // Calculate total current

        if( !m_single ) CircMatrix::self()->stampCoef( m_nodeGroup, m_nodeNum, m_totalCurr ); 
        m_currChanged  = false; 
    }
    if( m_single ) solveSingle(); 
}

void eNode::solveSingle()
{
    double volt = 0; 

    if( m_totalAdmit > 0 ) volt = m_totalCurr/m_totalAdmit; 

    setVolt( volt ); 
}

void  eNode::setVolt( double v )
{
    if( m_volt == v ) return; 

    m_voltChanged = true;  // Used for wire animation
    m_volt = v; 

    CallBackElement* linked = m_voltChEl;  // VoltChaneg callback
    while( linked )
    {
        eElement* el = linked->element; 
        linked = linked->next; 
        if( el->added ) continue; 
        Simulator::self()->addToChangedList( el ); 
        el->added = true; 
    }
    linked = m_nonLinEl ;               // Non Linear callback
    while( linked )
    {
        eElement* el = linked->element; 
        linked = linked->next; 
        if( el->added ) continue; 
        Simulator::self()->addToNoLinList( el ); 
        el->added = true; 
    }
}

void eNode::addEpin( ePin* epin )
{ if( !m_ePinList.contains(epin)) m_ePinList.append(epin);  }

void eNode::remEpin( ePin* epin )
{
    if( m_ePinList.contains(epin) ) m_ePinList.removeOne( epin ); 
}

void eNode::clear()
{
    for( ePin* epin : m_ePinList ){
        epin->setEnode( nullptr ); 
        epin->setEnodeComp( nullptr ); 
    }
    m_nodeCompList.clear(); 
}

QList<int> eNode::getConnections()
{ return m_nodeList;  }

void eNode::voltChangedCallback( eElement* el )
{
    CallBackElement* changed = m_voltChEl; 
    while( changed )
    {
        if( el == changed->element ) return;  // Element already in the list
        changed = changed->next; 
    }
    CallBackElement* newLinked = new CallBackElement( el ); 
    newLinked->next = m_voltChEl;  // Prepend
    m_voltChEl = newLinked; 
}

void eNode::remFromChangedCallback( eElement* el )
{
    CallBackElement* changed = m_voltChEl; 
    CallBackElement* last  = nullptr; 
    CallBackElement* next  = nullptr; 

    while( changed ){
        next = changed->next; 
        if( el == changed->element )
        {
            if( last ) last->next = next; 
            else       m_voltChEl = next; 
            delete changed; //changed->next = nullptr; 
        }
        else last = changed; 
        changed = next; 
    }
}

void eNode::addToNoLinList( eElement* el )
{
    CallBackElement* changed = m_nonLinEl; 
    while( changed )
    {
        if( el == changed->element ) return;  // Element already in the list
        changed = changed->next; 
    }
    CallBackElement* newLinked = new CallBackElement( el ); 
    newLinked->next = m_nonLinEl;  // Prepend
    m_nonLinEl = newLinked; 
    //qDebug() <<m_id<< el->getId(); 
}

void eNode::addNodeComp( Node* n )
{
    if( !m_nodeCompList.contains( n ) ) m_nodeCompList.append( n ); 
}

void eNode::updateConnectors()
{
    if( !m_voltChanged ) return; 
    m_voltChanged = false; 

    for( ePin* epin : m_ePinList ){
        Pin* pin = epin->getPin(); 
        if( pin && pin->isVisible() ){
            Connector* conn = pin->connector(); 
            if( conn ) conn->updateLines(); 
        }
    }
}

void eNode::updateCurrents()
{
    //for( ePin* epin : m_ePinList )
    //{
    //    epin->m_hasCurrent = false; 
    //    epin->m_current = 0; 
    //}
    Connection* conn = m_firstAdmit; 
    while( conn )
    {

        //if( fabs(current) < 1e-7 ) current = 0; 
        ePin* circuitPin = conn->epin->m_circuitPin; 
        if( circuitPin ){
            double admit = conn->value; 
            double volt1 = conn->node->getVolt(); 
            double current = (volt1-m_volt)*admit; 
            circuitPin->m_current += current + conn->epin->m_sourceCurrent; 
            circuitPin->m_hasCurrent = true; 
        }
        //else qDebug() << "eNode::updateCurrents Error: missing circuitPin" << conn->epin->getId(); 
        conn = conn->next; 
    }
}

void eNode::clearElmList( CallBackElement* first )
{
    while( first ){
        CallBackElement* del = first; 
        first = first->next; 
        delete del; 
    }
}

void eNode::clearConnList( Connection* first )
{
    while( first ){
        Connection* del = first; 
        first = first->next; 
        delete del; 
    }
}
