/**
 * @file magnitude_spectrum.c
 * @brief Example: compute and export the magnitude spectrum of a test signal.
 *
 * This program demonstrates MD_magnitude_spectrum() by:
 *   1. Generating a signal with three sinusoidal components (440 Hz,
 *      1000 Hz, and 2500 Hz) plus a DC offset.
 *   2. Applying a Hanning window to reduce spectral leakage.
 *   3. Computing the magnitude spectrum via MD_magnitude_spectrum().
 *   4. Writing the results to CSV and to an interactive HTML visualisation
 *      (Plotly.js + D3.js, no server required).
 *
 * Build and run (from the repository root):
 *   make                          # build libminidsp.a first
 *   make -C examples plot         # build example, run it, generate HTML
 *   open examples/magnitude_spectrum.html   # interactive spectrum plot
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "minidsp.h"
#include "plot_html.h"

/* -----------------------------------------------------------------------
 * Write the interactive HTML visualisation.
 *
 * The HTML file is completely self-contained: spectrum data is embedded
 * as JavaScript arrays, and Plotly.js / D3.js are loaded from CDN.
 * Just open the file in any modern browser -- no local server needed.
 * -----------------------------------------------------------------------*/
static int write_html(const char *path,
                      const double *freqs, const double *mags,
                      unsigned num_bins,
                      double sample_rate, unsigned N,
                      double freq1, double freq2, double freq3)
{
    FILE *fp = fopen(path, "w");
    if (!fp) {
        fprintf(stderr, "cannot open %s for writing\n", path);
        return -1;
    }

    char subtitle[256];
    snprintf(subtitle, sizeof(subtitle),
        "%.0f Hz + %.0f Hz + %.0f Hz &nbsp;|&nbsp;\n"
        "    sample rate %.0f Hz &nbsp;|&nbsp;\n"
        "    FFT size %u &nbsp;|&nbsp;\n"
        "    Hanning window",
        freq1, freq2, freq3, sample_rate, N);

    plot_html_begin(fp, "Magnitude Spectrum", subtitle, 1);

    fprintf(fp,
        "  <div id=\"linear-plot\" class=\"plot-container\"></div>\n"
        "  <div id=\"db-plot\" class=\"plot-container\"></div>\n"
        "  <div class=\"info\">\n"
        "    <strong>How to read this plot:</strong><br>\n"
        "    Each vertical spike corresponds to a sinusoidal component in the\n"
        "    input signal.  The x-axis shows frequency in Hz (bin index &times;\n"
        "    <code>sample_rate / N</code>).  The top plot uses a linear amplitude\n"
        "    scale; the bottom plot uses a logarithmic (dB) scale so you can see\n"
        "    low-level details.  Hover over the traces for exact values.  Use the\n"
        "    Plotly toolbar to zoom, pan, or export to PNG/SVG.\n"
        "  </div>\n\n");

    /* ---- Embed spectrum data as JS arrays ---- */
    fprintf(fp, "  <script>\n");
    fprintf(fp, "    // Spectrum data generated by magnitude_spectrum.c\n");
    plot_html_js_array(fp, "freqs", freqs, num_bins, "%.4f");
    plot_html_js_array(fp, "mags", mags, num_bins, "%.8f");
    fprintf(fp, "\n");

    /* ---- Plotly + D3 visualisation code ---- */
    fprintf(fp,
        "    // Use D3's d3.max() to compute axis ranges\n"
        "    const maxMag = d3.max(mags);\n"
        "\n"
        "    // Convert to dB scale (floor at -120 dB to avoid log(0))\n"
        "    const mags_db = mags.map(m => 20 * Math.log10(Math.max(m, 1e-6)));\n"
        "\n"
        "    // Find the three peak frequencies for annotation using d3\n"
        "    // (skip bin 0 / DC when searching for tone peaks)\n"
        "    const peakTargets = [%.0f, %.0f, %.0f];\n"
        "    const annotations_linear = [];\n"
        "    const annotations_db = [];\n"
        "\n"
        "    peakTargets.forEach(target => {\n"
        "      // Use d3.minIndex to find the closest frequency bin\n"
        "      const idx = d3.minIndex(freqs, f => Math.abs(f - target));\n"
        "      annotations_linear.push({\n"
        "        x: freqs[idx], y: mags[idx],\n"
        "        text: target + ' Hz',\n"
        "        showarrow: true, arrowhead: 2,\n"
        "        ax: 40, ay: -30,\n"
        "        font: { size: 12 }\n"
        "      });\n"
        "      annotations_db.push({\n"
        "        x: freqs[idx], y: mags_db[idx],\n"
        "        text: target + ' Hz (' + mags_db[idx].toFixed(1) + ' dB)',\n"
        "        showarrow: true, arrowhead: 2,\n"
        "        ax: 50, ay: -25,\n"
        "        font: { size: 12 }\n"
        "      });\n"
        "    });\n"
        "\n"
        "    // --- Linear magnitude plot ---\n"
        "    Plotly.newPlot('linear-plot', [{\n"
        "      x: freqs, y: mags,\n"
        "      type: 'scatter', mode: 'lines',\n"
        "      line: { color: '#2563eb', width: 1.2 },\n"
        "      name: '|X(k)| / N',\n"
        "      hovertemplate: '%%{x:.1f} Hz<br>Amplitude: %%{y:.6f}<extra></extra>'\n"
        "    }], {\n"
        "      title: { text: 'Linear Scale', font: { size: 15 } },\n"
        "      xaxis: { title: 'Frequency (Hz)', range: [0, d3.max(freqs)] },\n"
        "      yaxis: { title: 'Amplitude', range: [0, maxMag * 1.1] },\n"
        "      annotations: annotations_linear,\n"
        "      margin: { t: 50, r: 30, b: 50, l: 60 },\n"
        "      height: 320\n"
        "    }, { responsive: true });\n"
        "\n"
        "    // --- dB magnitude plot ---\n"
        "    Plotly.newPlot('db-plot', [{\n"
        "      x: freqs, y: mags_db,\n"
        "      type: 'scatter', mode: 'lines',\n"
        "      line: { color: '#dc2626', width: 1.2 },\n"
        "      name: '20·log₁₀(|X(k)|/N)',\n"
        "      hovertemplate: '%%{x:.1f} Hz<br>%%{y:.1f} dB<extra></extra>'\n"
        "    }], {\n"
        "      title: { text: 'Logarithmic (dB) Scale', font: { size: 15 } },\n"
        "      xaxis: { title: 'Frequency (Hz)', range: [0, d3.max(freqs)] },\n"
        "      yaxis: { title: 'Magnitude (dB)', range: [-80, 5] },\n"
        "      annotations: annotations_db,\n"
        "      margin: { t: 50, r: 30, b: 50, l: 60 },\n"
        "      height: 320\n"
        "    }, { responsive: true });\n"
        "  </script>\n",
        freq1, freq2, freq3);

    plot_html_end(fp);
    fclose(fp);
    return 0;
}

