#include "pttyhandler.h"
#include "logcategories.h"

#include <QDebug>
#include <QFile>

#ifndef Q_OS_WIN
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#endif

// Copyright 2017-2021 Elliott H. Liggett & Phil Taylor 

pttyHandler::pttyHandler(QString pty)
{
    //constructor
    if (pty == "" || pty.toLower() == "none")
    {
        // Just return if pty is not configured.
        return;
    }

    portName = pty;

#ifdef Q_OS_WIN
    // TODO: The following should become arguments and/or functions
    // Add signal/slot everywhere for comm port setup.
    // Consider how to "re-setup" and how to save the state for next time.
    baudRate = 115200;
    stopBits = 1;
    portName = pty;
#endif

    openPort();

}


void pttyHandler::openPort()
{
    serialError = false;
    bool success=false;
#ifdef Q_OS_WIN
    port = new QSerialPort();
    port->setPortName(portName);
    port->setBaudRate(baudRate);
    port->setStopBits(QSerialPort::OneStop);// OneStop is other option
    success = port->open(QIODevice::ReadWrite);

    if (success) {
        connect(port, &QSerialPort::readyRead, this, std::bind(&pttyHandler::receiveDataIn, this, (int)0));
    }
#else
    // Generic method in Linux/MacOS to find a pty
    ptfd = ::posix_openpt(O_RDWR | O_NONBLOCK);

    if (ptfd >=0)
    {
        qInfo(logSerial()) << "Opened pt device: " << ptfd << ", attempting to grant pt status";

        if (grantpt(ptfd))
        {
            qInfo(logSerial()) << "Failed to grantpt";
            return;
        }
        if (unlockpt(ptfd))
        {
            qInfo(logSerial()) << "Failed to unlock pt";
            return;
        }
        // we're good!
        qInfo(logSerial()) << "Opened pseudoterminal, slave name :" << ptsname(ptfd);

        ptReader = new QSocketNotifier(ptfd, QSocketNotifier::Read, this);
        connect(ptReader, &QSocketNotifier::activated,
                this, &pttyHandler::receiveDataIn);

        success=true;
    }
#endif

    if (!success)
    {
        ptfd = 0;
        qInfo(logSerial()) << "Could not open pseudo terminal port, please restart.";
        isConnected = false;
        serialError = true;
        emit haveSerialPortError(portName, "Could not open pseudo terminal port. Please restart.");
        return;
    }


#ifndef Q_OS_WIN
    ptDevSlave = QString::fromLocal8Bit(ptsname(ptfd));

    if (portName != "" && portName.toLower() != "none")
    {
        if (!QFile::link(ptDevSlave, portName))
        {
            qInfo(logSerial()) << "Error creating link to" << ptDevSlave << "from" << portName;
        } else {
            qInfo(logSerial()) << "Created link to" << ptDevSlave << "from" << portName;
        }
    }
#endif

    isConnected = true;
}

pttyHandler::~pttyHandler()
{
    this->closePort();
}

void pttyHandler::receiveDataFromRigToPtty(const QByteArray& data)
{

    int fePos=data.lastIndexOf((char)0xfe);
    if (fePos > 0 && data.length() > fePos+2)
        fePos=fePos-1;
    else
    {
        qDebug(logSerial()) << "Invalid command";
        printHex(data,false,true);
    }

    if (disableTransceive && ((unsigned char)data[fePos + 2] == 0x00 || (unsigned char)data[fePos + 3] == 0x00))
    {
        // Ignore data that is sent to/from transceive address as client has requested transceive disabled.
        qDebug(logSerial()) << "Transceive command filtered";
        return;
    }

    if (isConnected && (unsigned char)data[fePos + 2] != 0xE1 && (unsigned char)data[fePos + 3] != 0xE1)
    {
        // send to the pseudo port as well
        // index 2 is dest, 0xE1 is wfview, 0xE0 is assumed to be the other device.
        // Changed to "Not 0xE1"
        // 0xE1 = wfview
        // 0xE0 = pseudo-term host
        // 0x00 = broadcast to all
        //qInfo(logSerial()) << "Sending data from radio to pseudo-terminal";        
        sendDataOut(data);
    }
}

void pttyHandler::sendDataOut(const QByteArray& writeData)
{
    qint64 bytesWritten = 0;

    //qInfo(logSerial()) << "Data to pseudo term:";
    //printHex(writeData, false, true);
    if (isConnected) {
        mutex.lock();
#ifdef Q_OS_WIN
        bytesWritten = port->write(writeData);
#else
        bytesWritten = ::write(ptfd, writeData.constData(), writeData.size());
#endif
        if (bytesWritten != writeData.length()) {
            qInfo(logSerial()) << "bytesWritten: " << bytesWritten << " length of byte array: " << writeData.length()\
                << " size of byte array: " << writeData.size()\
                << " Wrote all bytes? " << (bool)(bytesWritten == (qint64)writeData.size());
        }
        mutex.unlock();
    }
}

