#include "DemodulatorInstance.h"

DemodulatorInstance::DemodulatorInstance() :
        t_Demod(NULL), t_PreDemod(NULL), t_Audio(NULL), threadQueueDemod(NULL), demodulatorThread(NULL), terminated(true), audioTerminated(true), demodTerminated(
        true), preDemodTerminated(true), active(false), squelch(false), stereo(false), currentFrequency(0), currentBandwidth(0), currentOutputDevice(-1) {

    label = new std::string("Unnamed");
    threadQueueDemod = new DemodulatorThreadInputQueue;
    threadQueuePostDemod = new DemodulatorThreadPostInputQueue;
    threadQueueCommand = new DemodulatorThreadCommandQueue;
    threadQueueNotify = new DemodulatorThreadCommandQueue;
    threadQueueControl = new DemodulatorThreadControlCommandQueue;

    demodulatorPreThread = new DemodulatorPreThread(threadQueueDemod, threadQueuePostDemod, threadQueueControl, threadQueueNotify);
    demodulatorPreThread->setCommandQueue(threadQueueCommand);
    demodulatorThread = new DemodulatorThread(threadQueuePostDemod, threadQueueControl, threadQueueNotify);

    audioInputQueue = new AudioThreadInputQueue;
    audioThread = new AudioThread(audioInputQueue, threadQueueNotify);

    demodulatorThread->setAudioOutputQueue(audioInputQueue);

    currentDemodType = demodulatorThread->getDemodulatorType();
}

DemodulatorInstance::~DemodulatorInstance() {
    delete audioThread;
    delete demodulatorThread;
    delete demodulatorPreThread;
    delete threadQueueDemod;
    delete threadQueuePostDemod;
    delete threadQueueCommand;
    delete threadQueueNotify;
    delete threadQueueControl;
    delete audioInputQueue;
}

void DemodulatorInstance::setVisualOutputQueue(DemodulatorThreadOutputQueue *tQueue) {
    demodulatorThread->setVisualOutputQueue(tQueue);
}

void DemodulatorInstance::run() {
    if (active) {
        return;
    }

//    while (!isTerminated()) {
//        std::this_thread::sleep_for(std::chrono::milliseconds(1));
//    }

    currentFrequency = demodulatorPreThread->getParams().frequency;
    currentDemodType = demodulatorThread->getDemodulatorType();
    currentAudioSampleRate = AudioThread::deviceSampleRate[getOutputDevice()];
    demodulatorPreThread->getParams().audioSampleRate = currentAudioSampleRate;

    t_Audio = new std::thread(&AudioThread::threadMain, audioThread);

#ifdef __APPLE__    // Already using pthreads, might as well do some custom init..
    pthread_attr_t attr;
    size_t size;

    pthread_attr_init(&attr);
    pthread_attr_setstacksize(&attr, 2048000);
    pthread_attr_getstacksize(&attr, &size);
    pthread_create(&t_PreDemod, &attr, &DemodulatorPreThread::pthread_helper, demodulatorPreThread);
    pthread_attr_destroy(&attr);

    pthread_attr_init(&attr);
    pthread_attr_setstacksize(&attr, 2048000);
    pthread_attr_getstacksize(&attr, &size);
    pthread_create(&t_Demod, &attr, &DemodulatorThread::pthread_helper, demodulatorThread);
    pthread_attr_destroy(&attr);

    std::cout << "Initialized demodulator stack size of " << size << std::endl;

#else
    t_PreDemod = new std::thread(&DemodulatorPreThread::threadMain, demodulatorPreThread);
    t_Demod = new std::thread(&DemodulatorThread::threadMain, demodulatorThread);
#endif
    active = true;
    audioTerminated = demodTerminated = preDemodTerminated = terminated = false;

}

void DemodulatorInstance::updateLabel(long long freq) {
    std::stringstream newLabel;
    newLabel.precision(3);
    newLabel << std::fixed << ((long double) freq / 1000000.0);
    setLabel(newLabel.str());
}

DemodulatorThreadCommandQueue *DemodulatorInstance::getCommandQueue() {
    return threadQueueCommand;
}

