///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016-2019, 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com>    //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai>                      //
//                                                                               //
// This program 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 as version 3 of the License, or                  //
// (at your option) any later version.                                           //
//                                                                               //
// This program 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 V3 for more details.                               //
//                                                                               //
// You should have received a copy of the GNU General Public License             //
// along with this program. If not, see <http://www.gnu.org/licenses/>.          //
///////////////////////////////////////////////////////////////////////////////////

#include <stdint.h>
#include <QChar>
#include <QDebug>
#include "cwkeyer.h"
#include "util/stepfunctions.h"

MESSAGE_CLASS_DEFINITION(CWKeyer::MsgConfigureCWKeyer, Message)

/**
 * 0:  dot
 * 1:  dash
 * -1: end of sequence
 */
const signed char CWKeyer::m_asciiToMorse[128][7] = {
        {-1,0,0,0,0,0,0}, // 0
        {-1,0,0,0,0,0,0}, // 1
        {-1,0,0,0,0,0,0}, // 2
        {-1,0,0,0,0,0,0}, // 3
        {-1,0,0,0,0,0,0}, // 4
        {-1,0,0,0,0,0,0}, // 5
        {-1,0,0,0,0,0,0}, // 6
        {-1,0,0,0,0,0,0}, // 7
        {-1,0,0,0,0,0,0}, // 8
        {-1,0,0,0,0,0,0}, // 9
        {-1,0,0,0,0,0,0}, // 10
        {-1,0,0,0,0,0,0}, // 11
        {-1,0,0,0,0,0,0}, // 12
        {-1,0,0,0,0,0,0}, // 13
        {-1,0,0,0,0,0,0}, // 14
        {-1,0,0,0,0,0,0}, // 15
        {-1,0,0,0,0,0,0}, // 16
        {-1,0,0,0,0,0,0}, // 17
        {-1,0,0,0,0,0,0}, // 18
        {-1,0,0,0,0,0,0}, // 19
        {-1,0,0,0,0,0,0}, // 20
        {-1,0,0,0,0,0,0}, // 21
        {-1,0,0,0,0,0,0}, // 22
        {-1,0,0,0,0,0,0}, // 23
        {-1,0,0,0,0,0,0}, // 24
        {-1,0,0,0,0,0,0}, // 25
        {-1,0,0,0,0,0,0}, // 26
        {-1,0,0,0,0,0,0}, // 27
        {-1,0,0,0,0,0,0}, // 28
        {-1,0,0,0,0,0,0}, // 29
        {-1,0,0,0,0,0,0}, // 30
        {-1,0,0,0,0,0,0}, // 31
        {-1,0,0,0,0,0,0}, // 32 space is treated as word separator
        {1,0,1,0,1,1,-1}, // 33 !
        {0,1,0,0,1,0,-1}, // 34 "
        {-1,0,0,0,0,0,0}, // 35
        {-1,0,0,0,0,0,0}, // 36
        {-1,0,0,0,0,0,0}, // 37
        {-1,0,0,0,0,0,0}, // 38
        {0,1,1,1,1,0,-1}, // 39 '
        {1,0,1,1,0,1,-1}, // 40 (
        {1,0,1,1,0,1,-1}, // 41 )
        {-1,0,0,0,0,0,0}, // 42
        {0,1,0,1,0,-1,0}, // 43 +
        {1,1,0,0,1,1,-1}, // 44 ,
        {1,0,0,0,0,1,-1}, // 45 -
        {0,1,0,1,0,1,-1}, // 46 .
        {1,0,0,1,0,-1,0}, // 47 /
        {1,1,1,1,1,-1,0}, // 48 0
        {0,1,1,1,1,-1,0}, // 49 1
        {0,0,1,1,1,-1,0}, // 50 2
        {0,0,0,1,1,-1,0}, // 51 3
        {0,0,0,0,1,-1,0}, // 52 4
        {0,0,0,0,0,-1,0}, // 53 5
        {1,0,0,0,0,-1,0}, // 54 6
        {1,1,0,0,0,-1,0}, // 55 7
        {1,1,1,0,0,-1,0}, // 56 8
        {1,1,1,1,0,-1,0}, // 57 9
        {1,1,1,0,0,0,-1}, // 58 :
        {1,0,1,0,1,0,-1}, // 59 ;
        {-1,0,0,0,0,0,0}, // 60 <
        {1,0,0,0,1,-1,0}, // 61 =
        {-1,0,0,0,0,0,0}, // 62 >
        {0,0,1,1,0,0,-1}, // 63 ?
        {0,1,1,0,1,0,-1}, // 64 @
        {0,1,-1,0,0,0,0}, // 65 A
        {1,0,0,0,-1,0,0}, // 66 B
        {1,0,1,0,-1,0,0}, // 67 C
        {1,0,0,-1,0,0,0}, // 68 D
        {0,-1,0,0,0,0,0}, // 69 E
        {0,0,1,0,-1,0,0}, // 70 F
        {1,1,0,-1,0,0,0}, // 71 G
        {0,0,0,0,-1,0,0}, // 72 H
        {0,0,-1,0,0,0,0}, // 73 I
        {0,1,1,1,-1,0,0}, // 74 J
        {1,0,1,-1,0,0,0}, // 75 K
        {0,1,0,0,-1,0,0}, // 76 L
        {1,1,-1,0,0,0,0}, // 77 M
        {1,0,-1,0,0,0,0}, // 78 N
        {1,1,1,-1,0,0,0}, // 79 O
        {0,1,1,0,-1,0,0}, // 80 P
        {1,1,0,1,-1,0,0}, // 81 Q
        {0,1,0,-1,0,0,0}, // 82 R
        {0,0,0,-1,0,0,0}, // 83 S
        {1,-1,0,0,0,0,0}, // 84 T
        {0,0,1,-1,0,0,0}, // 85 U
        {0,0,0,1,-1,0,0}, // 86 V
        {0,1,1,-1,0,0,0}, // 87 W
        {1,0,0,1,-1,0,0}, // 88 X
        {1,0,1,1,-1,0,0}, // 89 Y
        {1,1,0,0,-1,0,0}, // 90 Z
        {-1,0,0,0,0,0,0}, // 91 [
        {-1,0,0,0,0,0,0}, // 92 back /
        {-1,0,0,0,0,0,0}, // 93 ]
        {-1,0,0,0,0,0,0}, // 94 ^
        {-1,0,0,0,0,0,0}, // 95 _
        {-1,0,0,0,0,0,0}, // 96 `
        {0,1,-1,0,0,0,0}, // 97 A lowercase same as uppercase
        {1,0,0,0,-1,0,0}, // 98 B
        {1,0,1,0,-1,0,0}, // 99 C
        {1,0,0,-1,0,0,0}, // 100 D
        {0,-1,0,0,0,0,0}, // 101 E
        {0,0,1,0,-1,0,0}, // 102 F
        {1,1,0,-1,0,0,0}, // 103 G
        {0,0,0,0,-1,0,0}, // 104 H
        {0,0,-1,0,0,0,0}, // 105 I
        {0,1,1,1,-1,0,0}, // 106 J
        {1,0,1,-1,0,0,0}, // 107 K
        {0,1,0,0,-1,0,0}, // 108 L
        {1,1,-1,0,0,0,0}, // 109 M
        {1,0,-1,0,0,0,0}, // 110 N
        {1,1,1,-1,0,0,0}, // 111 O
        {0,1,1,0,-1,0,0}, // 112 P
        {1,1,0,1,-1,0,0}, // 113 Q
        {0,1,0,-1,0,0,0}, // 114 R
        {0,0,0,-1,0,0,0}, // 115 S
        {1,-1,0,0,0,0,0}, // 116 T
        {0,0,1,-1,0,0,0}, // 117 U
        {0,0,0,1,-1,0,0}, // 118 V
        {0,1,1,-1,0,0,0}, // 119 W
        {1,0,0,1,-1,0,0}, // 120 X
        {1,0,1,1,-1,0,0}, // 121 Y
        {1,1,0,0,-1,0,0}, // 122 Z
        {-1,0,0,0,0,0,0}, // 123 {
        {-1,0,0,0,0,0,0}, // 124 |
        {-1,0,0,0,0,0,0}, // 125 }
        {-1,0,0,0,0,0,0}, // 126 ~
        {-1,0,0,0,0,0,0}, // 127 DEL
};

