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

#include <QPainter>

#include "ds1307.h"
#include "itemlibrary.h"
#include "iopin.h"

#include "boolprop.h"

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

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

LibraryItem* DS1307::libraryItem()
{
    return new LibraryItem(
        "DS1307",
        "Peripherals",
        "dsxxx_ico.png",
        "DS1307",
        DS1307::construct ); 
}

DS1307::DS1307( QString type, QString id )
      : Component( type, id )
      , TwiModule( id )
      , m_clock( id+"_clock" )
{
    m_graphical = true; 

    m_area = QRect(-28,-20, 56, 40 ); 
    setBackground("dsxxx.png"); 
    setLabelPos(-20,-34, 0 ); 

    m_pin.resize( 3 ); 
    m_pin[0] = m_pinSda = new IoPin( 180, QPoint(-36,-12), id+"-PinSDA", 0, this, openCo ); 
    m_pinSda->setLabelText("SDA"); 
    TwiModule::setSdaPin( m_pinSda ); 

    m_pin[1] = m_clkPin = new IoPin( 180, QPoint(-36, -4), id+"-PinSCL", 0, this, openCo ); 
    m_clkPin->setLabelText("SCL"); 
    TwiModule::setSclPin( m_clkPin ); 

    m_pin[2] = m_outpin = new IoPin( 180, QPoint(-36, 12), id+"-PinSQW", 0, this, output ); 
    m_outpin->setLabelText("SQW"); 
    m_outpin->setOutHighV( 5 ); 
    m_clock.setPin( m_outpin ); 

    m_timeUpdtd = true; 

    m_address = m_cCode = 0b01101000;  // 0x68

    addPropGroup( { tr("Main"), {
        new BoolProp<DS1307>("Time_Updtd", tr("Set current time at start"),""
                            , this, &DS1307::timeUpdtd, &DS1307::setTimeUpdtd ),
        },0 } ); 
}
DS1307::~DS1307(){}

void DS1307::initialize()
{
    TwiModule::initialize(); 

    m_phase = 0; 
    for( int i=0;  i<64;  i++ ) m_data[i] = 0x00; 

    if( m_timeUpdtd )
    {
        m_clock.disable( false ); 
        m_clock.setCurrentTime(); 
    }else{
        m_clock.m_date.setDate( 2000, 1, 1 ); 
        m_clock.m_time.setHMS( 0, 0, 0 ); 
        m_data[0] = 1<<7;  // Disabled
    }
    m_data[7] = 0x03; 
}

void DS1307::stamp()
{
    TwiModule::setMode( TWI_SLAVE ); 
}

void DS1307::readByte()               // Write to RAM
{
    if( m_phase == 0 ){
        m_phase = 1; 
        m_addrPtr = m_rxReg; 
    }else{
        m_data[m_addrPtr] = m_rxReg; 
        if( m_addrPtr < 8 ){
            if     ( m_addrPtr < 3 ) updtTime(); 
            else if( m_addrPtr < 7 ) updtDate(); 
            else                     updtCtrl(); 
        }
        m_addrPtr++; 
        if( m_addrPtr == 64 ) m_addrPtr = 0; 
    }
    TwiModule::readByte(); 
}

void DS1307::writeByte()               // Read from RAM
{
    if( m_addrPtr < 7 ){
        switch( m_addrPtr ){
            case 0: {
                uint8_t disabled = m_data[0] & (1<<7); 
                m_txReg = decToBcd( m_clock.m_time.second() ) | disabled;  break; 
            }break; 
            case 1: m_txReg = decToBcd( m_clock.m_time.minute() );  break; 
            case 2: {
                int hour = m_clock.m_time.hour(); 
                uint8_t ampm = m_data[2] & (1<<6);  // 12/24 hour
                if( ampm && hour > 12 ) hour -= 12;  // 12 hour PM
                m_txReg = ampm | decToBcd( hour ); 
            }break; 
            case 3: m_txReg = decToBcd( m_clock.m_date.dayOfWeek() );  break; 
            case 4: m_txReg = decToBcd( m_clock.m_date.day() );  break; 
            case 5: m_txReg = decToBcd( m_clock.m_date.month() );  break; 
            case 6: m_txReg = decToBcd( m_clock.m_date.year() - 2000 );  break; 
        }
    }
    else m_txReg = m_data[m_addrPtr]; 
    m_addrPtr++; 
    if( m_addrPtr == 64 ) m_addrPtr = 0; 

    TwiModule::writeByte(); 
}

void DS1307::I2Cstop()
{
    m_phase = 0; 
    TwiModule::I2Cstop(); 
}

void DS1307::updtTime()
{
    switch( m_addrPtr ){
        case 0: {
            m_clock.disable( m_data[0] & 1<<7 ); 
            m_clock.m_time.setHMS( m_clock.m_time.hour(), m_clock.m_time.minute(), bcdToDec( m_rxReg & ~(1<<7) ) );  break; 
        }
        case 1: m_clock.m_time.setHMS( m_clock.m_time.hour(), bcdToDec( m_rxReg ), m_clock.m_time.second() );  break; 
        case 2: {
            int hour; 
            if( m_rxReg >= 0x40 ) // 12 hour
            {
                hour = bcdToDec( m_rxReg & 0b00011111 ); 
                if( m_rxReg & (1<<5) ) hour += 12;        // PM
            }
            else hour = bcdToDec( m_rxReg & 0b00111111 );  // 24 hout
            m_clock.m_time.setHMS( hour, m_clock.m_time.minute(), m_clock.m_time.second() ); 
        }
    }
}

void DS1307::updtDate()
{
    switch( m_addrPtr ){
        case 4: m_clock.m_date.setDate( m_clock.m_date.year()   , m_clock.m_date.month(), bcdToDec( m_rxReg )  );  break; 
        case 5: m_clock.m_date.setDate( m_clock.m_date.year()   , bcdToDec( m_rxReg )   , m_clock.m_date.day() );  break; 
        case 6: m_clock.m_date.setDate( 2000+bcdToDec( m_rxReg ), m_clock.m_date.month(), m_clock.m_date.day() ); 
    }
}

void DS1307::updtCtrl()
{
    char RS = m_data[7] & 0b00000011; 
    uint64_t freq = 0;   // Hz
    switch( RS ){
        case 0: freq = 1;      break; 
        case 1: freq = 4096;   break; 
        case 2: freq = 8192;   break; 
        case 3: freq = 32768;  break; 
    }
    m_clock.setFreq( freq ); 
    m_clock.enableOut( m_data[7] & (1<<4) ); 
    m_clock.setDisOut( m_data[7] & (1<<7) ); 
}

char DS1307::decToBcd(char val) {
    return( (val/10*16) + (val%10) );  }
char DS1307::bcdToDec(char val) {
    return( (val/16*10) + (val%16) );  }

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

    p->drawRoundedRect( m_area, 2, 2 ); 
    p->drawPixmap( QRect(-28,-20, 56, 40 ), *m_backPixmap ); 

    Component::paintSelected( p ); 
}
