#include "cwsidetone.h"

#include "logcategories.h"

cwSidetone::cwSidetone(int level, int speed, int freq, double ratio, QWidget* parent) :
    parent(parent),
    volume(level),
    speed(speed),
    frequency(freq),
    ratio(ratio)
{

    qInfo(logCW()) << "Starting sidetone. Thread=" << QThread::currentThreadId();
     /*
     * Characters to match Icom table
     * Unknown characters will return '?'
    */
    cwTable.clear();
    cwTable['0'] = "-----";
    cwTable['1'] = ".----";
    cwTable['2'] = "..---";
    cwTable['3'] = "...--";
    cwTable['4'] = "....-";
    cwTable['5'] = ".....";
    cwTable['6'] = "-....";
    cwTable['7'] = "--...";
    cwTable['8'] = "---..";
    cwTable['9'] = "----.";

    cwTable['A'] = ".-";
    cwTable['B'] = "-...";
    cwTable['C'] = "-.-.";
    cwTable['D'] = "-..";
    cwTable['E'] = ".";
    cwTable['F'] = "..-.";
    cwTable['G'] = "--.";
    cwTable['H'] = "....";
    cwTable['I'] = "..";
    cwTable['J'] = ".---";
    cwTable['K'] = "-.-";
    cwTable['L'] = ".-..";
    cwTable['M'] = "--";
    cwTable['N'] = "-.";
    cwTable['O'] = "---";
    cwTable['P'] = ".--.";
    cwTable['Q'] = "--.-";
    cwTable['R'] = ".-.";
    cwTable['S'] = "...";
    cwTable['T'] = "-";
    cwTable['U'] = "..-";
    cwTable['V'] = "...-";
    cwTable['W'] = ".--";
    cwTable['X'] = "-..-";
    cwTable['Y'] = "-.--";
    cwTable['Z'] = "--..";

    cwTable['/'] = "-..-.";
    cwTable['?'] = "..--..";
    cwTable['.'] = ".-.-.-";
    cwTable['-'] = "-....-";
    cwTable[','] = "--..--";
    cwTable[':'] = "---...";
    cwTable['\''] = ".----.";
    cwTable['('] = "-.--.-";
    cwTable[')'] = "-.--.-";
    cwTable['='] = "-...-";
    cwTable['+'] = ".-.-.";
    cwTable['"'] = ".-..-.";

    cwTable[' '] = " ";

    //init();
}

cwSidetone::~cwSidetone()
{
    qInfo(logCW()) << "cwSidetone() finished";
    this->stop();
    output->stop();
}

void cwSidetone::init()
{
    qInfo(logCW()) << "Sidetone init() Thread=" << QThread::currentThreadId();
    format.setSampleRate(44100);
    format.setChannelCount(1);
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
    format.setCodec("audio/pcm");
    format.setSampleSize(16);
    format.setByteOrder(QAudioFormat::LittleEndian);
    format.setSampleType(QAudioFormat::SignedInt);
    QAudioDeviceInfo device(QAudioDeviceInfo::defaultOutputDevice());
#else
    format.setSampleFormat(QAudioFormat::Int16);
    QAudioDevice device = QMediaDevices::defaultAudioOutput();
#endif
    if (!device.isNull()) {
        if (!device.isFormatSupported(format)) {
            qWarning(logCW()) << "Default format not supported, using preferred";
            format = device.preferredFormat();
        }

#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
        output.reset(new QAudioOutput(device,format));
#else
        output.reset(new QAudioSink(device,format));
#endif

#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
        qInfo(logCW()) << QString("Sidetone Output: %0 (volume: %1 rate: %2 size: %3 type: %4)")
                          .arg(device.deviceName()).arg(volume).arg(format.sampleRate()).arg(format.sampleSize()).arg(format.sampleType());
#else
        qInfo(logCW()) << QString("Sidetone Output: %0 (volume: %1 rate: %2 type: %3")
                          .arg(device.description()).arg(volume).arg(format.sampleRate()).arg(format.sampleFormat());
#endif        
    }
    this->start(); // Start QIODevice
}

void cwSidetone::send(QString text)
{
    text=text.simplified();

    for (int pos=0; pos < text.size(); pos++)
    {
        QChar ch = text.at(pos).toUpper();
        QString currentChar;
        if (ch == NULL)
        {
            currentChar = cwTable[' '];
        }
        else if (this->cwTable.contains(ch))
        {
            currentChar = cwTable[ch];
        }
        else
        {
            currentChar=cwTable['?'];
        }
        generateMorse(currentChar);
    }

    if (output->state() == QAudio::StoppedState)
    {
        output->start(this);
    }
    else if (output->state() == QAudio::IdleState) {
        output->suspend();
        output->resume();
    }
    return;
}


void cwSidetone::start()
{
    open(QIODevice::ReadOnly);
}

