/*
 * 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 "radio.hpp"

#include "rf_path.hpp"

#include "rffc507x.hpp"
#include "max2837.hpp"
#include "max2839.hpp"
#include "max5864.hpp"
#include "baseband_cpld.hpp"

#include "tuning.hpp"

#include "spi_arbiter.hpp"

#include "hackrf_hal.hpp"
#include "hackrf_gpio.hpp"
using namespace hackrf::one;

#include "cpld_update.hpp"

#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"

/* Direct access to the radio. Setting values incorrectly can damage
 * the device. Applications should use ReceiverModel or TransmitterModel
 * instead of calling these functions directly. */
namespace radio {

static constexpr uint32_t ssp1_cpsr = 2;

static constexpr uint32_t ssp_scr(
    const float pclk_f,
    const uint32_t cpsr,
    const float spi_f) {
    return static_cast<uint8_t>(pclk_f / cpsr / spi_f - 1);
}

static constexpr SPIConfig ssp_config_max283x = {
    .end_cb = NULL,
    .ssport = gpio_max283x_select.port(),
    .sspad = gpio_max283x_select.pad(),
    .cr0 =
        CR0_CLOCKRATE(ssp_scr(ssp1_pclk_f, ssp1_cpsr, max283x_spi_f)) | CR0_FRFSPI | CR0_DSS16BIT,
    .cpsr = ssp1_cpsr,
};

static constexpr SPIConfig ssp_config_max5864 = {
    .end_cb = NULL,
    .ssport = gpio_max5864_select.port(),
    .sspad = gpio_max5864_select.pad(),
    .cr0 =
        CR0_CLOCKRATE(ssp_scr(ssp1_pclk_f, ssp1_cpsr, max5864_spi_f)) | CR0_FRFSPI | CR0_DSS8BIT,
    .cpsr = ssp1_cpsr,
};

static spi::arbiter::Arbiter ssp1_arbiter(portapack::ssp1);

static spi::arbiter::Target ssp1_target_max283x{
    ssp1_arbiter,
    ssp_config_max283x};

static spi::arbiter::Target ssp1_target_max5864{
    ssp1_arbiter,
    ssp_config_max5864};

static rf::path::Path rf_path;
rffc507x::RFFC507x first_if;
max283x::MAX283x* second_if;
max2837::MAX2837 second_if_max2837{ssp1_target_max283x};
max2839::MAX2839 second_if_max2839{ssp1_target_max283x};
static max5864::MAX5864 baseband_codec{ssp1_target_max5864};
static baseband::CPLD baseband_cpld;

static rf::Direction direction{rf::Direction::Receive};
static bool baseband_invert = false;
static bool mixer_invert = false;

void init() {
    if (hackrf_r9) {
        gpio_r9_not_ant_pwr.write(1);
        gpio_r9_not_ant_pwr.output();
    }
    rf_path.init();
    first_if.init();
    second_if = hackrf_r9
                    ? (max283x::MAX283x*)&second_if_max2839
                    : (max283x::MAX283x*)&second_if_max2837;
    second_if->init();
    baseband_codec.init();
    baseband_cpld.init();
}

void set_direction(const rf::Direction new_direction) {
    /* TODO: Refactor all the various "Direction" enumerations into one. */
    /* TODO: Only make changes if direction changes, but beware of clock enabling. */

    // Hack to fix the CPLD (clocking ?) bug: toggle CPLD SRAM overlay depending on new direction
    // Use CPLD's EEPROM config when transmitting
    // Use the SRAM overlay when receiving

    // teixeluis: undone "Hack to fix the CPLD (clocking ?) bug".
    // Apparently with current CPLD code from the hackrf repo,
    // toggling CPLD overlay should no longer be necessary:
    if (direction != new_direction && new_direction == rf::Direction::Transmit) {
        hackrf::cpld::init_from_eeprom();
    }

    direction = new_direction;

    if (hackrf_r9) {
        /*
         * HackRF One r9 inverts analog baseband only for RX. Previous hardware
         * revisions inverted analog baseband for neither direction because of
         * compensation in the CPLD. If we ever simplify the CPLD to handle RX
         * and TX the same way, we will need to update this baseband_invert
         * logic.
         */
        baseband_invert = (direction == rf::Direction::Receive);
    } else {
        /*
         * Analog baseband is inverted in RX but not TX. The RX inversion is
         * corrected by the CPLD, but future hardware or CPLD changes may
         * change this for either or both directions. For a given hardware+CPLD
         * platform, baseband inversion is set here for RX and/or TX. Spectrum
         * inversion resulting from the mixer is tracked separately according
         * to the tuning configuration. We ask the CPLD to apply a correction
         * for the total inversion.
         */
        baseband_invert = false;
    }
    baseband_cpld.set_invert(mixer_invert ^ baseband_invert);

    second_if->set_mode((direction == rf::Direction::Transmit) ? max283x::Mode::Transmit : max283x::Mode::Receive);
    rf_path.set_direction(direction);

    baseband_codec.set_mode((direction == rf::Direction::Transmit) ? max5864::Mode::Transmit : max5864::Mode::Receive);

    if (direction == rf::Direction::Receive)
        led_rx.on();
    else
        led_tx.on();
}

bool set_tuning_frequency(const rf::Frequency frequency) {
    rf::Frequency final_frequency = frequency;
    // if converter feature is enabled
    if (portapack::persistent_memory::config_converter()) {
        // downconvert
        if (portapack::persistent_memory::config_updown_converter()) {
            final_frequency = frequency - portapack::persistent_memory::config_converter_freq();
        } else  // upconvert
        {
            final_frequency = frequency + portapack::persistent_memory::config_converter_freq();
        }
    }
    // apply frequency correction
    if (direction == rf::Direction::Transmit) {
        if (portapack::persistent_memory::config_freq_tx_correction_updown())  // tx freq correction down
            final_frequency = final_frequency - portapack::persistent_memory::config_freq_tx_correction();
        else  // tx freq correction up
            final_frequency = final_frequency + portapack::persistent_memory::config_freq_tx_correction();
    } else {
        if (portapack::persistent_memory::config_freq_rx_correction_updown())  // rx freq correction down
            final_frequency = final_frequency - portapack::persistent_memory::config_freq_rx_correction();
        else  // rx freq correction up
            final_frequency = final_frequency + portapack::persistent_memory::config_freq_rx_correction();
    }

    const auto tuning_config = tuning::config::create(final_frequency);
    if (tuning_config.is_valid()) {
        first_if.disable();

        if (tuning_config.first_lo_frequency) {
            first_if.set_frequency(tuning_config.first_lo_frequency);
            first_if.enable();
        }

        const auto result_second_if = second_if->set_frequency(tuning_config.second_lo_frequency);

        rf_path.set_band(tuning_config.rf_path_band);
        mixer_invert = tuning_config.mixer_invert;
        baseband_cpld.set_invert(mixer_invert ^ baseband_invert);

        return result_second_if;
    } else {
        return false;
    }
}

void set_rf_amp(const bool rf_amp) {
    rf_path.set_rf_amp(rf_amp);

    if (direction == rf::Direction::Transmit) {
        if (rf_amp)
            led_tx.on();
        else
            led_tx.off();
    }
}

void set_lna_gain(const int_fast8_t db) {
    second_if->set_lna_gain(db);
}

void set_vga_gain(const int_fast8_t db) {
    second_if->set_vga_gain(db);
}

void set_tx_gain(const int_fast8_t db) {
    second_if->set_tx_vga_gain(db);
}

void set_baseband_filter_bandwidth(const uint32_t bandwidth_minimum) {
    second_if->set_lpf_rf_bandwidth(bandwidth_minimum);
}

void set_baseband_rate(const uint32_t rate) {
    portapack::clock_manager.set_sampling_frequency(rate);
}

void set_antenna_bias(const bool on) {
    /* Pull MOSFET gate low to turn on antenna bias. */
    if (hackrf_r9) {
        gpio_r9_not_ant_pwr.write(on ? 0 : 1);
    } else {
        first_if.set_gpo1(on ? 0 : 1);
    }
}

/*void enable(Configuration configuration) {
    configure(configuration);
}

void configure(Configuration configuration) {
    set_tuning_frequency(configuration.tuning_frequency);
    set_rf_amp(configuration.rf_amp);
    set_lna_gain(configuration.lna_gain);
    set_vga_gain(configuration.vga_gain);
    set_baseband_rate(configuration.baseband_rate);
    set_baseband_filter_bandwidth(configuration.baseband_filter_bandwidth);
    set_direction(configuration.direction);
}*/

void disable() {
    set_antenna_bias(false);
    baseband_codec.set_mode(max5864::Mode::Shutdown);
    second_if->set_mode(max2837::Mode::Standby);
    first_if.disable();
    set_rf_amp(false);

    led_rx.off();
    led_tx.off();
}

namespace debug {

namespace first_if {

uint32_t register_read(const size_t register_number) {
    return radio::first_if.read(register_number);
}

} /* namespace first_if */

namespace second_if {

uint32_t register_read(const size_t register_number) {
    return radio::second_if->read(register_number);
}

uint8_t temp_sense() {
    return radio::second_if->temp_sense() & 0x1f;
}

} /* namespace second_if */

} /* namespace debug */

} /* namespace radio */