/*
 * scbus utils to switch card settings.
 *
 * Copyright (C) 2011 Otvos Attila oattila@onebithq.com
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with MPlayer; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */


#include "scbus-settings_window.h"

#include <QFile>
#include <QtGui>

#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "util.h"

const char *aboutHtmlText =
"<HTML>"
"<p><b>SCBUS-settings</b></p>"
"<p>Author: Otvos Attila<br>"
"License type: GPL v2<br>"
"Web page: <a href=\"http://onebithq.com/\">http://onebithq.com/</a></p>"
"</HTML>";

MainWindow::MainWindow()
{
    int validBauds[] = {9600,19200,38400,57600,1152000,0};
    int i,n;
    char tmp[32];

    serialDevIDs << 4 << 19 << 20 << 22 << 23 << 24 << 25 << 28 << 32 << 33 << 47 << 48 << 49 << 54 << 57 << 58
                 << 75 << 88 << 105 << 106 << 112 << 113 << 148 << 149 << 154 << 155 << 156 << 157 << 164 << 165 << 166
                  << 167 << 172 << 173 << 174 << 175 << 188 << 189 << 204 << 205 << 208 << 209 << 216 << 217 << 224 << 225;

    settings = new QSettings("scbus", "settings");
    settings->beginGroup("serial");
    serialDeviceValue=settings->value("device").toString();
    serialBaudValue=settings->value("baud").toInt();

    if(!serialBaudValue) {
        serialBaudValue=DEFAULT_BAUD;
        settings->setValue("baud",serialBaudValue);
    }
    if(serialDeviceValue.isEmpty()) {
        serialDeviceValue=QString(DEFAULT_SERIAL);
        settings->setValue("device",serialDeviceValue);
    }
    settings->endGroup();

    mainMenu = new QMenuBar();
    QMenu * helpMenu = new QMenu();
    QMenu * fileMenu = new QMenu();

    fileMenu = mainMenu->addMenu(tr("&File"));
    helpMenu = mainMenu->addMenu(tr("&Help"));

    QAction * exitAction = fileMenu->addAction(tr("&Exit"));
    QAction * aboutAction = helpMenu->addAction(tr("&About"));

    setMenuBar(mainMenu);

    connect( exitAction, SIGNAL( triggered(void) ), this, SLOT( menuExit(void) ) );
    connect( aboutAction, SIGNAL( triggered(void) ), this, SLOT( showAbout(void) ) );

    QTimer *timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(checkDevices()));
    timer->start(40);

    QHBoxLayout* hboxSerial = new QHBoxLayout();
    QLabel* serialLabel = new QLabel("Serial device:");
    serialDevice = new QComboBox();
    listSerial();

    QLabel* baudLabel = new QLabel("Baud rate:");
    serialBaud = new QComboBox();
    i=0;
    n=-1;
    while(validBauds[++i]) {
        snprintf(tmp,sizeof(tmp),"%7d",validBauds[i-1]);
        serialBaud->addItem(tmp,validBauds[i-1]);
        if(validBauds[i-1]==serialBaudValue)
            n=i-1;
    }
    if(n==-1 && serialBaudValue) {
        snprintf(tmp,sizeof(tmp),"%7d",serialBaudValue);
        serialBaud->addItem(tmp,serialBaudValue);
        n=i;
    }
    if(n>=0)
        serialBaud->setCurrentIndex(n);
    connect(serialDevice,SIGNAL(currentIndexChanged(int)),this,SLOT(selectDevice(int)));
    connect(serialBaud,SIGNAL(currentIndexChanged(int)),this,SLOT(selectBaud(int)));

    QLabel *dummyLabel = new QLabel("");
    dummyLabel->setAlignment(Qt::AlignBottom);
    dummyLabel->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);

    hboxSerial->addWidget(serialLabel);
    hboxSerial->addWidget(serialDevice);
    hboxSerial->addWidget(baudLabel);
    hboxSerial->addWidget(serialBaud);
    hboxSerial->addWidget(dummyLabel);
    QWidget* wSerial = new QWidget();
    wSerial->setLayout(hboxSerial);

    QVBoxLayout* vboxTail = new QVBoxLayout();
    raw = new QCheckBox("Enable raw packages");
    tail = new QTextEdit();
    vboxTail->addWidget(raw);
    vboxTail->addWidget(tail);
    QWidget* wTail = new QWidget();
    wTail->setLayout(vboxTail);

    QVBoxLayout* vboxConf = new QVBoxLayout();
    QStringList hdr;
    tstatus = new QTableWidget(1,3);
    hdr << "ID" << "Output" << "Input";
    tstatus->setHorizontalHeaderLabels(hdr);
    tstatus->setColumnWidth( 0, 40 );
    tstatus->setColumnWidth( 1, 6*20+8 );
    tstatus->setColumnWidth( 2, 6*20+8 );
    tab = new QTabWidget();
    dconf=new DeviceTab(0,&devices);
    tab->addTab(dconf,"Device");
    tab->setTabEnabled(0,false);
    fconf=new FileTab(0,&devices);
    tab->addTab(fconf,"File");
    tab->setTabEnabled(1,false);
    for(int i=0;i<6;i++) {
        tconf[i] = new ConfigTab(0,i+1,status_config[0].config[i],&devices);
        tab->addTab(tconf[i],QString("O%1").arg(i+1));
        tab->setTabEnabled(i+2,false);
        connect(tconf[i],SIGNAL(setModify(int,bool)),this,SLOT(setConfigModify(int,bool)));
        connect(tconf[i],SIGNAL(accept(int,int,RS485Config)),this,SLOT(configAccept(int,int,RS485Config)));
    }
    vboxConf->addWidget(tstatus);
    vboxConf->addWidget(tab);
    QWidget* wConf = new QWidget();
    wConf->setLayout(vboxConf);

    QHBoxLayout* hboxMain = new QHBoxLayout();
    hboxMain->addWidget(wTail);
    hboxMain->addWidget(wConf);

    QWidget* wMain = new QWidget();
    wMain->setLayout(hboxMain);

    QVBoxLayout* vboxMain = new QVBoxLayout();
    vboxMain->addWidget(wSerial);
    vboxMain->addWidget(wMain);

    QWidget* wCentral = new QWidget();
    wCentral->setLayout(vboxMain);

    setCentralWidget(wCentral);

    rs485thread = new RS485Thread(serialBaudValue,serialDeviceValue);
    if(!rs485thread->getError().isEmpty())
        QMessageBox::warning(this, tr("Serial error"), QString("Error: %1.").arg(rs485thread->getError()), QMessageBox::Ok);

    rs485thread->start();

    connect(tstatus,SIGNAL(cellActivated(int, int)),this,SLOT(cellActivated(int, int)));
    connect( raw, SIGNAL(stateChanged(int)),this,SLOT(rawState(int)));
    connect( dconf,SIGNAL(setModify(bool)),this,SLOT(setDeviceModify(bool)));
    connect( dconf,SIGNAL(accept(int)),this,SLOT(setDeviceID(int)));
    connect( fconf, SIGNAL(loadFile(QString)), this, SLOT(loadFile(QString)));
    connect( fconf, SIGNAL(saveFile(QString)), this, SLOT(saveFile(QString)));
    connect( fconf, SIGNAL(reset(void)), this, SLOT(resetAll(void)));
    connect( fconf, SIGNAL(accept(void)), this, SLOT(acceptAll(void)));
    connect( rs485thread, SIGNAL(isData(void)), this, SLOT(rs485Data(void)));
    connect( rs485thread, SIGNAL(changeStatus(int)), this, SLOT(rs485Status(int)));
    connect( rs485thread, SIGNAL(changeConfig(int,int)), this, SLOT(rs485Config(int,int)));
    connect( rs485thread, SIGNAL(changeDeviceID(int)), this, SLOT(changeDeviceID(int)));

}


