## -*- texinfo -*-
## @deftypefn {Function File} dsp_code_export (@var{h})
## Open an export window to generate and save filter coefficients as
## MATLAB/Octave, C, or Python code.  The window shows a live preview
## and allows saving to a file.
## @end deftypefn

function dsp_code_export(h)
    type_names = {"Low Pass", "High Pass", "Band Pass", "Band Stop"};
    arch_names = {"IIR", "FIR"};

    if ~isfield(h, "last_b") || isempty(h.last_b)
        errordlg("Please design a filter first.", "Export Error");
        return;
    end

    topo_items = get(h.popup_Topo, "String");
    arch_idx   = get(h.popup_Arch, "value");

    info.Fs        = str2double(get(h.edit_Fs, "string"));
    info.Fc        = str2double(get(h.edit_Fc, "string"));
    info.Fc2       = str2double(get(h.edit_Fc2, "string"));
    info.N         = str2double(get(h.edit_Order, "string"));
    info.type_name = type_names{get(h.popup_Type, "value")};
    info.topo_name = topo_items{get(h.popup_Topo, "value")};
    info.arch_name = arch_names{arch_idx};
    info.b         = h.last_b;
    info.a         = h.last_a;
    info.date      = datestr(now());

    codes    = cell(3, 1);
    codes{1} = gen_octave_code(info);
    codes{2} = gen_c_code(info);
    codes{3} = gen_python_code(info);

    exts   = {"*.m", "*.h", "*.py"};
    titles = {"Save MATLAB/Octave Script", "Save C Header", "Save Python Script"};

    % Sized to match the main FDA Tool window
    e.fig = dsp_new_window("Export Code");

    uicontrol(e.fig, "Style", "text", ...
              "String", "Format:", ...
              "Units", "normalized", ...
              "Position", [0.03, 0.93, 0.12, 0.04], ...
              "HorizontalAlignment", "left", ...
              "FontWeight", "bold");

    e.popup_fmt = uicontrol(e.fig, "Style", "popupmenu", ...
                            "String", {"MATLAB / Octave (.m)", ...
                                       "C Header (.h)", ...
                                       "Python / SciPy (.py)"}, ...
                            "Units", "normalized", ...
                            "Position", [0.16, 0.93, 0.50, 0.04], ...
                            "Callback", @cb_fmt);

    e.txt_preview = uicontrol(e.fig, "Style", "edit", ...
                              "Max", 2, ...
                              "Enable", "inactive", ...
                              "HorizontalAlignment", "left", ...
                              "FontName", "Monospaced", ...
                              "FontSize", 9, ...
                              "Units", "normalized", ...
                              "Position", [0.03, 0.14, 0.94, 0.77]);

    uicontrol(e.fig, "Style", "pushbutton", ...
              "String", "Save to File", ...
              "Units", "normalized", ...
              "Position", [0.03, 0.03, 0.44, 0.08], ...
              "FontSize", 10, "FontWeight", "bold", ...
              "Callback", @cb_save);

    uicontrol(e.fig, "Style", "pushbutton", ...
              "String", "Close", ...
              "Units", "normalized", ...
              "Position", [0.53, 0.03, 0.44, 0.08], ...
              "FontSize", 10, ...
              "Callback", @(~,~) delete(e.fig));

    show_code();

    function show_code()
        idx = get(e.popup_fmt, "value");
        set(e.txt_preview, "String", ...
            strsplit(codes{idx}, "\n", "collapsedelimiters", false));
    end

    function cb_fmt(~, ~)
        show_code();
    end

    function cb_save(~, ~)
        idx = get(e.popup_fmt, "value");
        [fname, fpath] = uiputfile(exts{idx}, titles{idx});
        if isequal(fname, 0); return; end

        filepath = fullfile(fpath, fname);
        fid = fopen(filepath, "w");
        if fid == -1
            errordlg(sprintf("Cannot write to:\n%s", filepath), "Save Error");
            return;
        end
        fprintf(fid, "%s", codes{idx});
        fclose(fid);
        msgbox(sprintf("Code saved to:\n%s", filepath), "Export Successful");
    end
end

% ---------------------------------------------------------------
% Code generators
% ---------------------------------------------------------------

