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

#include <QPainter>
#include <QtMath>

#include "transformer.h"
#include "itemlibrary.h"
#include "circuitwidget.h"
#include "simulator.h"
#include "circuit.h"
#include "e-reactive.h"
#include "pin.h"

#include "doubleprop.h"
#include "stringprop.h"

#define tr(str) simulideTr("Transformer",str)

Component* Transformer::construct( QString type, QString id )
{ return new Transformer( type, id );  }

LibraryItem* Transformer::libraryItem()
{
    return new LibraryItem(
        tr("Transformer"),
        "Reactive",
        "transformer.png",
        "Transformer",
        Transformer::construct ); 
}

Transformer::Transformer( QString type, QString id )
           : Component( type, id )
           , eElement( id )
{
    m_coupCoeff = 0.99; 
    m_baseInd = 1; 
    m_lenght = 0; 

    m_primary.prefix = m_id+"-P"; 
    m_primary.angle = 180; 
    m_primary.x     =-16; 

    m_secondary.prefix = m_id+"-S"; 
    m_secondary.angle = 0; 
    m_secondary.x     = 16; 

    addPropGroup( { tr("Main"), {
        new DoubProp<Transformer>("CoupCoeff", tr("Coupling Coefficient"), ""
                                 , this, &Transformer::coupCoeff, &Transformer::setCoupCoeff ),

        new DoubProp<Transformer>("BaseInd"  , tr("Base Inductance"), "H"
                                 , this, &Transformer::baseInd, &Transformer::setBaseInd ),

        new StrProp <Transformer>("Primary"  , tr("Primary"), ""
                                 , this, &Transformer::primary, &Transformer::setPrimary, propNoCopy ),

        new StrProp <Transformer>("Secondary", tr("Secondary"), ""
                                 , this, &Transformer::secondary, &Transformer::setSecondary, propNoCopy ),
    },0 } ); 

    setPrimary("1"); 
    setSecondary("1"); 
}
Transformer::~Transformer() {}

void Transformer::stamp()
{
    m_coils.clear(); 

    setupInducts( &m_primary ); 
    setupInducts( &m_secondary ); 

    m_reacStep = AnalogClock::self()->getStep(); 
    double tStep = (double)m_reacStep/1e12; 

    int i, j, k; 
    int n = m_coils.size(); 

    // Create admitance matrix
    double admit[n][n]; 
    for( int i=0;  i<n;  i++ ) admit[i][i] = m_coils.at(i)->inductance; 
    for( int i=0;  i<n;  i++ )
    {
        eCoil* coili = m_coils.at(i); 
        for( int j=0;  j<i;  ++j )
        {
            eCoil* coilj = m_coils.at(j); 
            admit[i][j] = admit[j][i] = m_coupCoeff*qSqrt( coili->inductance*coilj->inductance )*coili->sign*coilj->sign; 
        }
    }

    // Invert matrix
    for( j=0;  j<n;  ++j )
    {
        for( i=0;  i<j;  ++i )
        {
            double q = admit[i][j]; 
            for( k=0;  k<i;  ++k ) q -= admit[i][k]*admit[k][j]; 
            admit[i][j] = q; 
        }
        for( i=j;  i<n;  ++i )
        {
            double q = admit[i][j]; 
            for( k=0;  k<j;  ++k ) q -= admit[i][k]*admit[k][j]; 
            admit[i][j] = q; 
        }
        if( j == n-1 ) continue; 
        for( i=j+1;  i<n;  ++i ) admit[i][j] /= admit[j][j]; 
    }
    double b[n]; 
    m_inva.resize( n, std::vector<double>( n, 0 ) ); 

    for( k=0;  k<n;  k++)
    {
        for( j=0;  j<n;  j++) b[j] = 0; 
        b[k] = 1; 

        double tot; 
        for( i=k+1;  i<n;  ++i )
        {
            tot = b[i]; 
            for( int j=k;  j<i;  ++j ) tot -= admit[i][j]*b[j]; 
            b[i] = tot; 
        }
        for( i=n-1;  i>=0;  --i )
        {
            tot = b[i]; 
            for( int j=i+1;  j<n;  ++j ) tot -= admit[i][j]*b[j]; 
            b[i] = tot/admit[i][i]; 
        }
        for( int i=0;  i<n;  i++ ) m_inva[i][k] = b[i]*tStep; 
    }

    for( int i=0;  i<n;  i++ ) // Stamp coils before creating induced currents
    {
        m_coils.at(i)->setAdmit( m_inva[i][i] ); 
        m_coils.at(i)->stampCoil(); 
    }
    // Create induced current Sources
    for( int i=0;  i<n;  i++ )
    {
        eCoil* coili = m_coils.at(i); 
        for( int j=0;  j<n;  j++ )
            if( i != j ) coili->addIductor( m_coils.at(j), m_inva[i][j] ); 
    }
    Simulator::self()->addEvent( m_reacStep, this ); 
}