MainWindow::~MainWindow()
{
    disconnect( rs485thread, SIGNAL(isData(void)), this, 0);
    disconnect( rs485thread, SIGNAL(changeStatus(int)), this, 0);
    disconnect( rs485thread, SIGNAL(changeConfig(int,int)), this, 0);
    disconnect( rs485thread, SIGNAL(changeDeviceID(int)), this, 0);
    if(rs485thread) {
        rs485thread->stop();
        delete rs485thread;
    }
    for(int i=0;i<32;i++)
        if(vdata[i])
            free(vdata[i]);
}

void MainWindow::checkDevices(void) {
    double currTime = getTime();

    for(QMap<int, deviceTime>::iterator it = timemap.begin();it!=timemap.end();it++) {
        if(timemap[it.key()].time+5.0>currTime)
            continue;
        if(!timemap[it.key()].flag) {
            rs485thread->getDeviceID(it.key());
            timemap[it.key()].flag=true;
        }
        if(timemap[it.key()].time+7.0>currTime)
            continue;
        changeDeviceID(it.key());
    }
}

void MainWindow::menuExit(void)
{
    close();
}

void MainWindow::rs485Data(void) {
    QStringList data;

    data = rs485thread->getData();
    foreach(QString d, data) {
        tail->append(d);
//        qDebug() << d;
    }
}

