/*
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 *
 * This file is part of PortaPack.
 *
 * 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 2, 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; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#include "rssi_dma.hpp"

#include <cstdint>
#include <cstddef>
#include <array>

#include "hal.h"
#include "gpdma.hpp"

using namespace lpc43xx;

#include "portapack_dma.hpp"
#include "portapack_adc.hpp"

#include "thread_wait.hpp"

namespace rf {
namespace rssi {
namespace dma {

/* TODO: SO MUCH REPEATED CODE IN touch_dma.cpp!!! */

static constexpr auto& gpdma_channel = gpdma::channels[portapack::adc1_gpdma_channel_number];

constexpr uint32_t gpdma_ahb_master_peripheral = 1;
constexpr uint32_t gpdma_ahb_master_memory = 0;
constexpr uint32_t gpdma_ahb_master_lli_fetch = 0;

constexpr uint32_t gpdma_peripheral = 0xe;
constexpr uint32_t gpdma_src_peripheral = gpdma_peripheral;
constexpr uint32_t gpdma_dest_peripheral = gpdma_peripheral;

constexpr gpdma::channel::LLIPointer lli_pointer(const void* lli) {
    return {
        .lm = gpdma_ahb_master_lli_fetch,
        .r = 0,
        .lli = reinterpret_cast<uint32_t>(lli),
    };
}

constexpr gpdma::channel::Control control(const size_t number_of_transfers) {
    return {
        .transfersize = number_of_transfers,
        .sbsize = 0, /* Burst size: 1 transfer */
        .dbsize = 0, /* Burst size: 1 transfer */
        .swidth = 0, /* Source transfer width: byte (8 bits) */
        .dwidth = 2, /* Destination transfer width: word (32 bits) */
        .s = gpdma_ahb_master_peripheral,
        .d = gpdma_ahb_master_memory,
        .si = 0,
        .di = 1,
        .prot1 = 0,
        .prot2 = 0,
        .prot3 = 0,
        .i = 1,
    };
}

constexpr gpdma::channel::Config config() {
    return {
        .e = 0,
        .srcperipheral = gpdma_src_peripheral,
        .destperipheral = gpdma_dest_peripheral,
        .flowcntrl = gpdma::FlowControl::PeripheralToMemory_DMAControl,
        .ie = 1,
        .itc = 1,
        .l = 0,
        .a = 0,
        .h = 0,
    };
}

struct buffers_config_t {
    size_t count;
    size_t items_per_buffer;
};

static buffers_config_t buffers_config;

static sample_t* samples{nullptr};
static gpdma::channel::LLI* lli{nullptr};

static ThreadWait thread_wait;

static void transfer_complete() {
    const auto next_lli_index = gpdma_channel.next_lli() - &lli[0];
    thread_wait.wake_from_interrupt(next_lli_index);
}

static void dma_error() {
    thread_wait.wake_from_interrupt(-1);
    disable();
}

void init() {
    gpdma_channel.set_handlers(transfer_complete, dma_error);

    // LPC_GPDMA->SYNC |= (1 << gpdma_peripheral);
}

void allocate(size_t buffer_count, size_t items_per_buffer) {
    buffers_config = {
        .count = buffer_count,
        .items_per_buffer = items_per_buffer,
    };

    const auto peripheral = reinterpret_cast<uint32_t>(&LPC_ADC1->DR[portapack::adc1_rssi_input]) + 1;
    const auto control_value = control(gpdma::buffer_words(buffers_config.items_per_buffer, 1));

    samples = new sample_t[buffers_config.count * buffers_config.items_per_buffer];
    lli = new gpdma::channel::LLI[buffers_config.count];

    for (size_t i = 0; i < buffers_config.count; i++) {
        const auto memory = reinterpret_cast<uint32_t>(&samples[i * buffers_config.items_per_buffer]);
        lli[i].srcaddr = peripheral;
        lli[i].destaddr = memory;
        lli[i].lli = lli_pointer(&lli[(i + 1) % buffers_config.count]);
        lli[i].control = control_value;
    }
}

void free() {
    delete samples;
    delete lli;
}

void enable() {
    const auto gpdma_config = config();
    gpdma_channel.configure(lli[0], gpdma_config);
    gpdma_channel.enable();
}

bool is_enabled() {
    return gpdma_channel.is_enabled();
}

void disable() {
    gpdma_channel.disable();
}

rf::rssi::buffer_t wait_for_buffer() {
    const auto next_index = thread_wait.sleep();

    if (next_index >= 0) {
        const size_t free_index = (next_index + buffers_config.count - 2) % buffers_config.count;
        return {reinterpret_cast<sample_t*>(lli[free_index].destaddr), buffers_config.items_per_buffer};
    } else {
        // TODO: Should I return here, or loop if RDY_RESET?
        return {nullptr, 0};
    }
}

} /* namespace dma */
} /* namespace rssi */
} /* namespace rf */