void pttyHandler::receiveDataIn(int fd) {

#ifndef Q_OS_WIN
    ssize_t available = 255; // Read up to 'available' bytes
#else
    Q_UNUSED(fd);
#endif

   // Linux will correctly return the number of available bytes with the FIONREAD ioctl
   // Sadly MacOS always returns zero!
#ifdef Q_OS_LINUX
    int ret = ::ioctl(fd, FIONREAD, (char *) &available);
    if (ret != 0)
        return;
#endif

#ifdef Q_OS_WIN
    port->startTransaction();
    inPortData = port->readAll();
#else
    inPortData.resize(available);
    ssize_t got = ::read(fd, inPortData.data(), available);
    int err = errno;
    if (got < 0) {
        qInfo(logSerial()) << tr("Read failed: %1").arg(QString::fromLatin1(strerror(err)));
        return;
    }
    inPortData.resize(got);
#endif

    if (inPortData.startsWith("\xFE\xFE"))
    {
        if (inPortData.endsWith("\xFD"))
        {
            // good!
#ifdef Q_OS_WIN
            port->commitTransaction();
#endif

            int lastFE = inPortData.lastIndexOf((char)0xfe);
            if (civId == 0 && inPortData.length() > lastFE + 2 && (quint8)inPortData[lastFE + 2] > (quint8)0xdf && (quint8)inPortData[lastFE + 2] < (quint8)0xef) {
                // This is (should be) the remotes CIV id.
                civId = (quint8)inPortData[lastFE + 2];
                qInfo(logSerial()) << "pty detected remote CI-V:" << hex << civId;
            }
            else if (civId != 0 && inPortData.length() > lastFE + 2 && (quint8)inPortData[lastFE + 2] != civId)
            {
                civId = (quint8)inPortData[lastFE + 2];
                qInfo(logSerial()) << "pty remote CI-V changed:" << hex << (quint8)civId;
            }

            // filter 1A 05 01 12/27 = C-IV transceive command before forwarding on.
            if (inPortData.contains(QByteArrayLiteral("\x1a\x05\x01\x12")) || inPortData.contains(QByteArrayLiteral("\x1a\x05\x01\x27")))
            {
                //qInfo(logSerial()) << "Filtered transceive command";
                //printHex(inPortData, false, true);
                QByteArray reply= QByteArrayLiteral("\xfe\xfe\x00\x00\xfb\xfd");
                reply[2] = inPortData[3];
                reply[3] = inPortData[2];
                sendDataOut(inPortData); // Echo command back
                sendDataOut(reply);
                if (!disableTransceive) {
                    qInfo(logSerial()) << "pty requested CI-V Transceive disable";
                    disableTransceive = true;
                }
            }
            else if (inPortData.length() > lastFE + 2 && ((quint8)inPortData[lastFE + 1] == civId || (quint8)inPortData[lastFE + 2] == civId))
            {
                emit haveDataFromPort(inPortData);
                qDebug(logSerial()) << "Data from pseudo term:";
                printHex(inPortData, false, true);
            }

            if (rolledBack)
            {
                // qInfo(logSerial()) << "Rolled back and was successfull. Length: " << inPortData.length();
                //printHex(inPortData, false, true);
                rolledBack = false;
            }
        }
        else {
            // did not receive the entire thing so roll back:
            // qInfo(logSerial()) << "Rolling back transaction. End not detected. Lenth: " << inPortData.length();
            //printHex(inPortData, false, true);
            rolledBack = true;
#ifdef Q_OS_WIN
            port->rollbackTransaction();
        }
    }
    else {
        port->commitTransaction(); // do not emit data, do not keep data.
        //qInfo(logSerial()) << "Warning: received data with invalid start. Dropping data.";
        //qInfo(logSerial()) << "THIS SHOULD ONLY HAPPEN ONCE!!";
        // THIS SHOULD ONLY HAPPEN ONCE!
    }
#else
        }
    }
#endif
}



void pttyHandler::closePort()
{
#ifdef Q_OS_WIN
    if (port != Q_NULLPTR)
    {
        port->close();
        delete port;
    }
#else
    if (isConnected && portName != "" && portName.toLower() != "none")
    {
        QFile::remove(portName);
    }
#endif
    isConnected = false;
}


void pttyHandler::debugThis()
{
    // Do not use, function is for debug only and subject to change.
    qInfo(logSerial()) << "comm debug called.";

    inPortData = port->readAll();
    emit haveDataFromPort(inPortData);
}



void pttyHandler::printHex(const QByteArray& pdata, bool printVert, bool printHoriz)
{
    qDebug(logSerial()) << "---- Begin hex dump -----:";
    QString sdata("DATA:  ");
    QString index("INDEX: ");
    QStringList strings;

    for (int i = 0; i < pdata.length(); i++)
    {
        strings << QString("[%1]: %2").arg(i, 8, 10, QChar('0')).arg((unsigned char)pdata[i], 2, 16, QChar('0'));
        sdata.append(QString("%1 ").arg((unsigned char)pdata[i], 2, 16, QChar('0')));
        index.append(QString("%1 ").arg(i, 2, 10, QChar('0')));
    }

    if (printVert)
    {
        for (int i = 0; i < strings.length(); i++)
        {
            //sdata = QString(strings.at(i));
            qDebug(logSerial()) << strings.at(i);
        }
    }

    if (printHoriz)
    {
        qDebug(logSerial()) << index;
        qDebug(logSerial()) << sdata;
    }
    qDebug(logSerial()) << "----- End hex dump -----";
}


