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

#include <math.h>

#include "connector.h"
#include "connectorline.h"
#include "circuitwidget.h"
#include "simulator.h"
#include "circuit.h"
#include "pin.h"
#include "e-node.h"
#include "currentwidget.h"
#include "utils.h"

#include "stringprop.h"

Connector::Connector( QString type, QString id, Pin* startpin, Pin* endpin )
         : CompBase( type, id )
{
    if( id.isEmpty() ) qDebug() << "ERROR! Connector::Connector empty Id"; 

    m_step = 0; 
    m_actLine   = 0; 
    m_lastindex = 0; 
    m_freeLine = false; 
    m_animate = Circuit::self()->animateCurr(); 
    m_startPin = startpin; 
    setIsBus( m_startPin->isBus() ); 

    if( endpin ) closeCon( endpin ); 
    else m_endPin   = nullptr; 

    Circuit::self()->compMap()->insert( id, this ); 

    addPropGroup( {"Main", {
new StrProp<Connector>( "itemtype"  ,"","", this, &Connector::itemType,   &Connector::dummySetter ),
new StrProp<Connector>( "uid"       ,"","", this, &Connector::getUid,     &Connector::dummySetter ),
new StrProp<Connector>( "startpinid","","", this, &Connector::startPinId, &Connector::dummySetter ),
new StrProp<Connector>( "endpinid"  ,"","", this, &Connector::endPinId,   &Connector::dummySetter ),
new StrProp<Connector>( "pointList" ,"","", this, &Connector::pListStr,   &Connector::setPointListStr ),
    }, 0} ); 
}
Connector::~Connector()
{
    Circuit::self()->compMap()->remove( m_id ); 
}

void Connector::updateStep()
{
    if( Simulator::self()->isPaused() ) return; 
    m_current = getCurrent(); 

    m_currentSpeed = CurrentWidget::self()->speed() * m_current; 

    if     ( m_currentSpeed >  4 ) m_currentSpeed =  4; 
    else if( m_currentSpeed < -4 ) m_currentSpeed = -4; 

    m_step += m_currentSpeed; 
    if( fabs(m_step) > 8 ) m_step = fmod( m_step, 8 ); 
    if( m_step < 0 ) m_step += 8; 

    //m_step /= 8;  // 0 to 1

    for( ConnectorLine* line : m_conLineList ) line->updtLength(); 
}

void Connector::remNullLines()      // Remove lines with leght = 0 or aligned
{
    if( m_conLineList.length() < 2 ) { refreshPointList();  return;  }

    for( ConnectorLine* line : m_conLineList )
    {
        if( line->isDiagonal() ) continue; 
        
        int index = m_conLineList.indexOf( line ); 
        if( index < m_conLineList.length()-1 )      //
        {
            ConnectorLine* line2 = m_conLineList.at( index+1 ); 
            
            if( line2->isDiagonal() ) continue; 

            if( line->dx() == line2->dx() || line->dy() == line2->dy() ) // Lines aligned or null line
            {
                line2->sSetP1( line->p1() ); 
                remConLine( line  ); 
    }   }   }
    if( m_conLineList.length() < 2 )
    {
        m_lastindex = 0; 
        m_actLine   = 0; 
    }
    refreshPointList(); 
}

void Connector::remConLine( ConnectorLine* line  )
{
    int index = m_conLineList.indexOf( line ); 

    connectLines( index-1, index+1 ); 
    if( line->scene() ) Circuit::self()->removeItem( line ); 
    m_conLineList.removeOne( line ); 
    if( m_actLine > 0 ) m_actLine -= 1; 
}

void Connector::setPointListStr( QString pl )
{
    setPointList( pl.split(",") ); 
    //remNullLines(); 
}

void Connector::setPointList( QStringList plist )
{
    remLines(); 
    m_pointList = plist; 

    int index = 0; 
    int p1x = plist.at(0).toInt(); 
    int p1y = plist.at(1).toInt(); 
    int p2x = plist.at(plist.size()-2).toInt(); 
    int p2y = plist.last().toInt(); 

    ConnectorLine* line1 = nullptr; 

    for( int i=2;  i<plist.size();  i+=2 )
    {
        p2x = plist.at(i).toInt(); 
        p2y = plist.at(i+1).toInt(); 

        ConnectorLine* line2 = new ConnectorLine( p1x, p1y, p2x, p2y, this ); 
        line2->setIsBus( m_isBus ); 
        line2->m_animateCurrent = m_animate; 
        m_conLineList.insert( index, line2 ); 
        Circuit::self()->addItem( line2 ); 

        if( line1 )
        {
            line1->setNextLine( line2 ); 
            line2->setPrevLine( line1 ); 
        }
        index++; 
        line1 = line2; 
        p1x = p2x; 
        p1y = p2y; 
}   }

void Connector::refreshPointList()
{
    if( m_conLineList.isEmpty() ) return; 

    QStringList list; 
    list.append( QString::number( m_conLineList.at(0)->p1().x() ) ); 
    list.append( QString::number( m_conLineList.at(0)->p1().y() ) ); 

    for( int i=0;  i<m_conLineList.size();  i++ )
    {
        list.append( QString::number( m_conLineList.at(i)->p2().x() ) ); 
        list.append( QString::number( m_conLineList.at(i)->p2().y() ) ); 
    }
    m_pointList = list; 
}

