#include "PrgWid.h"
#include "MemoryDisplayer.h"
#include "BinWordsEditor.h"
#include "qvbox.h"
#include <qpushbutton.h>
#include <qmessagebox.h>
#include <kmenubar.h>
#include <ktoolbar.h>
#include <kpopupmenu.h>
#include <klocale.h>
#include <kstdaction.h>
#include <kedittoolbar.h>
#include <klocale.h>
#include <kurl.h>
#include <kfiledialog.h>
#include <kapplication.h>

#include <qgroupbox.h>
#include <qhgroupbox.h>
#include <qfiledialog.h>
#include <qregexp.h>

#include <qevent.h>
#include <qapplication.h>
#include <qtimer.h>
#include <qtooltip.h>
#include <qcombobox.h>
#include <qstringlist.h>

#include <sstream>
#include <iostream>
#include <assert.h>

using namespace std ;

// ------------------------------------------------------

PrgWid::PrgWid(ProgrammerBase *programmer_, const QString& fn, QWidget *parent) :
        KMainWindow(parent,"programmer mainwindow" ,
                    Qt::WDestructiveClose ),
        fileName(fn), curpic(programmer_->device()),
        programmer(programmer_)
{
    FragBuffer *mem ;
    int mem1, mem2, ee1, ee2, id1,id2, cf1,cf2, osccal_backup1, osccal_backup2 ;
    QString bits ;
    unsigned int memdisplay_h = 10 ; // number of lines for programm memory displayer

    QFont f = font() ;
    f.setPointSize(10) ;
    this->setFont(f) ;

    setCaption(programmer->longName()) ;

    // Fonctions "file"
    // definition des actions

    (KStdAction::open(  this , SLOT(loadHexAs()) , actionCollection() ,"loadhexas" ))->setGroup("filehex");

    (KStdAction::revert(  this , SLOT(loadHex()) , actionCollection() ,"loadhex" ))->setGroup("filehex");

    (KStdAction::save(  this , SLOT(saveHex()) , actionCollection() ,"savehex" ))->setGroup("filehex");

    (KStdAction::saveAs(  this , SLOT(saveHexAs()) , actionCollection() ,"savehexas" ))->setGroup("filehex");

    (KStdAction::close(  this , SLOT(quit()) , actionCollection() ,"quitprog" ))->setGroup("filehex");



    //
    // Les action "chip"
    //

    (new KAction(i18n("&Read PIC"), "pikdev_readchip" ,
                 Qt::Key_R+Qt::CTRL , actionCollection(), "readchip"))->setGroup("chip");
    connect(act("readchip"),SIGNAL(activated()), this , SLOT(readPic())) ;


    (new KAction(i18n("&Erase PIC"), "pikdev_erasechip" ,
                 Qt::Key_E+Qt::CTRL , actionCollection(), "erasechip"))->setGroup("chip");
    connect(act("erasechip"),SIGNAL(activated()), this , SLOT(erasePic())) ;

    (new KAction(i18n("&Program PIC"), "pikdev_burnchip" ,
                 Qt::Key_P+Qt::CTRL , actionCollection(), "burnchip"))->setGroup("chip");
    connect(act("burnchip"),SIGNAL(activated()), this , SLOT(programPic())) ;

    (new KAction(i18n("&Verify PIC"), "pikdev_verifychip" ,
                 Qt::Key_V+Qt::CTRL , actionCollection(), "verifychip"))->setGroup("chip");
    connect(act("verifychip"),SIGNAL(activated()), this , SLOT(verifyPic())) ;

    //
    // Extra function menu
    //
    (new KActionMenu(i18n("&Extra"), actionCollection(), "extra-ops"))->setGroup("extra");
    connect( ((KActionMenu *)act("extra-ops"))->popupMenu(), SIGNAL(aboutToShow()),this, SLOT(slotExtraMenuManagement()));

    //
    // Toolbar Configuration
    //
    KStdAction::configureToolbars(this, SLOT(slotConfigureToolbar()),actionCollection(),"configuretoolbar") ;



    // "pikdev-prg.rc" must be in .kde/share/apps/pikdev
    createGUI("pikdev-prg.rc") ;

    // ------------------------------------------------------------------


    // La boite horizontale la plus externe

    QHBox *h1 = new QHBox(this) ;

    h1->setSpacing(14) ;
    h1->setFrameStyle( QFrame::Panel | QFrame::Sunken );
    h1->setLineWidth( 2 );
    h1->setMargin(8) ;

    QVBox *v1 = new QVBox(h1) ;
    v1->setSpacing(8) ;

    //   display current settings
    QHBox *settingh = new  QHBox(v1) ;
    settingh->setSpacing(8) ;
    QHGroupBox *settingbox = new QHGroupBox( i18n("Current settings") , settingh) ;

    QLabel *pname = new QLabel(curpic->name(), settingbox) ;
    pname->setPaletteForegroundColor(QColor(255,0,0)) ;
    settingbox->setMaximumHeight(settingbox->sizeHint().height()+8) ;

    settingh-> setStretchFactor( settingbox , 10) ;

    fileNameLabel = new QLineEdit(settingbox) ;
    fileNameLabel->setReadOnly(true) ;

    setFileName(fileName) ;


    // init hook for progress bar management
    curpic->setProgressListener(this) ;

    curpic->setProgOptionNumber(-1) ;
    // display programming options, if any
    if( *curpic->progOptions() )
    {
        memdisplay_h -= 3 ; // save space
        QHGroupBox *optbox = new QHGroupBox( i18n("Programming options") , v1) ;
        QComboBox *progopt = new QComboBox(optbox) ;
        progopt->insertStringList( parseProgOptions(curpic->progOptions()) ) ;
        slotProgOptionSelected(0) ;
        connect(  progopt, SIGNAL(activated(int)),  this, SLOT(slotProgOptionSelected(int))) ;
    }

    curpic->getBuffers(&mem,  mem1, mem2, ee1,  ee2,id1, id2, cf1, cf2,
                       osccal_backup1, osccal_backup2 ) ;

    bits = QString(i18n("(%1 bits words)")).arg( curpic->wwidth()) ;

    MemoryDisplayer *md1 = new MemoryDisplayer(v1,*mem,
                           mem1, mem2-mem1+1  , memdisplay_h, 8U,
                           i18n("Program memory ") + bits ,
                           curpic->wmask(), curpic->byteAdresses(),
                           curpic->SPLocs());
    connect(this,SIGNAL(bufferModified()), md1, SLOT(updateDisplay())) ;

    if( ee1 != -1)
    {
        QString mess = i18n("Data memory (8 bits words)")  ;
        unsigned int mask = 0xFF ;
        if( curpic->deviceCore() == 16)
        {
            mess = i18n( "Data memory (packed 8 bits words)")  ;
            mask = 0xFFFF ;
        }
        MemoryDisplayer *md2 = new MemoryDisplayer(v1,*mem,
                               ee1, ee2-ee1+1  , 6U, 8U, mess ,mask, curpic->byteAdresses(),
                               curpic->SPLocs() );
        connect(this,SIGNAL(bufferModified()), md2, SLOT(updateDisplay())) ;
    }

    QHBox *h2 = new QHBox(v1) ;
    if(id1 != -1)
    {
        unsigned int nbid = id2-id1+1   ;
        unsigned int nbcol =  nbid < 8 ? nbid : 8 ;
        unsigned int nbline = ((nbid / nbcol) > 1) ? 2 : 1 ;

        QString mess =   i18n("ID data ") +bits  ;
        if( curpic->deviceCore() == 16)
            mess = i18n( "ID data (packed 8 bits words)")  ;
        MemoryDisplayer *md3 = new MemoryDisplayer(h2,*mem,
                               id1, nbid  , nbline , nbcol , mess ,curpic->wmask(),
                               curpic->byteAdresses(),
                               curpic->SPLocs());
        h2->setStretchFactor(md3,0) ;
        connect(this,SIGNAL(bufferModified()), md3, SLOT(updateDisplay())) ;
    }
    //  this is a filler (which absorb vertical space) for devices with no EEPROM memory
    new QVBox(v1) ;

    // filler a droite de ID data
    QHBox *pad1 = new QHBox(h2) ;
    pad1->setSpacing(0) ;
    h2->setStretchFactor(pad1,5) ;
    h2->setSpacing(8) ;

    // ----------------------
    QVBox *v2 = new QVBox(h1) ;
    v2->setSpacing(8) ;

    BinWordsEditor *confuses = new  BinWordsEditor(*mem, cf1, cf2,
                               curpic->config_bits() , i18n("Config Fuses"), curpic->byteAdresses(), v2) ;
    connect(this,SIGNAL(bufferModified()), confuses, SIGNAL(wordsChanged())) ;

    v2->setStretchFactor(confuses, 2) ;

    // display osccal backup
    if( osccal_backup1 != -1)
    {
        QString mess =   i18n("Calibration") ;

        int nbo =  osccal_backup2 -  osccal_backup1 + 1 ;
        MemoryDisplayer *md4 = new MemoryDisplayer(v2,*mem,
                               osccal_backup1 , nbo  , nbo , 1 , mess ,curpic->wmask(),
                               curpic->byteAdresses(),
                               curpic->SPLocs());
        v2->setStretchFactor(md4,0) ;
        connect(this,SIGNAL(bufferModified()), md4, SLOT(updateDisplay())) ;
    }


    setCentralWidget(h1) ;

    statBar = statusBar()  ;
    QHBox *statBox = new   QHBox(this) ;
    statBar->addWidget(statBox) ;

    progRunning = new KLed(QColor(0xFF,0x3C,0x0),  KLed::Off ,  KLed::Sunken, KLed::Rectangular,
                           statBox , "programmer led");
    progRunning->setDarkFactor(200) ;
    statusLabel =   new QLabel(statBox) ;
    progress = new KProgress(100, statBox) ;

    QHBox *detectBox =  new   QHBox(this) ;
    statBar->addWidget(detectBox,10) ;

    new QLabel(i18n("  Device: "), detectBox) ;
    detected  = new QLineEdit(detectBox) ;
    detected->setReadOnly(true) ;
    detectBox->setStretchFactor(detected,1) ;
    new QLabel(i18n(" Rev: "), detectBox) ;
    revdetected = new QLineEdit(detectBox) ;
    revdetected->setMaximumWidth(24) ;
    revdetected->setReadOnly(true) ;

    new QLabel("  ", detectBox) ;   // spacer

    QPushButton *detectButton =   new QPushButton(i18n("Detect now"),detectBox)     ;

    detectButton->setFont(f) ;
    detectButton->setMaximumHeight(18) ;
    detectButton ->updateGeometry() ;

    connect(detectButton, SIGNAL(clicked()), this , SLOT(slotDetectDeviceType()) ) ;

    setStatus(false,QString( i18n("Ok") )) ;

    setAutoSaveSettings() ;

}
PrgWid::~PrgWid()
{
    curpic->setProgressListener(0) ;
}

