/*
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 * Copyright (C) 2018 Furrtek
 * Copyright (C) 2023 gullradriel, Nilorea Studio 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 "ui_recon.hpp"
#include "ui_freqman.hpp"
#include "capture_app.hpp"
#include "convert.hpp"
#include "file.hpp"
#include "file_reader.hpp"
#include "tone_key.hpp"
#include "replay_app.hpp"
#include "string_format.hpp"
#include "ui_fileman.hpp"
#include "io_file.hpp"
#include "io_convert.hpp"
#include "oversample.hpp"
#include "baseband_api.hpp"
#include "metadata_file.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include "utility.hpp"
#include "replay_thread.hpp"

using namespace portapack;
using namespace tonekey;
using portapack::memory::map::backup_ram;
namespace fs = std::filesystem;

namespace ui {

void ReconView::reload_restart_recon() {
    frequency_file_load();
    if (frequency_list.size() > 0) {
        if (fwd) {
            button_dir.set_text("FW>");
        } else {
            button_dir.set_text("<RW");
        }
        recon_resume();
    }
    if (scanner_mode) {
        file_name.set_style(&Styles::red);
        button_scanner_mode.set_style(&Styles::red);
        button_scanner_mode.set_text("SCAN");
    } else {
        file_name.set_style(&Styles::blue);
        button_scanner_mode.set_style(&Styles::blue);
        button_scanner_mode.set_text("RECON");
    }
    if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
        file_name.set_style(&Styles::yellow);
    }
}

void ReconView::check_update_ranges_from_current() {
    if (frequency_list.size() && current_is_valid() && current_entry().type == freqman_type::Range) {
        if (update_ranges && !manual_mode) {
            button_manual_start.set_text(to_string_short_freq(current_entry().frequency_a));
            frequency_range.min = current_entry().frequency_a;
            if (current_entry().frequency_b != 0) {
                button_manual_end.set_text(to_string_short_freq(current_entry().frequency_b));
                frequency_range.max = current_entry().frequency_b;
            } else {
                button_manual_end.set_text(to_string_short_freq(current_entry().frequency_a));
                frequency_range.max = current_entry().frequency_a;
            }
        }
    }
}

bool ReconView::current_is_valid() {
    return (unsigned)current_index < frequency_list.size();
}

freqman_entry& ReconView::current_entry() {
    return *frequency_list[current_index];
}

void ReconView::set_loop_config(bool v) {
    continuous = v;
    button_loop_config.set_style(v ? &Styles::green : &Styles::white);
    persistent_memory::set_recon_continuous(continuous);
}

void ReconView::recon_stop_recording(bool exiting) {
    if (is_recording) {
        if (field_mode.selected_index_value() == SPEC_MODULATION)
            button_audio_app.set_text("RAW");
        else
            button_audio_app.set_text("AUDIO");
        button_audio_app.set_style(&Styles::white);
        record_view->stop();
        button_config.set_style(&Styles::white);
        is_recording = false;
        // repeater mode
        if (!exiting && persistent_memory::recon_repeat_recorded()) {
            start_repeat();
        }
    }
}

void ReconView::clear_freqlist_for_ui_action() {
    recon_stop_recording(false);
    if (field_mode.selected_index_value() != SPEC_MODULATION)
        audio::output::stop();
    // flag to detect and reload frequency_list
    if (!manual_mode) {
        // Clear doesn't actually free, re-assign so destructor runs on previous instance.
        frequency_list = freqman_db{};
    } else
        frequency_list.shrink_to_fit();
    freqlist_cleared_for_ui_action = true;
}

void ReconView::reset_indexes() {
    last_entry.modulation = freqman_invalid_index;
    last_entry.bandwidth = freqman_invalid_index;
    last_entry.step = freqman_invalid_index;
    current_index = 0;
}

void ReconView::update_description() {
    if (frequency_list.empty() || current_entry().description.empty()) {
        description = "...no description...";
    } else {
        switch (current_entry().type) {
            case freqman_type::Range:
                description = "R: ";
                break;
            case freqman_type::HamRadio:
                description = "H: ";
                break;
            case freqman_type::Repeater:
                description = "L: ";
                break;
            default:
                description = "S: ";
        }
        description += current_entry().description;
    }
    desc_cycle.set(description);
}

void ReconView::colorize_waits() {
    // colorize wait on match
    if (wait == 0) {
        field_wait.set_style(&Styles::blue);
    } else if (wait >= 500) {
        field_wait.set_style(&Styles::white);
    } else if (wait > -500 && wait < 500) {
        field_wait.set_style(&Styles::red);
    } else if (wait <= -500) {
        field_wait.set_style(&Styles::green);
    }
    // colorize lock time if in SPARSE mode as in continuous the lock_wait time is disarmed at first lock count
    if (recon_match_mode == RECON_MATCH_SPARSE) {
        if ((recon_lock_duration / STATS_UPDATE_INTERVAL) <= recon_lock_nb_match) {
            field_lock_wait.set_style(&Styles::yellow);
        } else {
            field_lock_wait.set_style(&Styles::white);
        }
    } else {
        field_lock_wait.set_style(&Styles::white);
    }
}

bool ReconView::recon_save_freq(const fs::path& path, size_t freq_index, bool warn_if_exists) {
    if (frequency_list.size() == 0 || !current_is_valid())
        return false;

    FreqmanDB freq_db;
    if (!freq_db.open(path, /*create*/ true))
        return false;

    freqman_entry entry = *frequency_list[freq_index];  // Makes a copy.

    // For ranges, save the current frequency instead.
    if (entry.type == freqman_type::Range) {
        entry.frequency_a = freq;
        entry.frequency_b = 0;
        entry.type = freqman_type::Single;
        entry.modulation = last_entry.modulation;
        entry.bandwidth = last_entry.bandwidth;
        entry.step = freqman_invalid_index;
    }

    auto it = freq_db.find_entry(entry);
    auto found = (it != freq_db.end());

    if (found && warn_if_exists)
        nav_.display_modal("Error", "Frequency already exists");

    if (!found)
        freq_db.append_entry(entry);

    return true;
}

