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

#include <QPainter>

#include "connector.h"
#include "simulator.h"
#include "hd44780_base.h"
#include "utils.h"

Hd44780_Base::Hd44780_Base( QString type, QString id )
            : Component( type, id )
            , m_fontImg( ":font2.png" )
{
    m_graphical = true; 
    
    m_rows = 2; 
    m_cols = 16; 

    setLabelPos( 70,-82, 0); 
    setShowId( true ); 
}
Hd44780_Base::~Hd44780_Base(){}

void Hd44780_Base::init()
{
    m_lastCircTime = Simulator::self()->circTime(); 
    m_lastClock = false; 
    m_writeDDRAM = true; 
    m_blinking = false; 
    m_cursPos     = 0; 
    m_shiftPos    = 0; 
    m_direction   = 1; 
    m_shiftDisp   = 0; 
    m_dispOn      = 0; 
    m_cursorOn    = 0; 
    m_cursorBlink = 0; 
    m_dataLength  = 8; 
    m_lineLength  = 80; 
    m_nibble      = 0; 
    m_input = 0; 
    
    m_DDaddr = 0; 
    m_CGaddr = 0; 
    
    m_imgWidth  = (m_cols*6-1)*2; 
    m_imgHeight = (m_rows*9-1)*2; 
    m_area = QRectF( 0, -(m_imgHeight+33), m_imgWidth+20, m_imgHeight+33 ); 
    setTransformOriginPoint( toGrid( m_area.center() )); 

    clearLcd(); 
}

void Hd44780_Base::writeData( int data )
{
    if( m_writeDDRAM )                                 // Write to DDRAM
    {
        m_DDram[m_DDaddr] = data; 
        m_DDaddr += m_direction; 
        
        if( m_DDaddr > 79 ) m_DDaddr = 0; 
        if( m_DDaddr < 0 )  m_DDaddr = 79; 
        
        if( m_shiftDisp )
        {
            m_shiftPos += m_direction; 
            int lineEnd = m_lineLength-1; 

            if( m_shiftPos>lineEnd ) m_shiftPos = 0; 
            if( m_shiftPos<0 )       m_shiftPos = lineEnd; 
    }   }
    else{                                             // Write to CGRAM
        m_CGram[m_CGaddr] = data; 
        m_CGaddr += 1; 
        
        if( m_CGaddr > 63 ) m_CGaddr = 0; 
}   }

void Hd44780_Base::proccessCommand( int command )
{
    //qDebug() << "Hd44780_Base::proccessCommand: " << command; 
    if( command == 0 )  return; 
    if( command & 128){ setDDaddr( command-128 ); return;  } //1....... Set DDRAM address    Sets the DDRAM address. DDRAM data are sent and received after this setting 37 μs
    if( command & 64) { setCGaddr( command-64 );  return;  } //01...... Set CGRAM address    Sets the CGRAM address. CGRAM data are sent and received after this setting 37 μs
    if( command & 32) { functionSet( command );   return;  } //001..... Function set         Sets interface data length(DL), nº of display line(N), and character font(F) 37 μs
    if( command & 16) { C_D_Shift( command );     return;  } //0001.... Cursor/display shift Sets cursor-move or display-shift(S/C), shift direction(R/L). DDRAM unchanged 37 μs
    if( command & 8 ) { dispControl( command );   return;  } //00001... Display on/off       Sets on/off of all display(D), cursor on/off(C), blink of cursor position character(B) 37 μs
    if( command & 4 ) { entryMode( command );     return;  } //000001.. Entry mode set       Sets cursor move direction(I/D);  Shift the display(S). Performed during data read/write. 37 μs
    if( command & 2 ) { cursorHome();             return;  } //0000001. Cursor home          Cursor to home position. Returns display being shifted to original position. DDRAM unchanged. 1.52 ms
    if( command & 1 ) { clearLcd();               return;  } //00000001 Clear display        Clears display and returns cursor to the home position (address 0). 1.52 ms
}

void Hd44780_Base::functionSet( int data )
{
    if( data & 16 ) m_dataLength = 8;     // Data Length
    else            m_dataLength = 4; 
    
    if( data & 8 ) m_lineLength = 40;     // Display Lines 
    else           m_lineLength = 80; 
    
    // Sets the character font.
    //if( data & 4 ) ; 
    //else            ; 
    //qDebug()<<m_dataLength<<m_lineLength<<(data & 16); 
}
  
void Hd44780_Base::C_D_Shift( int data )
{
    int dir = 1;                     // Move Right/Left
    if( data & 4 ) dir = -1; 

    if( data & 8 )                  // Shift Display
    {
        m_shiftPos += dir; 
        int lineEnd = m_lineLength-1; 

        if( m_shiftPos > lineEnd ) m_shiftPos = 0; 
        if( m_shiftPos < 0       ) m_shiftPos = lineEnd; 
    }
    else                            // Shift Cursor
    {
        m_DDaddr -= dir; 

        if( m_DDaddr > 79 ) m_DDaddr = 0; 
        if( m_DDaddr < 0 )  m_DDaddr = 79; 
    }
}

void Hd44780_Base::dispControl( int data )
{
    if( data & 4 ) m_dispOn = 1;          // Display On/Off
    else           m_dispOn = 0; 
    
    if( data & 2 ) m_cursorOn = 1;        // Cursor On/Off
    else           m_cursorOn = 0; 
    
    if( data & 1 ) m_cursorBlink = 1;     // Cursor Blink
    else           m_cursorBlink = 0; 
}