void cwSidetone::stop()
{
    close();
}

qint64 cwSidetone::readData(char *data, qint64 len)
{
    QMutexLocker locker(&mutex);
    const qint64 total = qMin(((qint64)buffer.size()), len);
    memcpy(data, buffer.constData(), total);
    buffer.remove(0,total);
    if (buffer.size() == 0) {
        emit finished();
    }
    return total;
}

qint64 cwSidetone::writeData(const char *data, qint64 len)
{
    Q_UNUSED(data);
    Q_UNUSED(len);

    return 0;
}

qint64 cwSidetone::bytesAvailable() const
{
    return buffer.size() + QIODevice::bytesAvailable();
}

void cwSidetone::generateMorse(QString morse)
{

    int dit = int(double(SIDETONE_MULTIPLIER / this->speed * SIDETONE_MULTIPLIER));
    int dah = int(double(dit * this->ratio));


    QMutexLocker locker(&mutex);
    for (int i=0;i<morse.size();i++)
    {
        QChar c = morse.at(i);
        if (c == '-')
        {
            buffer.append(generateData(dah,this->frequency));
        }
        else if (c == '.')
        {
            buffer.append(generateData(dit,this->frequency));
        }
        else // Space char
        {
            buffer.append(generateData(dit,0));
        }

        if (i<morse.size()-1)
        {
            buffer.append(generateData(dit,0));
        }
        else
        {
            buffer.append(generateData(dit*3,0)); // inter-character space
        }
    }
    return;
}

QByteArray cwSidetone::generateData(qint64 len, qint64 freq)
{
    QByteArray data;

    const int channels = format.channelCount();
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
    const int channelBytes = format.sampleSize() / 8;
#else
    const int channelBytes = format.bytesPerSample();
#endif

    const int sampleRate = format.sampleRate();
    //qint64 length = (sampleRate * channels * channelBytes) * len / 100000;
    qint64 length = format.bytesForDuration(len);
    const int sampleBytes = channels * channelBytes;

    length -= length % sampleBytes;
    Q_UNUSED(sampleBytes) // suppress warning in release builds

    data.resize(length);
    quint8 *ptr = reinterpret_cast<quint8 *>(data.data());
    int sampleIndex = 0;

    while (length) {
        const qreal x = (qSin(2 * M_PI * freq * qreal(sampleIndex % sampleRate) / sampleRate)) * qreal((volume/100.0));

        for (int i=0; i<channels; ++i) {
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
            if (format.sampleSize() == 8 && format.sampleType() == QAudioFormat::UnSignedInt)
                *reinterpret_cast<quint8 *>(ptr) = static_cast<quint8>((1.0 + x) / 2 * 255);
            else if (format.sampleSize() == 16 && format.sampleType() == QAudioFormat::SignedInt)
                *reinterpret_cast<qint16 *>(ptr) = static_cast<qint16>(x * std::numeric_limits<qint16>::max());
            else if (format.sampleSize() == 32 && format.sampleType() == QAudioFormat::SignedInt)
                *reinterpret_cast<qint32 *>(ptr) = static_cast<qint32>(x * std::numeric_limits<qint32>::max());
            else if (format.sampleType() == QAudioFormat::Float)
                *reinterpret_cast<float *>(ptr) = x;
            else
                qWarning(logCW()) << QString("Unsupported sample size: %0 type: %1").arg(format.sampleSize()).arg(format.sampleType());
#else
            if (format.sampleFormat() == QAudioFormat::UInt8)
                *reinterpret_cast<quint8 *>(ptr) = static_cast<quint8>((1.0 + x) / 2 * 255);
            else if (format.sampleFormat() == QAudioFormat::Int16)
                *reinterpret_cast<qint16 *>(ptr) = static_cast<qint16>(x * std::numeric_limits<qint16>::max());
            else if (format.sampleFormat() == QAudioFormat::Int32)
                *reinterpret_cast<qint32 *>(ptr) = static_cast<qint32>(x * std::numeric_limits<qint32>::max());
            else if (format.sampleFormat() == QAudioFormat::Float)
                *reinterpret_cast<float *>(ptr) = x;
            else
                qWarning(logCW()) << QString("Unsupported sample format: %0").arg(format.sampleFormat());
#endif

            ptr += channelBytes;
            length -= channelBytes;
        }
        ++sampleIndex;
    }

    return data;
}

void cwSidetone::setSpeed(quint8 speed)
{
    this->speed = (int)speed;
}

void cwSidetone::setFrequency(quint16 freq)
{
    this->frequency = freq;
}

void cwSidetone::setRatio(quint8 ratio)
{
    this->ratio = (double)ratio/10.0;
}

void cwSidetone::setLevel(int level) {
    volume = level;
}

void cwSidetone::stopSending() {
    QMutexLocker locker(&mutex);
    buffer.clear();
    emit finished();
}


