# This file is a part of SIS3316Digitizers.jl, licensed under the MIT License (MIT).

const shift_by_one = (x -> signed(x + 1), x -> unsigned(x) - 1)

function linear_conv(;offset::Real = 1, scale::Real = 1)
    (
        x -> fma(x, scale, offset),
        x -> trunc(UInt32, x - offset / scale)
    )
end

const word_byte_addr_conv = (
    x -> x * typeof(x)(4),
    x -> begin
        @argcheck rem(x, 4) == 0
        div(x, 4)
    end
)

function bool_val_conv(a, b)
    (
        x::Bool -> x ? b : a,
        x -> begin
            @argcheck x == a || x == b
            x == b ? true : false
        end
    )
end

const all_channels = 1:16
const all_fpga_groups = 1:4
const all_banks = 1:2
export all_channels, all_fpga_groups, all_banks

# VME interface registers
# -----------------------

const led_bits = BitGroup{JKRW}(
    mode        = Bit{JKRW}(4), # Application Mode
    state       = Bit{JKRW}(0), # on/off
)

"""Control/Status Register (0x00, `control_status_reg`)"""
const control_status_reg = Register{JKRW,RegVal}(
    MemAddr(0x00),
    (
        reboot_fpgas    = Bit{JKRW}(15),    # Status Reboot FPGAs
        led2            = led_bits << 2,    # Status LED 2 bits
        led1            = led_bits << 1,    # Status LED 1 bits
        ledU            = led_bits << 0,    # Status LED U bits
    )
)
export control_status_reg

"""Module Id. and Firmware Revision Register (0x04, `modid_fw_reg`)"""
const modid_fw_reg = Register{RO,RegVal}(
    MemAddr(0x04),
    (
        module_id       = BitRange{RO}(16:31),  # Module Id. (BCD)
        major_revision  = BitRange{RO}( 8:15),  # Major Revision (BCD)
        minor_revision  = BitRange{RO}( 0: 7),  # Minor Revision (BCD)
    )
)
export modid_fw_reg

# Not implemented: VME GW: Interrupt configuration register (0x8)
# Not implemented: VME GW: Interrupt control register (0xC)
# Not implemented: SIS3316: UDP Protocol Configuration Register (0x08)
# Not implemented: SIS3316: Last UDP Acknowledge Status Register (0x0C)

"""
    interface_access_arbitration_control_reg

(Link) Interface Access Arbitration Control Register (0x10, `interface_access_arbitration_control_reg`)

Note: Only write complete Register.
"""
const interface_access_arbitration_control_reg = Register{RW,RegVal}(
    MemAddr(0x10),
    (
        kill_other_if_req   = Bit{RW}(31),  # Kill of other interface request bit command
        # other_if_req_stat = Bit{RW}(21),  # Status of other interface grant bit
        # own_if_req_stat   = Bit{RW}(20),  # Status of own interface grant bit
        other_if_req_stat   = Bit{RW}(17),  # Status of other interface request bit
        own_if_req_stat     = Bit{RW}(16),  # Status of own interface request bit
        own_if_req          = Bit{RW}( 0),  # Own interface request bit
    )
)
export interface_access_arbitration_control_reg

# Not implemented: Broadcast Setup Register (0x14)

"""Hardware Version Register (0x1C, `hardware_version_reg`)"""
const hardware_version_reg = Register{RO,RegVal}(
    MemAddr(0x1C),
    (
        version         = BitRange{RO}(0:3),    # Hardware Version
    )
)
export hardware_version_reg

# ADC interface registers
# -----------------------

"""Temperature Register (0x20, R/W, `internal_temperature_reg`)"""
const internal_temperature_reg = Register{RW,RegVal}(
    MemAddr(0x20),
    (
        temperature     = BitRange{RO}(0:9, linear_conv(scale = 0.25)), # Temperature Data
    )
)
export internal_temperature_reg

# Not implemented: Onewire EEPROM Control Register (0x24)

"""Serial Number Register (0x28, R/O, `serial_number_reg`)"""
const serial_number_reg = Register{RO,RegVal}(
    MemAddr(0x28),
    (
        dhcp_option     = BitRange{RO}(24:31),  # DHCP Option Value
        serno_invalid   = Bit{RO}(16),            # Serial Number Not Valid Flag
        serno           = BitRange{RO}(0:15),   # Serial Number
    )
)
export serial_number_reg

# Not implemented: Internal Transfer Speed register (0x2C)
# Not implemented: ADC FPGA Boot control register (0x30)
# Not implemented: SPI Flash Control/Status register (0x34)
# Not implemented: SPI Flash Data register (0x38)

"""
Programmable ADC Clock I2C register (0x40, R/W, `clk_osc_i2c_reg`)
"""
const clk_osc_i2c_reg = Register{RW,RegVal}(
    MemAddr(0x40),
    (
        read_byte       = Bit{RW}(13),      # Read byte, put ACK
        write_byte      = Bit{RW}(12),      # Write byte, put ACK
        stop            = Bit{RW}(11),      # STOP
        repeat_start    = Bit{RW}(10),      # Repeat START
        start           = Bit{RW}( 9),      # START
        ack             = Bit{RW}( 8),      # Read: Received Ack on write cycle; Write: Ack on read cycle
        data            = BitRange{RW}(0:7), # Read: Read data; Write: Write data
    )
)
export clk_osc_i2c_reg

# Not implemented: Programmable MGT1 Clock I2C register (0x44, not used, according to manual)
# Not implemented: Programmable MGT2 Clock I2C register (0x48, not used, according to manual)
# Not implemented: Programmable DDR3 Clock I2C register (0x4C, not used, according to manual)

"""ADC Sample Clock distribution control Register (0x50, `sample_clock_distctl_reg`)"""
const sample_clock_distctl_reg = Register{RW,RegVal}(
    MemAddr(0x50),
    (
        adc_clk_mux     = BitRange{RW}(0:1),    # ADC Sample Clock Multiplexer select bits
    )
    # (0: Internal osc.; 1: VXS; 2: FP-LVDS; 3: FP-NIM)
)
export sample_clock_distctl_reg

"""
    nim_clk_multiplier_spi_reg

External NIM Clock Multiplier SPI Register (0x54, R/W)

* (Cmd Bit 1, Cmd Bit 0) == (0, 0): Execute SPI Write/Read Cmd
* (Cmd Bit 1, Cmd Bit 0) == (0, 1): Reset Cmd

Note: Only write complete Register.
"""
const nim_clk_multiplier_spi_reg = Register{RW,RegVal}(
    MemAddr(0x54),
    (
        cmd_bit_1       = Bit{RW}(31),          # Write: Cmd Bit 1; Read: Read/Write Cmd BUSY Flag
        cmd_bit_0       = Bit{RW}(30),          # Write: Cmd Bit 0; Read: Reset Cmd BUSY Flag
        instruction     = BitRange{RW}(8:15),   # Write: Instruction Byte
        addr_data       = BitRange{RW}(0:7),    # Write: Address/Data; Read: Data
    )
)
export nim_clk_multiplier_spi_reg

"""FP-Bus control Register (0x58, R/W, `fp_lvds_bus_control_reg`)"""
const fp_lvds_bus_control_reg = Register{RW,RegVal}(
    MemAddr(0x58),
    (
      # FP-Bus Sample Clock Out MUX bit
      # Selects the source of the FP-Bus Sample Clock
      #
      # * Value 0: Onboard programmable oscillator ( after power up: 125MHz)
      # * Value 1: External clock from NIM connector CI (via programmable clock multiplier)
      smpl_clk_out_mux  = Bit{RW}(5),

      # FP-Bus Sample Clock Out Enable
      # Enables the Sample Clock:the FP- Bus (only on one SIS3316)
      smpl_clk_out_en   = Bit{RW}(4),

      # FP-Bus Status Lines Output Enable
      # Enables the Status Lines:the FP-Bus (on all SIS3316)
      stat_lines_out_en = Bit{RW}(1),

      # FP-Bus Control Lines Output Enable
      # Enables the Control Lines:the FP-Bus (only on one SIS3316)
      ctrl_lines_out_en = Bit{RW}(0),
    )
)
export fp_lvds_bus_control_reg

"""NIM Input Control/Status Register (0x5C, R/W, `nim_input_control_reg`"""
const nim_input_control_reg = Register{RW,RegVal}(
    0x0000005C,
    (
        input_sig_ui_stat = Bit{RO}(25),    # Write: reserved; Read: Status of NIM Input signal UI
        ext_in_ui_stat    = Bit{RO}(24),    # Write: reserved; Read: Status of External NIM Input UI
        input_sig_ti_stat = Bit{RO}(21),    # Write: reserved; Read: Status of NIM Input signal UI
        ext_in_ti_stat    = Bit{RO}(20),    # Write: reserved; Read: Status of External NIM Input UI
        in_ui_pss_en      = Bit{RW}(13),    # NIM Input UI as PPS Enable
        in_ui_veto_en     = Bit{RW}(12),    # NIM Input UI as Veto Enable
        in_ui_func        = Bit{RW}(11),    # Set NIM Input UI Function
        in_ui_level_sens  = Bit{RW}(10),    # NIM Input UI Level sensitive
        in_ui_inv         = Bit{RW}( 9),    # NIM Input UI Invert
        in_ui_tsclear_en  = Bit{RW}( 8),    # NIM Input UI as Timestamp Clear Enable
        in_ti_func        = Bit{RW}( 7),    # Set NIM Input TI Function
        in_ti_level_sens  = Bit{RW}( 6),    # NIM Input TI Level sensitive
        in_ti_inv         = Bit{RW}( 5),    # NIM Input TI Invert
        in_ti_trig_en     = Bit{RW}( 4),    # NIM Input TI as Trigger Enable
        in_ci_func        = Bit{RW}( 3),    # Set NIM Input CI Function
        in_ci_level_sens  = Bit{RW}( 2),    # NIM Input CI Level sensitive
        in_ci_inv         = Bit{RW}( 1),    # NIM Input CI Invert
        in_ci_en          = Bit{RW}( 0),    # NIM Input CI Enable
    )
)
export nim_input_control_reg

const fpga_bits = BitGroup{RW}(
    mem_addr_thresh = Bit{RO}(1),  # Write: reserved; Read: Status of Memory Address Threshold Flag
    smpl_busy       = Bit{RO}(0),  # Write: reserved; Read: Status of Sample Logic Busy
)

"""
    acquisition_control_status_reg

Acquisition control/status Register (0x60, R/W)

* single_bank_mode value: 0 = double bank mode, 1 = single bank mode
* nim_ui_bank_swap value: 0/1 = disable/enable toggling of the active Sample Bank with a signal on NIM Input UI
* nim_ti_bank_swap value: 0/1 = disable/enable toggling of the active Sample Bank with a signal on NIM Input TI
* nim_bank_swap_en value: 0/1 = "Sample Bank Swap Control with NIM Input TI/UI" Logic is disabled/enabled
"""
const acquisition_control_status_reg = Register{RW,RegVal}(
    MemAddr(0x60),
    (
        fpga = BitGroup{RW}(
            fpga1 = fpga_bits << 24,  # FPGA 1 bits
            fpga2 = fpga_bits << 26,  # FPGA 2 bits
            fpga3 = fpga_bits << 28,  # FPGA 3 bits
            fpga4 = fpga_bits << 30,  # FPGA 4 bits
        ),
        pps_latch_stat    = Bit{RO}(23),  # Write: reserved; Read: Status of "PPS latch bit"
        nim_bank_swap_en  = Bit{RO}(22),  # Write: reserved; Read: Status of "Sample Bank Swap Control with NIM Input TI/UI" Logic enabled
        fp_addr_thresh    = Bit{RO}(21),  # Write: reserved; Read: Status of FP-Bus-In Status 2: Address Threshold flag
        fp_smpl_busy      = Bit{RO}(20),  # Write: reserved; Read: Status of FP-Bus-In Status 1: Sample Logic busy
        mem_addr_thresh   = Bit{RO}(19),  # Write: reserved; Read: Status of Memory Address Threshold flag (OR)
        smpl_busy         = Bit{RO}(18),  # Write: reserved; Read: Status of Sample Logic Busy (OR)
        smpl_armed_bank   = Bit{RO}(17, shift_by_one),  # Write: reserved; Read: Status of ADC Sample Logic Armed On Bank2 flag
        smpl_armed        = Bit{RO}(16),  # Write: reserved; Read: Status of ADC Sample Logic Armed
        ext_trig_dis_on_b = Bit{RW}(15),  # External Trigger Disable with internal Busy select
        int_trig_to_ext   = Bit{RW}(14),  # Feedback Selected Internal Trigger as External Trigger Enable
        nim_ui_bank_swap  = Bit{RW}(13),  # NIM Input UI as "disarm Bankx and arm alternate Bank" command Enable
        nim_ti_bank_swap  = Bit{RW}(12),  # NIM Input TI as "disarm Bankx and arm alternate Bank" command Enable
        local_veto        = Bit{RW}(11),  # Local Veto function as Veto Enable
        ext_tsclear_en    = Bit{RW}(10),  # External Timestamp-Clear function Enable
        ext_trig_as_veto  = Bit{RW}( 9),  # External Trigger function as Veto Enable
        ext_trig_en       = Bit{RW}( 8),  # External Trigger function as Trigger Enable
        fp_smpl_ctrl_en   = Bit{RW}( 7),  # FP-Bus-In Sample Control Enable
        fp_ctrl2_en       = Bit{RW}( 6),  # FP-Bus-In Control 2 Enable
        fp_ctrl2_as_veto  = Bit{RW}( 5),  # FP-Bus-In Control 1 as Veto Enable
        fp_ctrl1_as_trig  = Bit{RW}( 4),  # FP-Bus-In Control 1 as Trigger Enable
        single_bank_mode  = Bit{RW}( 0),  # Single Bank Mode Enable (reserved)
    )
)
export acquisition_control_status_reg

