/***************************************************************************
 *   Copyright (C) 2016 by Santiago González                               *
 *                                                                         *
 ***( see copyright.txt file at root folder )*******************************/
// Based on:
//
// C++ Implementation: pcd8544
//
// Description: This component emulates a graphic LCD module based on the
//              PCD8544 controller.
//
// Author: Roland Elek <elek.roland@gmail.com>, (C) 2010
//
// See copyright.txt file at root folder

#include <QPainter>

#include "itemlibrary.h"
#include "connector.h"
#include "simulator.h"
#include "pcd8544.h"

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

LibraryItem* Pcd8544::libraryItem()
{
    return new LibraryItem(
        "Pcd8544",
        "Displays",
        "pcd8544.png",
        "Pcd8544",
        Pcd8544::construct );
}

Pcd8544::Pcd8544( QString type, QString id )
       : Component( type, id )
       , eElement(  id+"-eElement"  )
       , m_pRst( 270, QPoint(-32, 40), id+"-PinRst", 0, this )
       , m_pCs ( 270, QPoint(-16, 40), id+"-PinCs" , 0, this )
       , m_pDc ( 270, QPoint(  0, 40), id+"-PinDc" , 0, this )
       , m_pSi ( 270, QPoint( 16, 40), id+"-PinSi" , 0, this )
       , m_pScl( 270, QPoint( 32, 40), id+"-PinScl", 0, this )
{
    m_graphical = true;

    m_area = QRectF( -52, -52, 104, 84 );

    m_pin.resize( 5 );
    m_pin[0] = &m_pRst;
    m_pin[1] = &m_pCs;
    m_pin[2] = &m_pDc;
    m_pin[3] = &m_pSi;
    m_pin[4] = &m_pScl;
    
    m_pRst.setLabelText("RST");
    m_pCs.setLabelText( "CS" );
    m_pDc.setLabelText( "D/C");
    m_pSi.setLabelText( "DIN");
    m_pScl.setLabelText("CLK");

    Simulator::self()->addToUpdateList( this );
    
    setLabelPos( -32,-66, 0);
    setShowId( true );
    
   Pcd8544::initialize();
}
Pcd8544::~Pcd8544(){}

void Pcd8544::stamp()
{
    m_pScl.changeCallBack( this ); // Register for Scl changes callback
    m_pRst.changeCallBack( this ); // Register for Rst changes callback
    m_pCs.changeCallBack( this ); // Register for Cs changes callback
}

void Pcd8544::initialize()
{
    clearDDRAM();
    reset() ;
    Pcd8544::updateStep();
}

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

