/*
 * This file is part of the MicroPython project, http://micropython.org/
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "audio_dma.h"

#include "shared-bindings/audiocore/RawSample.h"
#include "shared-bindings/audiocore/WaveFile.h"
#include "supervisor/background_callback.h"

#include "py/mpstate.h"
#include "py/runtime.h"

#include "src/rp2_common/hardware_irq/include/hardware/irq.h"

#if CIRCUITPY_AUDIOPWMIO || CIRCUITPY_AUDIOBUSIO

#define AUDIO_DMA_CHANNEL_COUNT NUM_DMA_CHANNELS

void audio_dma_reset(void) {
    for (size_t channel = 0; channel < AUDIO_DMA_CHANNEL_COUNT; channel++) {
        if (MP_STATE_PORT(playing_audio)[channel] == NULL) {
            continue;
        }

        audio_dma_stop(MP_STATE_PORT(playing_audio)[channel]);
    }
}

void audio_dma_convert_signed(audio_dma_t *dma, uint8_t *buffer, uint32_t buffer_length,
    uint8_t **output_buffer, uint32_t *output_buffer_length) {
    if (dma->first_buffer_free) {
        *output_buffer = dma->first_buffer;
    } else {
        *output_buffer = dma->second_buffer;
    }
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wcast-align"
    if (dma->signed_to_unsigned ||
        dma->unsigned_to_signed ||
        dma->sample_spacing > 1 ||
        (dma->sample_resolution != dma->output_resolution)) {
        *output_buffer_length = buffer_length / dma->sample_spacing;
        uint32_t out_i = 0;
        if (dma->sample_resolution <= 8 && dma->output_resolution > 8) {
            size_t shift = dma->output_resolution - dma->sample_resolution;

            for (uint32_t i = 0; i < buffer_length; i += dma->sample_spacing) {
                if (dma->signed_to_unsigned) {
                    ((uint16_t *)*output_buffer)[out_i] = ((uint16_t)((int8_t *)buffer)[i] + 0x80) << shift;
                } else if (dma->unsigned_to_signed) {
                    ((int16_t *)*output_buffer)[out_i] = ((int16_t)((uint8_t *)buffer)[i] - 0x80) << shift;
                } else {
                    ((uint16_t *)*output_buffer)[out_i] = ((uint16_t)((uint8_t *)buffer)[i]) << shift;
                }
                out_i += 1;
            }
        } else if (dma->sample_resolution <= 8 && dma->output_resolution <= 8) {
            for (uint32_t i = 0; i < buffer_length; i += dma->sample_spacing) {
                if (dma->signed_to_unsigned) {
                    ((uint8_t *)*output_buffer)[out_i] = ((int8_t *)buffer)[i] + 0x80;
                } else if (dma->unsigned_to_signed) {
                    ((int8_t *)*output_buffer)[out_i] = ((uint8_t *)buffer)[i] - 0x80;
                } else {
                    ((uint8_t *)*output_buffer)[out_i] = ((uint8_t *)buffer)[i];
                }
                out_i += 1;
            }
        } else if (dma->sample_resolution > 8 && dma->output_resolution > 8) {
            size_t shift = 16 - dma->output_resolution;
            for (uint32_t i = 0; i < buffer_length / 2; i += dma->sample_spacing) {
                if (dma->signed_to_unsigned) {
                    ((uint16_t *)*output_buffer)[out_i] = ((int16_t *)buffer)[i] + 0x8000;
                } else if (dma->unsigned_to_signed) {
                    ((int16_t *)*output_buffer)[out_i] = ((uint16_t *)buffer)[i] - 0x8000;
                } else {
                    ((uint16_t *)*output_buffer)[out_i] = ((uint16_t *)buffer)[i];
                }
                if (dma->output_resolution < 16) {
                    if (dma->output_signed) {
                        ((int16_t *)*output_buffer)[out_i] = ((int16_t *)*output_buffer)[out_i] >> shift;
                    } else {
                        ((uint16_t *)*output_buffer)[out_i] = ((uint16_t *)*output_buffer)[out_i] >> shift;
                    }
                }
                out_i += 1;
            }
        }
    } else {
        *output_buffer = buffer;
        *output_buffer_length = buffer_length;
    }
    #pragma GCC diagnostic pop
    dma->first_buffer_free = !dma->first_buffer_free;
}

void audio_dma_load_next_block(audio_dma_t *dma) {
    uint8_t dma_channel = dma->channel[1];
    if (dma->first_channel_free) {
        dma_channel = dma->channel[0];
    }
    dma->first_channel_free = !dma->first_channel_free;

    uint8_t *output_buffer;
    uint32_t output_buffer_length;
    audioio_get_buffer_result_t get_buffer_result;
    uint8_t *buffer;
    uint32_t buffer_length;
    get_buffer_result = audiosample_get_buffer(dma->sample,
        dma->single_channel, dma->audio_channel, &buffer, &buffer_length);

    if (get_buffer_result == GET_BUFFER_ERROR) {
        audio_dma_stop(dma);
        return;
    }

    audio_dma_convert_signed(dma, buffer, buffer_length, &output_buffer, &output_buffer_length);

    // If we don't have an output buffer, save the pointer to first_buffer for use in the single
    // buffer special case.
    if (dma->first_buffer == NULL) {
        dma->first_buffer = output_buffer;
    }

    dma_channel_set_trans_count(dma_channel, output_buffer_length / dma->output_size, false /* trigger */);
    dma_channel_set_read_addr(dma_channel, output_buffer, false /* trigger */);
    if (get_buffer_result == GET_BUFFER_DONE) {
        if (dma->loop) {
            audiosample_reset_buffer(dma->sample, dma->single_channel, dma->audio_channel);
        } else {
            // Set channel trigger to ourselves so we don't keep going.
            dma_channel_hw_t *c = &dma_hw->ch[dma_channel];
            c->al1_ctrl = (c->al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (dma_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB);
        }
    }
}