CWKeyer::CWKeyer() :
    m_textPointer(0),
	m_elementPointer(0),
    m_samplePointer(0),
    m_elementSpace(false),
    m_characterSpace(false),
    m_key(false),
    m_dot(false),
    m_dash(false),
    m_elementOn(false),
	m_asciiChar('\0'),
    m_keyIambicState(KeySilent),
	m_textState(TextStart)
{
    connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
    applySettings(m_settings, true);
}

CWKeyer::~CWKeyer()
{
}

void CWKeyer::setSampleRate(int sampleRate)
{
    CWKeyerSettings settings = m_settings;
    settings.m_sampleRate = sampleRate;
    MsgConfigureCWKeyer *msg = MsgConfigureCWKeyer::create(settings, false);
    m_inputMessageQueue.push(msg);
}

int CWKeyer::getSample()
{
    QMutexLocker mutexLocker(&m_mutex);

    if (m_settings.m_mode == CWKeyerSettings::CWText)
    {
    	nextStateText();
        return m_key ? 1 : 0;
    }
    else if ((m_settings.m_mode == CWKeyerSettings::CWDots) || (m_settings.m_mode == CWKeyerSettings::CWDashes))
    {
        nextStateIambic();
        return m_key ? 1 : 0;
    }
    else if (m_settings.m_mode == CWKeyerSettings::CWKeyboard)
    {
        if (m_settings.m_keyboardIambic)
        {
            nextStateIambic();
            return m_key ? 1 : 0;
        }
        else
        {
            return (m_dot || m_dash) ? 1 : 0;
        }
    }
    else
    {
    	return 0;
    }
}

