/*
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 * Copyright (C) 2016 Furrtek
 * Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
 * Copyright (C) 2023 Kyle Reed
 *
 * 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.
 */

#ifndef __FREQMAN_DB_H__
#define __FREQMAN_DB_H__

#include "file.hpp"
#include "file_wrapper.hpp"
#include "utility.hpp"

#include <array>
#include <memory>
#include <string>
#include <string_view>
#include <vector>

/* Defined in freqman_db.cpp */
extern const std::filesystem::path freqman_extension;

using freqman_index_t = uint8_t;
constexpr freqman_index_t freqman_invalid_index = static_cast<freqman_index_t>(-1);

/* Returns true if the value is not invalid_index. */
constexpr bool is_valid(freqman_index_t index) {
    return index != freqman_invalid_index;
}

/* Returns true if the value is invalid_index. */
constexpr bool is_invalid(freqman_index_t index) {
    return index == freqman_invalid_index;
}

enum class freqman_type : uint8_t {
    Single,    // f=
    Range,     // a=,b=
    HamRadio,  // r=,t=
    Repeater,  // l=,t=
    Raw,       // line content in description
    Unknown,
};

/* Tables for next round of changes.
 * // TODO: attempt to consolidate all of these values
 * // and settings into a single location.
 * // Should tone_keys get consolidated here too?
 * enum class freqman_modulation : uint8_t {
    AM,
    NFM,
    WFM,
    SPEC,
    Unknown,
 * };
 *
 * struct freqman_modulation_info {
 *     freqman_modulation modulation;
 *     std::string_view name;
 * };
 *
 * constexpr std::array<freqman_modulation_info, 5> freqman_modulations = {
    freqman_modulation_info{ freqman_modulation::AM, "AM" },
    freqman_modulation_info{ freqman_modulation::NFM, "NFM" },
    freqman_modulation_info{ freqman_modulation::WFM, "WFM" },
    freqman_modulation_info{ freqman_modulation::SPEC, "SPEC" },
    freqman_modulation_info{ freqman_modulation::Unknown, "Unknown" }
 * };
 * static_assert(std::size(freqman_modulations) == (size_t)freqman_modulation::Unknown + 1);
 *
 * enum class freqman_step : uint8_t {
    _100Hz,
    _1kHz,
    _5kHz,
    _6_25kHz,
    _8_33kHz,
    _9kHz,
    _10kHz,
    _12_5kHz,
    _15kHz,
    _25kHz,
    _30kHz,
    _50kHz,
    _100kHz,
    _250kHz,
    _500kHz,
    _1MHz,
    Unknown,
 * };
 *
 * struct freqman_step_info {
 *     freqman_step step;
 *     std::string_view name;
 *     std::string_view display_name;
 *     uint32_t value;
 * };
 *
 * // TODO: FrequencyStepView should use this list.
 * constexpr std::array<freqman_step_info, 18> freqman_steps = {
    freqman_step_info{ freqman_step::_100Hz,   "0.1kHz",  "0.1kHz      ", 100 },
    freqman_step_info{ freqman_step::_1kHz,    "1kHz",    "1kHz        ", 1'000 },
    freqman_step_info{ freqman_step::_5kHz,    "5kHz",    "5kHz (SA AM)", 5'000 },
    freqman_step_info{ freqman_step::_6_25kHz, "6.25kHz", "6.25kHz(NFM)", 6'250 },
    freqman_step_info{ freqman_step::_8_33kHz, "8.33kHz", "8.33kHz(AIR)", 8'330 },
    freqman_step_info{ freqman_step::_9kHz,    "9kHz",    "9kHz (EU AM)", 9'000 },
    freqman_step_info{ freqman_step::_10kHz,   "10kHz",   "10kHz(US AM)", 10'000 },
    freqman_step_info{ freqman_step::_12_5kHz, "12.5kHz", "12.5kHz(NFM)", 12'500 },
    freqman_step_info{ freqman_step::_15kHz,   "15kHz",   "15kHz  (HFM)", 15'000 },
    freqman_step_info{ freqman_step::_25kHz,   "25kHz",   "25kHz   (N1)", 25'000 },
    freqman_step_info{ freqman_step::_30kHz,   "30kHz",   "30kHz (OIRT)", 30'000 },
    freqman_step_info{ freqman_step::_50kHz,   "50kHz",   "50kHz  (FM1)", 50'000 },
    freqman_step_info{ freqman_step::_100kHz,  "100kHz",  "100kHz (FM2)", 100'000 },
    freqman_step_info{ freqman_step::_250kHz,  "250kHz",  "250kHz  (N2)", 250'000 },
    freqman_step_info{ freqman_step::_500kHz,  "500kHz",  "500kHz (WFM)", 500'000 },
    freqman_step_info{ freqman_step::_1MHz,    "1MHz",    "1MHz        ", 1'000'000 },
    freqman_step_info{ freqman_step::Unknown,  "Unknown", "Unknown     ", 0 },
 * };
 * static_assert(std::size(freqman_steps) == (size_t)freqman_step::Unknown + 1);
*/