void ReconView::load_persisted_settings() {
    autostart = persistent_memory::recon_autostart_recon();
    autosave = persistent_memory::recon_autosave_freqs();
    continuous = persistent_memory::recon_continuous();
    filedelete = persistent_memory::recon_clear_output();
    load_freqs = persistent_memory::recon_load_freqs();
    load_ranges = persistent_memory::recon_load_ranges();
    load_hamradios = persistent_memory::recon_load_hamradios();
    load_repeaters = persistent_memory::recon_load_repeaters();
    update_ranges = persistent_memory::recon_update_ranges_when_recon();
    auto_record_locked = persistent_memory::recon_auto_record_locked();
}

void ReconView::audio_output_start() {
    if (field_mode.selected_index_value() != SPEC_MODULATION)
        audio::output::start();
    receiver_model.set_headphone_volume(receiver_model.headphone_volume());  // WM8731 hack.
}

void ReconView::recon_redraw() {
    if (last_rssi_min != rssi.get_min() || last_rssi_med != rssi.get_avg() || last_rssi_max != rssi.get_max()) {
        last_rssi_min = rssi.get_min();
        last_rssi_med = rssi.get_avg();
        last_rssi_max = rssi.get_max();
        freq_stats.set("RSSI: " + to_string_dec_int(rssi.get_min()) + "/" + to_string_dec_int(rssi.get_avg()) + "/" + to_string_dec_int(rssi.get_max()) + " db");
    }
    if (last_entry.frequency_a != freq) {
        last_entry.frequency_a = freq;
        big_display.set("FREQ:" + to_string_short_freq(freq) + " MHz");
    }
    if (last_nb_match != recon_lock_nb_match || last_freq_lock != freq_lock) {
        last_freq_lock = freq_lock;
        last_nb_match = recon_lock_nb_match;
        text_nb_locks.set(to_string_dec_uint(freq_lock) + "/" + to_string_dec_uint(recon_lock_nb_match));
        if (freq_lock == 0) {
            // NO FREQ LOCK, ONGOING STANDARD SCANNING
            big_display.set_style(&Styles::white);
            if (recon)
                button_pause.set_text("<PAUSE>");
            else
                button_pause.set_text("<RESUME>");
        } else if (freq_lock == 1 && recon_lock_nb_match != 1) {
            // STARTING LOCK FREQ
            big_display.set_style(&Styles::yellow);
            button_pause.set_text("<SKPLCK>");
        } else if (freq_lock >= recon_lock_nb_match) {
            big_display.set_style(&Styles::green);
            button_pause.set_text("<UNLOCK>");
        }
    }
    if (last_db != db || last_list_size != frequency_list.size()) {
        last_list_size = frequency_list.size();
        last_db = db;
        text_max.set("/" + to_string_dec_uint(frequency_list.size()) + " " + to_string_dec_int(db) + " db");
    }
}

void ReconView::handle_retune() {
    if (last_freq != freq) {
        last_freq = freq;
        receiver_model.set_target_frequency(freq);  // Retune
    }
    if (frequency_list.size() > 0) {
        if (last_entry.modulation != current_entry().modulation && is_valid(current_entry().modulation)) {
            last_entry.modulation = current_entry().modulation;
            field_mode.set_selected_index(current_entry().modulation);
            last_entry.bandwidth = freqman_invalid_index;
        }
        // Set bandwidth if any
        if (last_entry.bandwidth != current_entry().bandwidth && is_valid(current_entry().bandwidth)) {
            last_entry.bandwidth = current_entry().bandwidth;
            field_bw.set_selected_index(current_entry().bandwidth);
        }
        if (last_entry.step != current_entry().step && is_valid(current_entry().step)) {
            last_entry.step = current_entry().step;
            step = freqman_entry_get_step_value(last_entry.step);
            step_mode.set_selected_index(step);
        }
        if (last_index != current_index) {
            last_index = current_index;
            check_update_ranges_from_current();
            text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
            update_description();
        }
    }
}

void ReconView::focus() {
    button_pause.focus();
}

ReconView::~ReconView() {
    if (recon_tx) {
        replay_thread.reset();
    }

    recon_stop_recording(true);

    if (field_mode.selected_index_value() != SPEC_MODULATION)
        audio::output::stop();

    transmitter_model.disable();
    receiver_model.disable();
    baseband::shutdown();
}

