#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();
    if (output)
        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 (!output)
        {
            qCritical(logCW) << "CW Sidetone: Cannot open default audio device";
            return;
        }
#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) {
        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 = format.bytesForDuration(len);
    const int sampleBytes = channels * channelBytes;

    // Align to whole samples
    length -= length % sampleBytes;
    if (length <= 0)
        return data;

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

    const qint64 totalSamples = length / sampleBytes;

    // Simple 5 ms ramp (fade in/out) for tone segments only
    const int maxFadeSamples = sampleRate / 200; // 5ms at given sampleRate
    const int fadeSamples = (freq > 0)
                                ? (int)qMin<qint64>(totalSamples / 2, maxFadeSamples)
                                : 0;

    for (qint64 n = 0; n < totalSamples; ++n) {

        // Envelope
        qreal env = 1.0;
        if (fadeSamples > 0) {
            if (n < fadeSamples) {
                // Fade in
                env = qreal(n) / qreal(fadeSamples);
            } else if (n >= totalSamples - fadeSamples) {
                // Fade out
                env = qreal(totalSamples - n) / qreal(fadeSamples);
            }
        }

        // Base waveform
        qreal s = 0.0;
        if (freq > 0) {
            s = qSin(2 * M_PI * freq * qreal(n % sampleRate) / sampleRate);
        }

        const qreal x = s * env * qreal(volume / 100.0);

        // Write same sample to all channels
        for (int ch = 0; ch < channels; ++ch) {
#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.0 * 255.0);
            } 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) = static_cast<float>(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.0 * 255.0);
            } 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) = static_cast<float>(x);
            } else {
                qWarning(logCW()) << QString("Unsupported sample format: %0").arg(format.sampleFormat());
            }
#endif
            ptr += channelBytes;
        }
    }

    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();
}