void PrgWid::go()
{

    setFixedSize(sizeHint()) ;

    show() ;
}

void PrgWid::quit()
{
    close(true) ;
}

void PrgWid::saveHex()
{
    const char *p ;
    p = curpic->writeHexFile((const char *)fileName , "inhx32") ;
    if( p != 0)
    {
        QMessageBox::critical(this,i18n("Sorry"),p,i18n("Ok")) ;
    }
    else
        emit fileUpdated(fileName) ;
}


void PrgWid::saveHexAs()
{
    QString fname
    = KFileDialog::getSaveFileName ( "." ,i18n("*.hex|Intel hex file (*.hex)\n*|All files (*)") ,  this ) ;


    if( !fname.isEmpty())
    {

        const char *p ;
        p = curpic->writeHexFile((const char *)fname , "inhx32") ;
        if( p != 0)
        {
            QMessageBox::critical(this,i18n("Sorry"),p,i18n("Ok") ) ;
        }
        else
        {
            emit fileUpdated(fname) ;
            setFileName(fname) ;
        }
    }
}



// reload
void PrgWid::loadHex()
{
    const char *p ;
    p = curpic->loadHexFile((const char *)fileName) ;
    //curpic->display_mem(cout) ;
    if( p != 0)
    {
        QMessageBox::critical(this,i18n("Sorry"),p,i18n("Ok") ) ;
    }
    else
        emit bufferModified() ;
}