ReconView::ReconView(NavigationView& nav)
    : nav_{nav} {
    chrono_start = chTimeNow();

    tx_view.hidden(true);

    // set record View
    record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
                                               u"AUTO_AUDIO", u"AUDIO",
                                               RecordView::FileType::WAV, 4096, 4);
    record_view->set_filename_date_frequency(true);
    record_view->set_auto_trim(false);
    record_view->hidden(true);
    record_view->on_error = [&nav](std::string message) {
        nav.display_modal("Error", message);
    };

    add_children({&labels,
                  &field_lna,
                  &field_vga,
                  &field_rf_amp,
                  &field_volume,
                  &field_bw,
                  &field_squelch,
                  &field_nblocks,
                  &field_wait,
                  &field_lock_wait,
                  &button_config,
                  &button_scanner_mode,
                  &button_loop_config,
                  &file_name,
                  &rssi,
                  &text_cycle,
                  &text_max,
                  &text_nb_locks,
                  &desc_cycle,
                  &big_display,
                  &freq_stats,
                  &text_timer,
                  &text_ctcss,
                  &button_manual_start,
                  &button_manual_end,
                  &button_manual_recon,
                  &field_mode,
                  &field_recon_match_mode,
                  &step_mode,
                  &button_pause,
                  &button_audio_app,
                  &button_add,
                  &button_dir,
                  &button_restart,
                  &button_mic_app,
                  &button_remove,
                  record_view.get(),
                  &progressbar,
                  &tx_view});

    def_step = 0;
    load_persisted_settings();

    // When update_ranges is set or range invalid, use the rx model frequency instead of the saved values.
    if (update_ranges || frequency_range.max == 0) {
        rf::Frequency stored_freq = receiver_model.target_frequency();
        frequency_range.min = clip<rf::Frequency>(stored_freq - OneMHz, 0, MAX_UFREQ);
        frequency_range.max = clip<rf::Frequency>(stored_freq + OneMHz, 0, MAX_UFREQ);
    }

    button_manual_start.set_text(to_string_short_freq(frequency_range.min));
    button_manual_end.set_text(to_string_short_freq(frequency_range.max));

    button_manual_start.on_select = [this, &nav](ButtonWithEncoder& button) {
        clear_freqlist_for_ui_action();
        auto new_view = nav_.push<FrequencyKeypadView>(frequency_range.min);
        new_view->on_changed = [this, &button](rf::Frequency f) {
            frequency_range.min = f;
            button_manual_start.set_text(to_string_short_freq(f));
        };
    };

    button_manual_end.on_select = [this, &nav](ButtonWithEncoder& button) {
        clear_freqlist_for_ui_action();
        auto new_view = nav.push<FrequencyKeypadView>(frequency_range.max);
        new_view->on_changed = [this, &button](rf::Frequency f) {
            frequency_range.max = f;
            button_manual_end.set_text(to_string_short_freq(f));
        };
    };

    text_cycle.on_select = [this, &nav](ButtonWithEncoder& button) {
        if (frequency_list.size() > 0) {
            auto new_view = nav_.push<FrequencyKeypadView>(current_index);
            new_view->on_changed = [this, &button](rf::Frequency f) {
                // NB: This is using the freq keypad to select an index.
                f = f / OneMHz;
                if (f >= 1 && f <= frequency_list.size()) {
                    index_stepper = f - 1 - current_index;
                    freq_lock = 0;
                }
            };
        }
    };

    button_manual_start.on_change = [this]() {
        auto step_val = freqman_entry_get_step_value(def_step);
        frequency_range.min = frequency_range.min + button_manual_start.get_encoder_delta() * step_val;
        if (frequency_range.min < 0) {
            frequency_range.min = 0;
        }
        if (frequency_range.min > (MAX_UFREQ - step_val)) {
            frequency_range.min = MAX_UFREQ - step_val;
        }
        if (frequency_range.min > (frequency_range.max - step_val)) {
            frequency_range.max = frequency_range.min + step_val;
            if (frequency_range.max > MAX_UFREQ) {
                frequency_range.min = MAX_UFREQ - step_val;
                frequency_range.max = MAX_UFREQ;
            }
        }
        button_manual_start.set_text(to_string_short_freq(frequency_range.min));
        button_manual_end.set_text(to_string_short_freq(frequency_range.max));
        button_manual_start.set_encoder_delta(0);
    };

    button_manual_end.on_change = [this]() {
        auto step_val = freqman_entry_get_step_value(def_step);
        frequency_range.max = frequency_range.max + button_manual_end.get_encoder_delta() * step_val;
        if (frequency_range.max < (step_val + 1)) {
            frequency_range.max = (step_val + 1);
        }
        if (frequency_range.max > MAX_UFREQ) {
            frequency_range.max = MAX_UFREQ;
        }
        if (frequency_range.max < (frequency_range.min + step_val)) {
            frequency_range.min = frequency_range.max - step_val;
            if (frequency_range.max < (step_val + 1)) {
                frequency_range.min = 1;
                frequency_range.max = (step_val + 1);
            }
        }
        button_manual_start.set_text(to_string_short_freq(frequency_range.min));
        button_manual_end.set_text(to_string_short_freq(frequency_range.max));
        button_manual_end.set_encoder_delta(0);
    };

    text_cycle.on_change = [this]() {
        on_index_delta(text_cycle.get_encoder_delta());
        text_cycle.set_encoder_delta(0);
    };

    button_pause.on_select = [this](ButtonWithEncoder&) {
        if (frequency_list.size() > 0) {
            if (freq_lock > 0) {
                if (fwd) {
                    on_stepper_delta(1);
                } else {
                    on_stepper_delta(-1);
                }
                button_pause.set_text("<PAUSE>");  // Show button for non continuous stop
            } else {
                if (!recon) {
                    recon_resume();
                    user_pause = false;
                } else {
                    recon_pause();
                    user_pause = true;
                }
            }
        }
    };

    button_pause.on_change = [this]() {
        on_stepper_delta(button_pause.get_encoder_delta());
        button_pause.set_encoder_delta(0);
    };

    button_audio_app.on_select = [this](Button&) {
        auto settings = receiver_model.settings();
        settings.frequency_step = step_mode.selected_index_value();
        if (field_mode.selected_index_value() == SPEC_MODULATION)
            nav_.replace<CaptureAppView>();
        else
            nav_.replace<AnalogAudioView>(settings);
    };

    button_loop_config.on_select = [this](Button&) {
        set_loop_config(!continuous);
    };
    set_loop_config(continuous);

    rssi.set_focusable(true);
    rssi.set_peak(true, 500);
    rssi.on_select = [this](RSSI&) {
        nav_.replace<LevelView>();
    };

    // TODO: *BUG* Both transmitter_model and receiver_model share the same pmem setting for target_frequency.
    button_mic_app.on_select = [this](Button&) {
        if (frequency_list.size() > 0 && current_index >= 0 && (unsigned)current_index < frequency_list.size()) {
            if (current_entry().type == freqman_type::HamRadio) {
                // if it's a HamRadio entry, then frequency_a is the freq at which the repeater receives, so we have to set it in transmit in mic app
                transmitter_model.set_target_frequency(current_entry().frequency_a);
                // if it's a HamRadio entry, then frequency_b is the freq at which the repeater transmits, so we have to set it in receive in mic app
                receiver_model.set_target_frequency(current_entry().frequency_b);
            } else {
                // it's single or range so we us actual tuned frequency
                transmitter_model.set_target_frequency(freq);
                receiver_model.set_target_frequency(freq);
            }
        }

        // MicTX wants Modulation and Bandwidth overrides, but that's only stored on the RX model.
        nav_.replace<MicTXView>(receiver_model.settings());
    };

    button_remove.on_select = [this](ButtonWithEncoder&) {
        handle_remove_current_item();
        timer = 0;
        freq_lock = 0;
        recon_redraw();
    };

    button_remove.on_change = [this]() {
        on_stepper_delta(button_remove.get_encoder_delta());
        button_remove.set_encoder_delta(0);
    };

    button_manual_recon.on_select = [this](Button&) {
        button_remove.set_text("<DELETE>");
        button_add.hidden(false);
        scanner_mode = false;
        manual_mode = true;
        recon_pause();
        if (!frequency_range.min || !frequency_range.max) {
            nav_.display_modal("Error", "Both START and END freqs\nneed a value");
        } else {
            if (frequency_range.min > frequency_range.max) {
                std::swap(frequency_range.min, frequency_range.max);
                button_manual_start.set_text(to_string_short_freq(frequency_range.min));
                button_manual_end.set_text(to_string_short_freq(frequency_range.max));
            }

            // no need to stop audio in SPEC
            if (field_mode.selected_index_value() != SPEC_MODULATION)
                audio::output::stop();

            // Clear doesn't actually free, re-assign so destructor runs on previous instance.
            frequency_list = freqman_db{};
            current_index = 0;
            frequency_list.push_back(std::make_unique<freqman_entry>());

            def_step = step_mode.selected_index();
            current_entry().type = freqman_type::Range;
            current_entry().description =
                to_string_short_freq(frequency_range.min).erase(0, 1) + ">" +  // euquiq: lame kludge to reduce spacing in step freq
                to_string_short_freq(frequency_range.max).erase(0, 1) + " S:" +
                freqman_entry_get_step_string_short(def_step);
            current_entry().frequency_a = frequency_range.min;
            current_entry().frequency_b = frequency_range.max;
            current_entry().modulation = freqman_invalid_index;
            current_entry().bandwidth = freqman_invalid_index;
            current_entry().step = def_step;

            big_display.set_style(&Styles::white);  // Back to white color

            freq_stats.set_style(&Styles::white);
            freq_stats.set("0/0/0");

            text_cycle.set_text("1");
            text_max.set("/1");
            button_scanner_mode.set_style(&Styles::white);
            button_scanner_mode.set_text("MANUAL");
            file_name.set_style(&Styles::white);
            file_name.set("MANUAL => " + output_file);
            desc_cycle.set_style(&Styles::white);

            last_entry.modulation = freqman_invalid_index;
            last_entry.bandwidth = freqman_invalid_index;
            last_entry.step = freqman_invalid_index;
            last_index = -1;

            freq = current_entry().frequency_a;
            handle_retune();
            recon_redraw();
            recon_resume();
        }
    };

    button_dir.on_select = [this](Button&) {
        if (fwd) {
            fwd = false;
            button_dir.set_text("<RW");
        } else {
            fwd = true;
            button_dir.set_text("FW>");
        }
        timer = 0;
        if (!recon)
            recon_resume();
    };

    button_restart.on_select = [this](Button&) {
        reload_restart_recon();
    };

    button_add.on_select = [this](ButtonWithEncoder&) {
        if (!scanner_mode) {
            recon_save_freq(freq_file_path, current_index, true);
        }
    };

    button_add.on_change = [this]() {
        on_stepper_delta(button_add.get_encoder_delta());
        button_add.set_encoder_delta(0);
    };

    button_scanner_mode.on_select = [this, &nav](Button&) {
        manual_mode = false;
        if (scanner_mode) {
            scanner_mode = false;
            button_scanner_mode.set_style(&Styles::blue);
            button_scanner_mode.set_text("RECON");
            button_remove.set_text("<REMOVE>");
        } else {
            scanner_mode = true;
            button_scanner_mode.set_style(&Styles::red);
            button_scanner_mode.set_text("SCAN");
            button_remove.set_text("<DELETE>");
        }
        frequency_file_load();
        if (autostart) {
            recon_resume();
        } else {
            recon_pause();
        }
        button_add.hidden(scanner_mode);
        if (scanner_mode)  // only needed when hiding, UI mayhem
            set_dirty();
    };

    button_config.on_select = [this, &nav](Button&) {
        if (is_recording)  // disabling config while recording
            return;

        clear_freqlist_for_ui_action();

        freq_lock = 0;
        timer = 0;
        auto open_view = nav.push<ReconSetupView>(input_file, output_file);
        open_view->on_changed = [this](std::vector<std::string> result) {
            input_file = result[0];
            output_file = result[1];
            freq_file_path = get_freqman_path(output_file).string();

            load_persisted_settings();
            ui_settings.save();

            frequency_file_load();
            freqlist_cleared_for_ui_action = false;

            if (autostart) {
                recon_resume();
            } else {
                recon_pause();
            }
        };
    };

    field_recon_match_mode.on_change = [this](size_t, OptionsField::value_t v) {
        recon_match_mode = v;
        colorize_waits();
    };

    field_wait.on_change = [this](int32_t v) {
        wait = v;
        // replacing -100 by 200 else it's freezing the device
        if (wait == -100)
            wait = -200;
        colorize_waits();
    };

    field_nblocks.on_change = [this](int32_t v) {
        recon_lock_nb_match = v;
        if ((unsigned)v < freq_lock)
            freq_lock = v;
        colorize_waits();
    };

    field_lock_wait.on_change = [this](uint32_t v) {
        recon_lock_duration = v;
        colorize_waits();
    };

    field_squelch.on_change = [this](int32_t v) {
        squelch = v;
    };

    // PRE-CONFIGURATION:
    button_scanner_mode.set_style(&Styles::blue);
    button_scanner_mode.set_text("RECON");
    file_name.set("=>");

    // Loading input and output file from settings
    freq_file_path = get_freqman_path(output_file).string();

    field_recon_match_mode.set_selected_index(recon_match_mode);
    field_squelch.set_value(squelch);
    field_wait.set_value(wait);
    field_lock_wait.set_value(recon_lock_duration);
    field_nblocks.set_value(recon_lock_nb_match);
    colorize_waits();

    // fill modulation and step options
    freqman_set_modulation_option(field_mode);
    freqman_set_step_option(step_mode);

    // set radio
    change_mode(AM_MODULATION);              // start on AM.
    field_mode.set_by_value(AM_MODULATION);  // reflect the mode into the manual selector

    // tx progress bar
    progressbar.hidden(true);

    if (filedelete) {
        delete_file(freq_file_path);
    }

    frequency_file_load(); /* do not stop all at start */
    if (autostart) {
        recon_resume();
    } else {
        recon_pause();
    }
    recon_redraw();
}