void Connector::addConLine( ConnectorLine* line, int index )
{
    if( index > 0  && index < m_conLineList.size() ) disconnectLines( index-1, index ); 

    m_conLineList.insert( index, line ); 
    Circuit::self()->addItem( line ); 

    if( index > 0 )
    {
        connectLines( index-1, index ); 
        m_conLineList.at( index-1 )->sSetP2( line->p1() ); 
    }
    if( index < m_conLineList.size()-1 )
    {
        if( m_conLineList.size() < 2 ) return; 

        connectLines( index, index+1 ); 
        m_conLineList.at( index+1 )->sSetP1( line->p2() ); 
    }
    line->setIsBus( m_isBus ); 
    line->m_animateCurrent = m_animate; 
    if( Circuit::self()->is_constarted() ) line->setCursor( Qt::ArrowCursor ); 
}

ConnectorLine* Connector::addConLine( int x1, int y1, int x2, int y2, int index )
{
    ConnectorLine* line = new ConnectorLine( x1, y1, x2, y2, this ); 
    addConLine( line, index ); 
    return line; 
}

void Connector::connectLines( int index1, int index2 )
{
    if( index1 < 0 || index2 < 0 || index2 > m_conLineList.length()-1 )
        return; 

    ConnectorLine* line1 = m_conLineList.at( index1 ); 
    ConnectorLine* line2 = m_conLineList.at( index2 ); 

    line1->setNextLine( line2 ); 
    line2->setPrevLine( line1 ); 
}

void Connector::disconnectLines( int index1, int index2 )
{
    if( index1 < 0 || index2 < 0 || index2 > m_conLineList.length()-1 )
        return; 

    m_conLineList.at( index1 )->setNextLine( nullptr ); 
    m_conLineList.at( index2 )->setPrevLine( nullptr ); 
}

void Connector::updateConRoute( Pin* pin, QPointF thisPoint )
{
    if( m_conLineList.isEmpty() ) return; 
    if( !m_conLineList.first()->isVisible() ) return; 
    if( Circuit::self()->pasting() )  { remNullLines();  return;  }

    bool diagonal = false; 
    int length = m_conLineList.length(); 
    ConnectorLine* line; 
    ConnectorLine* preline = nullptr; 

    if( pin == m_startPin )
    {
        line = m_conLineList.first(); 
        diagonal = line->isDiagonal(); 
        line->sSetP1( thisPoint.toPoint() ); 
        m_lastindex = 0; 

        if( length > 1 ){
            preline = m_conLineList.at(1); 
            m_actLine = 1; 
        }
        else m_actLine = 0; 
        
        if( diagonal ) { remNullLines();  return;  }
    }else{
        line = m_conLineList.last(); 
        diagonal = line->isDiagonal(); 
        
        line->sSetP2( toGrid( thisPoint ).toPoint() ); 
        
        m_lastindex = length-1; 

        if( length > 1 ){
            preline = m_conLineList.at( m_lastindex-1 ); 
            if( pin != nullptr ) m_actLine = m_lastindex-1; 
        }
        if( diagonal || m_freeLine ) 
        {
            m_freeLine = false; 
            if( m_lastindex == m_actLine )          // Add new corner
            {
                QPoint point = line->p2(); 
                ConnectorLine* newLine = addConLine( point.x(), point.y(), point.x()+4, point.y()+4, m_lastindex + 1 ); 
                if( line->isSelected() ) newLine->setSelected( true ); 
            }
            remNullLines(); 
            return; 
    }   }
    if( (line->dx() == 0) && (line->dy() == 0) && (length > 1) ) // Null Line
    {
        if( line->scene() ) Circuit::self()->removeItem( line ); 
        m_conLineList.removeOne( line ); 

        if( m_actLine > 0 )  m_actLine -= 1; 
    }
    else if( line->dx() != 0 && line->dy() != 0 )
    {
        QPoint point; 

        if( m_lastindex == m_actLine )          // Add new corner
        {
            point = line->p2(); 

            if( fabs(line->dx()) > fabs(line->dy()) ) point.setY( line->p1().y() ); 
            else                                      point.setX( line->p1().x() ); 

            ConnectorLine* newLine = addConLine( point.x(), point.y(), line->p2().x(), line->p2().y(), m_lastindex + 1 ); 

            if( line->isSelected() ) newLine->setSelected( true ); 
            line->setP2( point ); 
        }
        else if( m_lastindex < m_actLine )        // Update first corner
        {
            point = line->p2(); 

            if( preline->dx() == 0 ) point.setY( line->p1().y() ); 
            else                     point.setX( line->p1().x() ); 

            line->setP2( point ); 

            if( line->dx() == preline->dx() || line->dy() == preline->dy() ) // Lines aligned or null line
            {
                if( line->isSelected() || preline->isSelected() )
                {
                    preline->sSetP1( line->p1() ); 
                    remConLine( line  ); 
        }   }   }
        else{                                      // Update last corner
            point = line->p1(); 

            if( preline->dx() == 0 ) point.setY( line->p2().y() ); 
            else                     point.setX( line->p2().x() ); 

            line->setP1( point ); 

            if( line->dx() == preline->dx() || line->dy() == preline->dy() ) // Lines aligned or null line
            {
                if( line->isSelected() || preline->isSelected() )
                {
                    preline->sSetP2( line->p2() ); 
                    remConLine( line  ); 
    }   }   }   }
    remNullLines(); 
}