void DemodulatorInstance::terminate() {
    std::cout << "Terminating demodulator audio thread.." << std::endl;
    audioThread->terminate();
    std::cout << "Terminating demodulator thread.." << std::endl;
    demodulatorThread->terminate();
    std::cout << "Terminating demodulator preprocessor thread.." << std::endl;
    demodulatorPreThread->terminate();
}

std::string DemodulatorInstance::getLabel() {
    return *(label.load());
}

void DemodulatorInstance::setLabel(std::string labelStr) {
    std::string *newLabel = new std::string;
    newLabel->append(labelStr);
    std::string *oldLabel;
    oldLabel = label;
    label = newLabel;
    delete oldLabel;
}

bool DemodulatorInstance::isTerminated() {
    while (!threadQueueNotify->empty()) {
        DemodulatorThreadCommand cmd;
        threadQueueNotify->pop(cmd);

        switch (cmd.cmd) {
        case DemodulatorThreadCommand::DEMOD_THREAD_CMD_AUDIO_TERMINATED:
            t_Audio->join();
            audioTerminated = true;
            delete t_Audio;
            break;
        case DemodulatorThreadCommand::DEMOD_THREAD_CMD_DEMOD_TERMINATED:
#ifdef __APPLE__
            pthread_join(t_Demod, NULL);
#else
            t_Demod->join();
            delete t_Demod;
#endif
            demodTerminated = true;
            break;
        case DemodulatorThreadCommand::DEMOD_THREAD_CMD_DEMOD_PREPROCESS_TERMINATED:
#ifdef __APPLE__
            pthread_join(t_PreDemod, NULL);
#else
            t_PreDemod->join();
            delete t_PreDemod;
#endif
            preDemodTerminated = true;
            break;
        default:
            break;
        }
    }

    terminated = audioTerminated && demodTerminated && preDemodTerminated;

    return terminated;
}

bool DemodulatorInstance::isActive() {
    return active;
}

void DemodulatorInstance::setActive(bool state) {
    if (active && !state) {
        audioThread->setActive(state);
    } else if (!active && state) {
        audioThread->setActive(state);
    }
    if (!state) {
        tracking = false;
    }
    active = state;
}

bool DemodulatorInstance::isStereo() {
    return stereo;
}

void DemodulatorInstance::setStereo(bool state) {
    stereo = state;
    demodulatorThread->setStereo(state);
}

void DemodulatorInstance::squelchAuto() {
    DemodulatorThreadControlCommand command;
    command.cmd = DemodulatorThreadControlCommand::DEMOD_THREAD_CMD_CTL_SQUELCH_ON;
    threadQueueControl->push(command);
    squelch = true;
}

bool DemodulatorInstance::isSquelchEnabled() {
    return (demodulatorThread->getSquelchLevel() != 0.0);
}

void DemodulatorInstance::setSquelchEnabled(bool state) {
    if (!state && squelch) {
        DemodulatorThreadControlCommand command;
        command.cmd = DemodulatorThreadControlCommand::DEMOD_THREAD_CMD_CTL_SQUELCH_OFF;
        threadQueueControl->push(command);
    } else if (state && !squelch) {
        DemodulatorThreadControlCommand command;
        command.cmd = DemodulatorThreadControlCommand::DEMOD_THREAD_CMD_CTL_SQUELCH_ON;
        threadQueueControl->push(command);
    }

    squelch = state;
}

float DemodulatorInstance::getSignalLevel() {
    return demodulatorThread->getSignalLevel();
}

void DemodulatorInstance::setSquelchLevel(float signal_level_in) {
    demodulatorThread->setSquelchLevel(signal_level_in);
}

float DemodulatorInstance::getSquelchLevel() {
    return demodulatorThread->getSquelchLevel();
}

void DemodulatorInstance::setOutputDevice(int device_id) {
    if (!active) {
        audioThread->setInitOutputDevice(device_id);
    } else if (audioThread) {
        setAudioSampleRate(AudioThread::deviceSampleRate[device_id]);

        AudioThreadCommand command;
        command.cmd = AudioThreadCommand::AUDIO_THREAD_CMD_SET_DEVICE;
        command.int_value = device_id;
        audioThread->getCommandQueue()->push(command);
    }
    currentOutputDevice = device_id;
}