void ReconView::frequency_file_load() {
    if (field_mode.selected_index_value() != SPEC_MODULATION)
        audio::output::stop();

    def_step = step_mode.selected_index();  // use def_step from manual selector
    std::string file_input = input_file;    // default recon mode
    if (scanner_mode) {
        file_input = output_file;
        file_name.set_style(&Styles::red);
        button_scanner_mode.set_style(&Styles::red);
        desc_cycle.set_style(&Styles::red);
        button_scanner_mode.set_text("SCAN");
    } else {
        file_name.set_style(&Styles::blue);
        button_scanner_mode.set_style(&Styles::blue);
        desc_cycle.set_style(&Styles::blue);
        button_scanner_mode.set_text("RECON");
    }

    file_name.set(file_input + "=>" + output_file);

    freqman_load_options options{
        .load_freqs = load_freqs,
        .load_ranges = load_ranges,
        .load_hamradios = load_hamradios,
        .load_repeaters = load_repeaters};
    if (!load_freqman_file(file_input, frequency_list, options) || frequency_list.empty()) {
        file_name.set_style(&Styles::red);
        desc_cycle.set("...empty file...");
        frequency_list.clear();
        text_cycle.set_text(" ");
        return;
    }

    if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
        file_name.set_style(&Styles::yellow);
    }

    reset_indexes();
    step = freqman_entry_get_step_value(
        is_valid(current_entry().step) ? current_entry().step : def_step);

    if (current_entry().type == freqman_type::Single) {
        freq = current_entry().frequency_a;
    } else if (current_entry().type != freqman_type::Unknown) {
        minfreq = current_entry().frequency_a;
        maxfreq = current_entry().frequency_b;
        freq = fwd ? minfreq : maxfreq;
    }

    step_mode.set_selected_index(def_step);  // Impose the default step into the manual step selector
    receiver_model.enable();
    receiver_model.set_squelch_level(0);
    text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
    check_update_ranges_from_current();
    update_description();
    handle_retune();
}

