#  """
#    Copyright (c) 2016- 2024, Wiliot Ltd. All rights reserved.
#
#    Redistribution and use of the Software in source and binary forms, with or without modification,
#     are permitted provided that the following conditions are met:
#
#       1. Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#
#       2. Redistributions in binary form, except as used in conjunction with
#       Wiliot's Pixel in a product or a Software update for such product, must reproduce
#       the above copyright notice, this list of conditions and the following disclaimer in
#       the documentation and/or other materials provided with the distribution.
#
#       3. Neither the name nor logo of Wiliot, nor the names of the Software's contributors,
#       may be used to endorse or promote products or services derived from this Software,
#       without specific prior written permission.
#
#       4. This Software, with or without modification, must only be used in conjunction
#       with Wiliot's Pixel or with Wiliot's cloud service.
#
#       5. If any Software is provided in binary form under this license, you must not
#       do any of the following:
#       (a) modify, adapt, translate, or create a derivative work of the Software; or
#       (b) reverse engineer, decompile, disassemble, decrypt, or otherwise attempt to
#       discover the source code or non-literal aspects (such as the underlying structure,
#       sequence, organization, ideas, or algorithms) of the Software.
#
#       6. If you create a derivative work and/or improvement of any Software, you hereby
#       irrevocably grant each of Wiliot and its corporate affiliates a worldwide, non-exclusive,
#       royalty-free, fully paid-up, perpetual, irrevocable, assignable, sublicensable
#       right and license to reproduce, use, make, have made, import, distribute, sell,
#       offer for sale, create derivative works of, modify, translate, publicly perform
#       and display, and otherwise commercially exploit such derivative works and improvements
#       (as applicable) in conjunction with Wiliot's products and services.
#
#       7. You represent and warrant that you are not a resident of (and will not use the
#       Software in) a country that the U.S. government has embargoed for use of the Software,
#       nor are you named on the U.S. Treasury Department’s list of Specially Designated
#       Nationals or any other applicable trade sanctioning regulations of any jurisdiction.
#       You must not transfer, export, re-export, import, re-import or divert the Software
#       in violation of any export or re-export control laws and regulations (such as the
#       United States' ITAR, EAR, and OFAC regulations), as well as any applicable import
#       and use restrictions, all as then in effect
#
#     THIS SOFTWARE IS PROVIDED BY WILIOT "AS IS" AND "AS AVAILABLE", AND ANY EXPRESS
#     OR IMPLIED WARRANTIES OR CONDITIONS, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED
#     WARRANTIES OR CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, NONINFRINGEMENT,
#     QUIET POSSESSION, FITNESS FOR A PARTICULAR PURPOSE, AND TITLE, ARE DISCLAIMED.
#     IN NO EVENT SHALL WILIOT, ANY OF ITS CORPORATE AFFILIATES OR LICENSORS, AND/OR
#     ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
#     OR CONSEQUENTIAL DAMAGES, FOR THE COST OF PROCURING SUBSTITUTE GOODS OR SERVICES,
#     FOR ANY LOSS OF USE OR DATA OR BUSINESS INTERRUPTION, AND/OR FOR ANY ECONOMIC LOSS
#     (SUCH AS LOST PROFITS, REVENUE, ANTICIPATED SAVINGS). THE FOREGOING SHALL APPLY:
#     (A) HOWEVER CAUSED AND REGARDLESS OF THE THEORY OR BASIS LIABILITY, WHETHER IN
#     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE);
#     (B) EVEN IF ANYONE IS ADVISED OF THE POSSIBILITY OF ANY DAMAGES, LOSSES, OR COSTS; AND
#     (C) EVEN IF ANY REMEDY FAILS OF ITS ESSENTIAL PURPOSE.
#  """

from wiliot_core.packet_data.config_files.packet_map import *

N_BITS_PER_CHAR = 4