void Pcd8544::voltChanged()               // Called when Scl, Rst or Cs Pin changes
{
    if( m_pRst.getVoltage()<0.3 )            // Reset Pin is Low
    {
        reset();
        return;
    }
    if( m_pCs.getVoltage()>1.6 )            // Cs Pin High: Lcd not selected
    {
        m_cinBuf = 0;                    // Initialize serial buffer
        m_inBit  = 0;
        return;
    }
    if( m_pScl.getVoltage()<1.6 )           // This is an Scl Falling Edge
    {
        m_lastScl = false;
        return;
    }
    else if( m_lastScl ) return;         // Not a rising edge
    m_lastScl = true;

    m_cinBuf &= ~1; //Clear bit 0
    
    if( m_pSi.getVoltage()>1.6 ) m_cinBuf |= 1;
    
    if( m_inBit == 7 ) 
    {
        if( m_pDc.getVoltage()>1.6 )                        // Write Data
        {
            //qDebug() << "Pcd8544::setVChanged"<< m_addrY<<m_addrX<< m_cinBuf;
            m_aDispRam[m_addrY][m_addrX] = m_cinBuf;
            incrementPointer();
        } 
        else{                                           // Write Command
            //qDebug() << "Pcd8544::setVChanged    Command:  "<< m_cinBuf;
            //if(m_cinBuf == 0) { //(NOP) } 
                
            if((m_cinBuf & 0xF8) == 0x20)               // Function set
            {
                m_bH  = ((m_cinBuf & 1) == 1);
                m_bV  = ((m_cinBuf & 2) == 2);
                m_bPD = ((m_cinBuf & 4) == 4);
            }else{
                if(m_bH) 
                {
                    //(Extended instruction set)
                    //None implemented yet - are they relevant at all?
                    //Visualization of e.g. contrast setting could be
                    //useful in some cases, meaningless in others.
                } 
                else                            // Basic instruction set 
                {
                    if((m_cinBuf & 0xFA) == 0x08)     // Display control
                    {
                        m_bD = ((m_cinBuf & 0x04) == 0x04);
                        m_bE =  (m_cinBuf & 0x01);
                    } 
                    else if((m_cinBuf & 0xF8) == 0x40)// Set Y RAM address
                    {
                        int addrY = m_cinBuf & 0x07;
                        if( addrY<6 ) m_addrY = addrY;
                    } 
                    else if((m_cinBuf & 0x80) == 0x80)// Set X RAM address
                    {
                        int addrX = m_cinBuf & 0x7F;
                        if( addrX<84 ) m_addrX = addrX;
        }   }   }   }
        m_inBit = 0;
    }else{
        m_cinBuf <<= 1;
        m_inBit++;
}   }

void Pcd8544::clearDDRAM() 
{
    for(int row=0; row<6; row++)
        for( int col=0; col<84; col++ )
            m_aDispRam[row][col] = 0;
}

void Pcd8544::incrementPointer() 
{
    if( m_bV )
    {
        m_addrY++;
        if( m_addrY >= 6 ) { m_addrY = 0; m_addrX++; }
        if( m_addrX >= 84 )  m_addrX = 0;
    }else{
        m_addrX++;
        if( m_addrX >= 84 ) { m_addrX = 0; m_addrY++; }
        if( m_addrY >= 6  )   m_addrY = 0;
}   }

void Pcd8544::reset() 
{
    m_cinBuf = 0;
    m_inBit  = 0;
    m_addrX  = 0;
    m_addrY  = 0;
    m_bPD = true;
    m_bV  = false;
    m_bH  = false;
    m_bE  = false;
    m_bD  = false;
}

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

    QPen pen( Qt::black, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin );
    p->setPen( pen );
    
    p->setBrush( QColor(50, 70, 100) );
    p->drawRoundedRect( m_area,2,2 );
    p->setBrush( QColor(200, 220, 180) );
    p->drawRoundedRect( -48, -48, 96, 60, 8, 8 );

    if     ( m_bPD )          p->fillRect(-42,-42, 84, 48, QColor(200,215,180) ); // Power-Down mode
    else if( !m_bD && !m_bE ) p->fillRect(-42,-42, 84, 48, QColor(200,215,180) ); // Blank Display mode, blank the visuals
    else if( !m_bD &&  m_bE ) p->fillRect(-42,-42, 84, 48, Qt::black );           // All segments on
    else{
        QImage img( 84*3, 48*3, QImage::Format_RGB32 );
        QPainter painter;
        painter.begin( &img );
        painter.fillRect( 0, 0, 84*3, 48*3, QColor(200,215,180) );

        for(int row=0;row<6;row++){
            for( int col=0; col<84; col++ )
            {
                int x = col*3;
                char abyte = m_aDispRam[row][col];
                if( m_bD && m_bE ) abyte = ~abyte; // Display Inverted

                for( int bit=0; bit<8; bit++ )
                {
                    if( abyte & 1 ){
                        int y = row*8+bit;
                        painter.fillRect( x, y*3, 3, 3, Qt::black );
                    }
                    abyte >>= 1;
        }   }   }

        painter.end();
        p->drawImage(QRectF(-42,-42, 84, 48), img );
    }

    Component::paintSelected( p );
}