function code = gen_octave_code(info)
    hdr = sprintf([ ...
        "%% dsppack -- Filter Design\n" ...
        "%% Generated: %s\n" ...
        "%% Topology:  %s %s, Order %d\n" ...
        "%% Fs = %.0f Hz\n\n"], ...
        info.date, info.topo_name, info.type_name, info.N, info.Fs);

    body = sprintf([ ...
        "Fs = %.0f;\n\n" ...
        "b = %s;\n\n" ...
        "a = %s;\n\n"], ...
        info.Fs, ...
        fmt_vec(info.b, "[", "]", "     ", " ...", 5), ...
        fmt_vec(info.a, "[", "]", "     ", " ...", 5));

    vis = [ ...
        "[H, w] = freqz(b, a, 1024);\n", ...
        "wn = w / pi;   %% normalized frequency in units of pi rad/sample\n", ...
        "\n", ...
        "subplot(2,1,1);\n", ...
        "plot(wn, 20*log10(abs(H)), 'LineWidth', 2);\n", ...
        "title('Magnitude Response');\n", ...
        "xlabel('Normalized Frequency (\\times\\pi rad/sample)');\n", ...
        "ylabel('Magnitude (dB)');\n", ...
        "grid on;\n", ...
        "\n", ...
        "subplot(2,1,2);\n", ...
        "plot(wn, unwrap(angle(H)) * (180/pi), 'LineWidth', 2);\n", ...
        "title('Phase Response');\n", ...
        "xlabel('Normalized Frequency (\\times\\pi rad/sample)');\n", ...
        "ylabel('Phase (degrees)');\n", ...
        "grid on;\n"];

    code = [hdr, body, vis];
end

function code = gen_c_code(info)
    n_taps = numel(info.b);

    hdr = sprintf([ ...
        "/*\n" ...
        " * dsppack -- Filter Coefficients\n" ...
        " * Generated: %s\n" ...
        " * Topology:  %s %s, Order %d\n" ...
        " * Fs = %.0f Hz\n" ...
        " */\n\n"], ...
        info.date, info.topo_name, info.type_name, info.N, info.Fs);

    guard = sprintf([ ...
        "#ifndef DSPPACK_FILTER_H\n" ...
        "#define DSPPACK_FILTER_H\n\n"]);

    defs = sprintf([ ...
        "#define FILTER_ORDER    %d\n" ...
        "#define FILTER_NUM_TAPS %d\n" ...
        "#define FILTER_FS       %.1f\n\n"], ...
        info.N, n_taps, info.Fs);

    b_arr = sprintf([ ...
        "static const double filter_b[FILTER_NUM_TAPS] = %s;\n\n"], ...
        fmt_vec(info.b, "{\n    ", "\n}", "    ", "", 4));

    a_arr = sprintf([ ...
        "static const double filter_a[FILTER_NUM_TAPS] = %s;\n\n"], ...
        fmt_vec(info.a, "{\n    ", "\n}", "    ", "", 4));

    footer = "#endif /* DSPPACK_FILTER_H */\n";

    code = [hdr, guard, defs, b_arr, a_arr, footer];
end

function code = gen_python_code(info)
    hdr = sprintf([ ...
        "\"\"\"\n" ...
        "dsppack -- Filter Design\n" ...
        "Generated: %s\n" ...
        "Topology:  %s %s, Order %d\n" ...
        "Fs = %.0f Hz\n" ...
        "\"\"\"\n\n"], ...
        info.date, info.topo_name, info.type_name, info.N, info.Fs);

    imports = [ ...
        "import numpy as np\n", ...
        "from scipy import signal\n", ...
        "import matplotlib.pyplot as plt\n\n"];

    body = sprintf([ ...
        "Fs = %.0f\n\n" ...
        "b = %s\n\n" ...
        "a = %s\n\n"], ...
        info.Fs, ...
        fmt_vec(info.b, "np.array([", "])", "          ", "", 4), ...
        fmt_vec(info.a, "np.array([", "])", "          ", "", 4));

    vis = [ ...
        "w, h = signal.freqz(b, a, worN=1024)\n", ...
        "wn = w / np.pi   # normalized frequency in units of pi rad/sample\n\n", ...
        "fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))\n\n", ...
        "ax1.plot(wn, 20 * np.log10(np.abs(h)), linewidth=2)\n", ...
        "ax1.set_title('Magnitude Response')\n", ...
        "ax1.set_xlabel(r'Normalized Frequency ($\\times\\pi$ rad/sample)')\n", ...
        "ax1.set_ylabel('Magnitude (dB)')\n", ...
        "ax1.grid(True)\n\n", ...
        "ax2.plot(wn, np.unwrap(np.angle(h)) * (180 / np.pi), linewidth=2)\n", ...
        "ax2.set_title('Phase Response')\n", ...
        "ax2.set_xlabel(r'Normalized Frequency ($\\times\\pi$ rad/sample)')\n", ...
        "ax2.set_ylabel('Phase (degrees)')\n", ...
        "ax2.grid(True)\n\n", ...
        "plt.tight_layout()\n", ...
        "plt.show()\n"];

    code = [hdr, imports, body, vis];
end

% ---------------------------------------------------------------
% Format a coefficient vector with wrapping
% ---------------------------------------------------------------
function s = fmt_vec(v, open_str, close_str, indent, cont, per_line)
    s = open_str;
    for k = 1:numel(v)
        if k > 1
            s = [s, ", "];
            if mod(k - 1, per_line) == 0
                s = [s, cont, "\n", indent];
            end
        end
        s = [s, sprintf("%.10g", v(k))];
    end
    s = [s, close_str];
end