"""
    lookup_table_control_reg

Trigger Coincidence Lookup Table Control Register (0x64, R/W)

Note: Only write complete Register.
"""
const lookup_table_control_reg = Register{RW,RegVal}(
    MemAddr(0x64),
    (
        table_clear         = Bit{RW}(31),          # Write: Lookup Table Clear command; Read: Status Clear Busy
        table2_pulse_len    = BitRange{RW}(8:15),   # Lookup Table 2 Coincidence output pulse length
        table1_pulse_len    = BitRange{RW}(0:7),    # Lookup Table 1 Coincidence output pulse length
    )
)
export lookup_table_control_reg

"""
    lookup_table_addr_reg

Trigger Coincidence Lookup Table Address Register (0x68, R/W)

Note: Only write complete Register.
"""
const lookup_table_addr_reg= Register{RW,RegVal}(
    MemAddr(0x68),
    (
        mask        = BitRange{RW}(16:31),  # Lookup Table 1 and 2  Channel Trigger Mask
        rw_addr     = BitRange{RW}(0:15),   # Lookup Table 1 and 2 Write/Read Address
    )
)
export lookup_table_addr_reg

"""
    lookup_table_data_reg

Trigger Coincidence Lookup Table Data register (0x6C, R/W)

Note: Only write complete Register.
"""
const lookup_table_data_reg = Register{RW,RegVal}(
    MemAddr(0x6C),
    (
        table2_     = Bit{RW}(1),   # Lookup Table 2 Coincidence validation bit
        table1_     = Bit{RW}(0),   # Lookup Table 1 Coincidence validation bit
    )
)
export lookup_table_data_reg

# Not implemented: LEMO Out "CO" Select register (0x70)
# Not implemented: LEMO Out "TO" Select register (0x74)
# Not implemented: LEMO Out "UO" Select register (0x78)

"""Internal Trigger Feedback Select register (RW, 0x7C, `internal_trigger_feedback_select_reg`)"""
const internal_trigger_feedback_select_reg = Register{RW,RegVal}(
    MemAddr(0x7C),
    (
        lut1_coind_sel      = Bit{RW}(24),  # Select Lookup Table 1 Coincidence stretched output pulse
        sel_sum_trig = BitGroup{RW}(
            grp1    = Bit{RW}(16),  # Select internal SUM-Trigger stretched pulse ch1-4
            grp2    = Bit{RW}(17),  # Select internal SUM-Trigger stretched pulse ch5-8
            grp3    = Bit{RW}(18),  # Select internal SUM-Trigger stretched pulse ch19-12
            grp4    = Bit{RW}(19),  # Select internal SUM-Trigger stretched pulse ch13-16
        ),
        sel_trig = BitGroup{RW}(
            ch1     = Bit{RW}( 0),  # Select internal Trigger stretched pulse ch 1
            ch2     = Bit{RW}( 1),  # Select internal Trigger stretched pulse ch 2
            ch3     = Bit{RW}( 2),  # Select internal Trigger stretched pulse ch 3
            ch4     = Bit{RW}( 3),  # Select internal Trigger stretched pulse ch 4
            ch5     = Bit{RW}( 4),  # Select internal Trigger stretched pulse ch 5
            ch6     = Bit{RW}( 5),  # Select internal Trigger stretched pulse ch 6
            ch7     = Bit{RW}( 6),  # Select internal Trigger stretched pulse ch 7
            ch8     = Bit{RW}( 7),  # Select internal Trigger stretched pulse ch 8
            ch9     = Bit{RW}( 8),  # Select internal Trigger stretched pulse ch 9
            ch10    = Bit{RW}( 9),  # Select internal Trigger stretched pulse ch 10
            ch11    = Bit{RW}(10),  # Select internal Trigger stretched pulse ch 11
            ch12    = Bit{RW}(11),  # Select internal Trigger stretched pulse ch 12
            ch13    = Bit{RW}(12),  # Select internal Trigger stretched pulse ch 13
            ch14    = Bit{RW}(13),  # Select internal Trigger stretched pulse ch 14
            ch15    = Bit{RW}(14),  # Select internal Trigger stretched pulse ch 15
            ch16    = Bit{RW}(15),  # Select internal Trigger stretched pulse ch 16
        ),
    )
)
export internal_trigger_feedback_select_reg

"""ADC FPGA SPI BUSY Status register (0xA4, R/O, `fpga_spi_busy_status_reg`)"""
const fpga_spi_busy_status_reg = Register{RO,RegVal}(
    MemAddr(0xA4),
    (
        busy        = Bit{RO}(31),  # ADC FPGAx: Busy flag (or of ADC FPGA 1:4 Busy flags)
        fpga = BitGroup{RO}(
            fpga1   = Bit{RO}( 0),  # ADC FPGA 1: Busy flag
            fpga2   = Bit{RO}( 1),  # ADC FPGA 2: Busy flag
            fpga3   = Bit{RO}( 2),  # ADC FPGA 3: Busy flag
            fpga4   = Bit{RO}( 3),  # ADC FPGA 4: Busy flag
        )
    )
)
export fpga_spi_busy_status_reg

# Not implemented: Prescaler Output Pulse Divider register (0xB8)
# Not implemented: Prescaler Output Pulse Length register (0xBC)

# ADC key registers
# -----------------

"""Key address: Register Reset (0x400, `key_reset_addr`)"""
const key_reset_addr = MemAddr(0x400)
export key_reset_addr

"""Key address: User function logic (0x404, `key_user_function_addr`)"""
const key_user_function_addr = MemAddr(0x404)
export key_user_function_addr

"Key address: Arm sample logic (0x410, `key_arm`)"
const key_arm_addr = MemAddr(0x410)
export key_arm_addr

"""Key address: Disarm sample logic (0x414, `key_disarm_addr`)"""
const key_disarm_addr = MemAddr(0x414)
export key_disarm_addr

"""Key address: Trigger (0x418, `key_trigger_addr`)"""
const key_trigger_addr = MemAddr(0x418)
export key_trigger_addr

"""Key address: Timestamp Clear (0x41C, `key_timestamp_clear_addr`)"""
const key_timestamp_clear_addr = MemAddr(0x41C)
export key_timestamp_clear_addr

"""Key address: Disarm Bankx and Arm Bank1 (0x420, `key_disarm_and_arm_bank1_addr`)"""
const key_disarm_and_arm_bank1_addr = MemAddr(0x420)
export key_disarm_and_arm_bank1_addr

"""Key address: Disarm Bankx and Arm Bank2 (0x424, `key_disarm_and_arm_bank2_addr`)"""
const key_disarm_and_arm_bank2_addr = MemAddr(0x424)
export key_disarm_and_arm_bank2_addr

"""Key address: Disarm Bankx and Arm Banky (`key_disarm_and_arm_bank_addr`)"""
const key_disarm_and_arm_bank_addr = (
    key_disarm_and_arm_bank1_addr,
    key_disarm_and_arm_bank2_addr,
)
export key_disarm_and_arm_bank_addr

"""Key address: Enable Sample Bank Swap Control with NIM Input TI/UI Logic (0x428, `key_enable_sample_bank_swap_control_with_nim_input_addr`)"""
const key_enable_sample_bank_swap_control_with_nim_input_addr = MemAddr(0x428)
export key_enable_sample_bank_swap_control_with_nim_input_addr

"""Key address: Disable Prescaler Output Pulse Divider logic (0x42C, `key_disable_prescaler_output_pulse_divider_addr`)"""
const key_disable_prescaler_output_pulse_divider_addr = MemAddr(0x42C)
export key_disable_prescaler_output_pulse_divider_addr

"""Key address: PPS_Latch_Bit_clear (0x430, `key_pps_latch_bit_clear_addr`)"""
const key_pps_latch_bit_clear_addr = MemAddr(0x430)
export key_pps_latch_bit_clear_addr

"""Key address: Reset ADC-FPGA-Logic (0x434, `key_adc_fpga_reset_addr`)"""
const key_adc_fpga_reset_addr = MemAddr(0x434)
export key_adc_fpga_reset_addr

"""Key address: ADC Clock DCM/PLL Reset (0x438, `key_adc_clock_dcm_reset_addr`)"""
const key_adc_clock_dcm_reset_addr = MemAddr(0x438)
export key_adc_clock_dcm_reset_addr

# ADC FPGA Registers
# ------------------

function adc_fpga_registers(mklayout::Function, ::Val{Acc}, rel_addr::Unsigned) where Acc
    (
        ch01_04 = Register{RW,RegVal}(MemAddr(0x1000 + rel_addr), mklayout()),
        ch05_08 = Register{RW,RegVal}(MemAddr(0x2000 + rel_addr), mklayout()),
        ch09_12 = Register{RW,RegVal}(MemAddr(0x3000 + rel_addr), mklayout()),
        ch13_16 = Register{RW,RegVal}(MemAddr(0x4000 + rel_addr), mklayout()),
    )
end

"""
    input_tap_delay_regs

ADC Input Tap Delay Registers (R/W)

Note: Only write complete Register.
"""
const input_tap_delay_regs = adc_fpga_registers(Val(RW), 0x000) do
    (
        half_smpl_delay     = Bit{RW}(12),  # Add 1⁄2 Sample Clock period delay bit (since ADC Version V-0250-0004 and V-0125-0004)
        calib               = Bit{RW}(11),  # Calibration
        lnk_err_latch_clr   = Bit{RW}(10),  # Clear Link Error Latch bits
        sel_ch_34           = Bit{RW}(9),   # Ch. 3 and 4 select
        sel_ch_12           = Bit{RW}(8),   # Ch. 1 and 2 select
        tap_delay           = BitRange{RW}(0:7), # Tap delay value (times 40ps, max. 1⁄2 Sample Clock period)
    )
end
export input_tap_delay_regs

const analog_ctrl_chbits = BitGroup{RW}(
    term_dis    = Bit{RW}(2),           # Disable 50 Ohm Termination
    gain_ctrl   = BitRange{RW}(0:1),    # Gain Control
)

"""
    analog_ctrl_regs

ADC Gain and Termination Control Registers (R/W, `analog_ctrl_reg`)

Gain control value:

    * 0: 5 V
    * 1: 2 V
    * 2: 1.9 V
    * 3: 1.9 V
"""
const analog_ctrl_regs = adc_fpga_registers(Val(RW), 0x004) do
    (
        ch = BitGroup{RW}(
            ch1 = analog_ctrl_chbits <<  0,  # Ch. 1 bits
            ch2 = analog_ctrl_chbits <<  8,  # Ch. 2 bits
            ch3 = analog_ctrl_chbits << 16,  # Ch. 3 bits
            ch4 = analog_ctrl_chbits << 24,  # Ch. 4 bits
        ),
    )
end
export analog_ctrl_regs

"""ADC Offset (DAC) Control Registers (W/O, `dac_offset_ctrl_reg`)"""
const dac_offset_ctrl_regs = adc_fpga_registers(Val(WO), 0x008) do
    (
        crtl_mode         = BitRange{WO}(29:31),  # DAC Ctrl Mode
        command           = BitRange{WO}(24:27),  # DAC Command
        dac_addr          = BitRange{WO}(20:23),  # DAC Address
        data              = BitRange{WO}(4:19),   #  DAC Data
    )
end
export dac_offset_ctrl_regs

"""ADC Offset (DAC) Readback Registers (R/W, `dac_offset_readback_reg`)"""
const dac_offset_readback_regs = adc_fpga_registers(Val(RO), 0x108) do
    (
        data    = BitRange{RO}(0:15),   # DAC Read Data
    )
end
export dac_offset_readback_regs

@enum SPICmd::UInt8 begin
    NoOp            = 0  # no function
    Reserved        = 1  # Reserved (ADC Synch Cmd)
    Write           = 2  # Write Cmd (relevant bits: 22 and 20:0)
    Read            = 3  # Read Cmd (relevant bits: 22 and 20:0)
end

"""
    spi_ctrl_reg

ADC SPI Control Registers (R/W)

Note: Only write complete Register.
"""
const spi_ctrl_regs = adc_fpga_registers(Val(RW), 0x00c) do
    (
        cmd         = BitRange{RW}(30:31, SPICmd), # Command
        data_out_en = Bit{RW}(24),          # ADC Data Output Enable
        sel_ch34    = Bit{RW}(22),          # Select ADCx ch3/ch4 bit
        rw_addr     = BitRange{RW}(8:20),   # Address
        data        = BitRange{RW}(0:7),    # Write Data
    )
end
export spi_ctrl_regs

"""ADC SPI Readback Registers (R/O, `spi_readback_reg`)"""
const spi_readback_regs = adc_fpga_registers(Val(RO), 0x10c) do
    (
        data    = BitRange{RO}(0:15),   # Read Data
    )
end
export spi_readback_regs

const evt_cfg_chbits = BitGroup{RW}(
    ext_veto_en       = Bit{RW}(7),     # External Veto Enable bit
    ext_gate_en       = Bit{RW}(6),     # External Gate Enable bit
    int_gate2_en      = Bit{RW}(5),     # Internal Gate 2 Enable bit
    int_gate1_en      = Bit{RW}(4),     # Internal Gate 1 Enable bit
    ext_trig_en       = Bit{RW}(3),     # External Trigger Enable bit
    int_trig_en       = Bit{RW}(2),     # Internal Trigger Enable bit
    sum_trig_en       = Bit{RW}(1),     # Internal SUM-Trigger Enable bit
    input_inv         = Bit{RW}(0),     # Input Invert bit
)