void MainWindow::rebuildStatusmap(void) {
    int deviceid;

    statusmap.clear();
    for(int n=0;n<tstatus->rowCount();n++) {
        if(!tstatus->item(n,0))
            continue;
        deviceid=tstatus->item(n,0)->data(Qt::UserRole).toInt();
        statusmap[deviceid]=n+1;
    }
}

void MainWindow::rs485Status(int deviceid) {
    RS485Status status = rs485thread->getStatus(deviceid);
    char buffer[256];
    int n;
    LedStatus * ls;
    QWidget * w;

    status_config[deviceid]=status;
    if(!(devices.contains(deviceid))) {
        devices.append(deviceid);
        dconf->updateDevices();
        fconf->updateDevices();
        for(int i=0;i<6;i++)
            tconf[i]->updateDevices();
    }

    n=statusmap[deviceid];
    if(!n) {
        n=tstatus->rowCount();
        tstatus->setRowCount(n+1);
        snprintf(buffer,sizeof(buffer),"%02X",deviceid);
        QTableWidgetItem* twi = new QTableWidgetItem(buffer);
        twi->setData(Qt::UserRole,deviceid);
        tstatus->setItem(n,0,twi);
        tstatus->setItem(n,1,new QTableWidgetItem(""));
        tstatus->setItem(n,2,new QTableWidgetItem(""));
        ls = new LedStatus();
        ls->setBrush(QBrush(QColor(255,0,0)));
        ls->setStatus(status.input);
        tstatus->setCellWidget( n, 1, ls );
        ls = new LedStatus();
        ls->setBrush(QBrush(QColor(0,255,0)));
        ls->setStatus(status.input);
        tstatus->setCellWidget( n, 2, ls );
        tstatus->item(n,0)->setFlags(tstatus->item(n,0)->flags() &~ Qt::ItemIsEditable);
        tstatus->item(n,1)->setFlags(tstatus->item(n,0)->flags() &~ Qt::ItemIsEditable);
        tstatus->item(n,2)->setFlags(tstatus->item(n,0)->flags() &~ Qt::ItemIsEditable);
        tstatus->sortItems(0);
        rebuildStatusmap();
        n=statusmap[deviceid];
    }
    timemap[deviceid].time=getTime();
    timemap[deviceid].flag=false;
    n--;
    w = tstatus->cellWidget(n,1);
    ls = dynamic_cast<LedStatus*>(w);
    if(ls)
        ls->setStatus(status.output);
    w = tstatus->cellWidget(n,2);
    ls = dynamic_cast<LedStatus*>(w);
    if(ls)
        ls->setStatus(status.input);
}

void MainWindow::rs485Config(int deviceid, int output) {
    RS485Status status = rs485thread->getStatus(deviceid);
    RS485Config config = status.config[output];

    status_config[deviceid]=status;
    if(deviceid==tconf[output]->getDevice()) {
        tconf[output]->setConfig(config);
    }

//    qDebug() << "DID: " << deviceid << " O: " << output << " config: " << config.act << " " << config.device << " " << config.input << " " << config.type << " " << config.par;
}