/* Freqman Entry *******************************/
struct freqman_entry {
    int64_t frequency_a{0};     // 'f=freq' or 'a=freq_start' or 'r=recv_freq'
    int64_t frequency_b{0};     // 'b=freq_end' or 't=tx_freq'
    std::string description{};  // 'd=desc'
    freqman_type type{freqman_type::Unknown};
    freqman_index_t modulation{freqman_invalid_index};
    freqman_index_t bandwidth{freqman_invalid_index};
    freqman_index_t step{freqman_invalid_index};
    freqman_index_t tone{freqman_invalid_index};
};

bool operator==(const freqman_entry& lhs, const freqman_entry& rhs);

// TODO: These shouldn't be exported.
std::string freqman_entry_get_modulation_string(freqman_index_t modulation);
std::string freqman_entry_get_bandwidth_string(freqman_index_t modulation, freqman_index_t bandwidth);
std::string freqman_entry_get_step_string(freqman_index_t step);
std::string freqman_entry_get_step_string_short(freqman_index_t step);

/* A reasonable maximum number of items to load from a freqman file.
 * Apps using freqman_db should be tested and this value tuned to
 * ensure app memory stability. */
constexpr size_t freqman_default_max_entries = 150;

/* Limiting description to 30 as specified by the format */
constexpr size_t freqman_max_desc_size = 30;

struct freqman_load_options {
    /* Loads all entries when set to 0. */
    size_t max_entries{freqman_default_max_entries};
    bool load_freqs{true};
    bool load_ranges{true};
    bool load_hamradios{true};
    bool load_repeaters{true};
};

using freqman_entry_ptr = std::unique_ptr<freqman_entry>;
using freqman_db = std::vector<freqman_entry_ptr>;

/* Gets the full path for a given file stem (no extension). */
const std::filesystem::path get_freqman_path(const std::string& stem);
bool create_freqman_file(const std::string& file_stem);
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options);
void delete_freqman_file(const std::string& file_stem);

/* Gets a pretty string representation for an entry. */
std::string pretty_string(const freqman_entry& item, size_t max_length = 30);

/* Gets the freqman file representation for an entry. */
std::string to_freqman_string(const freqman_entry& entry);

bool parse_freqman_entry(std::string_view str, freqman_entry& entry);
bool parse_freqman_file(const std::filesystem::path& path, freqman_db& db, freqman_load_options options);

/* Returns true if the entry is well-formed. */
bool is_valid(const freqman_entry& entry);

/* API wrapper over a Freqman file. Provides CRUD operations
 * for freqman_entry instances that are read/written directly
 * to the underlying file. */
class FreqmanDB {
   public:
    using Index = FileWrapper::Line;

    /* NB: This iterator is very basic: forward only, read-only. */
    class iterator {
       public:
        iterator(FreqmanDB& db, Index index)
            : db_{db}, index_{index} {}
        iterator& operator++() {
            index_++;

            if (index_ >= db_.entry_count())
                index_ = end_index;

            return *this;
        }
        freqman_entry operator*() const {
            return db_[index_];
        }

        bool operator==(const iterator& other) {
            return &db_ == &other.db_ && index_ == other.index_;
        }

        bool operator!=(const iterator& other) {
            return !(*this == other);
        }

        Index index() const {
            return index_;
        }

        /* Value indicating the 'end' iterator. */
        static constexpr Index end_index = (Index)-1;

       private:
        FreqmanDB& db_;
        Index index_;
    };

    bool open(const std::filesystem::path& path, bool create = false);
    void close();

    freqman_entry operator[](Index index) const;
    void insert_entry(Index index, const freqman_entry& entry);
    void append_entry(const freqman_entry& entry);
    void replace_entry(Index index, const freqman_entry& entry);
    void delete_entry(Index index);
    bool delete_entry(const freqman_entry& entry);

    template <typename Fn>
    iterator find_entry(const Fn& predicate) {
        // TODO: use std::find, but need to make the iterator compliant.
        auto it = begin();
        const auto it_end = end();

        while (it != it_end) {
            if (predicate(*it))
                return it;
            ++it;
        }

        return it_end;
    }
    iterator find_entry(const freqman_entry& entry);

    uint32_t entry_count() const;
    bool empty() const;

    /* When true, Raw entries are returned instead of Unknown. */
    void set_read_raw(bool v) { read_raw_ = v; }
    iterator begin() {
        return {*this, 0};
    }
    iterator end() {
        return {*this, iterator::end_index};
    }

   private:
    std::unique_ptr<FileWrapper> wrapper_{};
    bool read_raw_{true};
};

#endif /* __FREQMAN_DB_H__ */