"""Event Configuration Registers (R/W, `event_config_regs`)"""
const event_config_regs = adc_fpga_registers(Val(RW), 0x010) do
    (
        ch = BitGroup{RW}(
            ch1 = evt_cfg_chbits <<  0, # Ch. 1 bits
            ch2 = evt_cfg_chbits <<  8, # Ch. 2 bits
            ch3 = evt_cfg_chbits << 16, # Ch. 3 bits
            ch4 = evt_cfg_chbits << 24, # Ch. 4 bits
        ),
    )
end
export event_config_regs

# Not implemented: Extended Event configuration registers (0x109C, 0x209C, 0x309C, 0x409C)

"""Channel Header ID Registers (R/W, `channel_header_regs`)"""
const channel_header_regs = adc_fpga_registers(Val(RW), 0x014) do
    (
        id = BitRange{RW}(20:31),   # Channel Header/ID
    )
end
export channel_header_regs

"""End Address Threshold Registers (R/W, `address_threshold_regs`)"""
const address_threshold_regs = adc_fpga_registers(Val(RW), 0x018) do
    (
        stop_on_thresh      = Bit{RW}(31),          # "Suppress saving of more Hits/Events if Memory Address Threshold Flag is valid" Enable
        addr_thresh_value   = BitRange{RW}(0:23),   # End Address Threshold value, value range 1:0xff0000, value is number of 32-bit words
    )
end
export address_threshold_regs

"""Active Trigger Gate Window Length Registers (R/W, `trigger_gate_window_length_regs`)"""
const trigger_gate_window_length_regs = adc_fpga_registers(Val(RW), 0x01C) do
    (
        length  = BitRange{RW}(0:15),   # Active Trigger Gate Window Length (bit 0 not used)
    )
end
export trigger_gate_window_length_regs

"""
raw_data_buffer_config_regs

Raw Data Buffer Configuration Registers (R/W)

Note: Bit 0 of start_index and sample_length is always zero (since data is
stored in packets of 2 consecutive samples).
"""
const raw_data_buffer_config_regs = adc_fpga_registers(Val(RW), 0x020) do
    (
        sample_length   = BitRange{RW}(16:31),  # Raw Buffer Sample_Length
        start_index     = BitRange{RW}(0:15),   # Raw Buffer_Start_Index
    )
end
export raw_data_buffer_config_regs

"""
    pileup_config_regs

Pileup Configuration Registers (R/W)

Note: Bit 0 of repileup_win_len and pileup_win_len is always zero.
"""
const pileup_config_regs = adc_fpga_registers(Val(RW), 0x024) do
    (
        repileup_win_len    = BitRange{RW}(16:31),  # Re-Pileup Window Length
        pileup_win_len      = BitRange{RW}(0:15),   # Pileup Window Length
    )
end
export pileup_config_regs

"""
    pre_trigger_delay_regs

Pre-Trigger Delay Registers (R/W)

Note: Bit 0 of delay is always zero. Maximum value of delay is 2042 for ADC
FPGA firmware versions <= 0006 and 16378 for versions >= 0007.
"""
const pre_trigger_delay_regs = adc_fpga_registers(Val(RW), 0x028) do
(
    additional  = Bit{RW}(15),  # Additional Delay of Fir Trigger P+G Bit
    delay       = BitRange{RW}(0:13),   # Pretrigger Delay
)
end
export pre_trigger_delay_regs

"""
    average_configuration_regs

Average Configuration Registers (R/W)

Only present for SIS3316-16bit.

Note: Valid values for pretrig_delay are 0, 1, 2:4094. Valid values for
sample_len are 0, 2, 4, 6:65534 (bit 0 of sample_len) is always zero).
"""
const average_configuration_regs = adc_fpga_registers(Val(RW), 0x02C) do
(
    mode            = BitRange{RW}(28:30),  # Average Mode
    pretrig_delay   = BitRange{RW}(16:27),  # Average Pretrigger Delay
    sample_len      = BitRange{RW}(0:15),   # Average Sample Length
)
end
export average_configuration_regs

const dfmtcfg_bits = BitGroup{RW}(
    sel_test_buf      = Bit{RW}(5),  # Select Energy MAW Test Buffer
    save_mawvals   = Bit{RW}(4),  # Save MAW Test Buffer Enable
    save_energy       = Bit{RW}(3),  # Save Start Energy MAW value and Max. Energy MAW value
    save_ft_maw       = Bit{RW}(2),  # Save 3 x Fast Trigger MAW values (max value, value before Trigger, value with Trigger)
    save_acc_78       = Bit{RW}(1),  # Save 2 x Accumulator values (Gates 7,8)
    save_ph_acc16     = Bit{RW}(0),  # Save Peak High values and 6 x Accumulator values (Gates 1,2, ..,6)
)

"""
    dataformat_config_regs

Data Format Configuration Registers (R/W)

sel_test_buf value:

* 0 for FIR Trigger MAW selected
* 1 for FIR Energy MAW selected
"""
const dataformat_config_regs = adc_fpga_registers(Val(RW), 0x030) do
(
    ch = BitGroup{RW}(
        ch1 = dfmtcfg_bits <<  0,   # Ch. 1 bits
        ch2 = dfmtcfg_bits <<  8,   # Ch. 2 bits
        ch3 = dfmtcfg_bits << 16,   # Ch. 3 bits
        ch4 = dfmtcfg_bits << 24,   # Ch. 4 bits
    ),
)
end
export dataformat_config_regs

"""
    maw_test_buffer_config_regs

MAW Test Buffer Configuration Registers (R/W)

Note:

* Valid values for pretrig_delay: 2, 4, 6:1022.
* Valid values for buffer_len: 0, 2, 4, 6:1022.
* Bit 0 of pretrig_delay and buffer_len is always zero.
"""
const maw_test_buffer_config_regs = adc_fpga_registers(Val(RW), 0x034) do
    (
        pretrig_delay   = BitRange{RW}(16:25),  # MAW Test Buffer Pretrigger Delay
        buffer_len      = BitRange{RW}(0:10),   # MAW Test Buffer Length
    )
end
export maw_test_buffer_config_regs

const trigdelay_bits = BitGroup{RW}(
    delay   = BitRange{RW}(0:7),    # Internal Trigger Delay
)

"""
    internal_trigger_delay_config_regs

Internal Trigger Delay Configuration Registers (R/W)

Delay time is delay value multiplied by 2 clock cycles.
"""
const internal_trigger_delay_config_regs = adc_fpga_registers(Val(RW), 0x0038) do
    (
        ch = BitGroup{RW}(
            ch1 = trigdelay_bits <<  0, # Ch. 1 bits
            ch2 = trigdelay_bits <<  8, # Ch. 2 bits
            ch3 = trigdelay_bits << 16, # Ch. 3 bits
            ch4 = trigdelay_bits << 24, # Ch. 4 bits
        ),
    )
end
export internal_trigger_delay_config_regs

const gatelen_bits = BitGroup{RW}(
    gate2_en          = Bit{RW}(4),  # Gate 2 Enable
    gate1_en          = Bit{RW}(0),  # Gate 1 Enable
)

"""
internal_gate_length_config_regs

Internal Gate Length Configuration Registers (R/W)

Gate time is length value multiplied by 2 clock cycles.
"""
const internal_gate_length_config_regs = adc_fpga_registers(Val(RW), 0x03C) do
(
    ch = BitGroup{RW}(
        ch1 = gatelen_bits << 16,   # Ch. 1 bits
        ch2 = gatelen_bits << 17,   # Ch. 2 bits
        ch3 = gatelen_bits << 18,   # Ch. 3 bits
        ch4 = gatelen_bits << 19,   # Ch. 4 bits
    ),
    gate_len        = BitRange{RW}(8:15),   # Internal Gate Length
    coind_gate_len  = BitRange{RW}(0:7),    # Internal Coincidence Gate Length
)
end
export internal_gate_length_config_regs

fir_trigger_setup_register(addr::Unsigned) = Register{RW,RegVal}(
    MemAddr(addr),
    (
        nim_tp_len  = BitRange{RW}(24:31),  # External NIM Out Trigger Pulse Length (streched).
        gap_time    = BitRange{RW}(12:23),  # G: Gap time (Flat Time)
        peak_time   = BitRange{RW}( 0:11),  # P : Peaking time
    )
)

"""
    fir_trigger_setup_regs

FIR Trigger Setup Registers (R/W)

Note: Valid values for gap_time and peak_time: 2, 4, 6, ...., 510 (bit 0 of g
and p is not used). Valid values for nim_tp_len: 2, 4, 6, ...., 256.
"""
const fir_trigger_setup_regs = (
    ch01 = fir_trigger_setup_register(0x1040),
    ch02 = fir_trigger_setup_register(0x1050),
    ch03 = fir_trigger_setup_register(0x1060),
    ch04 = fir_trigger_setup_register(0x1070),

    ch05 = fir_trigger_setup_register(0x2040),
    ch06 = fir_trigger_setup_register(0x2050),
    ch07 = fir_trigger_setup_register(0x2060),
    ch08 = fir_trigger_setup_register(0x2070),

    ch09 = fir_trigger_setup_register(0x3040),
    ch10 = fir_trigger_setup_register(0x3050),
    ch11 = fir_trigger_setup_register(0x3060),
    ch12 = fir_trigger_setup_register(0x3070),

    ch13 = fir_trigger_setup_register(0x4040),
    ch14 = fir_trigger_setup_register(0x4050),
    ch15 = fir_trigger_setup_register(0x4060),
    ch16 = fir_trigger_setup_register(0x4070),
)
export fir_trigger_setup_regs

"""
    sum_fir_trigger_setup_regs

Sum FIR Trigger Setup Registers (R/W)

Note: Valid values for gap_time and peak_time: 2, 4, 6, ...., 510 (bit 0 of g
and p is not used). Valid values for nim_tp_len: 2, 4, 6, ...., 256.
"""
const sum_fir_trigger_setup_regs = (
    ch01_04 = fir_trigger_setup_register(0x1080),
    ch05_08 = fir_trigger_setup_register(0x2080),
    ch09_10 = fir_trigger_setup_register(0x3080),
    ch13_15 = fir_trigger_setup_register(0x4080),
)
export sum_fir_trigger_setup_regs

@enum CfdCtrl::UInt8 begin
    CFDDisabled     = 0     # CFD function disabled
    CFDDisabled_alt = 1     # CFD function disabled
    CFDZeroCross    = 2     # CFD function enabled with Zero crossing
    CDF50Percent    = 3     # CFD function enabled with 50 percent
end

export CfdCtrl, CFDDisabled, CFDDisabled_alt, CFDDisabled_alt, CDF50Percent

fir_trigger_threshold_register(addr::Unsigned) = Register{RW,RegVal}(
    MemAddr(addr),
    (
        trig_en         = Bit{RW}(31),  # Trigger enable
        high_e_suppr    = Bit{RW}(30),  # High Energy Suppress Trigger Mode
        cfd_ctrl        = BitRange{RW}(28:29, CfdCtrl), # CFD control bits
        threshold       = BitRange{RW}(0:27,linear_conv(offset = -signed(0x8000000))) # Trigger threshold value
    )
)

"""
    fir_trigger_threshold_regs

Trigger Threshold Registers (R/W)

Note: High energy suppression only works with CFD enabled.
"""
const fir_trigger_threshold_regs = (
    ch01 = fir_trigger_threshold_register(0x1044),
    ch02 = fir_trigger_threshold_register(0x1054),
    ch03 = fir_trigger_threshold_register(0x1064),
    ch04 = fir_trigger_threshold_register(0x1074),

    ch05 = fir_trigger_threshold_register(0x2044),
    ch06 = fir_trigger_threshold_register(0x2054),
    ch07 = fir_trigger_threshold_register(0x2064),
    ch08 = fir_trigger_threshold_register(0x2074),

    ch09 = fir_trigger_threshold_register(0x3044),
    ch10 = fir_trigger_threshold_register(0x3054),
    ch11 = fir_trigger_threshold_register(0x3064),
    ch12 = fir_trigger_threshold_register(0x3074),

    ch13 = fir_trigger_threshold_register(0x4044),
    ch14 = fir_trigger_threshold_register(0x4054),
    ch15 = fir_trigger_threshold_register(0x4064),
    ch16 = fir_trigger_threshold_register(0x4074),
)

export fir_trigger_threshold_regs

"""
    sum_fir_trigger_threshold_regs

Sum Trigger Threshold Registers (R/W)

Note: High energy suppression only works with CFD enabled.
"""
const sum_fir_trigger_threshold_regs = (
    ch01_04 = fir_trigger_threshold_register(0x1084),
    ch05_08 = fir_trigger_threshold_register(0x2084),
    ch09_10 = fir_trigger_threshold_register(0x3084),
    ch13_15 = fir_trigger_threshold_register(0x4084),
)
export sum_fir_trigger_threshold_regs

high_energy_threshold_register(addr::Unsigned) = Register{RW,RegVal}(
    MemAddr(addr),
    (
        trig_both_edges = Bit{RW}(31),          # Trigger on both edges enable bit
        trig_out        = Bit{RW}(28),          # High Energy stretched Trigger Out select bit
        threshold       = BitRange{RW}(0:27),   # High Energy Trigger Threshold value
    )
)