// Playback should be shutdown before calling this.
audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
    mp_obj_t sample,
    bool loop,
    bool single_channel,
    uint8_t audio_channel,
    bool output_signed,
    uint8_t output_resolution,
    uint32_t output_register_address,
    uint8_t dma_trigger_source) {
    // Use two DMA channels to  because the DMA can't wrap to itself without the
    // buffer being power of two aligned.
    dma->channel[0] = dma_claim_unused_channel(false);
    dma->channel[1] = dma_claim_unused_channel(false);
    if (dma->channel[0] == NUM_DMA_CHANNELS || dma->channel[1] == NUM_DMA_CHANNELS) {
        if (dma->channel[0] < NUM_DMA_CHANNELS) {
            dma_channel_unclaim(dma->channel[0]);
        }
        return AUDIO_DMA_DMA_BUSY;
    }

    dma->sample = sample;
    dma->loop = loop;
    dma->single_channel = single_channel;
    dma->audio_channel = audio_channel;
    dma->signed_to_unsigned = false;
    dma->unsigned_to_signed = false;
    dma->output_signed = output_signed;
    dma->sample_spacing = 1;
    dma->first_channel_free = true;
    dma->output_resolution = output_resolution;
    dma->sample_resolution = audiosample_bits_per_sample(sample);
    audiosample_reset_buffer(sample, single_channel, audio_channel);

    bool single_buffer;
    bool samples_signed;
    uint32_t max_buffer_length;
    audiosample_get_buffer_structure(sample, single_channel, &single_buffer, &samples_signed,
        &max_buffer_length, &dma->sample_spacing);

    // Check to see if we have to scale the resolution up.
    if (dma->sample_resolution <= 8 && dma->output_resolution > 8) {
        max_buffer_length *= 2;
    }
    if (output_signed != samples_signed ||
        dma->sample_spacing > 1 ||
        (dma->sample_resolution != dma->output_resolution)) {
        max_buffer_length /= dma->sample_spacing;
        dma->first_buffer = (uint8_t *)m_realloc(dma->first_buffer, max_buffer_length);
        if (dma->first_buffer == NULL) {
            return AUDIO_DMA_MEMORY_ERROR;
        }

        dma->first_buffer_free = true;
        if (!single_buffer) {
            dma->second_buffer = (uint8_t *)m_realloc(dma->second_buffer, max_buffer_length);
            if (dma->second_buffer == NULL) {
                return AUDIO_DMA_MEMORY_ERROR;
            }
        }
        dma->signed_to_unsigned = !output_signed && samples_signed;
        dma->unsigned_to_signed = output_signed && !samples_signed;
    }

    if (output_resolution > 8) {
        dma->output_size = 2;
    } else {
        dma->output_size = 1;
    }
    // Transfer both channels at once.
    if (!single_channel && audiosample_channel_count(sample) == 2) {
        dma->output_size *= 2;
    }

    enum dma_channel_transfer_size dma_size = DMA_SIZE_8;
    if (dma->output_size == 2) {
        dma_size = DMA_SIZE_16;
    } else if (dma->output_size == 4) {
        dma_size = DMA_SIZE_32;
    }

    for (size_t i = 0; i < 2; i++) {
        dma_channel_config c = dma_channel_get_default_config(dma->channel[i]);
        channel_config_set_transfer_data_size(&c, dma_size);
        channel_config_set_dreq(&c, dma_trigger_source);
        channel_config_set_read_increment(&c, true);
        channel_config_set_write_increment(&c, false);
        // Chain to the other channel by default.
        channel_config_set_chain_to(&c, dma->channel[(i + 1) % 2]);
        dma_channel_set_config(dma->channel[i], &c, false /* trigger */);
        dma_channel_set_write_addr(dma->channel[i], (void *)output_register_address, false /* trigger */);
    }

    // We keep the audio_dma_t for internal use and the sample as a root pointer because it
    // contains the audiodma structure.
    MP_STATE_PORT(playing_audio)[dma->channel[0]] = dma;
    MP_STATE_PORT(playing_audio)[dma->channel[1]] = dma;

    // Load the first two blocks up front.
    audio_dma_load_next_block(dma);
    if (!single_buffer) {
        audio_dma_load_next_block(dma);
    }

    // Special case the DMA for a single buffer. It's commonly used for a single wave length of sound
    // and may be short. Therefore, we use DMA chaining to loop quickly without involving interrupts.
    // On the RP2040 we chain by having a second DMA writing to the config registers of the first.
    // Read and write addresses change with DMA so we need to reset the read address back to the
    // start of the sample.
    if (single_buffer) {
        dma_channel_config c = dma_channel_get_default_config(dma->channel[1]);
        channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
        channel_config_set_dreq(&c, 0x3f); // dma as fast as possible
        channel_config_set_read_increment(&c, false);
        channel_config_set_write_increment(&c, false);
        channel_config_set_chain_to(&c, dma->channel[1]); // Chain to ourselves so we stop.
        dma_channel_configure(dma->channel[1], &c,
            &dma_hw->ch[dma->channel[0]].al3_read_addr_trig, // write address
            &dma->first_buffer, // read address
            1, // transaction count
            false); // trigger
    } else {
        // Enable our DMA channels on DMA0 to the CPU. This will wake us up when
        // we're WFI.
        dma_hw->inte0 |= (1 << dma->channel[0]) | (1 << dma->channel[1]);
        irq_set_mask_enabled(1 << DMA_IRQ_0, true);
    }

    dma_channel_start(dma->channel[0]);

    return AUDIO_DMA_OK;
}