void Hd44780_Base::entryMode( int data )
{
    // Cursor move left/right
    if( data & 2 ) m_direction = 1; 
    else           m_direction = -1; 
    
    // Shift Display
    if( data & 1 ) m_shiftDisp = 1; 
    else           m_shiftDisp = 0; 
}

void Hd44780_Base::clearLcd()
{
    clearDDRAM(); 
    cursorHome(); 
}

void Hd44780_Base::cursorHome()
{
    setDDaddr( 0 ); 
    m_cursPos  = 0; 
    m_shiftPos = 0; 
}

void Hd44780_Base::setDDaddr( int addr )
{
    if( (m_lineLength==40) && (addr>63) ) addr -= 24; 
    m_DDaddr = addr & 0b01111111; 
    
    m_writeDDRAM = true; 
}

void Hd44780_Base::setCGaddr( int addr )
{
    m_CGaddr = addr & 0b00111111; 
    m_writeDDRAM = false; 
}

void Hd44780_Base::clearDDRAM()
{
    for(int i=0;  i<80;  i++) m_DDram[i] = 32; 
}

void Hd44780_Base::setCols( int cols )
{
    if( cols > 20 ) cols = 20; 
    if( cols < 8 ) cols = 8; 
    m_cols = cols; 
    init(); 
}

void Hd44780_Base::setRows( int rows )
{
    if( rows > 4 ) rows = 4; 
    if( rows < 1 ) rows = 1; 
    m_rows = rows; 
    init(); 
}

void Hd44780_Base::showPins( bool show )
{
    m_pinRS->setVisible( show ); 
    m_pinRW->setVisible( show ); 
    m_pinEn->setVisible( show ); 
    
    for( int i=0;  i<8;  i++ ) m_dataPin[i]->setVisible( show ); 
}

void Hd44780_Base::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( 4, -(29+m_imgHeight), m_imgWidth+12, m_imgHeight+12, 8, 8 ); 

    if( m_dispOn != 0 )
    {
        uint64_t circTime = Simulator::self()->circTime(); 
        if( circTime-m_lastCircTime >= 409600*1e6 )  // Blink period = 409.6 ms
        {
            m_lastCircTime = circTime; 
            if( m_cursorBlink ) m_blinking = !m_blinking; 
            else                m_blinking = false; 
        }

        for( int row=0;  row<m_rows;  row++ )
        {
            for( int col=0;  col<m_cols;  col++ )
            {
                int mem_pos = 0; 
                if( row < 2 ) mem_pos += row*40+col; 
                else          mem_pos += (row-2)*40+20+col; 

                int lineEnd = 79; 
                int lineStart = 0; 

                if( m_lineLength == 40 )
                {
                    if( mem_pos < 40 ) lineEnd   = 39; 
                    else               lineStart = 40; 
                }
                mem_pos += m_shiftPos; 
                if( mem_pos>lineEnd )   mem_pos -= m_lineLength; 
                if( mem_pos<lineStart ) mem_pos += m_lineLength; 

                int char_num = m_DDram[mem_pos]; 
                QImage charact( 10, 16, QImage::Format_RGB32 ); 

                if( mem_pos == m_DDaddr )
                {
                    if( m_blinking )     // Draw black instead of character
                    {
                        charact.fill(qRgb(0, 0, 0)); 
                        p->drawImage(10+col*12,-(m_imgHeight+22)+row*18,charact ); 
                        continue; 
                    }
                    if( m_cursorOn )     // Draw cursor if enabled
                    {
                        charact = m_fontImg.copy( 0, 0, 10, 16 ); 
                        int y = 14; 
                        for( int x=9;  x>0;  x-=2 )
                        {
                            charact.setPixel(x,   y,   qRgb(0, 0, 0)); 
                            charact.setPixel(x-1, y,   qRgb(0, 0, 0)); 
                            charact.setPixel(x,   y+1, qRgb(0, 0, 0)); 
                            charact.setPixel(x-1, y+1, qRgb(0, 0, 0)); 
                        }
                        p->drawImage(10+col*12,-(m_imgHeight+22)+row*18,charact ); 
                }   }
                if( char_num < 8 )                        // CGRam Character
                {
                    charact = m_fontImg.copy( 0, 0, 10, 16 ); 
                    int addr = char_num*8; 

                    for( int y=0;  y<16;  y+=2 )
                    {
                        int data = m_CGram[ addr ]; 
                        addr++; 

                        for( int x=9;  x>0;  x-=2 )
                        {
                            if( data & 1 ) {
                                charact.setPixel(x,   y,   qRgb(0, 0, 0)); 
                                charact.setPixel(x-1, y,   qRgb(0, 0, 0)); 
                                charact.setPixel(x,   y+1, qRgb(0, 0, 0)); 
                                charact.setPixel(x-1, y+1, qRgb(0, 0, 0)); 
                            }
                            data = data>>1; 
                }   }   }
                else charact = m_fontImg.copy( char_num*10, 0, 10, 16 ); 

                p->drawImage( 10+col*12,-(m_imgHeight+22)+row*18,charact ); 
        }   }
    }
    Component::paintSelected( p ); 
}