void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
    if (recon_tx)
        return;

    if (is_repeat_active())
        return;

    chrono_end = chTimeNow();
    systime_t time_interval = chrono_end - chrono_start;
    chrono_start = chrono_end;

    // hack to reload the list if it was cleared by going into CONFIG
    if (freqlist_cleared_for_ui_action) {
        if (!manual_mode) {
            frequency_file_load();
        }
        if (autostart && !user_pause) {
            recon_resume();
        } else {
            recon_pause();
        }
        freqlist_cleared_for_ui_action = false;
    }
    db = statistics.max_db;
    if (recon) {
        if (!timer) {
            status = 0;
            freq_lock = 0;
            timer = recon_lock_duration;
        }
        if (freq_lock < recon_lock_nb_match)  // LOCKING
        {
            if (status != 1) {
                status = 1;
                if (wait != 0) {
                    recon_stop_recording(false);
                    if (field_mode.selected_index_value() != SPEC_MODULATION)
                        audio::output::stop();
                }
            }
            if (db > squelch)  // MATCHING LEVEL
            {
                freq_lock++;
                timer += time_interval;  // give some more time for next lock
            } else {
                // continuous, direct cut it if not consecutive match after 1 first match
                if (recon_match_mode == RECON_MATCH_CONTINUOUS) {
                    freq_lock = 0;
                    timer = 0;
                }
            }
        }
        if (freq_lock >= recon_lock_nb_match)  // LOCKED
        {
            if (status != 2) {
                status = 2;
                // FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
                if ((!scanner_mode) && autosave && frequency_list.size() > 0) {
                    recon_save_freq(freq_file_path, current_index, false);
                }
                if (wait != 0) {
                    if (field_mode.selected_index_value() != SPEC_MODULATION)
                        audio_output_start();
                    // contents of a possible recon_start_recording(), but not yet since it's only called once
                    if (auto_record_locked && !is_recording) {
                        button_audio_app.set_style(&Styles::red);
                        if (field_mode.selected_index_value() == SPEC_MODULATION) {
                            button_audio_app.set_text("RAW REC");
                        } else
                            button_audio_app.set_text("WAV REC");
                        record_view->start();
                        button_config.set_style(&Styles::light_grey);  // disable config while recording as it's causing an IO error pop up at exit
                        is_recording = true;
                    }
                    // FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
                    if ((!scanner_mode) && autosave && frequency_list.size() > 0) {
                        recon_save_freq(freq_file_path, current_index, false);
                    }
                }
                if (wait >= 0) {
                    timer = wait;
                }
            }
            if (wait < 0) {
                if (db > squelch)  // MATCHING LEVEL IN STAY X AFTER LAST ACTIVITY
                {
                    timer = abs(wait);
                }
            }
        }
    }
    if (last_timer != timer) {
        last_timer = timer;
        text_timer.set("TIMER: " + to_string_dec_int(timer));
    }

    if (timer != 0) {
        timer -= time_interval;
        if (timer < 0) {
            timer = 0;
        }
    }
    if (recon || stepper != 0 || index_stepper != 0) {
        if (!timer || stepper != 0 || index_stepper != 0) {
            // IF THERE IS A FREQUENCY LIST ...
            if (frequency_list.size() > 0) {
                has_looped = false;
                entry_has_changed = false;
                if (recon || stepper != 0 || index_stepper != 0) {
                    if (index_stepper == 0) {
                        /* we are doing a range */
                        if (current_entry().type == freqman_type::Range) {
                            if ((fwd && stepper == 0) || stepper > 0) {
                                // forward
                                freq += step;
                                // if bigger than range max
                                if (freq > maxfreq) {
                                    // when going forward we already know that we can skip a whole range => two values in the list
                                    current_index++;
                                    entry_has_changed = true;
                                    // looping
                                    if ((uint32_t)current_index >= frequency_list.size()) {
                                        has_looped = true;
                                        current_index = 0;
                                    }
                                }
                            } else if ((!fwd && stepper == 0) || stepper < 0) {
                                // reverse
                                freq -= step;
                                // if lower than range min
                                if (freq < minfreq) {
                                    // when back we have to check one step at a time
                                    current_index--;
                                    entry_has_changed = true;
                                    // looping
                                    if (current_index < 0) {
                                        has_looped = true;
                                        current_index = frequency_list.size() - 1;
                                    }
                                }
                            }
                        } else if (current_entry().type == freqman_type::Single) {
                            if ((fwd && stepper == 0) || stepper > 0) {  // forward
                                current_index++;
                                entry_has_changed = true;
                                // looping
                                if ((uint32_t)current_index >= frequency_list.size()) {
                                    has_looped = true;
                                    current_index = 0;
                                }
                            } else if ((!fwd && stepper == 0) || stepper < 0) {
                                // reverse
                                current_index--;
                                entry_has_changed = true;
                                // if previous if under the list => go back from end
                                if (current_index < 0) {
                                    has_looped = true;
                                    current_index = frequency_list.size() - 1;
                                }
                            }
                        } else if (current_entry().type == freqman_type::HamRadio) {
                            if ((fwd && stepper == 0) || stepper > 0) {  // forward
                                if ((minfreq != maxfreq) && freq == minfreq) {
                                    freq = maxfreq;
                                } else {
                                    current_index++;
                                    entry_has_changed = true;
                                    // looping
                                    if ((uint32_t)current_index >= frequency_list.size()) {
                                        has_looped = true;
                                        current_index = 0;
                                    }
                                }
                            } else if ((!fwd && stepper == 0) || stepper < 0) {
                                // reverse
                                if ((minfreq != maxfreq) && freq == maxfreq) {
                                    freq = minfreq;
                                } else {
                                    current_index--;
                                    entry_has_changed = true;
                                    // if previous if under the list => go back from end
                                    if (current_index < 0) {
                                        has_looped = true;
                                        current_index = frequency_list.size() - 1;
                                    }
                                }
                            }
                        } else if (current_entry().type == freqman_type::Repeater) {
                            // repeater is like single, we only listen on frequency_a and then jump to next entry
                            if ((fwd && stepper == 0) || stepper > 0) {  // forward
                                current_index++;
                                entry_has_changed = true;
                                // looping
                                if ((uint32_t)current_index >= frequency_list.size()) {
                                    has_looped = true;
                                    current_index = 0;
                                }
                            } else if ((!fwd && stepper == 0) || stepper < 0) {
                                // reverse
                                current_index--;
                                entry_has_changed = true;
                                // if previous if under the list => go back from end
                                if (current_index < 0) {
                                    has_looped = true;
                                    current_index = frequency_list.size() - 1;
                                }
                            }
                        }
                        // set index to boundary if !continuous
                        if (has_looped && !continuous) {
                            entry_has_changed = true;
                            /* prepare values for the next run, when user will resume */
                            if ((fwd && stepper == 0) || stepper > 0) {
                                current_index = 0;
                            } else if ((!fwd && stepper == 0) || stepper < 0) {
                                current_index = frequency_list.size() - 1;
                            }
                        }
                    } else {
                        current_index += index_stepper;
                        if (current_index < 0)
                            current_index += frequency_list.size();
                        if ((unsigned)current_index >= frequency_list.size())
                            current_index -= frequency_list.size();

                        entry_has_changed = true;

                        // for some motive, audio output gets stopped.
                        if (!recon && field_mode.selected_index_value() != SPEC_MODULATION)
                            audio_output_start();
                    }
                    // reload entry if changed
                    if (entry_has_changed) {
                        timer = 0;
                        switch (current_entry().type) {
                            case freqman_type::Repeater:
                            case freqman_type::Single:
                                freq = current_entry().frequency_a;
                                break;
                            case freqman_type::Range:
                                minfreq = current_entry().frequency_a;
                                maxfreq = current_entry().frequency_b;
                                if ((fwd && !stepper && !index_stepper) || stepper > 0 || index_stepper > 0) {
                                    freq = minfreq;
                                } else if ((!fwd && !stepper && !index_stepper) || stepper < 0 || index_stepper < 0) {
                                    freq = maxfreq;
                                }
                                break;
                            case freqman_type::HamRadio:
                                minfreq = current_entry().frequency_a;
                                maxfreq = current_entry().frequency_b;
                                if ((fwd && !stepper && !index_stepper) || stepper > 0 || index_stepper > 0) {
                                    freq = minfreq;
                                } else if ((!fwd && !stepper && !index_stepper) || stepper < 0 || index_stepper < 0) {
                                    freq = maxfreq;
                                }
                                break;
                            default:
                                break;
                        }
                    }
                    if (has_looped && !continuous) {
                        recon_pause();
                    }
                    index_stepper = 0;
                    if (stepper < 0) stepper++;
                    if (stepper > 0) stepper--;
                }  // if( recon || stepper != 0 || index_stepper != 0 )
            }      // if (frequency_list.size() > 0 )
        }          /* on_statistics_updates */
    }
    handle_retune();
    recon_redraw();
}