void PrgWid::loadHexAs()
{
    QString fname
    = KFileDialog::getOpenFileName ( "." ,i18n("*.hex|Intel hex file (*.hex)\n*|All files (*)") ,  this ) ;


    if( !fname.isEmpty())
    {
        const char *p ;
        p = curpic->loadHexFile((const char *)fname) ;
        if( p != 0)
        {
            QMessageBox::critical(this,i18n("Sorry"),p,i18n("Ok") ) ;
        }
        else
        {
            emit bufferModified() ;
            setFileName(fname) ;
        }
    }
}


void PrgWid::readPic()
{

    if(! verifyDeviceType())
        return ;

    QString qtxt ;
    emit messageGenerated(qtxt) ;
    setStatus(true, i18n("Reading ...") ) ;
    KApplication::kApplication()->processEvents(200) ;
    curpic->read_pic() ;
    emit bufferModified() ;
    setStatus(false, i18n("Ok") );
}


void  PrgWid::erasePic()
{
    if(! verifyDeviceType())
        return ;

    QString qtxt ;

    emit messageGenerated(qtxt) ;
    setStatus(true,i18n("Erasing ..."))   ;
    KApplication::kApplication()->processEvents(200) ;
    int i = curpic->erase_pic() ;
    if( i != 0 )
        setStatus(false,curpic->error_msg(i)) ;
    else
        setStatus(false,i18n( "Ok") );
}

