from struct import pack


class BGAPICommandPacketBuilder(object):
    @staticmethod
    def system_reset(boot_in_dfu):
        return pack('<4BB', 0, 1, 0, 0, boot_in_dfu)

    @staticmethod
    def system_hello():
        return pack('<4B', 0, 0, 0, 1)

    @staticmethod
    def system_address_get():
        return pack('<4B', 0, 0, 0, 2)

    @staticmethod
    def system_reg_write(address, value):
        return pack('<4BHB', 0, 3, 0, 3, address, value)

    @staticmethod
    def system_reg_read(address):
        return pack('<4BH', 0, 2, 0, 4, address)

    @staticmethod
    def system_get_counters():
        return pack('<4B', 0, 0, 0, 5)

    @staticmethod
    def system_get_connections():
        return pack('<4B', 0, 0, 0, 6)

    @staticmethod
    def system_read_memory(address, length):
        return pack('<4BIB', 0, 5, 0, 7, address, length)

    @staticmethod
    def system_get_info():
        return pack('<4B', 0, 0, 0, 8)

    @staticmethod
    def system_endpoint_tx(endpoint, data):
        return pack('<4BBB' + str(len(data)) + 's', 0, 2 + len(data), 0, 9,
                    endpoint, len(data), b''.join(chr(i) for i in data))

    @staticmethod
    def system_whitelist_append(address, address_type):
        return pack('<4B6sB', 0, 7, 0, 10, b''.join(chr(i) for i in address),
                    address_type)

    @staticmethod
    def system_whitelist_remove(address, address_type):
        return pack('<4B6sB', 0, 7, 0, 11, b''.join(chr(i) for i in address),
                    address_type)

    @staticmethod
    def system_whitelist_clear():
        return pack('<4B', 0, 0, 0, 12)

    @staticmethod
    def system_endpoint_rx(endpoint, size):
        return pack('<4BBB', 0, 2, 0, 13, endpoint, size)

    @staticmethod
    def system_endpoint_set_watermarks(endpoint, rx, tx):
        return pack('<4BBBB', 0, 3, 0, 14, endpoint, rx, tx)

    @staticmethod
    def flash_ps_defrag():
        return pack('<4B', 0, 0, 1, 0)

    @staticmethod
    def flash_ps_dump():
        return pack('<4B', 0, 0, 1, 1)

    @staticmethod
    def flash_ps_erase_all():
        return pack('<4B', 0, 0, 1, 2)

    @staticmethod
    def flash_ps_save(key, value):
        return pack('<4BHB' + str(len(value)) + 's', 0, 3 + len(value), 1, 3,
                    key, len(value), b''.join(chr(i) for i in value))

    @staticmethod
    def flash_ps_load(key):
        return pack('<4BH', 0, 2, 1, 4, key)

    @staticmethod
    def flash_ps_erase(key):
        return pack('<4BH', 0, 2, 1, 5, key)

    @staticmethod
    def flash_erase_page(page):
        return pack('<4BB', 0, 1, 1, 6, page)

    @staticmethod
    def flash_write_words(address, words):
        return pack('<4BHB' + str(len(words)) + 's', 0, 3 + len(words), 1, 7,
                    address, len(words), b''.join(chr(i) for i in words))

    @staticmethod
    def attributes_write(handle, offset, value):
        return pack('<4BHBB' + str(len(value)) + 's', 0, 4 + len(value), 2, 0,
                    handle, offset, len(value), b''.join(chr(i) for i in value))

    @staticmethod
    def attributes_read(handle, offset):
        return pack('<4BHH', 0, 4, 2, 1, handle, offset)

    @staticmethod
    def attributes_read_type(handle):
        return pack('<4BH', 0, 2, 2, 2, handle)

    @staticmethod
    def attributes_user_read_response(connection, att_error, value):
        return pack('<4BBBB' + str(len(value)) + 's', 0, 3 + len(value), 2, 3,
                    connection, att_error, len(value),
                    b''.join(chr(i) for i in value))

    @staticmethod
    def attributes_user_write_response(connection, att_error):
        return pack('<4BBB', 0, 2, 2, 4, connection, att_error)

    @staticmethod
    def connection_disconnect(connection):
        return pack('<4BB', 0, 1, 3, 0, connection)

    @staticmethod
    def connection_get_rssi(connection):
        return pack('<4BB', 0, 1, 3, 1, connection)

    @staticmethod
    def connection_update(connection, interval_min, interval_max,
                          latency, timeout):
        return pack('<4BBHHHH', 0, 9, 3, 2, connection, interval_min,
                    interval_max, latency, timeout)

    @staticmethod
    def connection_version_update(connection):
        return pack('<4BB', 0, 1, 3, 3, connection)

    @staticmethod
    def connection_channel_map_get(connection):
        return pack('<4BB', 0, 1, 3, 4, connection)

    @staticmethod
    def connection_channel_map_set(connection, map):
        return pack('<4BBB' + str(len(map)) + 's', 0, 2 + len(map), 3, 5,
                    connection, len(map), b''.join(chr(i) for i in map))

    @staticmethod
    def connection_features_get(connection):
        return pack('<4BB', 0, 1, 3, 6, connection)

    @staticmethod
    def connection_get_status(connection):
        return pack('<4BB', 0, 1, 3, 7, connection)

    @staticmethod
    def connection_raw_tx(connection, data):
        return pack('<4BBB' + str(len(data)) + 's', 0, 2 + len(data), 3, 8,
                    connection, len(data), b''.join(chr(i) for i in data))

    @staticmethod
    def attclient_find_by_type_value(connection, start, end, uuid, value):
        return pack('<4BBHHHB' + str(len(value)) + 's', 0, 8 + len(value), 4, 0,
                    connection, start, end, uuid, len(value),
                    b''.join(chr(i) for i in value))

    @staticmethod
    def attclient_read_by_group_type(connection, start, end, uuid):
        return pack('<4BBHHB' + str(len(uuid)) + 's', 0, 6 + len(uuid), 4, 1,
                    connection, start, end, len(uuid),
                    b''.join(chr(i) for i in uuid))

    @staticmethod
    def attclient_read_by_type(connection, start, end, uuid=[0x03, 0x28]):
        # Using the default UUID type to find custom UUIDs, which seems to make
        # querying for characteristics faster.
        return pack('<4BBHHB' + str(len(uuid)) + 's', 0, 6 + len(uuid), 4, 2,
                    connection, start, end, len(uuid),
                    b''.join(chr(i) for i in uuid))

    @staticmethod
    def attclient_find_information(connection, start, end):
        return pack('<4BBHH', 0, 5, 4, 3, connection, start, end)

    @staticmethod
    def attclient_read_by_handle(connection, chrhandle):
        return pack('<4BBH', 0, 3, 4, 4, connection, chrhandle)

    @staticmethod
    def attclient_attribute_write(connection, atthandle, data):
        return pack('<4BBHB' + str(len(data)) + 's', 0, 4 + len(data), 4, 5,
                    connection, atthandle, len(data),
                    b''.join(chr(i) for i in data))

    @staticmethod
    def attclient_write_command(connection, atthandle, data):
        return pack('<4BBHB' + str(len(data)) + 's', 0, 4 + len(data), 4, 6,
                    connection, atthandle, len(data),
                    b''.join(chr(i) for i in data))

    @staticmethod
    def attclient_indicate_confirm(connection):
        return pack('<4BB', 0, 1, 4, 7, connection)

    @staticmethod
    def attclient_read_long(connection, chrhandle):
        return pack('<4BBH', 0, 3, 4, 8, connection, chrhandle)

    @staticmethod
    def attclient_prepare_write(connection, atthandle, offset, data):
        return pack('<4BBHHB' + str(len(data)) + 's', 0, 6 + len(data), 4, 9,
                    connection, atthandle, offset, len(data),
                    b''.join(chr(i) for i in data))

    @staticmethod
    def attclient_execute_write(connection, commit):
        return pack('<4BBB', 0, 2, 4, 10, connection, commit)

    @staticmethod
    def attclient_read_multiple(connection, handles):
        return pack('<4BBB' + str(len(handles)) + 's', 0, 2 + len(handles), 4,
                    11, connection, len(handles),
                    b''.join(chr(i) for i in handles))

    @staticmethod
    def sm_encrypt_start(handle, bonding):
        return pack('<4BBB', 0, 2, 5, 0, handle, bonding)

    @staticmethod
    def sm_set_bondable_mode(bondable):
        return pack('<4BB', 0, 1, 5, 1, bondable)

    @staticmethod
    def sm_delete_bonding(handle):
        return pack('<4BB', 0, 1, 5, 2, handle)

    @staticmethod
    def sm_set_parameters(mitm, min_key_size, io_capabilities):
        return pack('<4BBBB', 0, 3, 5, 3, mitm, min_key_size, io_capabilities)

    @staticmethod
    def sm_passkey_entry(handle, passkey):
        return pack('<4BBI', 0, 5, 5, 4, handle, passkey)

    @staticmethod
    def sm_get_bonds():
        return pack('<4B', 0, 0, 5, 5)

    @staticmethod
    def sm_set_oob_data(oob):
        return pack('<4BB' + str(len(oob)) + 's', 0, 1 + len(oob), 5, 6,
                    len(oob), b''.join(chr(i) for i in oob))

    @staticmethod
    def gap_set_privacy_flags(peripheral_privacy, central_privacy):
        return pack('<4BBB', 0, 2, 6, 0, peripheral_privacy, central_privacy)

    @staticmethod
    def gap_set_mode(discover, connect):
        return pack('<4BBB', 0, 2, 6, 1, discover, connect)

    @staticmethod
    def gap_discover(mode):
        return pack('<4BB', 0, 1, 6, 2, mode)

    @staticmethod
    def gap_connect_direct(address, addr_type, conn_interval_min,
                           conn_interval_max, timeout, latency):
        return pack('<4B6sBHHHH', 0, 15, 6, 3,
                    b''.join(reversed([chr(i) for i in address])), addr_type,
                    conn_interval_min, conn_interval_max, timeout, latency)

    @staticmethod
    def gap_end_procedure():
        return pack('<4B', 0, 0, 6, 4)

    @staticmethod
    def gap_connect_selective(conn_interval_min, conn_interval_max,
                              timeout, latency):
        return pack('<4BHHHH', 0, 8, 6, 5, conn_interval_min, conn_interval_max,
                    timeout, latency)

    @staticmethod
    def gap_set_filtering(scan_policy, adv_policy, scan_duplicate_filtering):
        return pack('<4BBBB', 0, 3, 6, 6, scan_policy, adv_policy,
                    scan_duplicate_filtering)

    @staticmethod
    def gap_set_scan_parameters(scan_interval, scan_window, active):
        return pack('<4BHHB', 0, 5, 6, 7, scan_interval, scan_window, active)

    @staticmethod
    def gap_set_adv_parameters(adv_interval_min, adv_interval_max,
                               adv_channels):
        return pack('<4BHHB', 0, 5, 6, 8, adv_interval_min, adv_interval_max,
                    adv_channels)

    @staticmethod
    def gap_set_adv_data(set_scanrsp, adv_data):
        return pack('<4BBB' + str(len(adv_data)) + 's', 0, 2 + len(adv_data), 6,
                    9, set_scanrsp, len(adv_data),
                    b''.join(chr(i) for i in adv_data))

    @staticmethod
    def gap_set_directed_connectable_mode(address, addr_type):
        return pack('<4B6sB', 0, 7, 6, 10, b''.join(chr(i) for i in address),
                    addr_type)

    @staticmethod
    def hardware_io_port_config_irq(port, enable_bits, falling_edge):
        return pack('<4BBBB', 0, 3, 7, 0, port, enable_bits, falling_edge)

    @staticmethod
    def hardware_set_soft_timer(time, handle, single_shot):
        return pack('<4BIBB', 0, 6, 7, 1, time, handle, single_shot)

    @staticmethod
    def hardware_adc_read(input, decimation, reference_selection):
        return pack('<4BBBB', 0, 3, 7, 2, input, decimation,
                    reference_selection)

    @staticmethod
    def hardware_io_port_config_direction(port, direction):
        return pack('<4BBB', 0, 2, 7, 3, port, direction)

    @staticmethod
    def hardware_io_port_config_function(port, function):
        return pack('<4BBB', 0, 2, 7, 4, port, function)

    @staticmethod
    def hardware_io_port_config_pull(port, tristate_mask, pull_up):
        return pack('<4BBBB', 0, 3, 7, 5, port, tristate_mask, pull_up)

    @staticmethod
    def hardware_io_port_write(port, mask, data):
        return pack('<4BBBB', 0, 3, 7, 6, port, mask, data)

    @staticmethod
    def hardware_io_port_read(port, mask):
        return pack('<4BBB', 0, 2, 7, 7, port, mask)

    @staticmethod
    def hardware_spi_config(channel, polarity, phase, bit_order,
                            baud_e, baud_m):
        return pack('<4BBBBBBB', 0, 6, 7, 8, channel, polarity, phase,
                    bit_order, baud_e, baud_m)

    @staticmethod
    def hardware_spi_transfer(channel, data):
        return pack('<4BBB' + str(len(data)) + 's', 0, 2 + len(data), 7, 9,
                    channel, len(data), b''.join(chr(i) for i in data))

    @staticmethod
    def hardware_i2c_read(address, stop, length):
        return pack('<4BBBB', 0, 3, 7, 10, address, stop, length)

    @staticmethod
    def hardware_i2c_write(address, stop, data):
        return pack('<4BBBB' + str(len(data)) + 's', 0, 3 + len(data), 7, 11,
                    address, stop, len(data), b''.join(chr(i) for i in data))

    @staticmethod
    def hardware_set_txpower(power):
        return pack('<4BB', 0, 1, 7, 12, power)

    @staticmethod
    def hardware_timer_comparator(timer, channel, mode, comparator_value):
        return pack('<4BBBBH', 0, 5, 7, 13, timer, channel, mode,
                    comparator_value)

    @staticmethod
    def test_phy_tx(channel, length, type):
        return pack('<4BBBB', 0, 3, 8, 0, channel, length, type)

    @staticmethod
    def test_phy_rx(channel):
        return pack('<4BB', 0, 1, 8, 1, channel)

    @staticmethod
    def test_phy_end():
        return pack('<4B', 0, 0, 8, 2)

    @staticmethod
    def test_phy_reset():
        return pack('<4B', 0, 0, 8, 3)

    @staticmethod
    def test_get_channel_map():
        return pack('<4B', 0, 0, 8, 4)

    @staticmethod
    def test_debug(input):
        return pack('<4BB' + str(len(input)) + 's', 0, 1 + len(input), 8, 5,
                    len(input), b''.join(chr(i) for i in input))
