#!/usr/bin/env python3
"""Baut cw.grc programmatisch aus den einzelnen epy_*.py-Quelldateien zusammen,
um Escaping-Fehler beim Von-Hand-Schreiben des gefalteten YAML-Strings zu
vermeiden. Nach jeder Aenderung an einer epy_*.py-Datei hier erneut ausfuehren.
"""
import yaml


def read(path):
    with open(path) as f:
        return f.read()


def block(name, id_, params, states_extra=None, io_cache=None):
    states = {
        "bus_sink": False, "bus_source": False, "bus_structure": None,
        "coordinate": [0, 0], "rotation": 0, "state": "enabled",
    }
    if states_extra:
        states.update(states_extra)
    if io_cache:
        states["_io_cache"] = io_cache
    return {"name": name, "id": id_, "parameters": params, "states": states}


def main():
    nf_src = read("epy_noise_floor.py")
    tagger_src = read("epy_element_tagger.py")
    decoder_src = read("epy_decoder.py")
    afc_src = read("epy_afc.py")

    doc = {
        "options": {
            "parameters": {
                "author": "berndewert", "catch_exceptions": "True",
                "category": "[GRC Hier Blocks]", "cmake_opt": "", "comment": "",
                "copyright": "", "description": "CW-Demodulator Stufe 3: adaptive Geschwindigkeit, Warm-up-Sperre, AFC",
                "gen_cmake": "On", "gen_linking": "dynamic",
                "generate_options": "qt_gui", "hier_block_src_path": ".:",
                "id": "cw", "max_nouts": "0", "output_language": "python",
                "placement": "(0,0)", "qt_qss_theme": "", "realtime_scheduling": "",
                "run": "True", "run_command": "{python} -u {filename}",
                "run_options": "prompt", "sizing_mode": "fixed",
                "thread_safe_setters": "", "title": "CW-Demodulator Stufe 3",
                "window_size": "(1000,1000)",
            },
            "states": {"bus_sink": False, "bus_source": False, "bus_structure": None,
                        "coordinate": [8, 8], "rotation": 0, "state": "enabled"},
        },
        "blocks": [
            block("samp_rate", "variable", {"comment": "", "value": "8000"},
                  {"coordinate": [184, 12]}),
            block("wpm", "variable",
                  {"comment": "Anfangs-/Referenzschaetzung; die eigentliche Geschwindigkeit wird live nachgefuehrt (siehe CW Element Tagger)",
                   "value": "20"}, {"coordinate": [328, 12]}),
            block("pitch", "variable", {"comment": "BFO-Tonhoehe (Startwert, wird von der AFC nachgefuehrt)", "value": "700"},
                  {"coordinate": [456, 12]}),
            block("cw_bw", "variable",
                  {"comment": "Akquisitionsbandbreite: Nutzbandbreite (4*WPM) plus AFC-Fangbereich",
                   "value": "max(4.0*wpm, 100.0) + 80.0"}, {"coordinate": [584, 12]}),
            block("decim", "variable", {"comment": "", "value": "8"}, {"coordinate": [712, 12]}),
            block("working_rate", "variable", {"comment": "", "value": "samp_rate/decim"},
                  {"coordinate": [840, 12]}),

            block("blocks_wavfile_source_0", "blocks_wavfile_source", {
                "affinity": "", "alias": "", "comment": "", "file": "cw_test_20wpm.wav",
                "maxoutbuf": "0", "minoutbuf": "0", "nchan": "1", "repeat": "False",
            }, {"coordinate": [16, 156]}),

            block("blocks_throttle_0", "blocks_throttle", {
                "affinity": "", "alias": "", "comment": "", "ignoretag": "True",
                "maxoutbuf": "0", "minoutbuf": "0", "samples_per_second": "samp_rate",
                "type": "float", "vlen": "1",
            }, {"coordinate": [104, 228]}),

            block("freq_xlating_fir_filter_xxx_0", "freq_xlating_fir_filter_xxx", {
                "affinity": "", "alias": "cw_xlate", "comment":
                    "Mischt pitch nach Basisband und filtert (Float->Complex, Real Taps) - "
                    "ersetzt Bandpassfilter+Hilbert aus Stufe 2 durch einen Block, der per "
                    "Message-Port 'freq' von der AFC live nachgestimmt werden kann.",
                "center_freq": "pitch", "decim": "decim", "maxoutbuf": "0", "minoutbuf": "0",
                "samp_rate": "samp_rate", "type": "fcf",
                "taps": "firdes.low_pass(1.0, samp_rate, cw_bw/2, cw_bw/2, window.WIN_HAMMING)",
            }, {"coordinate": [232, 212]}),

            block("blocks_complex_to_mag_0", "blocks_complex_to_mag", {
                "affinity": "", "alias": "", "comment": "", "maxoutbuf": "0", "minoutbuf": "0", "vlen": "1",
            }, {"coordinate": [432, 172]}),

            block("epy_block_0", "epy_block", {
                "_source_code": nf_src, "affinity": "", "alias": "cw_noise_floor", "comment": "",
                "maxoutbuf": "0", "minoutbuf": "0", "num_bins": "20", "ref_wpm": "30.0",
                "samp_rate": "working_rate",
            }, {"coordinate": [584, 172]},
                io_cache="('CW Noise-Floor Normalizer', 'blk', [('samp_rate', '1000.0'), "
                         "('ref_wpm', '30.0'), ('num_bins', '20')], [('0', 'float', 1)], "
                         "[('0', 'float', 1)], '', ['num_bins', 'ref_wpm', 'samp_rate'])"),

            block("blocks_threshold_ff_0", "blocks_threshold_ff", {
                "affinity": "", "alias": "", "comment":
                    "Hysterese auf das rauschflurnormierte Ratio-Signal",
                "high": "6.0", "init": "0", "low": "3.0", "maxoutbuf": "0", "minoutbuf": "0",
            }, {"coordinate": [792, 204]}),

            block("epy_block_1", "epy_block", {
                "_source_code": tagger_src, "affinity": "", "alias": "cw_element_tagger",
                "comment": "", "mu": "0.7", "maxoutbuf": "0", "minoutbuf": "0",
                "samp_rate": "working_rate", "wpm": "wpm",
            }, {"coordinate": [984, 204]},
                io_cache="('CW Element Tagger', 'blk', [('samp_rate', '1000.0'), ('wpm', '20.0'), "
                         "('mu', '0.7')], [('0', 'float', 1)], [('0', 'float', 1)], '', "
                         "['mu', 'samp_rate', 'wpm'])"),

            block("epy_block_2", "epy_block", {
                "_source_code": decoder_src, "affinity": "", "alias": "cw_decoder", "comment": "",
                "maxoutbuf": "0", "minoutbuf": "0",
            }, {"coordinate": [1192, 276]},
                io_cache="('CW Decoder', 'blk', [], [('0', 'float', 1)], "
                         "[('out', 'message', 1)], '', [])"),

            block("epy_block_3", "epy_block", {
                "_source_code": afc_src, "affinity": "", "alias": "cw_afc", "comment": "",
                "guard_samples": "15", "loop_gain": "0.05", "maxoutbuf": "0", "minoutbuf": "0",
                "pitch": "pitch", "samp_rate": "working_rate",
            }, {"coordinate": [432, 340]},
                io_cache="('CW AFC', 'blk', [('samp_rate', '1000.0'), ('pitch', '700.0'), "
                         "('loop_gain', '0.05'), ('guard_samples', '15')], "
                         "[('0', 'complex', 1)], [('freq', 'message', 1)], '', "
                         "['guard_samples', 'loop_gain', 'pitch', 'samp_rate'])"),

            block("blocks_message_debug_0", "blocks_message_debug", {
                "affinity": "", "alias": "", "comment": "", "en_uvec": "True",
            }, {"coordinate": [1400, 204]}),

            block("qtgui_time_sink_x_0", "qtgui_time_sink_x", {
                "affinity": "", "alias": "", "alpha1": "1.0", "alpha10": "1.0", "alpha2": "1.0",
                "alpha3": "1.0", "alpha4": "1.0", "alpha5": "1.0", "alpha6": "1.0", "alpha7": "1.0",
                "alpha8": "1.0", "alpha9": "1.0", "autoscale": "True", "axislabels": "True",
                "color1": "blue", "color10": "dark blue", "color2": "red", "color3": "green",
                "color4": "black", "color5": "cyan", "color6": "magenta", "color7": "yellow",
                "color8": "dark red", "color9": "dark green", "comment": "", "ctrlpanel": "True",
                "entags": "True", "grid": "True", "gui_hint": "", "label1": "Huellkurve",
                "label10": "Signal 10", "label2": "Ratio", "label3": "Signal 3", "label4": "Signal 4",
                "label5": "Signal 5", "label6": "Signal 6", "label7": "Signal 7", "label8": "Signal 8",
                "label9": "Signal 9", "legend": "True", "marker1": "-1", "marker10": "-1",
                "marker2": "-1", "marker3": "-1", "marker4": "-1", "marker5": "-1", "marker6": "-1",
                "marker7": "-1", "marker8": "-1", "marker9": "-1", "name": '""', "nconnections": "2",
                "size": "2048", "srate": "working_rate", "stemplot": "False", "style1": "1",
                "style10": "1", "style2": "1", "style3": "1", "style4": "1", "style5": "1",
                "style6": "1", "style7": "1", "style8": "1", "style9": "1", "tr_chan": "0",
                "tr_delay": "0", "tr_level": "0.0", "tr_mode": "qtgui.TRIG_MODE_FREE",
                "tr_slope": "qtgui.TRIG_SLOPE_POS", "tr_tag": '""', "type": "float",
                "update_time": "0.10", "width1": "1", "width10": "1", "width2": "1", "width3": "1",
                "width4": "1", "width5": "1", "width6": "1", "width7": "1", "width8": "1",
                "width9": "1", "ylabel": "Amplitude", "ymax": "10", "ymin": "0", "yunit": '""',
            }, {"coordinate": [792, 60]}),
        ],
        "connections": [
            ["blocks_wavfile_source_0", "0", "blocks_throttle_0", "0"],
            ["blocks_throttle_0", "0", "freq_xlating_fir_filter_xxx_0", "0"],
            ["freq_xlating_fir_filter_xxx_0", "0", "blocks_complex_to_mag_0", "0"],
            ["freq_xlating_fir_filter_xxx_0", "0", "epy_block_3", "0"],
            ["blocks_complex_to_mag_0", "0", "epy_block_0", "0"],
            ["blocks_complex_to_mag_0", "0", "qtgui_time_sink_x_0", "0"],
            ["epy_block_0", "0", "blocks_threshold_ff_0", "0"],
            ["epy_block_0", "0", "qtgui_time_sink_x_0", "1"],
            ["blocks_threshold_ff_0", "0", "epy_block_1", "0"],
            ["epy_block_1", "0", "epy_block_2", "0"],
            ["epy_block_2", "out", "blocks_message_debug_0", "print"],
        ],
        "metadata": {"file_format": 1, "grc_version": "3.10.12.0"},
    }

    # msg_connect wird in GRC ueber ganz normale connections-Eintraege mit
    # message-Ports abgebildet (kein Sonderfall im Dateiformat):
    doc["connections"].append(["epy_block_3", "freq", "freq_xlating_fir_filter_xxx_0", "freq"])

    with open("cw.grc", "w") as f:
        yaml.dump(doc, f, sort_keys=False, allow_unicode=True, width=100)
    print("Geschrieben: cw.grc")


if __name__ == "__main__":
    main()