void ReconView::recon_pause() {
    timer = 0;
    freq_lock = 0;
    recon = false;

    if (field_mode.selected_index_value() != SPEC_MODULATION)
        audio_output_start();

    big_display.set_style(&Styles::white);
    button_pause.set_text("<RESUME>");  // PAUSED, show resume
}

void ReconView::recon_resume() {
    timer = 0;
    freq_lock = 0;
    recon = true;

    if (field_mode.selected_index_value() != SPEC_MODULATION)
        audio::output::stop();

    big_display.set_style(&Styles::white);
    button_pause.set_text("<PAUSE>");
}

void ReconView::on_index_delta(int32_t v) {
    if (v > 0) {
        fwd = true;
        button_dir.set_text("FW>");
    }
    if (v < 0) {
        fwd = false;
        button_dir.set_text("<RW");
    }
    if (frequency_list.size() > 0)
        index_stepper = v;

    freq_lock = 0;
    timer = 0;
}

void ReconView::on_stepper_delta(int32_t v) {
    if (v > 0) {
        fwd = true;
        button_dir.set_text("FW>");
    }
    if (v < 0) {
        fwd = false;
        button_dir.set_text("<RW");
    }
    if (frequency_list.size() > 0)
        stepper = v;

    freq_lock = 0;
    timer = 0;
}