"""
    high_energy_threshold_regs

High Energy Trigger Threshold Registers (R/W)

`trig_both_edges` only available with firmware version “adc_fpga_V-0125-0004”
and higher, only works with CFD enabled.
"""
const high_energy_threshold_regs = (
    ch01 = high_energy_threshold_register(0x1048),
    ch02 = high_energy_threshold_register(0x1058),
    ch03 = high_energy_threshold_register(0x1068),
    ch04 = high_energy_threshold_register(0x1078),

    ch05 = high_energy_threshold_register(0x2048),
    ch06 = high_energy_threshold_register(0x2058),
    ch07 = high_energy_threshold_register(0x2068),
    ch08 = high_energy_threshold_register(0x2078),

    ch09 = high_energy_threshold_register(0x3048),
    ch10 = high_energy_threshold_register(0x3058),
    ch11 = high_energy_threshold_register(0x3068),
    ch12 = high_energy_threshold_register(0x3078),

    ch13 = high_energy_threshold_register(0x4048),
    ch14 = high_energy_threshold_register(0x4058),
    ch15 = high_energy_threshold_register(0x4068),
    ch16 = high_energy_threshold_register(0x4078),
)
export high_energy_threshold_regs

"""
    sum_high_energy_threshold_regs

Sum High Energy Trigger Threshold Registers (R/W)

Note: High energy suppression only works with CFD enabled.
"""
const sum_high_energy_threshold_regs = (
    ch01_04 = high_energy_threshold_register(0x1088),
    ch05_08 = high_energy_threshold_register(0x2088),
    ch09_10 = high_energy_threshold_register(0x3088),
    ch13_15 = high_energy_threshold_register(0x4088),
)
export sum_high_energy_threshold_regs

"""
    trigger_statistic_counter_regs

Trigger Statistic Counter Mode Registers (R/W)

* `update_mode value`:
    * 0: Readout of actual Trigger-Statistic-Counters
    * 1: Readout of the latched Trigger-Statistic-Counters (latch
        on bank switch)
"""
const trigger_statistic_counter_regs = adc_fpga_registers(Val(RW), 0x090) do
    (
        update_mode = Bit{RW}(0),   # Update Mode
    )
end
export trigger_statistic_counter_regs

"""
    peak_charge_configuration_regs

Peak/Charge Configuration Registers (R/W)

Valid values for bl_pregate_delay: 0, 2, 4, 6:510 (bit 0 is always zero).
"""
const peak_charge_configuration_regs = adc_fpga_registers(Val(RW), 0x094) do
(
    peak_charge_en      = Bit{RW}(31),          # Enable Peak/Charge Mode
    bl_avg_mode         = BitRange{RW}(28:29),  # Baseline Average Mode
    bl_pregate_delay    = BitRange{RW}(16:27),  # Baseline Pregate Delay
)
end
export peak_charge_configuration_regs

"""
    extended_raw_data_buffer_config_regs

Extended Raw Data Buffer Configuration Registers (R/W)

Maximum value of sample_len is (0x2000000 - 2), bit 0 is always zero.
"""
const extended_raw_data_buffer_config_regs = adc_fpga_registers(Val(RW), 0x098) do
    (
        sample_len  = BitRange{RW}(0:24),    # Extended Raw Buffer Sample Length
    )
end
export extended_raw_data_buffer_config_regs

accumulator_gate_config_registers(rel_addr::Unsigned) = adc_fpga_registers(Val(RW), rel_addr) do
    (
        gate_len    = BitRange{RW}(16:24, linear_conv(offset = 1)), # Gate Length
        gate_start  = BitRange{RW}(0:15),   # Gate Start Index (Address)
    )
end

"""
    accumulator_gate_config_regs

Accumulator Gate Configuration Registers (R/W)
"""
const accumulator_gate_config_regs = (
    gate1 = accumulator_gate_config_registers(0x0A0),
    gate2 = accumulator_gate_config_registers(0x0A4),
    gate3 = accumulator_gate_config_registers(0x0A8),
    gate4 = accumulator_gate_config_registers(0x0AC),
    gate5 = accumulator_gate_config_registers(0x0B0),
    gate6 = accumulator_gate_config_registers(0x0B4),
    gate7 = accumulator_gate_config_registers(0x0B8),
    gate8 = accumulator_gate_config_registers(0x0BC),
)
export accumulator_gate_config_regs

fir_energy_setup_register(addr::Unsigned) = Register{RW,RegVal}(
    MemAddr(addr),
    (
        tau_table       = BitRange{RW}(30:31),  # Tau table selection
        tau_factor      = BitRange{RW}(24:29),  # Tau factor
        extra_filter    = BitRange{RW}(22:23),  # Extra filter
        gap_time        = BitRange{RW}(12:21),  # G: Gap time (Flat Time)
        peak_time       = BitRange{RW}( 0:11),  # P : Peaking time
    )
)

"""
    fir_energy_setup_regs

FIR Energy Setup Registers (R/W)

Valid values:

    * tau_factor: 0, 1, 2, ...., 63
    * gap_time: 2, 4, 6, ...., 510 (bit 0 is not used)
    * peak_time: 2, 4, 6, ...., 2044 (bit 0 is not used)

Extra filter value:

    * 0: No extra filter
    * 1: Average of 4
    * 2: Average of 8
    * 3: Average of 16
"""
const fir_energy_setup_regs = (
    ch01 = fir_energy_setup_register(0x10c0),
    ch02 = fir_energy_setup_register(0x10c4),
    ch03 = fir_energy_setup_register(0x10c8),
    ch04 = fir_energy_setup_register(0x10cc),

    ch05 = fir_energy_setup_register(0x20c0),
    ch06 = fir_energy_setup_register(0x20c4),
    ch07 = fir_energy_setup_register(0x20c8),
    ch08 = fir_energy_setup_register(0x20cc),

    ch09 = fir_energy_setup_register(0x30c0),
    ch10 = fir_energy_setup_register(0x30c4),
    ch11 = fir_energy_setup_register(0x30c8),
    ch12 = fir_energy_setup_register(0x30cc),

    ch13 = fir_energy_setup_register(0x40c0),
    ch14 = fir_energy_setup_register(0x40c4),
    ch15 = fir_energy_setup_register(0x40c8),
    ch16 = fir_energy_setup_register(0x40cc),
)
export fir_energy_setup_regs

histogram_conf_register(addr::Unsigned) = Register{RW,RegVal}(
    MemAddr(addr),
    (
        mem_evt_write_dis   = Bit{RW}(31),          # Writing Hits/Events into Event Memory Disable bit
        clear_hist_w_ts     = Bit{RW}(30),          # Histogram clear with Timestamp-Clear Disable bit
        energy_div          = BitRange{RW}(16:27),  # Energy Divider (value = 0 is not allowed)
        energy_offs         = BitRange{RW}(8:11),   # Energy Subtract Offset
        pileup_en           = Bit{RW}(1),           # Pileup Enable bit
        hist_en             = Bit{RW}(0),           # Histogramming Enable bit
    )
)

"""
    histogram_conf_regs

Energy Histogram Configuration Registers (R/W)
"""
const histogram_conf_regs = (
    ch01 = histogram_conf_register(0x10d0),
    ch02 = histogram_conf_register(0x10d4),
    ch03 = histogram_conf_register(0x10d8),
    ch04 = histogram_conf_register(0x10dc),

    ch05 = histogram_conf_register(0x20d0),
    ch06 = histogram_conf_register(0x20d4),
    ch07 = histogram_conf_register(0x20d8),
    ch08 = histogram_conf_register(0x20dc),

    ch09 = histogram_conf_register(0x30d0),
    ch10 = histogram_conf_register(0x30d4),
    ch11 = histogram_conf_register(0x30d8),
    ch12 = histogram_conf_register(0x30dc),

    ch13 = histogram_conf_register(0x40d0),
    ch14 = histogram_conf_register(0x40d4),
    ch15 = histogram_conf_register(0x40d8),
    ch16 = histogram_conf_register(0x40dc),
)
export histogram_conf_regs

maw_start_energy_pickup_register(addr::Unsigned) = Register{RW,RegVal}(
    MemAddr(addr),
    (
        energy_pickup_idx   = BitRange{RW}(16:31),  # Energy Pickup Index
        maw_buf_start_idx   = BitRange{RW}(0:15),   # MAW Test Buffer Start Index
    )
)

"""
    maw_start_energy_pickup_regs

MAW Start Index and Energy Pickup Configuration Registers (R/W)
"""
const maw_start_energy_pickup_regs = (
    ch01 = maw_start_energy_pickup_register(0x10e0),
    ch02 = maw_start_energy_pickup_register(0x10e4),
    ch03 = maw_start_energy_pickup_register(0x10e8),
    ch04 = maw_start_energy_pickup_register(0x10ec),

    ch05 = maw_start_energy_pickup_register(0x20e0),
    ch06 = maw_start_energy_pickup_register(0x20e4),
    ch07 = maw_start_energy_pickup_register(0x20e8),
    ch08 = maw_start_energy_pickup_register(0x20ec),

    ch09 = maw_start_energy_pickup_register(0x30e0),
    ch10 = maw_start_energy_pickup_register(0x30e4),
    ch11 = maw_start_energy_pickup_register(0x30e8),
    ch12 = maw_start_energy_pickup_register(0x30ec),

    ch13 = maw_start_energy_pickup_register(0x40e0),
    ch14 = maw_start_energy_pickup_register(0x40e4),
    ch15 = maw_start_energy_pickup_register(0x40e8),
    ch16 = maw_start_energy_pickup_register(0x40ec),
)
export maw_start_energy_pickup_regs

"""
    firmware_version_regs

ADC FPGA Firmware Version Registers (R/W)

`fw_type` values:

    * 0x0125: 125 MHz 16-bit ADC
    * 0x0250: 250 MHz 14-bit ADC
"""
const firmware_version_regs = adc_fpga_registers(Val(RW), 0x100) do
    (
        fw_type     = BitRange{RO}(16:31),  # Firmware Type
        fw_version  = BitRange{RO}(8:15),   # Firmware Version
        fw_revision = BitRange{RO}(0:7),    # Firmware Revision
    )
end
export firmware_version_regs

"""
    adc_fpge_status_regs

ADC FPGA Status Registers (R/W)
"""
const adc_fpge_status_regs = adc_fpga_registers(Val(RW), 0x104) do
    (
        adc_clk_dcm_reset   = Bit{RO}(21),  # ADC-Clock DCM RESET flag
        adc_clk_dcm_ok      = Bit{RO}(20),  # ADC-Clock DCM OK flag
        mem2_ok             = Bit{RO}(17),  # Memory 2 OK flag (ch3 and ch4)
        mem1_ok             = Bit{RO}(16),  # Memory 1 OK flag (ch1 and ch2)
        link_speed_fl       = Bit{RO}(8),   # Data Link Speed flag
        vme_frame_err_la    = Bit{RO}(7),   # VME FPGA : Frame_error_latch
        vme_soft_err_la     = Bit{RO}(6),   # VME FPGA : Soft_error_latch
        vme_hard_err_la     = Bit{RO}(5),   # VME FPGA : Hard_error_latch
        vme_lane_up_fl      = Bit{RO}(4),   # VME FPGA : Lane_up_flag
        vme_ch_up_fl        = Bit{RO}(3),   # VME FPGA : Channel_up_flag
        vme_frame_err_fl    = Bit{RO}(2),   # VME FPGA : Frame_error_flag
        vme_soft_err_fl     = Bit{RO}(1),   # VME FPGA : Soft_error_flag
        vme_hard_err_fl     = Bit{RO}(0),   # VME FPGA : Hard_error_flag
    )
end
export adc_fpge_status_regs

sample_address_register(addr::Unsigned) = Register{RW,RegVal}(
    MemAddr(addr),
    (
        ch_offset   = Bit{RO}(25),  # Indicates the Channel Offset
        bank        = Bit{RO}(24, bool_val_conv(1, 2)),         # Indicates the Bank
        sample_addr = BitRange{RO}(0:23, word_byte_addr_conv),  # Actual Sample Address
    )
)

"""
    actual_sample_address_regs

Actual Sample Address Registers (R/W)
"""
const actual_sample_address_regs = (
    ch01 = sample_address_register(0x1110),
    ch02 = sample_address_register(0x1114),
    ch03 = sample_address_register(0x1118),
    ch04 = sample_address_register(0x111c),

    ch05 = sample_address_register(0x2110),
    ch06 = sample_address_register(0x2114),
    ch07 = sample_address_register(0x2118),
    ch08 = sample_address_register(0x211c),

    ch09 = sample_address_register(0x3110),
    ch10 = sample_address_register(0x3114),
    ch11 = sample_address_register(0x3118),
    ch12 = sample_address_register(0x311c),

    ch13 = sample_address_register(0x4110),
    ch14 = sample_address_register(0x4114),
    ch15 = sample_address_register(0x4118),
    ch16 = sample_address_register(0x411c),
)
export actual_sample_address_regs

"""
    previous_bank_sample_address_regs

Previous Bank Sample address Registers (R/W)
"""
const previous_bank_sample_address_regs = (
    ch01 = sample_address_register(0x1120),
    ch02 = sample_address_register(0x1124),
    ch03 = sample_address_register(0x1128),
    ch04 = sample_address_register(0x112c),

    ch05 = sample_address_register(0x2120),
    ch06 = sample_address_register(0x2124),
    ch07 = sample_address_register(0x2128),
    ch08 = sample_address_register(0x212c),

    ch09 = sample_address_register(0x3120),
    ch10 = sample_address_register(0x3124),
    ch11 = sample_address_register(0x3128),
    ch12 = sample_address_register(0x312c),

    ch13 = sample_address_register(0x4120),
    ch14 = sample_address_register(0x4124),
    ch15 = sample_address_register(0x4128),
    ch16 = sample_address_register(0x412c),
)
export previous_bank_sample_address_regs


using KCenters, KNearestCenters, TextSearch, TextClassification, SearchModels, UnicodePlots, StatsBase
using LIBLINEAR
using Distributed

const PROCS = parse(Int, get(ENV, "procs", "1"))
const isample = parse(Int, get(ENV, "isample", "64"))

addprocs(PROCS, exeflags="--project=.")

@everywhere begin
    include("utils.jl")
    include("commands.jl")