void Connector::remLines()
{
    while( !m_conLineList.isEmpty() )
    {
        ConnectorLine* line = m_conLineList.takeLast(); 
        if( line->scene() ) Circuit::self()->removeItem( line ); 
        delete line; 
}   }

void Connector::move( QPointF delta )
{
    if( !Circuit::self()->pasting() ) return; 

    for( ConnectorLine* line : m_conLineList ) line->move( delta ); 
    m_startPin->isMoved(); 
    m_endPin->isMoved(); 
}

void Connector::isMoved()
{
    if( m_startPin ) m_startPin->isMoved(); 
    if( m_endPin   ) m_endPin->isMoved(); 
}

void Connector::setSelected(  bool selected )
{ for( ConnectorLine* line : m_conLineList ) line->setSelected( selected );  }

void Connector::setVisib(  bool vis )
{ for( ConnectorLine* line : m_conLineList ) line->setVisible( vis );  }

void Connector::remove()
{
    if( Simulator::self()->isRunning() )  CircuitWidget::self()->powerCircOff(); 

    if( m_startPin ) m_startPin->connectorRemoved(); 
    if( m_endPin )   m_endPin->connectorRemoved(); 
    remLines(); 
}

void Connector::closeCon( Pin* endpin )
{
    if( Simulator::self()->isRunning() ) CircuitWidget::self()->powerCircOff(); 

    m_endPin = endpin; 
    m_startPin->setConnector( this ); 
    m_endPin->setConnector( this ); 
    m_startPin->setConPin( m_endPin ); 
    m_endPin->setConPin( m_startPin ); 
    m_endPin->setIsBus( m_isBus ); 
    m_startPin->setIsBus( m_isBus );  // StartPin will set conPin isBus

    updateConRoute( m_endPin, m_endPin->scenePos() ); 

    for( ConnectorLine* line : m_conLineList ) line->setCursor( Qt::CrossCursor ); 
}

void Connector::splitCon( int index, Pin* pin0, Pin* pin2 )
{
    if( !m_endPin ) return; 

    QString id = "Connector-"+Circuit::self()->newConnectorId(); 
    Connector* con0 = new Connector( "Connector", id, m_startPin ); 
    Circuit::self()->conList()->append( con0 ); 

    id = "Connector-"+Circuit::self()->newConnectorId(); 
    Connector* con1 = new Connector( "Connector", id, pin2 ); 
    Circuit::self()->conList()->append( con1 ); 

    disconnectLines( index-1, index ); 
    Connector* con = con0; 
    int newindex = 0; 
    int size = m_conLineList.size(); 
    for( int i=0;  i<size;  ++i )
    {
        if( i == index ){ con = con1;  newindex = 0;  }
        ConnectorLine* line = m_conLineList.takeFirst(); 
        con->lineList()->insert( newindex, line ); 
        line->setConnector( con ); 
        if( newindex > 1 ) con->incActLine(); 
        newindex++; 
    }
    con1->closeCon( m_endPin );     // Close con1 first please
    con0->closeCon( pin0 );         // con0

    m_startPin = nullptr; 
    m_endPin   = nullptr; 
    Circuit::self()->removeConnector( this ); 
}

void Connector::updateLines()
{
    for( ConnectorLine* line : m_conLineList ) line->update(); 

    /*eNode* enode = startPin()->getEnode(); 
    if( enode && enode->voltchanged() )
    {
        enode->setVoltChanged( false ); 
        for( ConnectorLine* line : m_conLineList ) line->update(); 
    }*/
}

void Connector::setIsBus( bool bus )
{
    for( ConnectorLine* line : m_conLineList ) line->setIsBus( bus ); 
    m_isBus = bus; 
}

void Connector::animate( bool an )
{
    m_animate = an; 

    for( ConnectorLine* line : m_conLineList ) line->m_animateCurrent = an; 

    if( an ) Simulator::self()->addToUpdateList( this ); 
    else     Simulator::self()->remFromUpdateList( this ); 
}

QString Connector::startPinId() { return m_startPin->pinId();  }
QString Connector::endPinId()   { return m_endPin->pinId();  }

double Connector::getVoltage() { return m_startPin->getVoltage();  }

double Connector::getCurrent()
{
    Pin* pin = (Pin*) m_startPin; 
    double current = pin->getCurrent(); 
    if( current == 0 )
    {
        pin = (Pin*) m_endPin; 
        current = -pin->getCurrent(); 
    }
    if( fabs( current ) < 1e-6 ) current = 0; 
    return current; 
}
