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

#include <QPainter>

#include "ws2812.h"
#include "itemlibrary.h"
#include "simulator.h"
#include "circuit.h"
#include "iopin.h"

#include "intprop.h"
#include "doubleprop.h"

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

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

LibraryItem* WS2812::libraryItem()
{
    return new LibraryItem(
        tr("WS2812 Led"),
        "Leds",
        "ws2812.png",
        "WS2812",
        WS2812::construct ); 
}

WS2812::WS2812( QString type, QString id )
      : Component( type, id )
      , eClockedDevice( id )
{
    m_graphical = true; 

    // WS2812B-V2
    m_RES = 50;   // us
    m_T0H = 400;  // ns
    m_T1H = 850;  // ns
    m_T0L = 850;  // ns
    m_T1L = 400;  // ns

    m_rgb.resize( 3 ); 
    m_pin.resize( 2 ); 

    m_pin[0] = m_clkPin = new IoPin( 180, QPoint(-12,0), id+"-InPin", 0, this, input ); 
    m_pin[1] = m_output = new IoPin(   0, QPoint( 12,0), id+"-OutPin",1, this, output ); 
    m_output->setOutHighV( 5 ); 

    m_cols = 1; 
    m_rows = 1; 
    updateLeds(); 

    Simulator::self()->addToUpdateList( this ); 

    addPropGroup( { tr("Main"), {
        new IntProp<WS2812>("Rows", tr("Rows"),""
                           , this, &WS2812::rows, &WS2812::setRows ),

        new IntProp<WS2812>("Cols", tr("Columns"),""
                           , this, &WS2812::cols, &WS2812::setCols,0,"uint" ),
    }, groupNoCopy} ); 

    addPropGroup( { tr("Timing"), {
        new IntProp<WS2812>("RstTime", tr("Minimum Reset pulse"),"_µs"
                            , this, &WS2812::resetPulse, &WS2812::setResetPulse ),

        new IntProp<WS2812>("T0H", tr("0, High time"),"_ns"
                           , this, &WS2812::t0H, &WS2812::setT0H ),

        new IntProp<WS2812>("T0L", tr("0, Low time"),"_ns"
                            , this, &WS2812::t0L, &WS2812::setT0L ),

        new IntProp<WS2812>("T1H", tr("1, High time"),"_ns"
                            , this, &WS2812::t1H, &WS2812::setT1H ),

        new IntProp<WS2812>("T1L", tr("1, Low time"),"_ns"
                            , this, &WS2812::t1L, &WS2812::setT1L ),
    }, groupNoCopy} ); 
}
WS2812::~WS2812() { }

void WS2812::updateStep() { update();  }

void WS2812::initialize()
{
    for( int i=0;  i<m_leds;  i++ ) m_led[i] = QColor( 0, 0, 0 ); 
    m_lastTime = 0; 
    m_data = 0; 
    m_word = 0; 
    m_byte = 0; 
    m_bit  = 7; 
}

void WS2812::voltChanged()
{
    updateClock(); 

    uint64_t CircTime = Simulator::self()->circTime(); 
    uint64_t time = CircTime - m_lastTime; 
    m_lastTime = CircTime; 

    if( m_clkState == Clock_Rising )    // Input Rising edge
    {
        if( time > m_RES*1e6 )             // Time >  -> Reset
        {
            m_data = 0; 
            m_word = 0; 
            m_byte = 0; 
            m_bit  = 7; 
        }
        else if(( time > (m_T1L-150)*1e3 )&&( time < (m_T0L+150)*1e3 )) // Valid L State Time
        {
            if( m_word >= m_leds ) setOut( true ); 
            else if( m_newWord )
            {
                if( m_lastHstate  ) { if( time < (m_T1L+150)*1e3 ) saveBit( 1 ); } // Valid bit = 1
                else                  if( time > (m_T0L-150)*1e3 ) saveBit( 0 );   // Valid bit = 0
        }   }
        m_newWord = true; 
    }
    else if( m_clkState == Clock_Falling )              // Input Falling edge
    {
        if( m_word >= m_leds ) setOut( false ); 
        else if(( time > (m_T0H-150)*1e3 )&&( time < (m_T1H+150)*1e3 )) // Valid H State Time
        {
            if     ( time > (m_T1H-150)*1e3 ) m_lastHstate = true; 
            else                              m_lastHstate = false; 

            if(( m_byte == 2 )&&( m_bit == 0 )) // Low time for last bit can be anything?
            {                                   // so save it here and skip saving
                saveBit( m_lastHstate );         // at next Rising Edge Using m_newWord
                m_newWord = false; 
}   }   }   }

void WS2812::setOut( bool state )
{
    m_output->scheduleState( state, 0 ); 
    /// Simulator::self()->addEvent( 1, NULL ); 
}

void WS2812::saveBit( bool bit )
{
    if( bit ) m_data |=  (1 << m_bit); 
    else      m_data &= ~(1 << m_bit); 

    if( --m_bit < 0 )
    {
        m_rgb[ m_byte ] = m_data; 
        m_data = 0; 
        m_bit  = 7; 

        if( ++m_byte > 2 )
        {
            m_led[ m_word ] = QColor( m_rgb[1], m_rgb[0], m_rgb[2] ); 
            m_byte = 0; 
            m_word++; 
}   }   }

void WS2812::setRows( int rows )
{
    if( rows == m_rows ) return; 
    if( rows < 1 ) rows = 1; 
    m_rows = rows; 
    updateLeds(); 
}

void WS2812::setCols( int cols )
{
    if( cols == m_cols ) return; 
    if( cols < 1 ) cols = 1; 
    m_cols = cols; 
    updateLeds(); 
}

void WS2812::updateLeds()
{
    m_leds = m_rows*m_cols; 
    m_led.resize( m_leds ); 
    m_area = QRect( -7, -7, m_cols*12+2, m_rows*12+2 ); 
    m_output->setPos( 12*m_cols, 12*m_rows-12); 
    Circuit::self()->update(); 
}

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

    p->setBrush( QColor(Qt::black) ); 
    p->drawRect( m_area ); 

    if( !m_hidden )
    {
        p->setBrush( QColor( *m_foreColor ) ); 
        QPointF points[3] = {
        QPointF( -9,-2 ),
        QPointF( -9+3,0 ),
        QPointF( -9, 2 )     }; 
        p->drawPolygon(points, 3); 
    }
    p->setPen( Qt::NoPen ); 
    for( int row=0;  row<m_rows;  row++ ){
        for( int col=0;  col<m_cols;  col++ ){
            p->setBrush( m_led[row*m_cols+col] ); 
            p->drawEllipse( -6+col*12, -6+row*12, 12, 12 ); 
    }   }

    Component::paintSelected( p ); 
}