end

function main_search_model(nick, train, textconfig, textmodel, cls; score=:macrorecall)
    labelkey = get(ENV, "klass", "klass")
    textkey = get(ENV, "text", "text")
    bsize = parse(Int, get(ENV, "bsize", "4"))

    # search hyper-parameters
    search_kwargs = Dict(
        #:score => (name=:classrecall, label=1),
        :score => score, # :accuracy,
        #:cv => (kind=:folds, folds=5),
        #:cv => (kind=:montecarlo, repeat=15, ratio=0.5),
        :cv => (kind=:montecarlo, repeat=10, ratio=0.7),
        :initialpopulation => isample,
        :maxpopulation => 32,
        :bsize => bsize,
        :mutbsize => 64,
        :crossbsize => 0,
        :tol => 0.001,
        :maxiters => 20,
        :verbose => true,
        :parallel => (PROCS>1 ? :distributed : :none)
        #:parallel => :threads
    )

    space = MicroTC_ConfigSpace(textconfig=textconfig, textmodel=textmodel, cls=cls)
    outdir = nick
    !isdir(outdir) && mkdir(outdir)
    paramsfile = "$outdir/params"
    D = loadjson(train; textkey=textkey, labelkey=labelkey)
    @info "class distribution:" countmap(D.labels)
    run_params(D, paramsfile, space, search_kwargs)
end

if !isinteractive()
    textconfig = TextConfigSpace(
        del_diac=[true, false],
        del_dup=[true, false],
        del_punc=[true, false],
        group_num=[true, false],
        group_url=[true, false],
        group_usr=[true, false],
        group_emo=[true, false],
        lc=[true],
        qlist=[[3], [3, 5], [3, 7], []],
        nlist=[[1], [1, 2], [2], [1, 2, 3], []],
        slist=[[Skipgram(2,1)], []],
        # nlist_space = [1, 2, 3],
        # qlist_space = [3, 5, 7],
        # slist_space = []
    )

    #llspace = LiblinearConfigSpace()
    #knnspace = KnnClassifierConfigSpace()
    kncspace = KncPerClassConfigSpace{0.5}(
        centerselection=[TextCentroidSelection()],
        kernel=[k_(CosineDistance()) for k_ in [DirectKernel]]
    )
    llspace = LiblinearConfigSpace(scale_C=nothing, scale_eps=nothing)
    knnspace = KnnClassifierConfigSpace(k=[1, 3], scale_k=nothing)
    entspace = EntModelConfigSpace(
        weights=[:balance],
        #local_weighting=[TpWeighting()],
        #local_weighting=[BinaryLocalWeighting()],
        #scale_mindocs=nothing,
        #scale_smooth=nothing
    )
    vecspace = VectorModelConfigSpace()
    #classifiers = [llspace, knnspace]
    #classifiers = [knnspace]
    classifiers = [llspace]
    #classifiers = [kncspace]
    method = get(ENV, "method", "microtc")
    train = ENV["train"]
    nick = get(ENV, "nick", "_$(method)_" * replace(basename(train), ".json" => ""))
    score = Symbol(get(ENV, "score", "macrorecall"))

    if method == "entropy"
        main_search_model(nick, train, textconfig, entspace, classifiers; score)
    elseif method == "no-entropy"
        main_search_model(nick, train, textconfig, vecspace, classifiers; score)
    elseif method == "microtc"
        main_search_model(nick, train, textconfig, [vecspace, entspace], classifiers; score)
    else
        error("unknown method $method")
    end
end


import ModelingToolkit
import ModelingToolkit: build_function, substitute
import Catalyst
import Catalyst: ReactionSystem
using StaticArrays
import Base.show
using Setfield

struct ReactionSet
    rates::Vector{Float64}
    rstoich::Vector{Vector{Pair{Int64,Int64}}}
    nstoich::Vector{Vector{Pair{Int64,Int64}}}
    nspecies::Int
end

abstract type AbstractJumpRateAggregatorAlgorithm end

struct GillespieDirect <: AbstractJumpRateAggregatorAlgorithm end

struct DepGraphDirect <: AbstractJumpRateAggregatorAlgorithm end

abstract type AbstractJumpRateAggregator end

struct DirectAggregator{U} <: AbstractJumpRateAggregator
    "the sum of all reaction rates"
    sumrate::Float64

    "a vector of the current reaction rates"
    rates::Vector{Float64}

    "sum of group propensities"
    gsums::Vector{Float64}

    "maps reaction indices to group indices"
    ridtogroup::U

    "time span for aggregation"
    tspan::Tuple{Float64,Float64}

    "time of last recorded reaction"
    tprev::Float64

    "accumulated log-probability"
    weight::Float64
end

function build_aggregator(alg::GillespieDirect, reactions::ReactionSet, ridtogroup, tspan=(0.0, Inf64))
    ngroups = maximum(ridtogroup)
    nreactions = length(reactions.rates)
    DirectAggregator(0.0, zeros(nreactions), zeros(ngroups), ridtogroup, tspan, tspan[1], 0.0)
end

struct DepGraphAggregator{U,DepGraph} <: AbstractJumpRateAggregator
    "the sum of all reaction rates"
    sumrate::Float64

    "a vector of the current reaction rates"
    rates::Vector{Float64}

    "sum of group propensities"
    gsums::Vector{Float64}

    "maps reaction indices to group indices"
    ridtogroup::U

    "time span for aggregation"
    tspan::Tuple{Float64,Float64}

    "time of last recorded reaction"
    tprev::Float64

    "accumulated log-probability"
    weight::Float64

    "dependency graph"
    depgraph::DepGraph

    "previously executed reaction"
    prev_reaction::Int
end

function build_aggregator(alg::DepGraphDirect, reactions::ReactionSet, ridtogroup, tspan=(0.0, Inf64))
    ngroups = maximum(ridtogroup)
    nreactions = length(reactions.rates)
    depgraph = make_depgraph(reactions)
    DepGraphAggregator(0.0, zeros(nreactions), zeros(ngroups), ridtogroup, tspan, tspan[1], 0.0, depgraph, 0)
end

function initialize_aggregator(agg::AbstractJumpRateAggregator; tspan=(0.0, Inf64))
    agg = @set agg.tspan = tspan
    agg = @set agg.tprev = tspan[1]
    agg = @set agg.weight = 0.0
    agg = @set agg.sumrate = 0.0
    agg.rates .= 0.0
    agg.gsums .= 0.0
    agg
end

function initialize_aggregator(agg::DepGraphAggregator; tspan=(0.0, Inf64))
    agg = @set agg.tspan = tspan
    agg = @set agg.tprev = tspan[1]
    agg = @set agg.weight = 0.0
    agg = @set agg.sumrate = 0.0
    agg = @set agg.prev_reaction = 0
    agg.rates .= 0.0
    agg.gsums .= 0.0
    agg
end

function ReactionSet(js::ModelingToolkit.JumpSystem, p)
    parammap = map(Pair, ModelingToolkit.parameters(js), p)
    statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(ModelingToolkit.states(js)))

    rates = Float64[]
    rstoich_vec = Vector{Pair{Int64,Int64}}[]
    nstoich_vec = Vector{Pair{Int64,Int64}}[]

    for eq in ModelingToolkit.equations(js)
        rate = ModelingToolkit.value(ModelingToolkit.substitute(eq.scaled_rates, parammap))
        rstoich = sort!([statetoid[ModelingToolkit.value(spec)] => stoich for (spec, stoich) in eq.reactant_stoch])
        nstoich = sort!([statetoid[ModelingToolkit.value(spec)] => stoich for (spec, stoich) in eq.net_stoch])

        push!(rates, rate)
        push!(rstoich_vec, rstoich)
        push!(nstoich_vec, nstoich)
    end

    ReactionSet(rates, rstoich_vec, nstoich_vec, length(ModelingToolkit.states(js)))
end

function species_to_dependent_reaction_map(reactions::ReactionSet)
    nspecies = reactions.nspecies
    # map from a species to reactions that depend on it
    spec_to_dep_rxs = [Vector{Int}() for n = 1:nspecies]
    for (rx, complex) in enumerate(reactions.rstoich)
        for (spec, stoch) in complex
            push!(spec_to_dep_rxs[spec], rx)
        end
    end

    foreach(s -> unique!(sort!(s)), spec_to_dep_rxs)
    spec_to_dep_rxs
end

function make_depgraph(reactions::ReactionSet)
    nreactions = length(reactions.rates)
    spec_to_dep_rxs = species_to_dependent_reaction_map(reactions)

    # create map from rx to reactions depending on it
    dep_graph = [Vector{Int}() for n = 1:nreactions]
    for rx in 1:nreactions
        # rx changes spec, hence rxs depending on spec depend on rx
        for (spec, stoch) in reactions.nstoich[rx]
            for dependent_rx in spec_to_dep_rxs[spec]
                push!(dep_graph[rx], dependent_rx)
            end
        end
    end

    foreach(deps -> unique!(sort!(deps)), dep_graph)
    dep_graph
end

struct TrajectoryDistribution{A}
    reactions::ReactionSet
    aggregator::A
end

function TrajectoryDistribution(reactions::ReactionSet, alg::AbstractJumpRateAggregatorAlgorithm, ridtogroup=1:length(reactions.rates))
    agg = build_aggregator(alg, reactions, ridtogroup)
    TrajectoryDistribution(reactions, agg)
end

distribution(rn::ReactionSystem, p; update_map=1:Catalyst.numreactions(rn), alg=GillespieDirect()) = TrajectoryDistribution(ReactionSet(convert(ModelingToolkit.JumpSystem, rn), p), alg, update_map)

@fastmath function Distributions.logpdf(dist::TrajectoryDistribution, trajectory)::Float64
    traj_iter = trajectory_iterator(trajectory)
    tprev = 0.0
    result = 0.0
    agg = initialize_aggregator(dist.aggregator)
    for (u, t, i) in traj_iter
        agg = update_rates(agg, (u, t, i), dist.reactions)

        dt = t - tprev
        result -= dt * agg.sumrate
        if i != 0
            @inbounds gid = agg.ridtogroup[i]
            gid != 0 && @inbounds result += log(agg.gsums[gid])
        end

        tprev = t
    end

    result
end

@fastmath function fold_logpdf(dist::TrajectoryDistribution, agg::AbstractJumpRateAggregator, (u, t, i))
    agg = update_rates(agg, (u, t, i), dist.reactions)
    dt = t - agg.tprev
    log_jump_prob = 0.0
    if i != 0
        @inbounds gid = agg.ridtogroup[i]
        gid != 0 && @inbounds log_jump_prob = log(agg.gsums[gid])
    end
    log_surv_prob = -dt * agg.sumrate
    agg = @set agg.weight = agg.weight + log_surv_prob + log_jump_prob
    agg = @set agg.tprev = t
end

function step_energy(dist::TrajectoryDistribution, agg::AbstractJumpRateAggregator, (u, t, i))
    if t <= agg.tspan[1]
        return agg
    end
    if t > agg.tspan[2]
        if agg.tprev >= agg.tspan[2]
            return agg
        end
        fold_logpdf(dist, agg, (u, agg.tspan[2], 0))
    else
        fold_logpdf(dist, agg, (u, t, i))
    end
end

function trajectory_energy(dist::TrajectoryDistribution, traj; tspan=(0.0, Inf64))
    agg = initialize_aggregator(dist.aggregator, tspan=tspan)
    traj_iter = trajectory_iterator(traj)
    agg = Base.foldl((acc, x) -> step_energy(dist, acc, x), traj_iter; init=agg)
    agg.weight
end

function cumulative_logpdf!(result::AbstractVector, dist::TrajectoryDistribution, traj, dtimes::AbstractVector)
    tspan = (first(dtimes), last(dtimes))
    agg = initialize_aggregator(dist.aggregator, tspan=tspan)
    result[1] = zero(eltype(result))
    traj_iter = trajectory_iterator(traj)
    result_agg, k = Base.foldl(traj_iter; init=(agg, 1)) do (agg, k), (u, t, i)
        if t <= agg.tspan[1]
            return agg, k
        end

        t = min(t, agg.tspan[2])
        agg = update_rates(agg, (u, t, i), dist.reactions)

        tprev = agg.tprev
        while k <= length(dtimes) && dtimes[k] < t
            result[k] -= (dtimes[k] - tprev) * agg.sumrate
            tprev = dtimes[k]
            k += 1
            result[k] = result[k-1]
        end
        result[k] -= (t - tprev) * agg.sumrate

        log_jump_prob = 0.0
        if i != 0
            @inbounds gid = agg.ridtogroup[i]
            gid != 0 && @inbounds log_jump_prob = log(agg.gsums[gid])
        end
        result[k] += log_jump_prob

        agg = @set agg.weight = agg.weight - (t - agg.tprev) * agg.sumrate + log_jump_prob
        agg = @set agg.tprev = t
        agg, k
    end

    result
end

cumulative_logpdf(dist::TrajectoryDistribution, trajectory, dtimes::AbstractVector) = cumulative_logpdf!(zeros(length(dtimes)), dist, trajectory, dtimes)

@inline @fastmath function evalrxrate(speciesvec::AbstractVector{T}, rxidx::Int64, rs::ReactionSet) where {T}
    val = Float64(1.0)
    @inbounds for specstoch in rs.rstoich[rxidx]
        @inbounds specpop = speciesvec[specstoch[1]]
        val *= Float64(specpop)
        @inbounds for k = 2:specstoch[2]
            specpop -= one(specpop)
            val *= Float64(specpop)
        end
    end

    @inbounds val * rs.rates[rxidx]
end