size_t ReconView::change_mode(freqman_index_t new_mod) {
    if (recon_tx || is_repeat_active())
        return 0;
    field_mode.on_change = [this](size_t, OptionsField::value_t) {};
    field_bw.on_change = [this](size_t, OptionsField::value_t) {};
    recon_stop_recording(false);
    if (record_view != nullptr) {
        remove_child(record_view.get());
        record_view.reset();
    }
    if (persistent_memory::recon_repeat_recorded()) {
        record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
                                                   u"RECON_REPEAT.C16", u"CAPTURES",
                                                   RecordView::FileType::RawS16, 16384, 3);
        record_view->set_filename_as_is(true);
    } else if (new_mod == SPEC_MODULATION) {
        audio::output::stop();
        record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
                                                   u"AUTO_RAW", u"CAPTURES",
                                                   RecordView::FileType::RawS16, 16384, 3);
    } else {
        record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
                                                   u"AUTO_AUDIO", u"AUDIO",
                                                   RecordView::FileType::WAV, 4096, 4);
        record_view->set_filename_date_frequency(true);
    }
    record_view->set_auto_trim(false);
    add_child(record_view.get());
    record_view->hidden(true);
    record_view->on_error = [this](std::string message) {
        nav_.display_modal("Error", message);
    };
    receiver_model.disable();
    transmitter_model.disable();
    baseband::shutdown();
    size_t recording_sampling_rate = 0;
    switch (new_mod) {
        case AM_MODULATION:
            freqman_set_bandwidth_option(new_mod, field_bw);
            baseband::run_image(portapack::spi_flash::image_tag_am_audio);
            receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
            receiver_model.set_am_configuration(field_bw.selected_index_value());
            field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_am_configuration(n); };
            // bw DSB (0) default
            field_bw.set_by_value(0);
            text_ctcss.set("        ");
            recording_sampling_rate = 12000;
            break;
        case NFM_MODULATION:
            freqman_set_bandwidth_option(new_mod, field_bw);
            baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
            receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
            receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
            field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_nbfm_configuration(n); };
            // bw 16k (2) default
            field_bw.set_by_value(2);
            recording_sampling_rate = 24000;
            break;
        case WFM_MODULATION:
            freqman_set_bandwidth_option(new_mod, field_bw);
            baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
            receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
            receiver_model.set_wfm_configuration(field_bw.selected_index_value());
            field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
            // bw 200k (0) default
            field_bw.set_by_value(0);
            text_ctcss.set("        ");
            recording_sampling_rate = 48000;
            break;
        case SPEC_MODULATION:
            freqman_set_bandwidth_option(new_mod, field_bw);
            baseband::run_image(portapack::spi_flash::image_tag_capture);
            receiver_model.set_modulation(ReceiverModel::Mode::Capture);
            field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
                // record_view determines the correct oversampling to apply and returns the actual sample rate.
                auto actual_sampling_rate = record_view->set_sampling_rate(sampling_rate);
                // The radio needs to know the effective sampling rate.
                receiver_model.set_sampling_rate(actual_sampling_rate);
                receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
            };
            // bw 12k5 (0) default
            field_bw.set_by_value(0);
            text_ctcss.set("        ");
            break;
        default:
            break;
    }
    if (new_mod != SPEC_MODULATION) {
        button_audio_app.set_text("AUDIO");
        record_view->set_sampling_rate(recording_sampling_rate);
        // reset receiver model to fix bug when going from SPEC to audio, the sound is distorted
        receiver_model.set_sampling_rate(3072000);
        receiver_model.set_baseband_bandwidth(1750000);
    } else {
        button_audio_app.set_text("RAW");
    }

    field_mode.set_selected_index(new_mod);
    field_mode.on_change = [this](size_t, OptionsField::value_t v) {
        if (v != -1) {
            change_mode(v);
        }
    };
    // for some motive, audio output gets stopped.
    if (!recon && field_mode.selected_index_value() != SPEC_MODULATION)
        audio::output::start();  // so if recon was stopped we resume audio
    receiver_model.enable();

    return freqman_entry_get_step_value(def_step);
}

void ReconView::handle_coded_squelch(const uint32_t value) {
    if (field_mode.selected_index() == NFM_MODULATION)
        text_ctcss.set(tone_key_string_by_value(value, text_ctcss.parent_rect().width() / 8));
    else
        text_ctcss.set("        ");
}