void  PrgWid::verifyPic()
{
    if(! verifyDeviceType())
        return ;

    QString qtxt="" ;

    emit messageGenerated(qtxt) ;

    setStatus(true,i18n( "Verifying ...") ) ;
    KApplication::kApplication()->processEvents(200) ;
    ostringstream out ;

    int i = curpic->verify_pic(out) ;

    if( i != 0 )
    {
        QString txt(out.str().c_str()) ;
        emit messageGenerated(txt) ;
        setStatus(false,curpic->error_msg(i)) ;
    }
    else
        setStatus(false, i18n( "Ok") );
}

void  PrgWid::programPic()
{
    if(! verifyDeviceType())
        return ;

    QString qtxt ;
    setStatus(true, QString(i18n( "Programming ...")) ) ;
    emit messageGenerated(qtxt) ;

    KApplication::kApplication()->processEvents(400) ;

    ostringstream out ;
    int i = curpic->program_pic(out) ;

    if( i != 0 )
    {
        qtxt = out.str().c_str() ;
        emit messageGenerated(qtxt) ;
        setStatus(false,  curpic->error_msg(i)) ;
    }
    else
        setStatus(false, i18n( "Ok") );

}

void PrgWid::setStatus(bool active, const QString& txt)
{
    if(active)
        progRunning->on() ;
    else
        progRunning->off() ;
    statusLabel->setText(" "+txt+" ") ;
    KApplication::kApplication()->processEvents(200) ;
}

//
// change filename - update UI
//
void PrgWid::setFileName(const QString &fname)
{
    fileName = fname ;
    QString fn( (fname == "") ? i18n("<none>") : fname)   ;
    fileNameLabel->setText(fn) ;
}

//
//	Find the KAction "name" in the KMainWindow actionCollection()
//
KAction * PrgWid::act(const char *name) const
{
    KAction *actionPtr = actionCollection()->action(name) ;
    assert( actionPtr != 0 );

    return actionPtr ;

}