@inline function update_reaction_rate!(aggregator::AbstractJumpRateAggregator, u::AbstractVector, reactions, rx::Int)
    @inbounds gid = aggregator.ridtogroup[rx]
    if gid != 0
        rate = evalrxrate(u, rx, reactions)
        @inbounds aggregator.gsums[gid] += -aggregator.rates[rx] + rate
        @inbounds aggregator.rates[rx] = rate
    end
end

@fastmath function update_rates(aggregator::DirectAggregator, (u, t, i), reactions::ReactionSet)
    for rx in eachindex(reactions.rates)
        update_reaction_rate!(aggregator, u, reactions, rx)
    end
    aggregator = @set aggregator.sumrate = sum(aggregator.gsums)
end

@fastmath function update_rates(aggregator::DepGraphAggregator, (u, t, i), reactions::ReactionSet)
    if aggregator.prev_reaction == 0
        for rx in eachindex(reactions.rates)
            update_reaction_rate!(aggregator, u, reactions, rx)
        end
    else
        @inbounds for rx in aggregator.depgraph[aggregator.prev_reaction]
            update_reaction_rate!(aggregator, u, reactions, rx)
        end
    end
    aggregator = @set aggregator.sumrate = sum(aggregator.gsums)
    aggregator = @set aggregator.prev_reaction = i
end


"""
`module  JAC.PhotoIonizationAutoion`
    ... a submodel of JAC that contains all methods for computing photo-excitation-autoionization cross sections and rates.
"""
module PhotoIonizationAutoion

    using ..AutoIonization, ..Basics, ..ManyElectron, ..Radial, ..PhotoEmission, ..TableStrings

    """
    `struct  PhotoIonizationAutoion.Settings`
        ... defines a type for the details and parameters of computing photon-impact excitation-autoionization pathways
            |i(N)>  --> |m(N)>  --> |f(N-1)>.

        + multipoles              ::Array{EmMultipole,1}               ... Specifies the multipoles of the radiation field that are to be included.
        + gauges                  ::Array{UseGauge,1}                  ... Specifies the gauges to be included into the computations.
        + printBefore             ::Bool                               ... True, if all energies and lines are printed before their evaluation.
        + selectPathways          ::Bool                               ... True if particular pathways are selected for the computations.
        + incidentStokes          ::ExpStokes               ... Stokes parameters of the incident radiation.
        + selectedPathways        ::Array{Tuple{Int64,Int64,Int64},1}  ... List of list of pathways, given by tupels (inital, inmediate, final).
        + maxKappa                ::Int64                              ... Maximum kappa value of partial waves to be included.
    """
    struct Settings
        multipoles                ::Array{EmMultipole,1}
        gauges                    ::Array{UseGauge,1}
        printBefore    ::Bool
        selectPathways            ::Bool
        selectedPathways          ::Array{Tuple{Int64,Int64,Int64},1}
        maxKappa                  ::Int64
    end

    """
    `PhotoIonizationAutoion.Settings()`  ... constructor for the default values of photon-impact excitation-autoionizaton settings.
    """
    function Settings()
        Settings( Basics.EmMultipole[], Basics.UseGauge[], false,  false, Tuple{Int64,Int64,Int64}[], 0)
    end

    # `Base.show(io::IO, settings::PhotoIonizationAutoion.Settings)`
    #		 ... prepares a proper printout of the variable settings::PhotoIonizationAutoion.Settings.
    function Base.show(io::IO, settings::PhotoIonizationAutoion.Settings)
        println(io, "multipoles:              $(settings.multipoles)  ")
        println(io, "gauges:                  $(settings.gauges)  ")
        println(io, "printBefore:  $(settings.printBefore)  ")
        println(io, "selectPathways:          $(settings.selectPathways)  ")
        println(io, "selectedPathways:        $(settings.selectedPathways)  ")
        println(io, "maxKappa:                $(settings.maxKappa)  ")
    end

    """
    `struct  PhotoIonizationAutoion.Channel`
        ... defines a type for a photon-impact excitaton & autoionization channel that specifies all quantum numbers, phases and
            amplitudes.

        + excitationChannel  ::PhotoEmission.Channel       ... Channel that describes the photon-impact excitation process.
        + augerChannel       ::AutoIonization.Channel      ... Channel that describes the subsequent Auger/autoionization process.
    """
    struct  Channel
        excitationChannel    ::PhotoEmission.Channel
        augerChannel         ::AutoIonization.Channel
    end

    """
    `struct  PhotoIonizationAutoion.Pathway`  ... defines a type for a photon-impact excitation pathway that may include the definition
                                                      of different excitation and autoionization channels and their corresponding amplitudes.

        + initialLevel        ::Level           ... initial-(state) level
        + intermediateLevel   ::Level           ... intermediate-(state) level
        + finalLevel          ::Level           ... final-(state) level
        + photonEnergy        ::Float64         ... energy of the (incoming) electron
        + electronEnergy      ::Float64         ... energy of the (finally outgoing, scattered) electron
        + crossSection        ::EmProperty      ... total cross section of this pathway
        + hasChannels         ::Bool            ... Determines whether the individual excitation and autoionization channels
                                                    are defined in terms of their multipole, gauge, free-electron kappa,
                                                    phases and the total angular momentum/parity as well as the amplitude, or not.
        + channels            ::Array{PhotoIonizationAutoion.Channel,1}     ... List of channels of this pathway.
    """
    struct  Pathway
        initialLevel          ::Level
        intermediateLevel     ::Level
        finalLevel            ::Level
        photonEnergy          ::Float64
        electronEnergy        ::Float64
        crossSection          ::EmProperty
        hasChannels           ::Bool
        channels              ::Array{PhotoIonizationAutoion.Channel,1}
    end

    """
    `PhotoIonizationAutoion.Pathway()`
        ... 'empty' constructor for an photon-impact excitation-autoionization pathway between a specified initial, intermediate and
            final level.
    """
    function Pathway()
        Pathway(Level(), Level(), Level(), 0., 0., 0., false, PhotoIonizationAutoion.Channel[] )
    end

    # `Base.show(io::IO, pathway::PhotoIonizationAutoion.Pathway)`
    #		 ... prepares a proper printout of the variable pathway::PhotoIonizationAutoion.Pathway.
    function Base.show(io::IO, pathway::PhotoIonizationAutoion.Pathway)
        println(io, "initialLevel:               $(pathway.initialLevel)  ")
        println(io, "intermediateLevel:          $(pathway.intermediateLevel)  ")
        println(io, "finalLevel:                 $(pathway.finalLevel)  ")
        println(io, "photonEnergy                $(pathway.photonEnergy)  ")
        println(io, "electronEnergy              $(pathway.electronEnergy)  ")
        println(io, "crossSection:               $(pathway.crossSection)  ")
        println(io, "hasChannels:                $(pathway.hasChannels)  ")
        println(io, "channels:                   $(pathway.channels)  ")
    end

    """
    `PhotoIonizationAutoion.computePathways(finalMultiplet::Multiplet, intermediateMultiplet::Multiplet, initialMultiplet::Multiplet,
                                            grid::Radial.Grid, settings::PhotoIonizationAutoion.Settings; output=true)`
        ... to compute the photo-excitation-fluorescence amplitudes and all properties as requested by the given settings. A list of
            lines::Array{PhotoIonizationAutoion.Lines} is returned.
    """
    function  computePathways(finalMultiplet::Multiplet, intermediateMultiplet::Multiplet, initialMultiplet::Multiplet, grid::Radial.Grid,
                              settings::PhotoIonizationAutoion.Settings; output=true)
        println("")
        printstyled("PhotoIonizationAutoion.computePathways(): The computation of photo-excitation-fluorescence amplitudes starts now ... \n", color=:light_green)
        printstyled("-------------------------------------------------------------------------------------------------------------------- \n", color=:light_green)
        println("")
        #
        println("Not yet implemented: Data structures and properties still need to be worked out.")
        #
        pathways = "Not yet implemented !"
        #
        if    output    return( pathways )
        else            return( nothing )
        end
    end

end # module


@with_kw mutable struct Domain{FT}
    # Something CUDA
    max_streams::IndexT
    streams::Nothing

    # Elem-centered
    matElemlist::VD{IndexT} # material indexset
    nodelist::VD{IndexT}    # elemToNode connectivity

    lxim::VD{IndexT}         # element connectivity through face g
    lxip::VD{IndexT}
    letam::VD{IndexT}
    letap::VD{IndexT}
    lzetam::VD{IndexT}
    lzetap::VD{IndexT}

    elemBC::VD{Int}         # elem face symm/free-surf flag g

    e::VD{FT}             # energy g
    d_e::VD{FT}           # change in energy g

    p::VD{FT}             # pressure g

    q::VD{FT}             # q g
    ql::VD{FT}            # linear term for q g
    qq::VD{FT}            # quadratic term for q g

    v::VD{FT}             # relative volume g

    volo::VD{FT}          # reference volume g
    delv::VD{FT}          # m_vnew - m_v g
    vdov::VD{FT}          # volume derivative over volume g

    arealg::VD{FT}        # char length of an element g

    ss::VD{FT}            # "sound speed" g

    elemMass::VD{FT}      # mass g

    # TODO: Not making distinction between pointers to arrays down here

    vnew::VD{FT}          # new relative volume -- temporary g

    delv_xi::VD{FT}       # velocity gradient -- temporary g
    delv_eta::VD{FT}
    delv_zeta::VD{FT}

    delx_xi::VD{FT}       # coordinate gradient -- temporary g
    delx_eta::VD{FT}
    delx_zeta::VD{FT}

    dxx::VD{FT}           # principal strains -- temporary g
    dyy::VD{FT}
    dzz::VD{FT}

    # Node-centered g

    x::VD{FT}             # coordinates g
    y::VD{FT}
    z::VD{FT}

    xd::VD{FT}            # velocities g
    yd::VD{FT}
    zd::VD{FT}

    xdd::VD{FT}           # accelerations g
    ydd::VD{FT}
    zdd::VD{FT}

    fx::VD{FT}            # forces g
    fy::VD{FT}
    fz::VD{FT}

    dfx::VD{FT}          # AD of the forces g
    dfy::VD{FT}
    dfz::VD{FT}

    nodalMass::VD{FT}     # mass g
    h_nodalMass::Vector{FT}     # mass - host g

    # device pointers for comms g
    # TODO: Not sure how to store the pointers or if even necessary
    # Real_t *d_delv_xi       # velocity gradient -- temporary g
    # Real_t *d_delv_eta
    # Real_t *d_delv_zeta

    # Real_t *d_x             # coordinates g
    # Real_t *d_y
    # Real_t *d_z

    # Real_t *d_xd            # velocities g
    # Real_t *d_yd
    # Real_t *d_zd

    # Real_t *d_fx            # forces g
    # Real_t *d_fy
    # Real_t *d_fz
    # Boundary nodesets

    symmX::VD{IndexT}      # symmetry plane nodesets
    symmY::VD{IndexT}
    symmZ::VD{IndexT}

    nodeElemCount::VD{Int}
    nodeElemStart::VD{Int}
    nodeElemCornerList::VD{IndexT}

    # Parameters

    dtfixed::FT                # fixed time increment g
    deltatimemultlb::FT
    deltatimemultub::FT
    stoptime::FT               # end time for simulation g
    dtmax::FT                  # maximum allowable time increment g
    cycle::Int                 # iteration count for simulation g

    dthydro_h::FT             # hydro time constraint g
    d_dthydro_h::FT           # AD change of the hydro time constraint g
    dtcourant_h::FT           # courant time constraint g
    d_dtcourant_h::FT         # AD of the courant time constraint g
    bad_q_h::IndexT              # flag to indicate Q error g
    bad_vol_h::IndexT            # flag to indicate volume error g

    # cuda Events to indicate completion of certain kernels
    # TODO Will check later how this works with KA
    # cudaEvent_t time_constraint_computed;
    time_h::FT                # current time g
    deltatime_h::FT           # variable time increment g

    u_cut::FT                 # velocity tolerance g
    hgcoef::FT                # hourglass control g
    qstop::FT                 # excessive q indicator g
    monoq_max_slope::FT
    monoq_limiter_mult::FT
    e_cut::FT                 # energy tolerance g
    p_cut::FT                 # pressure tolerance g
    ss4o3::FT
    q_cut::FT                 # q tolerance g
    v_cut::FT                 # relative volume tolerance g
    qlc_monoq::FT             # linear term coef for q g
    qqc_monoq::FT             # quadratic term coef for q g
    qqc::FT
    eosvmax::FT
    eosvmin::FT
    pmin::FT                  # pressure floor g
    emin::FT                  # energy floor g
    dvovmax::FT               # maximum allowable volume change g
    refdens::FT               # reference density g

    m_colLoc::IndexT
    m_rowLoc::IndexT
    m_planeLoc::IndexT
    m_tp::IndexT
    sizeX::IndexT
    sizeY::IndexT
    sizeZ::IndexT
    maxPlaneSize::IndexT
    maxEdgeSize::IndexT

    numElem::IndexT
    # padded_numElem::IndexT

    numNode::IndexT
    # padded_numNode::IndexT

    numSymmX::IndexT
    numSymmY::IndexT
    numSymmZ::IndexT

    octantCorner::IndexT

    # Region information
    numReg::Int                     # number of regions (def:11)
    balance::Int                    # Load balance between regions of a domain (def: 1)
    cost::Int                       # imbalance cost (def: 1)
    regElemSize::Vector{Int}        # Size of region sets
    regCSR::VD{Int}                 # records the begining and end of each region
    regReps::VD{Int}                # records the rep number per region
    regNumList::VD{IndexT}         # Region number per domain element
    regElemlist::VD{IndexT}        # region indexset
    regSorted::VD{IndexT}          # keeps index of sorted regions

    # MPI-Related additional data

    # TODO I think we can handle this differently

    # IndexT m_numRanks;
    # IndexT& numRanks() { return m_numRanks ; }

    # # Used in setup
    # IndexT m_rowMin, m_rowMax;
    # IndexT m_colMin, m_colMax;
    # IndexT m_planeMin, m_planeMax ;

    # # Communication Work space
    # Real_t *commDataSend ;
    # Real_t *commDataRecv ;

    # Real_t *d_commDataSend ;
    # Real_t *d_commDataRecv ;

    # # Maximum number of block neighbors
    # MPI_Request recvRequest[26] ; # 6 faces + 12 edges + 8 corners
    # MPI_Request sendRequest[26] ; # 6 faces + 12 edges + 8 corners