void CWKeyer::nextStateIambic()
{
    switch (m_keyIambicState)
    {
    case KeySilent:
        if (m_dot)
        {
            m_keyIambicState = KeyDot;
            m_samplePointer = 0;
        }
        else if (m_dash)
        {
            m_keyIambicState = KeyDash;
            m_samplePointer = 0;
        }

        m_key = false;
        break;
    case KeyDot:
        if (m_samplePointer < m_dotLength) // dot key
        {
            m_key = true;
            m_samplePointer++;
        }
        else if (m_samplePointer < 2*m_dotLength) // dot silence (+1 dot length)
        {
            m_key = false;
            m_samplePointer++;
        }
        else // end
        {
            if (m_dash)
            {
                m_keyIambicState = KeyDash;
            }
            else if (!m_dot)
            {
                m_keyIambicState = KeySilent;
            }

            m_samplePointer = 0;
            m_key = false;
        }
        break;
    case KeyDash:
        if (m_samplePointer < 3*m_dotLength) // dash key
        {
            m_key = true;
            m_samplePointer++;
        }
        else if (m_samplePointer < 4*m_dotLength) // dash silence (+1 dot length)
        {
            m_key = false;
            m_samplePointer++;
        }
        else // end
        {
            if (m_dot)
            {
                m_keyIambicState = KeyDot;
            }
            else if (!m_dash)
            {
                m_keyIambicState = KeySilent;
            }

            m_samplePointer = 0;
            m_key = false;
        }
        break;
    default:
        m_samplePointer = 0;
        m_key = false;
        break;
    }
}

