/*
    AudioOutputMixer
    Simple mixer which can combine multiple inputs to a single output stream

    Copyright (C) 2018  Earle F. Philhower, III

    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, either 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 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 <Arduino.h>
#include "AudioOutputMixer.h"

AudioOutputMixerStub::AudioOutputMixerStub(AudioOutputMixer *sink, int id) : AudioOutput() {
    this->id = id;
    this->parent = sink;
    SetGain(1.0);
}

AudioOutputMixerStub::~AudioOutputMixerStub() {
    parent->RemoveInput(id);
}

bool AudioOutputMixerStub::SetRate(int hz) {
    return parent->SetRate(hz, id);
}

bool AudioOutputMixerStub::SetBitsPerSample(int bits) {
    return parent->SetBitsPerSample(bits, id);
}

bool AudioOutputMixerStub::SetChannels(int channels) {
    return parent->SetChannels(channels, id);
}

bool AudioOutputMixerStub::begin() {
    return parent->begin(id);
}

bool AudioOutputMixerStub::ConsumeSample(int16_t sample[2]) {
    int16_t amp[2];
    amp[LEFTCHANNEL] = Amplify(sample[LEFTCHANNEL]);
    amp[RIGHTCHANNEL] = Amplify(sample[RIGHTCHANNEL]);
    return parent->ConsumeSample(amp, id);
}

bool AudioOutputMixerStub::stop() {
    return parent->stop(id);
}



AudioOutputMixer::AudioOutputMixer(int buffSizeSamples, AudioOutput *dest) : AudioOutput() {
    buffSize = buffSizeSamples;
    leftAccum = (int32_t*)calloc(buffSize, sizeof(int32_t));
    rightAccum = (int32_t*)calloc(buffSize, sizeof(int32_t));
    for (int i = 0; i < maxStubs; i++) {
        stubAllocated[i] = false;
        stubRunning[i] = false;
        writePtr[i] = 0;
    }
    readPtr = 0;
    sink = dest;
    sinkStarted = false;
}

AudioOutputMixer::~AudioOutputMixer() {
    free(leftAccum);
    free(rightAccum);
}


// Most "standard" interfaces should fail, only MixerStub should be able to talk to us
bool AudioOutputMixer::SetRate(int hz) {
    (void) hz;
    return false;
}

bool AudioOutputMixer::SetBitsPerSample(int bits) {
    (void) bits;
    return false;
}

bool AudioOutputMixer::SetChannels(int channels) {
    (void) channels;
    return false;
}

bool AudioOutputMixer::ConsumeSample(int16_t sample[2]) {
    (void) sample;
    return false;
}

bool AudioOutputMixer::begin() {
    return false;
}

bool AudioOutputMixer::stop() {
    return false;
}


// TODO - actually ensure all samples are same speed, size, channels, rate
bool AudioOutputMixer::SetRate(int hz, int id) {
    (void) id;
    return sink->SetRate(hz);
}

bool AudioOutputMixer::SetBitsPerSample(int bits, int id) {
    (void) id;
    return sink->SetBitsPerSample(bits);
}

bool AudioOutputMixer::SetChannels(int channels, int id) {
    (void) id;
    return sink->SetChannels(channels);
}

bool AudioOutputMixer::begin(int id) {
    stubRunning[id] = true;

    if (!sinkStarted) {
        sinkStarted = true;
        return sink->begin();
    } else {
        return true;
    }
}

AudioOutputMixerStub *AudioOutputMixer::NewInput() {
    for (int i = 0; i < maxStubs; i++) {
        if (!stubAllocated[i]) {
            stubAllocated[i] = true;
            stubRunning[i] = false;
            writePtr[i] = readPtr; // TODO - should it be 1 before readPtr?
            AudioOutputMixerStub *stub = new AudioOutputMixerStub(this, i);
            return stub;
        }
    }
    return nullptr;
}

void AudioOutputMixer::RemoveInput(int id) {
    stubAllocated[id] = false;
    stubRunning[id] = false;
}

bool AudioOutputMixer::loop() {
    // First, try and fill I2S...
    // This is not optimal, but algorithmically should work fine
    bool avail;
    do {
        avail = true;
        for (int i = 0; i < maxStubs && avail; i++) {
            if (stubRunning[i] && writePtr[i] == readPtr) {
                avail = false;  // The read pointer is touching an active writer, can't advance
            }
        }
        if (avail) {
            int16_t s[2];
            if (leftAccum[readPtr] > 32767) {
                s[LEFTCHANNEL] = 32767;
            } else if (leftAccum[readPtr] < -32767) {
                s[LEFTCHANNEL] = -32767;
            } else {
                s[LEFTCHANNEL] = leftAccum[readPtr];
            }
            if (rightAccum[readPtr] > 32767) {
                s[RIGHTCHANNEL] = 32767;
            } else if (rightAccum[readPtr] < -32767) {
                s[RIGHTCHANNEL] = -32767;
            } else {
                s[RIGHTCHANNEL] = rightAccum[readPtr];
            }
            //      s[LEFTCHANNEL] = Amplify(s[LEFTCHANNEL]);
            //      s[RIGHTCHANNEL] = Amplify(s[RIGHTCHANNEL]);
            if (!sink->ConsumeSample(s)) {
                break; // Can't stuff any more in I2S...
            }
            // Clear the accums and advance the pointer to next potential sample
            leftAccum[readPtr] = 0;
            rightAccum[readPtr] = 0;
            readPtr = (readPtr + 1) % buffSize;
        }
    } while (avail);
    return true;
}

bool AudioOutputMixer::ConsumeSample(int16_t sample[2], int id) {
    loop(); // Send any pre-existing, completed I2S data we can fit

    // Now, do we have space for a new sample?
    int nextWritePtr = (writePtr[id] + 1) % buffSize;
    if (nextWritePtr == readPtr) {
        return false;
    }

    leftAccum[writePtr[id]] += sample[LEFTCHANNEL];
    rightAccum[writePtr[id]] += sample[RIGHTCHANNEL];
    writePtr[id] = nextWritePtr;
    return true;
}

bool AudioOutputMixer::stop(int id) {
    stubRunning[id] = false;
    return true;
}