end


#!/usr/bin/julia
#
# Plotting functions.
#
using RecipesBase;
using Plots: @layout, grid;
#using IJulia; # Required to prevent plotting window opening and crashing.

export check_path, gen_filename, format_plot_title_variable,
       GeneralPlotOptions, LanczosPlotOptions, SplitStepPlotOptions, SimPlot;

"""
Implementations contain mainly boolean objects specifying what should be
included within plots.

All implementations should at least implement all the properties of
`GeneralPlotOptions`.

See also: [`GeneralPlotOptions`](@ref).
"""
abstract type PlotOptions end;

"Plotting options for a simulation agnostic plot"
struct GeneralPlotOptions <: PlotOptions
  """
  If the real and imaginary parts of the wave function should be plotted in
  addition to the absolute square value.
  """
  components::Bool;

  """
  If the value of the wave function is less than the value of this property at
  the start or end of the plot range, then they will not be plotted.
  """
  extremaϵ::Number;

end
GeneralPlotOptions(;components::Bool=false, extremaϵ::Number=0.1) =
  GeneralPlotOptions(components, extremaϵ);

"Plotting options for split step simulations."
struct SplitStepPlotOptions <: PlotOptions
  components::Bool;
  extremaϵ::Number;
end
SplitStepPlotOptions(;components::Bool=false, extremaϵ::Number=0.1) =
  SplitStepPlotOptions(components, extremaϵ);

"Plotting options for Lanczos simulations."
struct LanczosPlotOptions <: PlotOptions
  components::Bool;
  extremaϵ::Number;
  "Plot basis functions."
  basis::Bool;
  "Plot eigenfunctions of the reduced Hamiltonian."
  eigenfct::Bool;
end

abstract type PlotType end;
struct SimPlot<:PlotType end;

"""
    check_path(path::String)

Check the path given exists and create it if it does not.
"""
function check_path(path::String)
  if (!ispath(path))
    mkpath(path)
  end
end

# Parameters common to all simulation methods.
function gen_filename(p::SimParams, grid::UniformGrid{S,1}) where S
  return string(real(p.Δt) == 0 ? "ti" : "td",
    round(typeof(grid.Δ), grid.Δ, sigdigits=4), "d",
    round(typeof(grid.max), grid.max, sigdigits=4), "mx",
    round(typeof(p.Δt), abs(p.Δt), sigdigits=4), "dt-", p.steps, "s");
end
# Simulation variation method.
function gen_filename(var::Vararg{SimVariation})
  name = string();

  # Iterate through simulation variations and append parameters to filename.
  for i ∈ var
    if i isa AdaptiveΔt
      name *= string("-", round(i.ϵmax, sigdigits=4), "emx",
                     round(i.ϵmin, sigdigits=4), "emn",
                     round(i.Δtdown, sigdigits=4), "tdn",
                     round(i.Δtup, sigdigits=4), "tup",
                     round(i.Δtmax, sigdigits=4), "tmx",
                     round(i.Δtmin, sigdigits=4), "tmn",
                     i.maxeval, "mxev");
      if i.maxevallimit != nothing
        name *= string(i.maxevallimit, "mxevlm");
      end
    elseif i isa ConvergenceVariation
      name *= string("-", i.ϵ, "ecnv");
    end
  end

  return name;
end
# Common plotting options.
function gen_filename(plt::PlotOptions)
  if plt.components
    return string("pcmpnt");
  end
  return string();
end
# Simulation specific methods.
function gen_filename(sim::Lanczos, plt::LanczosPlotOptions)
  name = string("-sil-", sim.basisno, "b", sim.basissteps, "bs-" );

  if plt.basis name *= string("pb"); end
  if plt.eigenfct name *= string("pefct"); end

  name *= gen_filename(plt);

  return name;
end
function gen_filename(sim::SplitStep, plt::SplitStepPlotOptions)
  name = string("-ss-");

  name *= gen_filename(plt);

  return name
end
"""
    gen_filename(p::SimParams, grid::UniformGrid{S,1},
                      sim::SimMethod, plt::PlotOptions,
                      var::Union{Vararg{SimVariation},Nothing}=nothing) where S

Generate an appropriate filename reflecting the parameters used in the
simulation.
"""
function gen_filename(p::SimParams, grid::UniformGrid{S,1},
                      sim::SimMethod, plt::PlotOptions,
                      var::Vararg{Union{Nothing,SimVariation}}=nothing) where S
  name = gen_filename(p, grid);
  name *= gen_filename(sim, plt);
  if (var[1] != nothing) name *= gen_filename(var...); end

  return name;
end

"""
    format_plot_title_variable(variable::Number, decimalplaces::Integer)

Format `variable` with a fixed number of decimal places given by `decimalplaces`
such that it is in a suitable format to be printed in a GIF frame.
"""
function format_plot_title_variable(variable::Unitful.Quantity, decimalplaces::Integer)
  return string(format_plot_title_variable(ustrip(variable), decimalplaces),
                " ", unit(variable));
end
function format_plot_title_variable(variable::Number, decimalplaces::Integer)
  # Determine number of digits before the decimal place.
  predecimal = findfirst(".", string(variable));
  if predecimal === nothing predecimal = length(string(variable));
  else predecimal = predecimal[1]-1 end

  return string(rpad(round(variable, digits=decimalplaces),
                     predecimal+decimalplaces+1, '0'));
end

"""
    get_effective_minmax(v::AbstractVector, ϵ::Float64=0.1)

Return the minimum and maximum indices of `v` below and above which the value of
`v` does not exceed `ϵ`.
"""
function get_effective_minmax(v::AbstractVector, ϵ::Number=0.1)
  len = length(v);
  min = 1;
  max = len;
  # Find the first and last indices of v where v > ϵ.
  for i ∈ 1:len
    if abs(v[i]) > ϵ
      min = i;
      break;
    end;
  end
  for i ∈ len:-1:1
    if abs(v[i]) > ϵ
      max = i;
      break;
    end
  end

  return (min, max);
end
function get_effective_minmax(v::Vararg{AbstractVector}; ϵ=0.1)
  len = length(v);
  mins = Vector{Int}(undef, len);
  maxs = Vector{Int}(undef, len);

  for i ∈ 1:len
    (mins[i], maxs[i]) = get_effective_minmax(v[i], ϵ);
  end

  return (mins, maxs);
end
"""
    get_effective_minmax(m::AbstractMatrix, ϵ::Number=0.1)

Treat the columns of the matrix `m` as vectors.
"""
function get_effective_minmax(m::AbstractMatrix, ϵ::Number=0.1)
  numv = size(m)[2];
  mins = Vector{Int}(undef, numv);
  maxs = Vector{Int}(undef, numv);

  for i ∈ 1:numv
    (mins[i], maxs[i]) = get_effective_minmax(m[:,i], ϵ);
  end

  return (mins, maxs);
end

"""
    get_global_effective_minmax(v::Vararg{AbstractVector}; ϵ=0.1)

Returns the maximum and minimum values found by the respective
`get_effective_minmax()` function.

See also: [`get_effective_minmax`](@ref)
"""
function get_global_effective_minmax(v::Vararg{AbstractVector}; ϵ=0.1)
  (xmins, xmaxs) = get_effective_minmax(v..., ϵ=ϵ);
  return (minimum(xmins), maximum(xmaxs));
end
function get_global_effective_minmax(m::AbstractMatrix, ϵ::Number=0.1)
  (xmins, xmaxs) = get_effective_minmax(m, ϵ);
  return (minimum(xmins), maximum(xmaxs));
end

"""
    get_ranged_minmax(min::Int, max::Int, v::Vararg{AbstractVector})

Find the minimum and maximum of the `v` in the index range `min:max`.

Note: values of the `v` should be real to allow size comparison.
"""
function get_ranged_minmax(min::Int, max::Int, v::Vararg{AbstractVector})
  ymin = 0.0;
  ymax = 0.0;
  for i ∈ 2:numψ
    w = v[i][xmin:xmax];
    t = minimum(w);
    if (t < ymin || i == 1) ymin = t; end
    t = maxmum(w);
    if (t > ymmax || i ==1) ymax = t; end
  end

  return (ymin, ymax);
end

"""
    subscript(i::Integer)

Generate a string with a subscript number `i`. From Stack Overflow user
improbable https://stackoverflow.com/a/46709534.
"""
subscript(i::Integer) = i<0 ? error("$i is negative") : join('₀'+d for d in reverse(digits(i)));

"""
    generate_wavefunction_labels(num::Int)

Generate labels to identify `num` wave functions.
"""
function generate_wavefunction_labels(num::Int)
  labels = Vector{String}(undef, num);
  if (num == 1) labels[1] = "|ψ|²";
  else
    for i ∈ 1:num labels[i] = string("|ψ", subscript(i), "|²"); end
  end
  return reshape(labels, 1, num);
end

"Set appropriate axis labels"
@recipe function f(::Type{O}, x::Vector, y::Array) where
                   {T,N,S<:ConfigurationSpace,O<:WaveObject{T,N,S,1}}
  xguide --> "r";
  yguide --> "Probability density";
  (x, y)
end
@recipe function f(::Type{O}, x::Vector, y::Array) where
                   {T,N,S<:MomentumSpace,O<:WaveObject{T,N,S,1}}
  xguide --> "k";
  yguide --> "Probability density";
  (x, y)
end

"""
    vectors_to_matrix(v::Tuple{Vararg{Vector}}, min::Int=1, max::Int=0)

Construct a matrix from the vectors `v` between the indices `min` and `max`.
"""
function vectors_to_matrix(v::Tuple{Vararg{AbstractVector}}, min::Int=1, max::Int=0)
  num = length(v);
  if (max == 0) max = num; end

  m = Matrix{Number}(undef, max - min + 1, num);
  for i ∈ 1:num m[:,i] = abs2.(v[i][min:max]); end

  return m;
end

"Plot absolute square of wave function across relevant range."
@recipe function f(grid::Grid1D, ψ::Vararg{WaveFunction{T,1}};
                   opts=GeneralPlotOptions()) where T
  (opts, grid, ψ...);
end
# Keyword arguments are not dispatched properly in the recipe chain,
# so define this alternative order of the arguments to implement the function.
# 2020/12/01
@recipe function f(opts::PlotOptions, grid::Grid1D,
                   ψ::Vararg{WaveFunction{T,1}}) where T
  label --> generate_wavefunction_labels(length(ψ));

  (min, max) = get_global_effective_minmax(ψ..., ϵ=opts.extremaϵ);

  (typeof(ψ[1]), grid[min:max], vectors_to_matrix(ψ, min, max));
end

"""
Plot potential function and absolute square of wave function across interesting
wave function range.
"""
@recipe function f(grid::Grid1D, H::SeparableHamiltonian,
                   ψ::Vararg{WaveFunction{T,1}};
                   opts=GeneralPlotOptions()) where T
  (opts, grid, H, ψ...);
end
# Workaround for same issue as above.
@recipe function f(opts::PlotOptions, grid::Grid1D, H::SeparableHamiltonian,
                   ψ::Vararg{WaveFunction{T,1}}) where T
  label --> hcat(["Potential"], generate_wavefunction_labels(length(ψ)));

  (min, max) = get_global_effective_minmax(ψ..., ϵ=opts.extremaϵ);

  (typeof(ψ[1]), grid[min:max], hcat(real.(H.V[min:max]),
                                     vectors_to_matrix(ψ, min, max)));
end

"""
Plot absolute square of wave function components of wave matrix across relevant
range.
"""
@recipe function f(grid::G, ϕ::WaveMatrix{T,S,G},
                   opts::PlotOptions=GeneralPlotOptions()) where {T,S,G}
  label --> generate_wavefunction_labels(size(ϕ)[2]);

  (min, max) = get_global_effective_minmax(ϕ, opts.extremaϵ);

  (typeof(ϕ), grid[min:max], abs2.(ϕ[min:max,:]))
end

"""
Plot potential function and absolute square of wave function components of wave
matrix across relevant range.
"""
@recipe function f(grid::G, H::SeparableHamiltonian, ϕ::WaveMatrix{T,S,G},
                   opts::PlotOptions=GeneralPlotOptions()) where {T,S,G}
  label --> hcat(["Potential"], generate_wavefunction_labels(size(ϕ)[2]));

  (min, max) = get_global_effective_minmax(ϕ, opts.extremaϵ);

  (typeof(ϕ), grid[min:max], hcat(real.(H.V[min:max]),
                                  abs2.(ϕ[min:max,:])))
end

"Plot potential functions included in the passed Hamiltonians."
@recipe function f(grid::Grid1D, H::Vararg{SeparableHamiltonian})
  potentials = Vector{Vector{real(eltype(H[1]))}}(undef, length(H));
  for i ∈ 1:length(H) potentials[i] = real.(H[i].V); end
  (grid, potentials)
end

