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

#include <QPainter>

#include <QDial>
#include <QGraphicsProxyWidget>

#include <math.h>

#include "ds1621.h"
#include "itemlibrary.h"
#include "dialed.h"
#include "circuit.h"
#include "iopin.h"
#include "simulator.h"
#include "updobutton.h"

#include "doubleprop.h"

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

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

LibraryItem *DS1621::libraryItem()
{
    return new LibraryItem(
        tr("DS1621"),
        "Sensors",
        "ic_comp.png",
        "DS1621",
        DS1621::construct); 
}

DS1621::DS1621( QString type, QString id )
      : IoComponent( type, id )
      , TwiModule( id )
      , m_vdd(   0, QPoint( 24, -8 ), id+"-PinVdd", 0, this )
      , m_gnd( 180, QPoint(-24, 16 ), id+"-PinGnd", 0, this )
{
    m_graphical = true; 

    m_width  = 4; 
    m_height = 5; 

    init({           // Inputs:
      "IL01SDA", // type: Input, side: Left, pos: 01, label: "SDA"
      "IL02SCL",
      "IR02A0",
      "IR03A1",
      "IR04A2",
      // Outputs:
      "OL03Tou"
     }); 

    m_area = QRect(-16,-20-4, m_width*8, m_height*8+8 ); 

    m_inpPin[0]->setPinMode( openCo ); 
    TwiModule::setSdaPin( m_inpPin[0] ); 

    m_inpPin[1]->setPinMode( openCo ); 
    TwiModule::setSclPin( m_inpPin[1] ); 

    for( IoPin* pin : m_inpPin ) pin->setLabelColor( QColor( 250, 250, 200 ) ); 
    for( IoPin* pin : m_outPin ) pin->setLabelColor( QColor( 250, 250, 200 ) ); 

    m_vdd.setLabelColor( QColor( 250, 250, 200 ) ); 
    m_gnd.setLabelColor( QColor( 250, 250, 200 ) ); 
    m_vdd.setLabelText("Vdd"); 
    m_gnd.setLabelText("Gnd"); 
    m_vdd.setUnused( true ); 
    m_gnd.setUnused( true ); 

    UpDoButton* u_button = new UpDoButton( true ); 
    QGraphicsProxyWidget* proxy = Circuit::self()->addWidget( u_button ); 
    proxy->setParentItem( this ); 
    proxy->setPos( QPoint(-20,-27 ) ); 

    UpDoButton* d_button = new UpDoButton( false ); 
    proxy = Circuit::self()->addWidget( d_button ); 
    proxy->setParentItem( this ); 
    proxy->setPos( QPoint(-20,-22 ) ); 

    QObject::connect( u_button, &UpDoButton::pressed, [=](){ upbuttonclicked();  } ); 
    QObject::connect( d_button, &UpDoButton::pressed, [=](){ downbuttonclicked();  } ); 

    m_font.setFamily("Ubuntu Mono"); 
#ifdef _WIN32
    m_font.setStretch( 99 ); 
#else
    m_font.setStretch( 93 ); 
#endif
    m_font.setPixelSize( 9 ); 
    m_font.setBold( true ); 
    m_font.setLetterSpacing( QFont::PercentageSpacing, 100 ); 
    setLabelPos(-24,-28 ); 

    setTemp( 22.5 ); 
    setTempInc( 0.5 ); 
    m_changed = true; 

    //IoComponent::initState(); 
    m_cCode = 0b01001000;  // 0x98 >> 1  : I2C Addr

    addPropGroup( { tr("Main"), {
        new DoubProp<DS1621>("Temp", tr("Temperature"), "°C"
                            , this, &DS1621::temp, &DS1621::setTemp ),

        new DoubProp<DS1621>("TempInc", tr("Temp. increment"), "°C"
                            , this, &DS1621::tempInc, &DS1621::setTempInc ),
    },0} ); 
}

DS1621::~DS1621() {}

void DS1621::stamp() // Called at Simulation Start
{
    m_command = 0; 
    m_changed = true; 
    m_tempCount = 0; 
    m_tempSlope = 0; 
    m_writeByte = -1; 
    m_config = 0; 
    m_convert = false; 
    m_oneShot = false; 
    m_outPol  = false; 

    m_Th = 0; 
    m_Tl = 0; 

    TwiModule::setMode( TWI_SLAVE ); 

    for( int i=2;  i<5;  i++ ) m_inpPin[i]->changeCallBack( this ); 
}

void DS1621::runEvent() // Conversion done 750  ms
{
    if( !m_convert ) return; 

    m_config |= 1<<7;  // Set DONE bit

    if( m_changed ) doConvert(); 
    if( !m_oneShot ) Simulator::self()->addEvent( 750*1e9, this );  // Continuous
}

void DS1621::voltChanged()             // Some Pin Changed State, Manage it
{
    m_address = m_cCode; 
    if( m_inpPin[2]->getInpState() ) m_address += 1; 
    if( m_inpPin[3]->getInpState() ) m_address += 2; 
    if( m_inpPin[4]->getInpState() ) m_address += 4; 

    TwiModule::voltChanged();         // Run I2C Engine
}