void ReconView::handle_remove_current_item() {
    if (frequency_list.empty() || !current_is_valid())
        return;

    auto entry = current_entry();  // Copy the current entry.

    // In Scanner or Recon modes, remove from the in-memory list.
    if (mode() != recon_mode::Manual) {
        if (current_is_valid()) {
            frequency_list.erase(frequency_list.begin() + current_index);
        }
    }

    // In Scanner or Manual mode, remove the entry from the output file.
    if (mode() != recon_mode::Recon) {
        FreqmanDB freq_db;
        if (!freq_db.open(freq_file_path))
            return;

        freq_db.delete_entry(entry);
    }

    // Clip
    if (frequency_list.size() > 0) {
        current_index = clip<int32_t>(current_index, 0u, frequency_list.size() - 1);
        text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
        entry = current_entry();
        freq = entry.frequency_a;
    } else {
        current_index = 0;
        text_cycle.set_text(" ");
    }
    update_description();
}

void ReconView::on_repeat_tx_progress(const uint32_t progress) {
    progressbar.set_value(progress);
}

void ReconView::repeat_file_error(const std::filesystem::path& path, const std::string& message) {
    nav_.display_modal("Error", "Error opening file \n" + path.string() + "\n" + message);
}

bool ReconView::is_repeat_active() const {
    return replay_thread != nullptr;
}

void ReconView::start_repeat() {
    // Prepare to send a file.
    std::filesystem::path rawfile = u"/" + repeat_rec_path + u"/" + repeat_rec_file;
    std::filesystem::path rawmeta = u"/" + repeat_rec_path + u"/" + repeat_rec_meta;

    if (recon_tx == false) {
        recon_tx = true;

        if (record_view != nullptr) {
            record_view->stop();
            remove_child(record_view.get());
            record_view.reset();
        }

        receiver_model.disable();
        transmitter_model.disable();
        baseband::shutdown();

        baseband::run_image(portapack::spi_flash::image_tag_replay);

        size_t rawsize = 0;
        {
            File capture_file;
            auto error = capture_file.open(rawfile);
            if (error) {
                repeat_file_error(rawfile, "Can't open file to send for size");
                return;
            }
            rawsize = capture_file.size();
        }

        // Reset the transmit progress bar.
        uint8_t sample_size = std::filesystem::capture_file_sample_size(rawfile);
        progressbar.set_value(0);
        progressbar.set_max(rawsize * sizeof(complex16_t) / sample_size);
        progressbar.hidden(false);

        auto metadata = read_metadata_file(rawmeta);
        // If no metadata found, fallback to the TX frequency.
        if (!metadata) {
            metadata = {freq, 500'000};
            repeat_file_error(rawmeta, "Can't open file to read meta, using default rx_freq,500'000");
        }

        // Update the sample rate in proc_replay baseband.
        baseband::set_sample_rate(metadata->sample_rate,
                                  get_oversample_rate(metadata->sample_rate));

        transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate));
        transmitter_model.set_baseband_bandwidth(metadata->sample_rate <= 500'000 ? 1'750'000 : 2'500'000);  // TX LPF min 1M75 for SR <=500K, and  2M5 (by experimental test) for SR >500K

        // set TX to repeater TX freq if entry is Repeater and have a valid freq, else use recorded one
        if (current_entry().type == freqman_type::Repeater && current_entry().frequency_b != 0) {
            transmitter_model.set_target_frequency(current_entry().frequency_b);
        } else {
            transmitter_model.set_target_frequency(metadata->center_frequency);
        }

        // set TX powers and enable transmitter
        transmitter_model.set_tx_gain(persistent_memory::recon_repeat_gain());
        transmitter_model.set_rf_amp(persistent_memory::recon_repeat_amp());
        transmitter_model.enable();
    }

    // clear replay thread and set reader
    replay_thread.reset();
    auto reader = std::make_unique<FileConvertReader>();
    auto error = reader->open(rawfile);
    if (error) {
        repeat_file_error(rawfile, "Can't open file to send to thread");
        return;
    }
    // wait for TX if needed (hackish, direct screen update since the UI will be blocked)
    if (persistent_memory::recon_repeat_delay() > 0) {
        uint8_t delay = persistent_memory::recon_repeat_delay();
        Painter p;
        while (delay > 0) {
            std::string delay_message = "TX DELAY: " + to_string_dec_uint(delay) + "s";

            // update display information
            p.fill_rectangle({0, (SCREEN_H / 2) - 16, SCREEN_W, 64}, Color::light_grey());
            p.draw_string({(SCREEN_W / 2) - 7 * 8, SCREEN_H / 2}, Styles::red, delay_message);

            // sleep 1 second
            chThdSleepMilliseconds(1000);

            // decre delay
            if (delay > 0)
                delay = delay - 1;
            else
                break;
        }
    }

    // ReplayThread starts immediately on construction; must be set before creating.
    repeat_ready_signal = true;
    repeat_cur_rep++;
    replay_thread = std::make_unique<ReplayThread>(
        std::move(reader),
        /* read_size */ repeat_read_size,
        /* buffer_count */ repeat_buffer_count,
        &repeat_ready_signal,
        [](uint32_t return_code) {
            ReplayThreadDoneMessage message{return_code};
            EventDispatcher::send_message(message);
        });
}

void ReconView::stop_repeat(const bool do_loop) {
    repeat_ready_signal = false;

    if (is_repeat_active()) {
        replay_thread.reset();
        transmitter_model.disable();
    }

    // repeat transmit if current number of repetitions (repeat_cur_rep) is < recon configured number of repetitions (recon_repeat_nb)
    if (do_loop && repeat_cur_rep < persistent_memory::recon_repeat_nb()) {
        start_repeat();
    } else {
        repeat_cur_rep = 0;
        recon_tx = false;
        reload_restart_recon();
        progressbar.hidden(true);
        set_dirty();  // fix progressbar no hiding
    }
}

void ReconView::handle_repeat_thread_done(const uint32_t return_code) {
    if (return_code == ReplayThread::END_OF_FILE) {
        stop_repeat(true);
    } else if (return_code == ReplayThread::READ_ERROR) {
        stop_repeat(false);
        repeat_file_error(u"/" + repeat_rec_path + u"/" + repeat_rec_file, "Can't open file to send.");
    }
}

} /* namespace ui */