void MainWindow::setDeviceModify(bool m) {
    int i;

    if(!dconf->getDevice()) {
        tab->setTabText(0,QString("Device"));
        for(i=0;i<8;i++)
            tab->setTabEnabled(i,false);
        return;
    }
    if(m) {
        tab->setTabText(0,QString("Device *"));
        for(i=0;i<7;i++)
            tab->setTabEnabled(i+1,false);
    } else {
        tab->setTabText(0,QString("Device"));
        for(i=0;i<7;i++)
            tab->setTabEnabled(i+1,true);
    }
}

void MainWindow::setDeviceID(int deviceid) {
    int i;

    rs485thread->setDeviceID(dconf->getDevice(), deviceid);
    dconf->setDevice(0);
    for(i=0;i<7;i++)
        tab->setTabEnabled(i,false);
}

void MainWindow::changeDeviceID(int deviceid) {
    int n;

    devices.removeAll(deviceid);
    timemap.erase(timemap.find(deviceid));
    for(n=0;n<tstatus->rowCount();n++) {
        if(tstatus->item(n,0)->data(Qt::UserRole)==deviceid) {
            tstatus->removeRow(n);
            break;
        }
    }
    tstatus->sortItems(0);
    rebuildStatusmap();
    dconf->updateDevices();
    fconf->updateDevices();
}

void MainWindow::cellActivated(int row, int column) {
    int i, d;

    d = tstatus->item(row,0)->data(Qt::UserRole).toInt();
    dconf->setDevice(d);
    fconf->setDevice(d);
    tab->setTabEnabled(0,true);
    tab->setTabEnabled(1,true);
    for(i=0;i<6;i++) {
        tconf[i]->setDevice(d,status_config[d].config[i]);
        tab->setTabEnabled(i+2,true);
    }
}

void MainWindow::setConfigModify(int id, bool m) {
    if(m)
        tab->setTabText(id+1,QString("O%1*").arg(id));
    else
        tab->setTabText(id+1,QString("O%1").arg(id));
}

void MainWindow::configAccept(int deviceid, int output, RS485Config config) {
    rs485thread->setConfig(deviceid, output, config);
}

void MainWindow::loadFile(QString fname) {
    FILE *fh;
    RS485Config conf;
    int deviceid;
    unsigned char wparam[32];
    char buffer[8192];

    deviceid=tconf[0]->getDevice();
    if((fh=fopen(fname.toStdString().c_str(),"r"))) {
        while(!feof(fh)) {
            memset(buffer,0,sizeof(buffer));
            fgets(buffer,sizeof(buffer),fh);
            if(buffer[0]=='\n')
                continue;
            if(!process_line(buffer, wparam, sizeof(wparam)))
                continue;
            if(wparam[0]!='L' || wparam[1]!=deviceid || wparam[2]<0 || wparam[2]>5)
                continue;
            conf.device=wparam[3];
            conf.input=wparam[4];
            conf.type=wparam[5];
            conf.par=wparam[6];
            tconf[wparam[2]]->setEditedConfig(conf);
        }
        fclose(fh);
    } else {
        QMessageBox::warning(this, tr("Load error"), QString("Error: %1.").arg(strerror(errno)), QMessageBox::Ok);
    }
}

void MainWindow::saveFile(QString fname) {
    FILE *fh;
    RS485Config conf;
    int deviceid;

    if((fh=fopen(fname.toStdString().c_str(),"w"))) {
        for(int n=0;n<6;n++) {
            deviceid=tconf[n]->getDevice();
            conf=tconf[n]->getEditedConfig();
//            fprintf(fh,"*L%02X%02X%02X%02X%02X%02X\n",deviceid,n,conf.device,conf.input,conf.type,conf.par);
        }
        fclose(fh);
    } else {
        QMessageBox::warning(this, tr("Save error"), QString("Error: %1.").arg(strerror(errno)), QMessageBox::Ok);
    }
}

