# Copyright (c) 2015, Nordic Semiconductor
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form 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.
#
# * Neither the name of Nordic Semiconductor ASA nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from enum import Enum
import struct


INIT_PACKET_USES_CRC16 = 0
INIT_PACKET_USES_HASH = 1
INIT_PACKET_EXT_USES_ECDS = 2


class PacketField(Enum):
    DEVICE_TYPE = 1
    DEVICE_REVISION = 2
    APP_VERSION = 3
    REQUIRED_SOFTDEVICES_ARRAY = 4
    OPT_DATA = 5
    NORDIC_PROPRIETARY_OPT_DATA_EXT_PACKET_ID = 6
    NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_LENGTH = 7
    NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_HASH = 8
    NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_CRC16 = 9
    NORDIC_PROPRIETARY_OPT_DATA_INIT_PACKET_ECDS = 10


class Packet(object):
    """
    Class that implements the INIT packet format.
    http://developer.nordicsemi.com/nRF51_SDK/doc/7.1.0/s110/html/a00065.html
    """

    UNSIGNED_SHORT = "H"
    UNSIGNED_INT = "I"
    UNSIGNED_CHAR = "B"
    CHAR_ARRAY = "s"

    def __init__(self, init_packet_fields):
        """

            :param init_packet_fields: Dictionary with packet fields
        """
        self.init_packet_fields = init_packet_fields

    def generate_packet(self):
        """
        Generates a binary packet from provided init_packet_fields provided in constructor.
        This version includes the extended data

        :return str: Returns a string representing the init_packet (in binary)

        """
        # Create struct format string based on keys that are
        # present in self.init_packet_fields
        format_string = self.__generate_struct_format_string()
        args = []

        # If you got error message AttributeError: 'int' object has no attribute 'value'.
        # Uncomment line 84 and comment out line 85 and run 'python setup.py install'
        #for key in sorted(self.init_packet_fields.keys(), key=lambda x: x):
        for key in sorted(list(self.init_packet_fields.keys()), key=lambda x: x.value):
            # Add length to fields that required that
            if key in [PacketField.REQUIRED_SOFTDEVICES_ARRAY,
                       PacketField.OPT_DATA]:
                args.append(len(self.init_packet_fields[key]))
                args.extend(self.init_packet_fields[key])
            else:
                args.append(self.init_packet_fields[key])

        return struct.pack(format_string, *args)

    def __generate_struct_format_string(self):
        format_string = "<"  # Use little endian format with standard sizes for python,
        # see https://docs.python.org/2/library/struct.html

        # If you got error message AttributeError: 'int' object has no attribute 'value'.
        # Uncomment line 102 and comment out line 103 and run 'python setup.py install'
        #for key in sorted(self.init_packet_fields.keys(), key=lambda x: x):
        for key in sorted(list(self.init_packet_fields.keys()), key=lambda x: x.value):
            if key in [PacketField.DEVICE_TYPE,
                       PacketField.DEVICE_REVISION,
                       ]:
                format_string += Packet.UNSIGNED_SHORT

            elif key in [PacketField.APP_VERSION]:
                format_string += Packet.UNSIGNED_INT
            elif key in [PacketField.REQUIRED_SOFTDEVICES_ARRAY]:
                array_elements = self.init_packet_fields[key]
                format_string += Packet.UNSIGNED_SHORT  # Add length field to format packet

                for _ in range(len(array_elements)):
                    format_string += Packet.UNSIGNED_SHORT
            elif key in [PacketField.OPT_DATA]:
                format_string += Packet.UNSIGNED_SHORT  # Add length field to optional data
                format_string += "{0}{1}".format(len(self.init_packet_fields[key]), Packet.CHAR_ARRAY)
            elif key in [PacketField.NORDIC_PROPRIETARY_OPT_DATA_EXT_PACKET_ID]:
                format_string += Packet.UNSIGNED_INT  # Add the extended packet id field
            elif key == PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_LENGTH:
                format_string += Packet.UNSIGNED_INT  # Add the firmware length field
            elif key == PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_HASH:
                format_string += "32{0}".format(Packet.CHAR_ARRAY)  # SHA-256 requires 32 bytes
            elif key == PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_CRC16:
                format_string += Packet.UNSIGNED_SHORT
            elif key == PacketField.NORDIC_PROPRIETARY_OPT_DATA_INIT_PACKET_ECDS:
                format_string += "64{0}".format(Packet.CHAR_ARRAY)  # ECDS based on P-256 using SHA-256 requires 64 bytes

        return format_string