/** Uses KEditToolbar and XML UI description to dynamically reconfigure toolbar */
void PrgWid::slotConfigureToolbar()
{
    saveMainWindowSettings( KGlobal::config(), "PrgMainWindow" );
    KEditToolbar dlg(actionCollection(),"pikdev-prg.rc",false, 0,
                     i18n("Programmer Window Toolbar Editor") ) ;
    connect(&dlg,SIGNAL(newToolbarConfig()),this,SLOT(slotApplyToolbarSettings()));
    if(dlg.exec() )
        createGUI("pikdev-prg.rc")	;
}


/** No descriptions */
void PrgWid::slotApplyToolbarSettings()
{
    applyMainWindowSettings(KGlobal::config(), "PrgMainWindow") ;
}


/** No descriptions */
void PrgWid::slotDetectDeviceType()
{
    verifyDeviceType()  ;
}


/** Verify the currently plugged device type
\return true if detected device type is compatible with current pic model
or if device type is not autodetectable
 */
bool PrgWid::verifyDeviceType()
{
    QString dev  ;

    revdetected->setText( "" ) ;

    if( !curpic->isDetectable() )
    {
        detected->setText(i18n("<Non autodetectable device>")) ;
        return true ;
    }

    dev = curpic->read_device_name().c_str()   ;

    if(dev == "")
    {
        detected->setText(i18n("<No device detected>")) ;
        return false ;
    }
    else if(dev == "?")
    {
        detected->setText(i18n("<Unknown device>")) ;
        return false ;
    }
    detected->setText(dev) ;
    revdetected->setText( curpic->read_device_revision().c_str()) ;

    bool ok =   verifyDeviceType(dev ) ;
    setStatus(false, ok ? i18n("Ok") : i18n("Mismatch") ) ;
    return ok ;
}

bool PrgWid::verifyDeviceType(const QString& type)
{
    if( type == curpic->specificName() )
        return true  ;

    // Not valid, ALERT
    int resp = QMessageBox::warning(this,i18n("Sorry"),
                                    i18n("Detected device type does not match specified target device."),
                                    i18n("Continue"), i18n("Abort")) ;
    return resp == 0  ;
}
/** No descriptions */
bool PrgWid::queryClose()
{
    return true ;
}
/** No descriptions */
QStringList PrgWid::parseProgOptions(const QString &op)
{
    QRegExp r("\\s*\\{([^}]+)\\}") ;
    int index = 0 ;
    QStringList list ;

    while( r.search(op, index) != -1)
    {
        index += r.matchedLength() ;
        list += r.cap(1) ;
    }
    return list ;
}

/** No descriptions */
void PrgWid::slotProgOptionSelected(int opt)
{
    curpic->setProgOptionNumber(opt) ;
}
/** No descriptions */
void PrgWid::updateProgressBar(int percent)
{
    progress->setProgress(percent);
    KApplication::kApplication()->processEvents(200) ;
}
/** No descriptions */
void PrgWid::updateProgressText(const char * text)
{
    statusLabel->setText(text);
    KApplication::kApplication()->processEvents(200) ;
}


/*!
    \fn PrgWid::slotExtraMenuManagement()
 */
void PrgWid::slotExtraMenuManagement()
{
    QString extra = programmer->extraFct() ;
    QStringList extraList = parseProgOptions(extra ) ;

    QStringList::iterator i ;

    KPopupMenu *pop =  ((KActionMenu *)act("extra-ops"))->popupMenu()  ;
    pop->clear ();

    int nb ;
    for (i = extraList.begin() , nb = 0 ; i != extraList.end() ; ++i, ++nb)
    {
        pop->insertItem ( *i , this, SLOT(slotExecExtra(int)), 0  , nb );
    }
}


/*!
    \fn PrgWid::slotExecExtra(int function_number)
 */
void PrgWid::slotExecExtra(int function_number)
{
    programmer->executeExtraFct(  function_number) ;
}