void audio_dma_stop(audio_dma_t *dma) {
    // Disable our interrupts.
    dma_hw->inte0 &= ~((1 << dma->channel[0]) | (1 << dma->channel[1]));
    irq_set_mask_enabled(1 << DMA_IRQ_0, false);

    // Run any remaining audio tasks because we remove ourselves from
    // playing_audio.
    RUN_BACKGROUND_TASKS;

    for (size_t i = 0; i < 2; i++) {
        size_t channel = dma->channel[i];

        dma_channel_config c = dma_channel_get_default_config(dma->channel[i]);
        channel_config_set_enable(&c, false);
        dma_channel_set_config(channel, &c, false /* trigger */);

        if (dma_channel_is_busy(channel)) {
            dma_channel_abort(channel);
        }
        dma_channel_set_read_addr(channel, NULL, false /* trigger */);
        dma_channel_set_write_addr(channel, NULL, false /* trigger */);
        dma_channel_set_trans_count(channel, 0, false /* trigger */);
        dma_channel_unclaim(channel);
        MP_STATE_PORT(playing_audio)[channel] = NULL;
        dma->channel[i] = NUM_DMA_CHANNELS;
    }

    // Hold onto our buffers.
}

// To pause we simply stop the DMA. It is the responsibility of the output peripheral
// to hold the previous value.
void audio_dma_pause(audio_dma_t *dma) {
    dma_hw->ch[dma->channel[0]].al1_ctrl &= ~DMA_CH0_CTRL_TRIG_EN_BITS;
    dma_hw->ch[dma->channel[1]].al1_ctrl &= ~DMA_CH0_CTRL_TRIG_EN_BITS;
}