void Transformer::runEvent()
{
    int n = m_coils.size(); 

    for( int i=0;  i<n;  i++ )
    {
        double current = 0; 
        for( int j=0;  j<n;  ++j ) current += m_coils.at(j)->getVolt()*m_inva[i][j]; 
        m_coils.at(i)->stampCurrent( current ); 
    }
    Simulator::self()->addEvent( m_reacStep, this ); 
}

void Transformer::setupInducts( winding_t* w )  // Setup Inductances
{
    int pinIndex = -1; 

    for( eCoil* coil : w->coils ) // connect coils to Pins
    {
        if( coil->index == 0 ) pinIndex++; 

        coil->setEnode( 0, w->pins.at( pinIndex )->getEnode() ); 
        pinIndex++; 
        coil->setEnode( 1, w->pins.at( pinIndex )->getEnode() ); 
        coil->setBaseInd( m_baseInd ); 

        m_coils.append( coil ); 
    }
}

void Transformer::setBaseInd( double i )
{
    if( m_baseInd == i ) return; 
    if( Simulator::self()->isRunning() ) CircuitWidget::self()->powerCircOff(); 
    m_baseInd = i; 
}


void Transformer::setPrimary( QString p )
{
    if( m_primary.definition == p ) return; 
    if( p.isEmpty() ) p = " "; 
    m_primary.definition = p; 
    createCoils(); 
}

void Transformer::setSecondary( QString s )
{
    if( m_secondary.definition == s ) return; 
    if( s.isEmpty() ) s = " "; 
    m_secondary.definition = s; 
    createCoils(); 
}

void Transformer::setCoupCoeff( double c )
{
    if( m_coupCoeff == c ) return; 
    if( Simulator::self()->isRunning() ) CircuitWidget::self()->powerCircOff(); 
    if( c > 0.9999 ) c = 0.9999; 
    if( c < 0.0001 ) c = 0.0001; 
    m_coupCoeff = c; 
}

void Transformer::createCoils()
{
    if( Simulator::self()->isRunning() ) CircuitWidget::self()->powerCircOff(); 

    QString priTopo = m_primary.topology; 
    QString secTopo = m_secondary.topology; 

    int priLenght = getLeght( &m_primary ); 
    int secLenght = getLeght( &m_secondary ); 

    if( priLenght > secLenght ) m_lenght = priLenght; 
    else                        m_lenght = secLenght; 

    m_pin.clear(); 

    if( priTopo == m_primary.topology   ) modify( &m_primary ); 
    else                                  create( &m_primary ); 

    if( secTopo == m_secondary.topology ) modify( &m_secondary ); 
    else                                  create( &m_secondary ); 
}

int Transformer::getLeght( winding_t* w )
{
    QStringList sectsStr = w->definition.split(":"); 
    sectsStr.removeAll(""); 
    int nSections = sectsStr.size(); 

    w->topology.clear(); 
    int nCoils = 0; 
    for( int s=0;  s<nSections;  ++s )
    {
        QString sectStr = sectsStr.at( s ); 
        if( s > 0 ) w->topology.append(":"); 
        QStringList coils = sectStr.remove(" ").split(","); 
        coils.removeAll(""); 
        nCoils += coils.size(); 
        for( int c=0;  c<nCoils;  ++c )
        {
            if( c > 0 ) w->topology.append(","); 
            w->topology.append("c"); 
        }
    }
    w->nCoils = nCoils; 
    if( !nSections ) return 0; 
    return nCoils*3+nSections-1; 
}

void Transformer::modify( winding_t* w )
{
    for( Pin* pin : w->pins ) m_pin.emplace_back( pin ); 

    QList<eCoil*>* coils = &w->coils; 

    int nCoils = w->nCoils; 

    QString defin = w->definition; 
    QStringList sectsStr = defin.remove(" ").split(":"); 
    sectsStr.removeAll(""); 

    int nSections = sectsStr.size(); 
    if( nSections == 0 ) return;  // No coils

    int coilSize = (m_lenght-nSections+1)/nCoils; 
    int y = ((m_lenght-(nCoils*coilSize+nSections-1)))*4;  // Position of first Pin
    w->start = y; 

    int c = 0; 
    int p = 0; 
    for( int sectN=0;  sectN<nSections;  ++sectN )
    {
        w->pins.at( p )->setY( y ); 
        p++; 

        QString sectStr = sectsStr.at( sectN ); 
        QStringList coilsStr = sectStr.split(","); 
        coilsStr.removeAll(""); 
        coilsStr.removeAll("0"); 

        for( int coilN=0;  coilN<coilsStr.size();  ++coilN )
        {
            y += coilSize*8; 
            w->pins.at( p )->setY( y ); 
            p++; 

            double rel = coilsStr.at( coilN ).toDouble(); 
            int sign = rel>=0 ? 1:-1; 

            eCoil* coil = coils->at( c ); 
            coil->sign = sign; 
            coil->size = coilSize; 
            coil->relation = rel; 
            coil->setBaseInd( m_baseInd ); 
            c++; 
        }
        y += 8; 
    }
    int l = m_primary.nCoils   ? 12:4; 
    int r = m_secondary.nCoils ? 12:4; 
    m_area = QRect(-l, 0, l+r, 8*m_lenght ); 
    Circuit::self()->update(); 
}