void CWKeyer::nextStateText()
{
	switch (m_textState)
	{
	case TextStart:
		m_samplePointer = 0;
		m_elementPointer = 0;
		m_textPointer = 0;
		m_textState = TextStartChar;
		m_key = false;
		m_dot = false;
		m_dash = false;
		break;
	case TextStartChar:
		m_samplePointer = 0;
		m_elementPointer = 0;
		if (m_textPointer < m_settings.m_text.length())
		{
            m_asciiChar = (m_settings.m_text.at(m_textPointer)).toLatin1();
//            qDebug() << "CWKeyer::nextStateText: TextStartChar: " << m_asciiChar;

            if (m_asciiChar < 0) { // non ASCII
                m_asciiChar = 0;
            }

            if (m_asciiChar == ' ')
            {
                m_textState = TextWordSpace;
            }
            else
            {
                m_textState = TextStartElement;
            }
            m_textPointer++;
		}
		else // end of text
		{
		    m_textState = TextEnd;
		}
		break;
	case TextStartElement:
		m_samplePointer = 0;
//        qDebug() << "CWKeyer::nextStateText: TextStartElement: " << (int) m_asciiToMorse[m_asciiChar][m_elementPointer];
		if (m_asciiToMorse[(uint8_t)(m_asciiChar&0x7F)][m_elementPointer] == -1) // end of morse character
		{
		    m_elementPointer = 0;
		    m_textState = TextCharSpace;
		}
		else
		{
            if (m_asciiToMorse[(uint8_t)(m_asciiChar&0x7F)][m_elementPointer] == 0) // dot
            {
                m_dot = true;
                m_dash = false;
            }
            else // dash
            {
                m_dot = false;
                m_dash = true;
            }
	        m_keyIambicState = KeySilent; // reset iambic state
	        nextStateIambic(); // init dash or dot
            m_dot = false; // release keys
            m_dash = false;
            m_textState = TextElement;
            m_elementPointer++;
		}
		break;
	case TextElement:
		nextStateIambic(); // dash or dot
		if (m_keyIambicState == KeySilent) // done
		{
			m_textState = TextStartElement; // next element
		}
		break;
	case TextCharSpace:
		if (m_samplePointer < 2*m_dotLength) // - 1 dot length space from element
		{
			m_samplePointer++;
			m_key = false;
		}
		else
		{
			m_textState = TextStartChar;
		}
		break;
	case TextWordSpace:
		if (m_samplePointer < 4*m_dotLength) // - 3 dot length space from character
		{
			m_samplePointer++;
			m_key = false;
		}
		else
		{
			m_textState = TextStartChar;
		}
		break;
	case TextEnd:
	    if (m_settings.m_loop)
	    {
	        m_textState = TextStart;
	    }
        m_key = false;
        m_dot = false;
        m_dash = false;
	    break;
	case TextStop:
	default:
		m_key = false;
        m_dot = false;
        m_dash = false;
		break;
	}
}

bool CWKeyer::eom()
{
    return !(m_textPointer < m_settings.m_text.length());
}

void CWKeyer::setKeyboardDots()
{
    m_dot = true;
    m_dash = false;
    m_keyIambicState = KeySilent;
}

void CWKeyer::setKeyboardDashes()
{
    m_dot = false;
    m_dash = true;
    m_keyIambicState = KeySilent;
}

void CWKeyer::setKeyboardSilence()
{
    m_dot = false;
    m_dash = false;
}

CWSmoother::CWSmoother() :
        m_fadeInCounter(0),
        m_fadeOutCounter(0),
        m_nbFadeSamples(0),
        m_fadeInSamples(0),
        m_fadeOutSamples(0)
{
    setNbFadeSamples(192); // default is 4 ms at 48 kHz sample rate
}

CWSmoother::~CWSmoother()
{
    delete[] m_fadeInSamples;
    delete[] m_fadeOutSamples;
}

void CWSmoother::setNbFadeSamples(unsigned int nbFadeSamples)
{
    if (nbFadeSamples != m_nbFadeSamples)
    {
        QMutexLocker mutexLocker(&m_mutex);

        m_nbFadeSamples = nbFadeSamples;

        if (m_fadeInSamples) delete[] m_fadeInSamples;
        if (m_fadeOutSamples) delete[] m_fadeOutSamples;

        m_fadeInSamples = new float[m_nbFadeSamples];
        m_fadeOutSamples = new float[m_nbFadeSamples];

        for (unsigned int i = 0; i < m_nbFadeSamples; i++)
        {
            float x = i/ (float) m_nbFadeSamples;
            float y = 1.0f -x;

            m_fadeInSamples[i] = StepFunctions::smootherstep(x);
            m_fadeOutSamples[i] = StepFunctions::smootherstep(y);
        }

        m_fadeInCounter = 0;
        m_fadeOutCounter = 0;
    }
}