class ConvertCloudPacket(object):
    def __init__(self, enriched_packet_row, original_packet=None, first_timestamp=0):
        """
        :param enriched_packet_row is one row from the enrich packet table from DB
        :type enriched_packet_row pd.Series
        """
        self.row = enriched_packet_row
        self.first_time = first_timestamp
        if 'rawPacket' in self.row.keys() and ('packetVersion' in self.row.keys() or 'flowVersion' in self.row.keys()):
            pass
        else:
            raise Exception('cannot convert cloud packet without the following fields: '
                            '[rawPacket and packetVersion] or [rawPacket and flowVersion]')

        flow_ver = self.get_flow_ver()
        self.gw_packet = self.get_gw_data()
        base_fields = self.get_fields_before_payload(original_packet=original_packet, flow_ver=flow_ver)
        packet_out = self.combined_packet_parts(base_fields)
        packet_fixed = self.get_packet_data(packet_out)
        self.base_fields = base_fields
        self.reconstruct_packet = packet_fixed

    def get_reconstruct_packet(self):
        return self.reconstruct_packet

    def get_flow_ver(self):
        flow_ver_hex = '****'
        if 'flowVersion' in self.row:
            flow_ver_hex = str(self.row['flowVersion'])
            flow_ver_hex = flow_ver_hex.upper().replace('.', '').replace('X', '').zfill(FLOW_VER_SIZE)
            if len(flow_ver_hex) != FLOW_VER_SIZE:
                raise Exception(f'ConvertCloudPacket: get_flow_ver: invlid flow version: {self.row["flowVersion"]}')
        return flow_ver_hex

    def get_fields_before_payload(self, original_packet, flow_ver):
        if original_packet is None:
            adva = flow_ver[:int(FLOW_VER_SIZE/2)] + \
                   ''.join(['*'] * (FieldsLength.ADVA.value - FLOW_VER_SIZE)) + \
                   flow_ver[-int(FLOW_VER_SIZE/2):]
            en = ''.join(['*'] * FieldsLength.EN.value)
            b_type = ''.join(['*'] * FieldsLength.TYPE.value)
            e_header = ''.join(['*']) * FieldsLength.EXTENDED_HEADER.value
            adi = ''.join(['*']) * FieldsLength.ADI.value
            gw_packet = self.gw_packet
        else:
            adva = original_packet.packet_data['adv_address']
            en = original_packet.packet_data['en']
            b_type = original_packet.packet_data['type']
            e_header = original_packet.packet_data['extended_header'] \
                if 'extended_header' in original_packet.packet_data.keys() else \
                ''.join(['*']) * FieldsLength.EXTENDED_HEADER.value
            adi = original_packet.packet_data['adi'] \
                if 'adi' in original_packet.packet_data.keys() else \
                ''.join(['*']) * FieldsLength.ADI.value
            gw_packet = np.take(original_packet.gw_data['gw_packet'], 0)

        return {'adva': adva, 'en': en, 'b_type': b_type, 'e_header': e_header, 'adi': adi, 'gw_packet': gw_packet}

    def get_gw_data(self):
        gw_fields = {}
        gw_data = ''
        if 'rssi' in self.row:
            gw_fields['rssi'] = abs(int(self.row['rssi']))
        if 'timestamp' in self.row:
            gw_fields['gw_clock'] = int(self.row['timestamp']) - self.first_time

        if gw_fields:
            gw_bit_data = ''
            gw_fields['crc_valid'] = 1
            for field in gw_bit_structure:
                if field['name'] in gw_fields.keys():
                    bin_field = bin(int(gw_fields[field['name']]))[2:]
                else:
                    bin_field = '0'
                bin_field = bin_field.zfill(field['bit_len'])
                bin_field = bin_field[-field['bit_len']:]
                gw_bit_data += str(bin_field)
            gw_data = hex(int(gw_bit_data, 2))[2:].upper().zfill(
                int(sum(f['bit_len'] for f in gw_bit_structure) / N_BITS_PER_CHAR))
        return gw_data

    def combined_packet_parts(self, all_fields):
        cloud_packet = str(self.row['rawPacket']).upper()

        if any(cloud_packet.startswith(x) for x in WILIOT_EN_TYPE) and len(cloud_packet) == BLE_PAYLOAD_LENGTH:

            return all_fields['adva'] + cloud_packet + all_fields['gw_packet']

        if any(cloud_packet.startswith(x) for x in WILIOT_DATA_UID) and \
                len(cloud_packet) == BLE_PAYLOAD_LENGTH - FieldsLength.EN.value - FieldsLength.TYPE.value:

            return all_fields['adva'] + all_fields['en'] + all_fields['b_type'] + cloud_packet + all_fields['gw_packet']

        if any(cloud_packet.startswith(x) for x in WILIOT_EN_TYPE) and \
                len(cloud_packet) == BLE_DOUBLE_PAYLOAD_LENGTH:
            header = [k for k, v in packet_length_types.items() if v['name'] == 'BLE5-EXT']

            return header[0] + all_fields['adva'] + all_fields['e_header'] + all_fields[
                'adi'] + cloud_packet + all_fields['gw_packet']

        if any(cloud_packet.startswith(x) for x in WILIOT_DATA_UID) and \
                len(cloud_packet) == BLE_DOUBLE_PAYLOAD_LENGTH - FieldsLength.EN.value - FieldsLength.TYPE.value:
            header = [k for k, v in packet_length_types.items() if v['name'] == 'BLE5-DBL-EXT']

            return header[0] + all_fields['adva'] + all_fields['e_header'] + all_fields['adi'] + all_fields[
                'en'] + all_fields['b_type'] + cloud_packet + all_fields['gw_packet']

    def get_packet_data(self, packet_out):
        packet_data, gw_data = parse_packet(packet_data_input=packet_out, is_full_packet=None, ignore_crc=False)
        fields_to_zero, fields_to_convert = None, None
        if 'decryptedData' in self.row.keys() and 'tagId' in self.row.keys():
            fields_to_zero = ['mic']
            dec_tag_id = reverse_data(str(self.row['tagId']))
            fields_to_convert = {'enc_uid': dec_tag_id,
                                 'enc_payload': str(self.row['decryptedData']).upper()}
        new_packet = construct_packet_from_fields(packet_data=packet_data,
                                                  fields_to_zero=fields_to_zero,
                                                  fields_to_convert=fields_to_convert,
                                                  gw_data=self.gw_packet)
        return new_packet