"Plot real and imaginary parts of wave functions alongside main plot."
@recipe function f(p::SimPlot, opts::PlotOptions, g::G, ψ::WaveFunction{T,1,S,G},
                   H::Union{SeparableHamiltonian,Nothing}=nothing) where {T,S,G}
  size := (800, 450);
  dpi := 300;

  if opts.components
    #layout := @layout [ a{0.6w} grid(2,1) ]
    layout := @layout [ a{0.5w} b ]

    @series begin
      subplot := 2;
      title := "Wave function decomposition";
      ylims := (-1,1);
      realψ = real.(ψ);
      (remin, remax) = get_effective_minmax(realψ, opts.extremaϵ);
      imagψ = imag.(ψ);
      (immin, immax) = get_effective_minmax(imagψ, opts.extremaϵ);
      min = Base.min(immin, remin);
      max = Base.max(immax, remax);
      label := hcat(["Re(ψ)"], ["Im(ψ)"]);
      (g[min:max], hcat(realψ[min:max], imagψ[min:max]))
    end
    # Allow negative y values on subplots.
    #=
    try
      subylims = ylims();
      if subylims[1] >= 0
        subylims = (-subylims[2], subylims[2]);
      end
    catch
    end=#

    #=@series begin
      subplot := 2;
      title := "Re(ψ)";
      ylims := (-1,1);
      realψ = real.(ψ);
      (min, max) = get_effective_minmax(realψ, opts.extremaϵ);
      (g[min:max], realψ[min:max])
    end

    @series begin
      subplot := 3;
      title := "Im(ψ)";
      ylims := (-1,1);
      imagψ = imag.(ψ);
      (min, max) = get_effective_minmax(imagψ, opts.extremaϵ);
      (g[min:max], imagψ[min:max])
    end=#
  end

  @series begin
    subplot := 1;
    if (H === nothing) return (opts, g, ψ);
    else return (opts, g, H, ψ); end
  end
end

"Plot Krylov basis and reduced Hamiltonian eigenfunctions from Lanczos method."
@recipe function f(p::SimPlot, opts::LanczosPlotOptions, g::G, ψ::WaveFunction{T,1,S,G},
                   Φ::KrylovBasis{T,S,G}, H::Union{SeparableHamiltonian,Nothing}=nothing) where {T,S,G}
  if opts.components && opts.basis && opts.eigenfct
    layout := @layout [ a{0.7w} grid(2,1); grid(1,2){0.4h}]
    size := (1400, 750);
  elseif !opts.components && opts.basis && opts.eigenfct
    layout := @layout [ a; grid(1,2)]
    size := (1400, 750);
  elseif !opts.components && !opts.basis && !opts.eigenfct
    size := (1400, 750);
  elseif opts.components && !opts.basis && !opts.eigenfct
    layout := @layout [ a{0.6w} grid(2,1) ]
    size := (1400, 750);
  else
    error("the requested plotting format has not yet been implemented.");
  end

  if opts.components
    @series begin
      subplot := 2;
      title := "Re(ψ)";
      ylims := (-1,1);
      realψ = real.(ψ);
      (min, max) = get_effective_minmax(realψ, opts.extremaϵ);
      (g[min:max], realψ[min:max])
    end
    @series begin
      subplot := 3;
      title := "Im(ψ)";
      ylims := (-1,1);
      imagψ = imag.(ψ);
      (min, max) = get_effective_minmax(imagψ, opts.extremaϵ);
      (g[min:max], imagψ[min:max])
    end
  end
  if opts.basis && opts.eigenfct
    # Plot basis functions.
    @series begin
      if !opts.components subplot := 2;
      else subplot := 4; end
      title := "Krylov Basis"
      (g, Φ, opts)
    end

    # Plot eigenfunctions.
    @series begin
      if !opts.components subplot := 3;
      else subplot := 5; end

      # Label the eigenstates by their energies.
      len = length(Φ.λ);
      labels = Vector{String}(undef, len)
      for i ∈ 1:len labels[i] = string("|ψ|² where E = ", round(Φ.λ[i], sigdigits=4)); end
      label := reshape(labels, 1, len);

      title := "Reduced Hamiltonian Eigenstates"
      (g, KrylovBasis{T,S,G}(Φ*Φ.v, Φ.λ, Φ.v, Φ.Δ), opts)
    end
  end

  @series begin
    subplot := 1;
    if (H === nothing) return (opts, g, ψ);
    else return (opts, g, H, ψ); end
  end
end


require("ArgParse")
require("Iterators")

using ArgParse, Iterators
import JSON
push!(LOAD_PATH, dirname(@__FILE__()))

function parse_commandline()
    s = ArgParseSettings()

    @add_arg_table s begin
        "--E-ONE", "-o"
            help = "converter for e-one energy term"
            action = :store_true
            #required = true
        "--input-file"
            help = "file with E-one values"
            required = true
        "--output-file"
            help = ".json file name form saving results"
            required = true
    end

    return parse_args(s)
end
aaNames = ["GLY", "ALA", "SER", "CYS",
  "VAL", "THR", "ILE", "PRO",
  "MET", "ASP", "ASN", "LEU",
  "LYS", "GLU", "GLN", "ARG",
  "HIS", "PHE", "TYR", "TRP",
  "CYX"]
aaIndices = Dict(collect(zip(aaNames, 1:21)))

immutable E12Data
  aa1 :: String
  aa2 :: String
  n14 :: Int
  e214 :: Array{Number, 1}
end

function process_r14aa12(result :: Dict{String, Dict{String, E12Data}})
  {"data"=>
  [
  aa1 =>
    [
      aa2 => result[aa1][aa2]
      for aa2 in intersect(aaNames, keys(result[aa1]))
    ]
    for aa1 in intersect(aaNames, keys(result))
    ],
    "startIndex" => -86,
    "endIndex" => 91,
    "binRightBorders" => [-79, -55, -35, -25, -12, 14, 26, 46, 65, 72, 81]
    }
end

function readE12part(input_file)
  (aa1, aa2, n14) = split(rstrip(readline(input_file)))
  numbers = Number[]
  while(!eof(input_file))
    s = readline(input_file)
    if beginswith(s, "=")
      break
    end
    append!(numbers, map(x->float(x), split(rstrip(s))))
  end
  return (aa1, aa2, int(n14), numbers)
end

function main()
    parsed_args = parse_commandline()
    input_file = open(parsed_args["input-file"], "r")
    result = Dict{String, Dict{String, E12Data}}()
    while !eof(input_file)
      (aa1, aa2, n14, numbers) = readE12part(input_file)
      if !haskey(result, aa1)
        result[aa1] = Dict{String, E12Data}()
      end
      result[aa1][aa2] = E12Data(aa1, aa2, n14, numbers)
      #push!(result, rstrip(readline(input_file)))
    end
    close(input_file)
    r1 = process_r14aa12(result)
    output_file = open(parsed_args["output-file"], "w")
    println(output_file, JSON.json(r1, 1))
    close(output_file)
end

main()


@testset "ElasticDistortion" begin
    @test (ElasticDistortion <: Augmentor.AffineOperation) == false
    @test (ElasticDistortion <: Augmentor.ImageOperation) == true

    @testset "constructor" begin
        @test_throws MethodError ElasticDistortion()
        let op = @inferred ElasticDistortion(3)
            @test op.gridheight == 3
            @test op.gridwidth == 3
            @test str_show(op) == "Augmentor.ElasticDistortion(3, 3)"
            @test str_showconst(op) == "ElasticDistortion(3, 3)"
            @test str_showcompact(op) == "Distort using a smoothed and normalized 3×3 grid with pinned border"
        end
        let op = @inferred ElasticDistortion(3, 4)
            @test op.gridheight == 3
            @test op.gridwidth == 4
            @test str_show(op) == "Augmentor.ElasticDistortion(3, 4)"
            @test str_showconst(op) == "ElasticDistortion(3, 4)"
            @test str_showcompact(op) == "Distort using a smoothed and normalized 3×4 grid with pinned border"
        end
        let op = @inferred ElasticDistortion(3, 4, 0.6)
            @test op.gridheight == 3
            @test op.gridwidth == 4
            @test op.scale == 0.6
            @test str_show(op) == "Augmentor.ElasticDistortion(3, 4, scale=0.6)"
            @test str_showconst(op) == "ElasticDistortion(3, 4, scale=0.6)"
            @test str_showcompact(op) == "Distort using a smoothed and normalized 3×4 grid with pinned border"
        end
        let op = @inferred ElasticDistortion(3, 4, 0.6, 0.2)
            @test op.gridheight == 3
            @test op.gridwidth == 4
            @test op.scale == 0.6
            @test op.sigma == 0.2
            @test str_show(op) == "Augmentor.ElasticDistortion(3, 4, scale=0.6, sigma=0.2)"
            @test str_showconst(op) == "ElasticDistortion(3, 4, scale=0.6, sigma=0.2)"
            @test str_showcompact(op) == "Distort using a smoothed and normalized 3×4 grid with pinned border"
        end
        let op = @inferred ElasticDistortion(3, 4, 0.6, 0.2, 2, false, true)
            @test op.gridheight == 3
            @test op.gridwidth == 4
            @test op.scale == 0.6
            @test op.sigma == 0.2
            @test op.iterations == 2
            @test op.border == false
            @test op.normalize == true
            @test str_show(op) == "Augmentor.ElasticDistortion(3, 4, scale=0.6, sigma=0.2, iter=2)"
            @test str_showconst(op) == "ElasticDistortion(3, 4, scale=0.6, sigma=0.2, iter=2)"
            @test str_showcompact(op) == "Distort using a 2-times smoothed and normalized 3×4 grid with pinned border"
        end
        let op = @inferred ElasticDistortion(3, 4, 0.6, 0.2, 1, true, false)
            @test op.gridheight == 3
            @test op.gridwidth == 4
            @test op.scale == 0.6
            @test op.sigma == 0.2
            @test op.iterations == 1
            @test op.border == true
            @test op.normalize == false
            @test str_show(op) == "Augmentor.ElasticDistortion(3, 4, scale=0.6, sigma=0.2, border=true, norm=false)"
            @test str_showconst(op) == "ElasticDistortion(3, 4, scale=0.6, sigma=0.2, border=true, norm=false)"
            @test str_showcompact(op) == "Distort using a smoothed 3×4 grid"
        end
        let op = ElasticDistortion(3, 4, 0.6, sigma=0.2, iter=2, norm=false, border=false)
            @test op.gridheight == 3
            @test op.gridwidth == 4
            @test op.scale == 0.6
            @test op.sigma == 0.2
            @test op.iterations == 2
            @test op.border == false
            @test op.normalize == false
            @test str_show(op) == "Augmentor.ElasticDistortion(3, 4, scale=0.6, sigma=0.2, iter=2, norm=false)"
            @test str_showconst(op) == "ElasticDistortion(3, 4, scale=0.6, sigma=0.2, iter=2, norm=false)"
            @test str_showcompact(op) == "Distort using a 2-times smoothed 3×4 grid with pinned border"
        end
        let op = ElasticDistortion(3, 4, scale=0.6, sigma=0.2, iter=2, norm=false, border=false)
            @test op.gridheight == 3
            @test op.gridwidth == 4
            @test op.scale == 0.6
            @test op.sigma == 0.2
            @test op.iterations == 2
            @test op.border == false
            @test op.normalize == false
            @test str_show(op) == "Augmentor.ElasticDistortion(3, 4, scale=0.6, sigma=0.2, iter=2, norm=false)"
            @test str_showconst(op) == "ElasticDistortion(3, 4, scale=0.6, sigma=0.2, iter=2, norm=false)"
            @test str_showcompact(op) == "Distort using a 2-times smoothed 3×4 grid with pinned border"
        end
    end
    imgs = [
        (rect),
        (Augmentor.prepareaffine(rect)),
        (OffsetArray(rect, -2, -1)),
        (view(rect, IdentityRange(1:2), IdentityRange(1:3))),
    ]
    @testset "eager" begin
        @test Augmentor.supports_eager(ElasticDistortion) === false
        # TODO: better tests
        @testset "single image" begin
            for img_in in imgs
                res = @inferred(Augmentor.applyeager(ElasticDistortion(4,4), img_in))
                @test size(res) == size(rect)
                @test typeof(res) <: Array{eltype(img_in),2}
            end
        end
        @testset "multiple images" begin
            for img_in1 in imgs, img_in2 in imgs
                img_in = (img_in1, img_in2)
                res = @inferred(Augmentor.applyeager(ElasticDistortion(4,4), img_in))
                @test res[1] == res[2]
                @test typeof(res) <: NTuple{2,Array{eltype(img_in1),2}}
            end
        end
    end
    @testset "affine" begin
        @test Augmentor.supports_affine(ElasticDistortion) === false
    end
    @testset "affineview" begin
        @test Augmentor.supports_affineview(ElasticDistortion) === false
    end
    @testset "lazy" begin
        @test Augmentor.supports_lazy(ElasticDistortion) === true
        @testset "single image" begin
            for img in imgs
                res = @inferred(Augmentor.applylazy(ElasticDistortion(4,4), img))
                @test size(res) == size(rect)
                @test typeof(res) <: Augmentor.DistortedView{eltype(rect)}
                @test parent(res) === img
            end
        end
        @testset "multiple images" begin
            for img_in1 in imgs, img_in2 in imgs
                img_in = (img_in1, img_in2)
                res = @inferred(Augmentor.applylazy(ElasticDistortion(4,4), img_in))
                @test res[1] == res[2]
                @test res[1].field.itp.coefs === res[2].field.itp.coefs
                @test typeof(res) <: NTuple{2,Augmentor.DistortedView{eltype(img_in1)}}
            end
        end
    end
    @testset "view" begin
        @test Augmentor.supports_view(ElasticDistortion) === false
    end
    @testset "stepview" begin
        @test Augmentor.supports_stepview(ElasticDistortion) === false
    end
    @testset "permute" begin
        @test Augmentor.supports_permute(ElasticDistortion) === false
    end
end