bool CWSmoother::getFadeSample(bool on, float& sample)
{
    QMutexLocker mutexLocker(&m_mutex);

    if (on)
    {
        m_fadeOutCounter = 0;

        if (m_fadeInCounter < m_nbFadeSamples)
        {
            sample = m_fadeInSamples[m_fadeInCounter];
            m_fadeInCounter++;
            return true;
        }
        else
        {
            sample = 1.0f;
            return false;
        }
    }
    else
    {
        m_fadeInCounter = 0;

        if (m_fadeOutCounter < m_nbFadeSamples)
        {
            sample = m_fadeOutSamples[m_fadeOutCounter];
            m_fadeOutCounter++;
            return true;
        }
        else
        {
            sample = 0.0f;
            return false;
        }
    }
}

bool CWKeyer::handleMessage(const Message& cmd)
{
    if (MsgConfigureCWKeyer::match(cmd))
    {
        MsgConfigureCWKeyer& cfg = (MsgConfigureCWKeyer&) cmd;
        qDebug() << "CWKeyer::handleMessage: MsgConfigureCWKeyer";

        applySettings(cfg.getSettings(), cfg.getForce());

        return true;
    }

    return true;
}

void CWKeyer::handleInputMessages()
{
	Message* message;

	while ((message = m_inputMessageQueue.pop()) != 0)
	{
		if (handleMessage(*message)) {
			delete message;
		}
	}
}

void CWKeyer::applySettings(const CWKeyerSettings& settings, bool force)
{
    qDebug() << "CWKeyer::applySettings: "
        << " m_dashKey: " << settings.m_dashKey
        << " m_dashKeyModifiers: " << settings.m_dashKeyModifiers
        << " m_dotKey: " << settings.m_dotKey
        << " m_dotKeyModifiers: " << settings.m_dotKeyModifiers
        << " m_keyboardIambic: " << settings.m_keyboardIambic
        << " m_loop: " << settings.m_loop
        << " m_mode: " << settings.m_mode
        << " m_sampleRate: " << settings.m_sampleRate
        << " m_text: " << settings.m_text
        << " m_wpm: " << settings.m_wpm;

    if ((m_settings.m_wpm != settings.m_wpm)
     || (m_settings.m_sampleRate != settings.m_sampleRate) || force)
    {
        QMutexLocker mutexLocker(&m_mutex);
        m_dotLength = (int) ((1.2f / settings.m_wpm) * settings.m_sampleRate);
        m_cwSmoother.setNbFadeSamples(m_dotLength/10); // 10% the dot time
    }

    if ((m_settings.m_mode != settings.m_mode) || force)
    {
        QMutexLocker mutexLocker(&m_mutex);

        if (settings.m_mode == CWKeyerSettings::CWText)
        {
            m_textState = TextStart;
        }
        else if (settings.m_mode == CWKeyerSettings::CWDots)
        {
            m_dot = true;
            m_dash = false;
            m_keyIambicState = KeySilent;
        }
        else if (settings.m_mode == CWKeyerSettings::CWDashes)
        {
            m_dot = false;
            m_dash = true;
            m_keyIambicState = KeySilent;
        }
        else if (settings.m_mode == CWKeyerSettings::CWKeyboard)
        {
            m_dot = false;
            m_dash = false;
            m_keyIambicState = KeySilent;
        }
    }

    if ((m_settings.m_text != settings.m_text) || force)
    {
        QMutexLocker mutexLocker(&m_mutex);
        m_textState = TextStart;
    }

    m_settings = settings;
}

void CWKeyer::webapiSettingsPutPatch(
    const QStringList& channelSettingsKeys,
    CWKeyerSettings& cwKeyerSettings,
    SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings
)
{
    cwKeyerSettings.updateFrom(channelSettingsKeys, apiCwKeyerSettings);
}

void CWKeyer::webapiFormatChannelSettings(
    SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings,
    const CWKeyerSettings& cwKeyerSettings
)
{
    cwKeyerSettings.formatTo(apiCwKeyerSettings);
}
