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

#include <math.h>

#include <QDebug>
#include <QPainter>
#include <QGraphicsProxyWidget>

#include "ds18b20.h"
#include "iopin.h"
#include "simulator.h"
#include "circuit.h"
#include "itemlibrary.h"
#include "utils.h"
#include "updobutton.h"

#include "doubleprop.h"
#include "stringprop.h"

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

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

LibraryItem* Ds18b20::libraryItem()
{
    return new LibraryItem(
        "DS18B20",
        "Sensors",
        "ic2_comp.png",
        "DS18B20",
        Ds18b20::construct ); 
}

Ds18b20::Ds18b20( QString type, QString id )
       : Component( type, id )
       , OneWire( id )
       , m_vdd( 270, QPoint( 8, 28 ), id+"-PinVdd", 0, this )
       , m_gnd( 270, QPoint(-8, 28 ), id+"-PinGnd", 0, this )
{
    m_graphical = true; 
    m_area = QRect(-16,-16, 32, 36 ); 

    m_pin.resize(1); 
    m_pin[0] = m_inpin = new IoPin( 270, QPoint( 0, 28 ), id+"-inPin", 0, this, openCo ); 

    m_inpin->setLabelColor( QColor( 250, 250, 200 ) ); 
    m_inpin->setLabelText("Dat"); 

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

    srand( time(0) ); 

    m_parPower = false; 
    m_tempInc  = 0.5; 
    setTemp( 22 ); 

    generateROM( 0x28 );   // FAMILY CODE = 28h for DS18B20

    m_scratchpad[0] = 0xFF; 
    m_scratchpad[1] = 0x07; 
    m_scratchpad[2] = 0x4B;  /// TH  reg, default value
    m_scratchpad[3] = 0x46;  /// TL  reg, default value
    m_scratchpad[4] = 0x7F;  /// CFG reg, default value
    m_scratchpad[5] = 0xFF; 
    m_scratchpad[6] = 0x01; 
    m_scratchpad[7] = 0x10; 
    m_scratchpad[8] = 0x2F; 

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

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

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

    m_font.setFamily("Ubuntu Mono"); 
    m_font.setPixelSize( 9 ); 
    m_font.setBold( true ); 
    m_font.setLetterSpacing( QFont::PercentageSpacing, 100 ); 
    setLabelPos(-24,-28 ); 

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

    addPropGroup( { tr("Main"), {
        new StrProp <Ds18b20>("ROM","ROM", ""
                             , this, &Ds18b20::getROM, &Ds18b20::setROM, propNoCopy ),

        new DoubProp<Ds18b20>("Temp", tr("Temperature"), "°C"
                             , this, &Ds18b20::temp, &Ds18b20::setTemp ),

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

void Ds18b20::updateStep()
{
    setTemp( m_temp ); 
    Simulator::self()->remFromUpdateList( this ); 
}

void Ds18b20::readByte()
{
    if( m_lastCommand == 0x4E )              // Write Scratchpad
    {
        m_scratchpad[m_byte] = m_rxReg; 
        if( m_byte == 4 )
        {
            m_scratchpad[8] = crc8( m_scratchpad, 8 ); 
            m_state = W1_IDLE;  // Transaction finished
        }
        else m_byte++; 
    }
}

// Function COMMANDS --------------------------------------------------

void Ds18b20::funCommand( uint8_t cmd )
{
    switch( cmd ){
        case 0x44: convertTemp();      break; 
        case 0x48: copyScratchpad();   break; 
        case 0x4E: writeScratchpad();  break; 
        case 0xB4: readPowerSupply();  break; 
        case 0xB8: recallE2();         break; 
        case 0xBE: readScratchpad();   break; 
        default:{  m_state = W1_IDLE; 
            qDebug()<<idLabel() << "Ds18b20::command : Warning: Function command Not implemented"; 
    }   }
    m_lastCommand = cmd; 
}

void Ds18b20::convertTemp() // Code 44h, temperature already in the Scratchpad, nothing to do
{
    uint16_t temp = round( fabs( m_temp )*16 );  // Make it positive and shift to make integer value
    if( m_temp < 0 ) temp = ~temp + 1;           // Temp under 0? Make 2nd complement

    m_scratchpad[0] = temp; 
    m_scratchpad[1] = temp >> 8; 
    m_scratchpad[8] = crc8( m_scratchpad, 8 ); 

    int res = ((m_scratchpad[4] >> 5) & 0b11)+9;  // Resolution
    switch( res ){                               // Resol.  Busy time
        case  9: m_busyTime =  93750*1e6;  break;  // 9-bits  93.75 ms
        case 10: m_busyTime = 187500*1e6;  break;  // 10-bit  187.5 ms
        case 11: m_busyTime = 375000*1e6;  break;  // 11-bit  375   ms
        case 12: m_busyTime = 750000*1e6;  break;  // 12-bit  750   ms
    }
    m_busyTime += Simulator::self()->circTime(); 
    m_state = W1_BUSY; 

    //qDebug() << idLabel() <<"Ds18b20::setTemp"<<arrayToHex( m_scratchpad, 9 ); 
}

void Ds18b20::writeScratchpad() // Code 4Eh : Master will write TH, TL, Config
{
    //qDebug() <<idLabel()<< "Ds18b20::writeScratchpad"; 

    m_byte = 2;         // start writting to byte 2 of the scratchpad
    m_state = W1_DATA; 
}

void Ds18b20::readScratchpad() // Code BEh : send Scratchpad LSB
{
    m_txBuff.clear(); 
    for( int i=8;  i>=0;  i-- ) m_txBuff.push_back( m_scratchpad[i] ); 

    //qDebug() << idLabel() <<"Ds18b20::readScratchpad :"<<arrayToHex( m_txBuff.data(), m_txBuff.size() ); 

    sendData( m_txBuff.back() ); 
}

void Ds18b20::copyScratchpad() // Code 48h : Copy TH, TL, CFG (bytes 2, 3, 4) to EEPROM.
{
    //qDebug() <<idLabel()<< "Ds18b20::copyScratchpad"; 

    m_TH  = m_scratchpad[2]; 
    m_TL  = m_scratchpad[3]; 
    m_CFG = m_scratchpad[4]; 

    m_busyTime = Simulator::self()->circTime()+10000*1e6;  // 10 ms
    m_state = W1_BUSY; 
}

void Ds18b20::recallE2() // Code B8h :Copy EEPROM to TH, TL, CFG (bytes 2, 3, 4)
{
    //qDebug() <<idLabel()<< "Ds18b20::recallE2"; 

    m_scratchpad[2] = m_TH; 
    m_scratchpad[3] = m_TL; 
    m_scratchpad[4] = m_CFG; 
    m_scratchpad[8] = crc8( m_scratchpad, 8 ); 

    m_busyTime = Simulator::self()->circTime()+10*1e3*1e6;  // 10 ms ????????????
    m_state = W1_BUSY; 
}

void Ds18b20::readPowerSupply() // Code B4h : using parasite power? pull down time???
{
    //qDebug() <<idLabel()<< "Ds18b20::readPowerSupply No parasite power";  // By now we don't use parasite power
    /// TODO add property ?
}

// End Commands --------------------------------------------------------------------

void Ds18b20::setTemp( double t ) // This should convert temp wrote in UI
{
    m_temp = t; 
    if( m_temp > 125 ) m_temp = 125; 
    if( m_temp < -55 ) m_temp = -55; 
    update(); 
}

void Ds18b20::upbuttonclicked()
{
    m_temp += m_tempInc; 
    if( Simulator::self()->isRunning() ) Simulator::self()->addToUpdateList( this ); 
    else updateStep(); 
}

void Ds18b20::downbuttonclicked()
{
    m_temp -= m_tempInc; 
    if( Simulator::self()->isRunning() ) Simulator::self()->addToUpdateList( this ); 
    else updateStep(); 
}

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

    p->setBrush( QColor( 20, 30, 60 ) ); 
    p->drawRoundedRect( QRect(-12,-7, 24, 27 ), 1, 1 ); 

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

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

    Component::paintSelected( p ); 
}