int main(void)
{
    /* ------------------------------------------------------------------
     * Signal parameters
     * ----------------------------------------------------------------*/
    const unsigned   N           = 4096;        /* FFT size (samples)       */
    const double     sample_rate = 16000.0;     /* Hz                       */
    const unsigned   num_bins    = N / 2 + 1;   /* unique frequency bins    */

    /* Three tones at different amplitudes, plus a small DC offset */
    const double     freq1 =  440.0;   /* A4 note               */
    const double     amp1  =    1.0;
    const double     freq2 = 1000.0;   /* 1 kHz reference tone  */
    const double     amp2  =    0.6;
    const double     freq3 = 2500.0;   /* higher partial        */
    const double     amp3  =    0.3;
    const double     dc    =    0.1;   /* small DC offset       */

    /* ------------------------------------------------------------------
     * Generate the test signal
     * ----------------------------------------------------------------*/
    double *signal  = malloc(N * sizeof(double));
    double *windowed = malloc(N * sizeof(double));
    double *window  = malloc(N * sizeof(double));
    double *mag     = malloc(num_bins * sizeof(double));
    double *freqs   = malloc(num_bins * sizeof(double));

    if (!signal || !windowed || !window || !mag || !freqs) {
        fprintf(stderr, "allocation failed\n");
        return 1;
    }

    //! [generate-signal]
    double *tone = malloc(N * sizeof(double));
    for (unsigned i = 0; i < N; i++) signal[i] = dc;

    MD_sine_wave(tone, N, amp1, freq1, sample_rate);
    for (unsigned i = 0; i < N; i++) signal[i] += tone[i];

    MD_sine_wave(tone, N, amp2, freq2, sample_rate);
    for (unsigned i = 0; i < N; i++) signal[i] += tone[i];

    MD_sine_wave(tone, N, amp3, freq3, sample_rate);
    for (unsigned i = 0; i < N; i++) signal[i] += tone[i];

    free(tone);
    //! [generate-signal]

    /* ------------------------------------------------------------------
     * Apply a Hanning window to reduce spectral leakage
     * ----------------------------------------------------------------*/
    //! [apply-window]
    MD_Gen_Hann_Win(window, N);
    for (unsigned i = 0; i < N; i++) {
        windowed[i] = signal[i] * window[i];
    }
    //! [apply-window]

    /* ------------------------------------------------------------------
     * Compute the magnitude spectrum
     * ----------------------------------------------------------------*/
    //! [compute-magnitude]
    MD_magnitude_spectrum(windowed, N, mag);
    //! [compute-magnitude]

    /* Convert to single-sided amplitude spectrum:
     *   - Divide all bins by N
     *   - Multiply interior bins by 2 (because negative frequencies
     *     are folded into the positive side)
     *   - DC and Nyquist bins appear only once, so no doubling */
    //! [one-sided-amplitude]
    for (unsigned k = 0; k < num_bins; k++) {
        freqs[k] = (double)k * sample_rate / (double)N;
        mag[k] /= (double)N;
        if (k > 0 && k < N / 2) {
            mag[k] *= 2.0;
        }
    }
    //! [one-sided-amplitude]

    /* ------------------------------------------------------------------
     * Write results to CSV (for data interchange)
     * ----------------------------------------------------------------*/
    const char *csv_file = "magnitude_spectrum.csv";
    FILE *fp = fopen(csv_file, "w");
    if (!fp) {
        fprintf(stderr, "cannot open %s for writing\n", csv_file);
        return 1;
    }

    fprintf(fp, "bin,frequency_hz,magnitude\n");
    for (unsigned k = 0; k < num_bins; k++) {
        fprintf(fp, "%u,%.4f,%.8f\n", k, freqs[k], mag[k]);
    }
    fclose(fp);

    printf("Wrote %u frequency bins to %s\n", num_bins, csv_file);

    /* ------------------------------------------------------------------
     * Write interactive HTML visualisation (Plotly.js + D3.js)
     * ----------------------------------------------------------------*/
    const char *html_file = "magnitude_spectrum.html";
    if (write_html(html_file, freqs, mag, num_bins,
                   sample_rate, N, freq1, freq2, freq3) == 0) {
        printf("Wrote interactive plot to %s\n", html_file);
    }

    printf("Signal: %.0f Hz + %.0f Hz + %.0f Hz + DC offset\n",
           freq1, freq2, freq3);
    printf("Sample rate: %.0f Hz, FFT size: %u\n", sample_rate, N);

    /* ------------------------------------------------------------------
     * Cleanup
     * ----------------------------------------------------------------*/
    free(freqs);
    free(mag);
    free(window);
    free(windowed);
    free(signal);
    MD_shutdown();

    return 0;
}