void DS1621::readByte() // read from I2C
{
    bool convert = m_convert; 

    if( m_command == 0x00 )      // Master sending Command
    {
        m_writeByte = -1; 
        if     ( m_rxReg == 0x22 ) convert = false; 
        else if( m_rxReg == 0xEE ) convert = true; 
        else{
            m_command = m_rxReg; 
            if     ( m_command == 0xA1 ) m_writeByte = 1;   // Access TH
            else if( m_command == 0xA2 ) m_writeByte = 1;   // Access TL
            else if( m_command == 0xA8 ) m_writeByte = 0;   // Read Counter
            else if( m_command == 0xA9 ) m_writeByte = 0;   // Read Slope
            else if( m_command == 0xAA ) m_writeByte = 1;   // Read Temp
            else if( m_command == 0xAC ) m_writeByte = 0;   // Access Config
        }
        if( convert != m_convert ){
            m_convert = convert; 
            Simulator::self()->cancelEvents( this ); 

            if( convert ) // Start Conversion
            {
                m_config &= ~(1<<7);  // Clear DONE bit
                Simulator::self()->addEvent( 750*1e9, this );  // 750 ms
            }
        }
    }else{                         // Master writting Data
        if( m_writeByte >= 0  ){
            if     ( m_command == 0xA1 ) // Write TH
            {
                m_ThReg[ m_writeByte ] = m_rxReg; 
                m_Th = m_ThReg[1]; 
                double thH = m_ThReg[0] ? 0.5 : 0.0; 
                if( m_Th < 0 ) thH = -thH; 
                m_Th += thH; 
            }
            else if( m_command == 0xA2 ) // Write TL
            {
                m_TlReg[ m_writeByte ] = m_rxReg; 
                m_Tl = m_TlReg[1]; 
                double tlH = m_TlReg[0] ? 0.5 : 0.0; 
                if( m_Tl < 0 ) tlH = -tlH; 
                m_Tl += tlH; 
            }
            else if( m_command == 0xAC ) // Write Config
            {
                m_config = m_rxReg; 
                m_oneShot = m_config & 1; 
                m_outPol  = m_config & 2; 
            }
        }
        if( --m_writeByte < 0 ) m_command = 0; 
    }
    TwiModule::readByte(); 
}

void DS1621::writeByte() // write to I2C
{
    m_txReg = 0; 
    if( m_writeByte >= 0 ){
        if     ( m_command == 0xA1 ) m_txReg = m_ThReg[ m_writeByte ];  // Read TH
        else if( m_command == 0xA2 ) m_txReg = m_TlReg[ m_writeByte ];  // Read TL
        else if( m_command == 0xA8 ) m_txReg = m_tempCount;             // Read Counter
        else if( m_command == 0xA9 ) m_txReg = m_tempSlope;             // Read Slope
        else if( m_command == 0xAA ) m_txReg = m_tempReg[m_writeByte];  // Read Temp
        else if( m_command == 0xAC ) m_txReg = m_config;                // Read Config
    }
    if( --m_writeByte < 0 ) m_command = 0; 

    TwiModule::writeByte(); 
}

void DS1621::doConvert()
{
    m_changed = false; 

    float temp_abs = fabs( m_temp );  // make comptutations with absolute value
    m_tempReg[1] = temp_abs; 
    float temp_frac = temp_abs - m_tempReg[1]; 
    if( temp_frac >= 0.75) m_tempReg[1] += 1; 

    m_tempReg[0] = 0; 
    if( (temp_frac >= 0.25) && (temp_frac < 0.75)) m_tempReg[0] = 0x80; 

    m_tempSlope = 16;           // compute high resolution
    m_tempCount = m_tempSlope * (0.75 + m_tempReg[1] - temp_abs); 

    if( m_temp < 0.0) m_tempReg[1] = -m_tempReg[1];  // take sign into account

    if( m_temp > m_Th ){
        m_config |= 1<<6;   // Set THF bit
        m_outPin[0]->setOutState( m_outPol ); 
    }
    else if( m_temp < m_Tl ){
        m_config |= 1<<5;   // Set TLF bit
        m_outPin[0]->setOutState( !m_outPol ); 
    }
}

void DS1621::upbuttonclicked()
{
    setTemp( m_temp + m_tempInc ); 
}

void DS1621::downbuttonclicked()
{
    setTemp( m_temp - m_tempInc ); 
}

void  DS1621::setTemp( double temp )
{
    m_temp = temp; 
    if( m_temp > 125 ) m_temp = 125; 
    if( m_temp < -55 ) m_temp = -55; 
    m_changed = true; 
    update(); 
}

void DS1621::setTempInc( double inc )
{
    m_tempInc = trim( inc ); 
    update(); 
}

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

    p->setBrush( QColor( 20, 30, 60 ) ); 
    p->drawRoundedRect( QRect(-16,-16, m_width*8, m_height*8 ), 1, 1 ); 

    p->setOpacity( .6 ); 
    p->fillRect( QRectF(-14,-26, 32, 8 ), QColor( Qt::white ) ); 
    p->setOpacity( 1 ); 

    p->setFont( m_font ); 
    p->drawText( QRectF(-14,-26, 32, 8 ), Qt::AlignCenter, QString::number( m_temp, 'f', 1 )+"°C" ); 

    p->setPen( QColor( 170, 170, 150 ) ); 
    p->drawArc(-4,-20, 8, 8, 0, -2880 /* -16*180 */ ); 

    Component::paintSelected( p ); 
}