void Transformer::create( winding_t* w  )
{
    QList<Pin*>*   pins  = &w->pins; 
    QList<eCoil*>* coils = &w->coils; 

    for( Pin* pin : *pins ) deletePin( pin ); 
    pins->clear(); 
    for( eCoil* coil : *coils ) delete coil; 
    coils->clear(); 

    int nCoils = w->nCoils; 

    QString defin = w->definition; 
    QStringList sectsStr = defin.remove(" ").split(":"); 
    sectsStr.removeAll(""); 

    int nSections = sectsStr.size(); 
    if( nCoils == 0 || nSections == 0 ) return;  // No coils

    int angle = w->angle; 
    int x     = w->x; 
    int coilSize = (m_lenght-nSections+1)/nCoils; 
    int y = ((m_lenght-(nCoils*coilSize+nSections-1)))*4;  // Position of first Pin
    w->start = y; 

    for( int sectN=0;  sectN<nSections;  ++sectN )
    {
        QString baseId = w->prefix+QString::number (sectN ); 
        Pin* pin = new Pin( angle, QPoint( x, y ), baseId+"0Pin", 0, this ); 
        pin->setLength( 6 ); 
        pins->append( pin ); 
        m_pin.emplace_back( pin ); 

        QString sectStr = sectsStr.at( sectN ); 
        QStringList coilsStr = sectStr.split(","); 
        coilsStr.removeAll(""); 
        coilsStr.removeAll("0"); 

        for( int coilN=0;  coilN<coilsStr.size();  ++coilN )
        {
            y += coilSize*8; 
            QString id = baseId+QString::number( coilN+1 ); 

            pin = new Pin( angle, QPoint( x, y ), id+"Pin", 0, this ); 
            pin->setLength( 6 ); 
            pins->append( pin ); 
            m_pin.emplace_back( pin ); 

            double rel = coilsStr.at( coilN ).toDouble(); 
            int sign = rel>=0 ? 1:-1; 

            eCoil* coil = new eCoil( coilN, sign, coilSize, m_baseInd, rel, id+"Coil" ); 
            coils->push_back( coil ); 
        }
        y += 8; 
    }
    int l = m_primary.nCoils   ? 12:4; 
    int r = m_secondary.nCoils ? 12:4; 
    m_area = QRect(-l, 0, l+r, 8*m_lenght ); 
    Circuit::self()->update(); 
}

void Transformer::paint( QPainter* p, const QStyleOptionGraphicsItem* o, QWidget* w )
{
    Component::paint( p, o, w ); 

    QPen pen = p->pen(); 
    pen.setWidthF( 2 ); 
    p->setPen(pen); 

    for( int i=0;  i<2;  ++i )
    {
        winding_t* w = i? &m_secondary : &m_primary; 
        int offset = i? -4:4; 
        int y = w->start-8; 
        int x = w->x+offset; 
        QList<eCoil*> coils = w->coils; 

        for( int coilN=0;  coilN<coils.size();  ++coilN )
        {
            const eCoil* coil = coils.at( coilN ); 
            if( coil->index == 0 ) y += 8; 

            if( coil->sign >= 0 )
                 p->drawLine( QPointF( x-0.01, y+4 ), QPointF( x+0.01, y+4 ) ); 
            else p->drawLine( QPointF( x-0.01, y+20), QPointF( x+0.01, y+20) ); 

            for( int n=0;  n<coil->size;  ++n ){
                if( i ) p->drawArc( QRectF(  6, y, 8, 8 ), 90*16,  180*16 ); 
                else    p->drawArc( QRectF(-14, y, 8, 8 ), 90*16, -180*16 ); 
                y += 8; 
            }
        }
    }
    p->drawLine(-2, 0,-2, 8*m_lenght ); 
    p->drawLine( 2, 0, 2, 8*m_lenght ); 

    pen.setBrush( Qt::red ); 
    pen.setWidthF( 2.8 ); 
    p->setPen( pen ); 
    p->drawLine( QPointF(-2, 0 ), QPointF(-2, 0.1) ); 

    Component::paintSelected( p ); 
}

// Coil structure, example section with 3 coils: (VVVV = resistor)
//
// ┏━VVVV━━⚫ Pin0 -- eNode
// ┃
// ┗━VVVV━━⚫ Pin1 -- eNode
//                      |
// ┏━VVVV--- ePin ------
// ┃
// ┗━VVVV━━⚫ Pin2 -- eNode
//                      |
// ┏━VVVV--- ePin ------
// ┃
// ┗━VVVV━━⚫ Pin3 -- eNode