int DemodulatorInstance::getOutputDevice() {
    if (currentOutputDevice == -1) {
        currentOutputDevice = audioThread->getOutputDevice();
    }

    return currentOutputDevice;
}

void DemodulatorInstance::checkBandwidth() {
//    if ((currentDemodType == DEMOD_TYPE_USB || currentDemodType == DEMOD_TYPE_LSB) && (getBandwidth() % 2)) {
//        setBandwidth(getBandwidth()+1);
//    }
}

void DemodulatorInstance::setDemodulatorType(int demod_type_in) {
    if (!active) {
        currentDemodType = demod_type_in;
        checkBandwidth();
        demodulatorPreThread->getParams().demodType = currentDemodType;
        demodulatorThread->setDemodulatorType(currentDemodType);
    } else if (demodulatorThread && threadQueueControl) {
        DemodulatorThreadControlCommand command;
        command.cmd = DemodulatorThreadControlCommand::DEMOD_THREAD_CMD_CTL_TYPE;
        currentDemodType = demod_type_in;
        command.demodType = demod_type_in;
        checkBandwidth();
        threadQueueControl->push(command);
    }
}

int DemodulatorInstance::getDemodulatorType() {
    return currentDemodType;
}

void DemodulatorInstance::setBandwidth(int bw) {
    if (!active) {
        currentBandwidth = bw;
        checkBandwidth();
        demodulatorPreThread->getParams().bandwidth = currentBandwidth;
    } else if (demodulatorPreThread && threadQueueCommand) {
        DemodulatorThreadCommand command;
        command.cmd = DemodulatorThreadCommand::DEMOD_THREAD_CMD_SET_BANDWIDTH;
        currentBandwidth = bw;
        checkBandwidth();
        command.llong_value = currentBandwidth;
        threadQueueCommand->push(command);
    }
}

int DemodulatorInstance::getBandwidth() {
    if (!currentBandwidth) {
        currentBandwidth = demodulatorPreThread->getParams().bandwidth;
    }
    return currentBandwidth;
}

void DemodulatorInstance::setFrequency(long long freq) {
    if ((freq - getBandwidth() / 2) <= 0) {
        freq = getBandwidth() / 2;
    }
    if (!active) {
        currentFrequency = freq;
        demodulatorPreThread->getParams().frequency = currentFrequency;
    } else if (demodulatorPreThread && threadQueueCommand) {
        DemodulatorThreadCommand command;
        command.cmd = DemodulatorThreadCommand::DEMOD_THREAD_CMD_SET_FREQUENCY;
        currentFrequency = freq;
        command.llong_value = freq;
        threadQueueCommand->push(command);
    }
}

long long DemodulatorInstance::getFrequency() {
    if (!currentFrequency) {
        currentFrequency = demodulatorPreThread->getParams().frequency;
    }
    return currentFrequency;
}


void DemodulatorInstance::setAudioSampleRate(int sampleRate) {
    if (terminated) {
        currentAudioSampleRate = sampleRate;
        demodulatorPreThread->getParams().audioSampleRate = sampleRate;
    } else if (demodulatorPreThread && threadQueueCommand) {
        DemodulatorThreadCommand command;
        command.cmd = DemodulatorThreadCommand::DEMOD_THREAD_CMD_SET_AUDIO_RATE;
        currentAudioSampleRate = sampleRate;
        command.llong_value = sampleRate;
        threadQueueCommand->push(command);
    }
}

int DemodulatorInstance::getAudioSampleRate() {
    if (!currentAudioSampleRate) {
        currentAudioSampleRate = audioThread->getSampleRate();
    }
    return currentAudioSampleRate;
}


void DemodulatorInstance::setGain(float gain_in) {
    audioThread->setGain(gain_in);
}

float DemodulatorInstance::getGain() {
   return audioThread->getGain();
}

bool DemodulatorInstance::isFollow()  {
    return follow;
}

void DemodulatorInstance::setFollow(bool follow) {
    this->follow = follow;
}

bool DemodulatorInstance::isTracking()  {
    return tracking;
}

void DemodulatorInstance::setTracking(bool tracking) {
    this->tracking = tracking;
}