void MainWindow::resetAll(void) {
    for(int n=0;n<6;n++)
        tconf[n]->pressReset();
}

void MainWindow::acceptAll(void) {
    for(int n=0;n<6;n++)
        tconf[n]->pressAccept();
}

void MainWindow::rawState(int state) {
    if(state) {
        rs485thread->enableRaw(true);
    } else {
        rs485thread->enableRaw(false);
    }
}

void MainWindow::selectDevice(int index) {
    serialDeviceValue=serialDevice->itemData(index).toString();
    settings->beginGroup("serial");
    settings->setValue("device",serialDeviceValue);
    settings->endGroup();
    reinitRS485();
}

void MainWindow::selectBaud(int index) {
    serialBaudValue=serialBaud->itemData(index).toInt();

    settings->beginGroup("serial");
    settings->setValue("baud",serialBaudValue);
    settings->endGroup();
    reinitRS485();
}

void MainWindow::reinitRS485(void) {
    RS485Config conf;

    disconnect( rs485thread, SIGNAL(isData(void)), this, 0);
    disconnect( rs485thread, SIGNAL(changeStatus(int)), this, 0);
    disconnect( rs485thread, SIGNAL(changeConfig(int,int)), this, 0);
    disconnect( rs485thread, SIGNAL(changeDeviceID(int)), this, 0);
    if(rs485thread) {
        rs485thread->stop();
        delete rs485thread;
    }
    devices.clear();
    statusmap.clear();
    timemap.clear();
    status_config.clear();
    tail->clear();
    tstatus->clear();
    tstatus->setRowCount(0);
    dconf->setDevice(0);
    fconf->setDevice(0);
    for(int i=0;i<6;i++)
        tconf[i]->setDevice(0,conf);
    rs485thread = new RS485Thread(serialBaudValue,serialDeviceValue);
    if(!rs485thread->getError().isEmpty())
        QMessageBox::warning(this, tr("Serial error"), QString("Error: %1.").arg(rs485thread->getError()), QMessageBox::Ok);
    rs485thread->start();
    connect( rs485thread, SIGNAL(isData(void)), this, SLOT(rs485Data(void)));
    connect( rs485thread, SIGNAL(changeStatus(int)), this, SLOT(rs485Status(int)));
    connect( rs485thread, SIGNAL(changeConfig(int,int)), this, SLOT(rs485Config(int,int)));
    connect( rs485thread, SIGNAL(changeDeviceID(int)), this, SLOT(changeDeviceID(int)));
}


void MainWindow::listSerial(void) {
    struct stat s;
    int major,i,n;
    char c;

    disconnect(serialDevice,SIGNAL(currentIndexChanged(int)),this,0);
    QDir dir("/dev");
    dir.setFilter(QDir::System);
    QFileInfoList list = dir.entryInfoList();
    serialDevices.clear();
    for (int i = 0; i < list.size(); ++i) {
        if(stat(list.at(i).filePath().toStdString().c_str(),&s)<0)
            continue;
        if((s.st_mode & S_IFMT) != S_IFCHR)
            continue;
        major=s.st_rdev >> 8;
        if(!serialDevIDs.contains(major))
            continue;
        if(strncmp(list.at(i).fileName().toStdString().c_str(),"tty",3))
            continue;
        c=list.at(i).fileName().toStdString().c_str()[3];
        if(c>='0' && c<='9')
            continue;
        serialDevices << list.at(i).filePath();
    }
    serialDevice->clear();
    n=-1;
    for(i=0;i<serialDevices.size();i++) {
        serialDevice->addItem(serialDevices.at(i),serialDevices.at(i));
        if(serialDevices.at(i)==serialDeviceValue)
            n=i;
    }
    if(n==-1) {
        serialDevice->addItem(serialDeviceValue,serialDeviceValue);
        n=i;
    }
    if(n>=0)
        serialDevice->setCurrentIndex(n);
    connect(serialDevice,SIGNAL(currentIndexChanged(int)),this,SLOT(selectDevice(int)));
}

void MainWindow::showAbout(void)
{
    QMessageBox::about(this, "About scbus-setings", aboutHtmlText);
}
