/* Raw Data Unit Tests
 *
 * Copyright (c) 2021 The OSCAR Team
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License. See the file COPYING in the main directory of the source code
 * for more details. */

#include "rawdatatests.h"
#include "rawdata.h"

#include <QBuffer>

// Check QIODevice interface for consistency.
void RawDataTests::testQIODeviceInterface()
{
    // Create sample data.
    static const int DATA_SIZE = 256;
    QByteArray data(DATA_SIZE, 0);
    for (int i = 0; i < data.size(); i++) {
        data[i] = (DATA_SIZE-1) - i;
    }
    QBuffer qio(&data);

    // Create raw data wrapper.
    RawDataDevice raw_instance(qio, "sample");
    Q_ASSERT(raw_instance.name() == "sample");
    QIODevice & raw(raw_instance);  // cast to its generic interface for accurate testing


    // Connect signals for testing.
    _RawDataTestSignalSink sink;
    connect(&raw, SIGNAL(channelReadyRead(int)), &sink, SLOT(onChannelReadyRead(int)));
    connect(&raw, SIGNAL(readyRead()), &sink, SLOT(onReadyRead()));
    connect(&raw, SIGNAL(readChannelFinished()), &sink, SLOT(onReadChannelFinished()));
    connect(&raw, SIGNAL(aboutToClose()), &sink, SLOT(onAboutToClose()));


    // Open
    Q_ASSERT(raw.isOpen() == qio.isOpen());
    Q_ASSERT(raw.isReadable() == qio.isReadable());
    Q_ASSERT(raw.isWritable() == qio.isWritable());
    Q_ASSERT(raw.isWritable() == false);
    Q_ASSERT(raw.isSequential() == qio.isSequential());
    Q_ASSERT(raw.openMode() == qio.openMode());

    Q_ASSERT(raw.open(QIODevice::ReadWrite) == false);
    Q_ASSERT(raw.open(QIODevice::ReadOnly) == true);
    Q_ASSERT(raw.isOpen() == qio.isOpen());
    Q_ASSERT(raw.isReadable() == qio.isReadable());
    Q_ASSERT(raw.isWritable() == qio.isWritable());
    Q_ASSERT(raw.isWritable() == false);
    Q_ASSERT(raw.isSequential() == qio.isSequential());
    Q_ASSERT(raw.openMode() == qio.openMode());


    // waitForReadyRead and ready signals
    Q_ASSERT(raw.waitForReadyRead(10000) == false);
    //Q_ASSERT(sink.m_channelReadyRead != -1);
    //Q_ASSERT(sink.m_readyRead == true);


    // Channels
    Q_ASSERT(raw.readChannelCount() == qio.readChannelCount());
    for (int i = 0; i < raw.readChannelCount(); i++) {
        raw.setCurrentReadChannel(i);
        Q_ASSERT(raw.currentReadChannel() == i);
        Q_ASSERT(raw.currentReadChannel() == qio.currentReadChannel());
    }


    // Text mode
    // Text mode is pretty awful, it just drops all \x0D, even without a trailing \x0A.
    Q_ASSERT(raw.isTextModeEnabled() == false);
    Q_ASSERT(raw.isTextModeEnabled() == qio.isTextModeEnabled());
    raw.setTextModeEnabled(true);
    Q_ASSERT(raw.isTextModeEnabled() == true);
    raw.peek(1);  // force a sync of text mode
    Q_ASSERT(raw.isTextModeEnabled() == qio.isTextModeEnabled());
    raw.setTextModeEnabled(false);
    raw.peek(1);  // force a sync of text mode
    Q_ASSERT(raw.isTextModeEnabled() == qio.isTextModeEnabled());


    // seek/pos/getChar/ungetChar/readAll/atEnd
    // skip() is 5.10 or later, so we don't use or test it
    char ch;
    int pos = raw.pos();
    Q_ASSERT(raw.pos() == qio.pos() - 1);  // peek (above) only retracts raw's position after reading qio
    Q_ASSERT(raw.getChar(&ch) == true);
    Q_ASSERT(raw.pos() == qio.pos());
    raw.ungetChar(ch);
    Q_ASSERT(raw.pos() == pos);
    Q_ASSERT(raw.pos() == qio.pos() - 1);  // ungetChar only affects raw's buffer/position
    Q_ASSERT(ch == data[0]);

    Q_ASSERT(raw.size() == qio.size());

    Q_ASSERT(raw.seek(16) == true);
    Q_ASSERT(raw.pos() == 16);
    Q_ASSERT(raw.pos() == qio.pos());
    Q_ASSERT(raw.atEnd() == qio.atEnd());
    
    
    // Check boundary conditions at end of device.
    Q_ASSERT(raw.seek(255) == true);
    Q_ASSERT(raw.getChar(&ch) == true);
    Q_ASSERT(raw.pos() == qio.pos());
    Q_ASSERT(raw.atEnd() == true);
    Q_ASSERT(raw.atEnd() == qio.atEnd());
    Q_ASSERT(raw.bytesAvailable() == qio.bytesAvailable());
    raw.ungetChar(ch);
    Q_ASSERT(raw.atEnd() == false);
    Q_ASSERT(raw.atEnd() != qio.atEnd());
    Q_ASSERT(raw.bytesAvailable() == qio.bytesAvailable() + 1);
    
    Q_ASSERT(raw.reset() == true);
    Q_ASSERT(raw.pos() == 0);
    Q_ASSERT(raw.pos() == qio.pos());
    QByteArray all = raw.readAll();
    Q_ASSERT(all == data);
    Q_ASSERT(raw.atEnd() == qio.atEnd());
    Q_ASSERT(raw.bytesAvailable() == qio.bytesAvailable());


    // canReadLine
    Q_ASSERT(raw.canReadLine() == qio.canReadLine());
    raw.seek(255 - 0x0A);
    Q_ASSERT(raw.canReadLine() == true);
    Q_ASSERT(raw.canReadLine() == qio.canReadLine());
    Q_ASSERT(raw.getChar(&ch) == true);
    Q_ASSERT(ch == 0x0A);
    Q_ASSERT(raw.canReadLine() == false);
    Q_ASSERT(raw.canReadLine() == qio.canReadLine());
    raw.ungetChar(ch);
    Q_ASSERT(raw.canReadLine() == true);
    Q_ASSERT(raw.canReadLine() != qio.canReadLine());


    // readLine x2
    Q_ASSERT(raw.reset() == true);
    Q_ASSERT(raw.canReadLine() == qio.canReadLine());

    char line[DATA_SIZE+1];  // plus trailing null
    int length = raw.readLine(line, sizeof(line));
    pos = raw.pos();
    raw.reset();
    char line2[DATA_SIZE+1];  // plus trailing null
    int length2 = qio.readLine(line2, sizeof(line2));
    Q_ASSERT(length == length2);
    Q_ASSERT(strcmp(line, line2) == 0);
    
    raw.reset();
    
    QByteArray raw_readLine = raw.readLine();
    raw.reset();
    Q_ASSERT(raw_readLine == qio.readLine());


    // read & peek x2
    Q_ASSERT(raw.reset() == true);
    
    length = raw.read(line, 128);
    Q_ASSERT(length == 128);
    Q_ASSERT(raw.pos() == 128);
    Q_ASSERT(raw.pos() == qio.pos());
    Q_ASSERT(memcmp(data.constData(), line, 128) == 0);

    Q_ASSERT(raw.pos() == 128);
    length2 = raw.peek(line2, 128);
    Q_ASSERT(raw.pos() == 128);
    Q_ASSERT(length == 128);
    Q_ASSERT(raw.pos() == qio.pos() - length);  // peek only retracts raw's position after reading qio
    Q_ASSERT(memcmp(data.constData()+128, line2, 128) == 0);

    raw.reset();

    QByteArray raw_read = raw.read(128);
    Q_ASSERT(length == 128);
    Q_ASSERT(raw.pos() == 128);
    Q_ASSERT(raw.pos() == qio.pos());
    Q_ASSERT(raw_read == data.mid(0, 128));

    Q_ASSERT(raw.pos() == 128);
    QByteArray raw_peek = raw.peek(128);
    Q_ASSERT(raw.pos() == 128);
    Q_ASSERT(length == 128);
    Q_ASSERT(raw.pos() == qio.pos() - 128);  // peek only retracts raw's position after reading qio
    Q_ASSERT(raw_peek == data.mid(128, 128));

    raw.reset();


    // Transactions
    // These exist solely within raw and don't pass through to the underlying device.
    Q_ASSERT(raw.isTransactionStarted() == false);
    raw.startTransaction();
    Q_ASSERT(raw.isTransactionStarted() == true);
    raw_peek = raw.read(128);
    Q_ASSERT(raw.pos() == 128);
    raw.rollbackTransaction();
    Q_ASSERT(raw.isTransactionStarted() == false);
    Q_ASSERT(raw.pos() == 0);
    raw.startTransaction();
    Q_ASSERT(raw.isTransactionStarted() == true);
    raw_read = raw.read(128);
    Q_ASSERT(raw.pos() == 128);
    raw.commitTransaction();
    Q_ASSERT(raw.isTransactionStarted() == false);
    Q_ASSERT(raw.pos() == 128);


    // Close
    raw.close();
    Q_ASSERT(raw.isOpen() == qio.isOpen());
    Q_ASSERT(sink.m_aboutToClose);
    //Q_ASSERT(sink.m_readChannelFinished);


    // Unimplemented/untested:
    // bytesToWrite
    // currentWriteChannel
    // setCurentWriteChannel
    // putChar
    // waitForBytesWritten
    // write x3
    // writeChannelCount
    // bytesWritten signal
    // channelBytesWritten signal
}

_RawDataTestSignalSink::_RawDataTestSignalSink() : QObject() {
    m_channelReadyRead = -1;
    m_readyRead = false;
    m_readChannelFinished = false;
    m_aboutToClose = false;
}

void _RawDataTestSignalSink::onAboutToClose()
{
    m_aboutToClose = true;
}

void _RawDataTestSignalSink::onChannelReadyRead(int channel)
{
    m_channelReadyRead = channel;
}

void _RawDataTestSignalSink::onReadChannelFinished()
{
    m_readChannelFinished = true;
}

void _RawDataTestSignalSink::onReadyRead()
{
    m_readyRead = true;
}


// TODO: Test sequential devices when we have a test case.
// TODO: Test waitForReadySignal when we have a test case.
// TODO: Test readyRead/channelReadyRead/onReadChannelFinished signals when we have a test case.
