package com.radio.codec2talkie.protocol;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;

import androidx.preference.PreferenceManager;

import com.radio.codec2talkie.protocol.message.TextMessage;
import com.radio.codec2talkie.protocol.position.Position;
import com.radio.codec2talkie.settings.PreferenceKeys;
import com.radio.codec2talkie.settings.SettingsWrapper;
import com.radio.codec2talkie.tools.AudioTools;
import com.radio.codec2talkie.transport.Transport;
import com.ustadmobile.codec2.Codec2;

import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ShortBuffer;
import java.util.Arrays;

public class Freedv implements Protocol {
    private static final String TAG = Freedv.class.getSimpleName();

    private static final int CRC_LENGTH = 2;
    private static final int PKT_SIZE_LENGTH = 2;

    private ProtocolCallback _parentProtocolCallback;
    private Transport _transport;

    private long _freedv;
    private long _freedvData;

    private short[] _modemTxBuffer;
    private short[] _speechRxBuffer;

    private short[] _dataSamplesBuffer;
    private byte[] _dataBuffer;

    ShortBuffer _dataSamples;
    ShortBuffer _speechSamples;

    public Freedv() {
    }

    @Override
    public void initialize(Transport transport, Context context, ProtocolCallback parentProtocolCallback) throws IOException {
        _transport = transport;
        _parentProtocolCallback = parentProtocolCallback;

        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
        int mode = SettingsWrapper.getFreeDvSoundModemModulation(sharedPreferences);
        int dataMode = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.PORTS_SOUND_MODEM_FREEDV_DATA_MODE, "12"));
        boolean isSquelchEnabled = sharedPreferences.getBoolean(PreferenceKeys.PORTS_SOUND_MODEM_FREEDV_ENABLE_SQUELCH, true);
        float squelchSnr = Float.parseFloat( sharedPreferences.getString(PreferenceKeys.PORTS_SOUND_MODEM_FREEDV_SQUELCH_SNR, "0.0"));

        Log.i(TAG, "Using freedv mode " + AudioTools.getFreedvModeAsText(sharedPreferences));

        _freedv = Codec2.freedvCreate(mode, isSquelchEnabled, squelchSnr, 0);   // unset for speech
        _modemTxBuffer = new short[Codec2.freedvGetNomModemSamples(_freedv)];
        _speechRxBuffer = new short[Codec2.freedvGetMaxSpeechSamples(_freedv)];
        _speechSamples  = ShortBuffer.allocate(1024*10);

        _freedvData = Codec2.freedvCreate(dataMode, isSquelchEnabled, squelchSnr, 1);
        _dataBuffer = new byte[Codec2.freedvGetBitsPerModemFrame(_freedvData) / 8];
        _dataSamplesBuffer = new short[Codec2.freedvGetNTxSamples(_freedvData)];
        _dataSamples  = ShortBuffer.allocate(1024*10);
    }

    @Override
    public int getPcmAudioRecordBufferSize() {
        return Codec2.freedvGetNSpeechSamples(_freedv);
    }

    @Override
    public void sendPcmAudio(String src, String dst, short[] pcmFrame) throws IOException {
        Codec2.freedvTx(_freedv, _modemTxBuffer, pcmFrame);
        //Log.i(TAG, "send pcm " + _modemTxBuffer.length);
        _transport.write(_modemTxBuffer);
        _parentProtocolCallback.onTransmitPcmAudio(src, dst, pcmFrame);
    }

    @Override
    public void sendCompressedAudio(String src, String dst, byte[] frame) throws IOException {
        Log.w(TAG, "sendCompressedAudio() is not supported");
    }

    @Override
    public void sendTextMessage(TextMessage textMessage) throws IOException {
        Log.w(TAG, "sendTextMessage() is not supported");
    }

    @Override
    public void sendData(String src, String dst, String path, byte[] dataPacket) throws IOException {
        if (dataPacket.length > _dataBuffer.length - CRC_LENGTH - PKT_SIZE_LENGTH) {
            Log.e(TAG, "Too large packet " + dataPacket.length + " > " + _dataBuffer.length);
            return;
        }
        long cnt = Codec2.freedvRawDataPreambleTx(_freedvData, _dataSamplesBuffer);
        _transport.write(Arrays.copyOf(_dataSamplesBuffer, (int) cnt));

        Arrays.fill(_dataBuffer, (byte) 0);
        // transmit packet size first
        _dataBuffer[0] = (byte)((dataPacket.length >> 8) & 0xff);
        _dataBuffer[1] = (byte)(dataPacket.length & 0xff);
        System.arraycopy(dataPacket, 0, _dataBuffer, 2, dataPacket.length);
        Codec2.freedvRawDataTx(_freedvData, _dataSamplesBuffer, _dataBuffer);
        _transport.write(_dataSamplesBuffer);

        cnt = Codec2.freedvRawDataPostambleTx(_freedvData, _dataSamplesBuffer);
        _transport.write(Arrays.copyOf(_dataSamplesBuffer, (int) cnt));
    }

    @Override
    public boolean receive() throws IOException {
        int nin = Codec2.freedvNin(_freedv);
        int ninData = Codec2.freedvNin(_freedvData);
        int cntToReadAll = Math.max(nin, ninData);
        short[] samples = new short[cntToReadAll];
        int cntReadAll = _transport.read(samples);
        if (cntReadAll == 0) return false;

        try {
            _speechSamples.put(samples);
            _dataSamples.put(samples);
        } catch (BufferOverflowException e) {
            _speechSamples.clear();
            _dataSamples.clear();
            return false;
        }

        boolean isRead = false;

        while (_speechSamples.position() >= nin) {
            //Log.i(TAG, "read speech " + nin + " " + _speechSamples.position());
            short[] samplesSpeech = new short[nin];
            _speechSamples.flip();
            _speechSamples.get(samplesSpeech);
            _speechSamples.compact();
            long cntRead = Codec2.freedvRx(_freedv, _speechRxBuffer, samplesSpeech);
            if (cntRead > 0) {
                //Log.i(TAG, "receive " + cntRead);
                _parentProtocolCallback.onReceivePcmAudio(null, null, Arrays.copyOf(_speechRxBuffer, (int) cntRead));
                float snr = Codec2.freedvGetModemStat(_freedv);
                _parentProtocolCallback.onReceiveSignalLevel((short) 0, (short)(100 * snr));
                isRead = true;
            }
            nin = Codec2.freedvNin(_freedv);
        }

        while (_dataSamples.position() >= ninData) {
            //Log.i(TAG, "read data " + ninData + " " + _dataSamples.position());
            short[] samplesData = new short[ninData];
            _dataSamples.flip();
            _dataSamples.get(samplesData);
            _dataSamples.compact();
            long cntRead = Codec2.freedvRawDataRx(_freedvData, _dataBuffer, samplesData);
            if (cntRead > 0) {
                Log.i(TAG, "receive " + cntRead);
                // extract packet length
                int pktLen = (((int)_dataBuffer[0] & 0xff) << 8) | ((int)_dataBuffer[1] & 0xff);
                byte [] pkt = new byte[pktLen];
                System.arraycopy(_dataBuffer, 2, pkt, 0, pktLen);
                // NOTE, default data is compressed audio, upper layer should distinguish
                _parentProtocolCallback.onReceiveCompressedAudio(null, null, pkt);
                float snr = Codec2.freedvGetModemStat(_freedvData);
                _parentProtocolCallback.onReceiveSignalLevel((short) 0, (short)(100 * snr));
                isRead = true;
            }
            ninData = Codec2.freedvNin(_freedvData);
        }

        return isRead;
    }

    @Override
    public void sendPosition(Position position) throws IOException {
        Log.w(TAG, "sendPosition() is not supported");
    }

    @Override
    public void flush() throws IOException {
        _speechSamples.clear();
        _dataSamples.clear();
    }

    @Override
    public void close() {
        Codec2.freedvDestroy(_freedvData);
        Codec2.freedvDestroy(_freedv);
    }
}