void audio_dma_resume(audio_dma_t *dma) {
    // Always re-enable the non-busy channel first so it's ready to continue when the busy channel
    // finishes and chains to it. (An interrupt could make the time between enables long.)
    size_t first = 0;
    size_t second = 1;
    if (dma_channel_is_busy(dma->channel[0])) {
        first = 1;
        second = 0;
    }
    dma_hw->ch[dma->channel[first]].al1_ctrl |= DMA_CH0_CTRL_TRIG_EN_BITS;
    dma_hw->ch[dma->channel[second]].al1_ctrl |= DMA_CH0_CTRL_TRIG_EN_BITS;
}

bool audio_dma_get_paused(audio_dma_t *dma) {
    if (dma->channel[0] >= AUDIO_DMA_CHANNEL_COUNT) {
        return false;
    }
    uint32_t control = dma_hw->ch[dma->channel[0]].ctrl_trig;

    return (control & DMA_CH0_CTRL_TRIG_EN_BITS) == 0;
}

void audio_dma_init(audio_dma_t *dma) {
    dma->first_buffer = NULL;
    dma->second_buffer = NULL;
}

void audio_dma_deinit(audio_dma_t *dma) {
    m_free(dma->first_buffer);
    dma->first_buffer = NULL;

    m_free(dma->second_buffer);
    dma->second_buffer = NULL;
}

bool audio_dma_get_playing(audio_dma_t *dma) {
    if (dma->channel[0] == NUM_DMA_CHANNELS) {
        return false;
    }
    if (!dma_channel_is_busy(dma->channel[0]) &&
        !dma_channel_is_busy(dma->channel[1])) {
        audio_dma_stop(dma);
        return false;
    }

    return true;
}

// WARN(tannewt): DO NOT print from here, or anything it calls. Printing calls
// background tasks such as this and causes a stack overflow.
STATIC void dma_callback_fun(void *arg) {
    audio_dma_t *dma = arg;
    if (dma == NULL) {
        return;
    }

    audio_dma_load_next_block(dma);
}

void isr_dma_0(void) {
    for (size_t i = 0; i < NUM_DMA_CHANNELS; i++) {
        uint32_t mask = 1 << i;
        if ((dma_hw->intr & mask) != 0 && MP_STATE_PORT(playing_audio)[i] != NULL) {
            audio_dma_t *dma = MP_STATE_PORT(playing_audio)[i];
            background_callback_add(&dma->callback, dma_callback_fun, (void *)dma);
            dma_hw->ints0 = mask;
        }
    }
}

#endif
