/* Copyright (C) 2023 Salvatore Sanfilippo -- All Rights Reserved
 * Copyright (C) 2023 Maciej Wojtasik -- All Rights Reserved
 * See the LICENSE file for information about the license. */

#include "app.h"
#include <stream/stream.h>
#include <flipper_format/flipper_format_i.h>

/* ========================= Signal file operations ========================= */

/* This function saves the current logical signal on disk. What is saved here
 * is not the signal as level and duration as we received it from CC1101,
 * but it's logical representation stored in the app->msg_info bitmap, where
 * each 1 or 0 means a puls or gap for the specified short pulse duration time
 * (te). */
bool save_signal(ProtoViewApp* app, const char* filename) {
    /* We have a message at all? */
    if(app->msg_info == NULL || app->msg_info->pulses_count == 0) return false;

    Storage* storage = furi_record_open(RECORD_STORAGE);
    FlipperFormat* file = flipper_format_file_alloc(storage);
    Stream* stream = flipper_format_get_raw_stream(file);
    FuriString* file_content = NULL;
    bool success = true;

    if(flipper_format_file_open_always(file, filename)) {
        /* Write the file header. */
        FuriString* file_content = furi_string_alloc();
        const char* preset_id = ProtoViewModulations[app->modulation].id;

        furi_string_printf(
            file_content,
            "Filetype: Flipper SubGhz RAW File\n"
            "Version: 1\n"
            "Frequency: %ld\n"
            "Preset: %s\n",
            app->frequency,
            preset_id ? preset_id : "FuriHalSubGhzPresetCustom");

        /* For custom modulations, we need to emit a set of registers. */
        if(preset_id == NULL) {
            FuriString* custom = furi_string_alloc();
            uint8_t* regs = ProtoViewModulations[app->modulation].custom;
            furi_string_printf(
                custom,
                "Custom_preset_module: CC1101\n"
                "Custom_preset_data: ");

            /* We will know the size of the preset data once we reach the end
             * of the registers (null address). For now it's INT_MAX. */
            int preset_data_size = INT_MAX;
            bool patable_reached = false;
            for(int j = 0; j <= preset_data_size; j += 2) {
                // End reached, set the size to write the remaining 8 bytes (PATABLE)
                if(!patable_reached && regs[j] == 0) {
                    preset_data_size = j + 8;
                    patable_reached = true;
                }
                furi_string_cat_printf(custom, "%02X %02X ", (int)regs[j], (int)regs[j + 1]);
            }
            size_t len = furi_string_size(custom);
            furi_string_set_char(custom, len - 1, '\n');
            furi_string_cat(file_content, custom);
            furi_string_free(custom);
        }

        /* We always save raw files. */
        furi_string_cat_printf(
            file_content,
            "Protocol: RAW\n"
            "RAW_Data: -10000\n"); // Start with 10 ms of gap

        /* Write header. */
        size_t len = furi_string_size(file_content);
        if(stream_write(stream, (uint8_t*)furi_string_get_cstr(file_content), len) != len) {
            FURI_LOG_W(TAG, "Short write to file");
            success = false;
            goto write_err;
        }
        furi_string_reset(file_content);

        /* Write raw data sections. The Flipper subghz parser can't handle
         * too much data on a single line, so we generate a new one
         * every few samples. */
        uint32_t this_line_samples = 0;
        uint32_t max_line_samples = 100;
        uint32_t idx = 0; // Iindex in the signal bitmap.
        ProtoViewMsgInfo* i = app->msg_info;
        while(idx < i->pulses_count) {
            bool level = bitmap_get(i->bits, i->bits_bytes, idx);
            uint32_t te_times = 1;
            idx++;
            /* Count the duration of the current pulse/gap. */
            while(idx < i->pulses_count && bitmap_get(i->bits, i->bits_bytes, idx) == level) {
                te_times++;
                idx++;
            }
            // Invariant: after the loop 'idx' is at the start of the
            // next gap or pulse.

            int32_t dur = (int32_t)i->short_pulse_dur * te_times;
            if(level == 0) dur = -dur; /* Negative is gap in raw files. */

            /* Emit the sample. If this is the first sample of the line,
             * also emit the RAW_Data: field. */
            if(this_line_samples == 0) furi_string_cat_printf(file_content, "RAW_Data: ");
            furi_string_cat_printf(file_content, "%d ", (int)dur);
            this_line_samples++;

            /* Store the current set of samples on disk, when we reach a
             * given number or the end of the signal. */
            bool end_reached = (idx == i->pulses_count);
            if(this_line_samples == max_line_samples || end_reached) {
                /* If that's the end, terminate the signal with a long
                 * gap. */
                if(end_reached) furi_string_cat_printf(file_content, "-10000 ");

                /* We always have a trailing space in the last sample. Make it
                 * a newline. */
                size_t len = furi_string_size(file_content);
                furi_string_set_char(file_content, len - 1, '\n');

                if(stream_write(stream, (uint8_t*)furi_string_get_cstr(file_content), len) !=
                   len) {
                    FURI_LOG_W(TAG, "Short write to file");
                    success = false;
                    goto write_err;
                }

                /* Prepare for next line. */
                furi_string_reset(file_content);
                this_line_samples = 0;
            }
        }
    } else {
        success = false;
        FURI_LOG_W(TAG, "Unable to open file");
    }

write_err:
    furi_record_close(RECORD_STORAGE);
    flipper_format_free(file);
    if(file_content != NULL) furi_string_free(file_content);
    return success;
}
