# * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/
#  *
#  *
#  *  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 Texas Instruments Incorporated 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
#  *  OWNER 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.
#  *
# */



import sys, glob, serial, time, os
import serial.tools.list_ports
import tkinter as tk
from tkinter import *
import global_vars


def write_func(self, message):
    self.Lb3.insert(END, message)
    self.Lb3.yview(END)
    return


# Calculates the FCS of a frame
def fcs_calc(frame):
    if frame[0:2] == 'fe':
        frame = frame.lstrip('fe')
    fcs_check = int(frame[:2], 16) ^ int(frame[2:4], 16)
    for x in range(4, len(frame), 2):
        fcs_check = fcs_check ^ int(frame[x:x + 2], 16)
    return fcs_check


# Reverses and appends bytes to a frame
def frame_append(message, value):
    # Converting the order of the bytes
    content = "".join(reversed([value[i:i + 2] for i in range(0, len(value), 2)]))
    message += chr(int(content[0:2], 16)).encode('latin-1')
    message += chr(int(content[2:4], 16)).encode('latin-1')
    return message


# Read the response of a device and return frame without SOF byte
def read_response(port):
    buf_hex = []
    try:
        buf = port.read(2)  # Read the SOF and LEN
        buf = buf.hex()  # Convert read data to a hexadecimal string
        if buf[0:2] == 'fe':  # Verify the SOF byte
            buf2 = port.read(int(buf[2:4], 16) + 3)  # Read the rest of the frame + CMD0/CMD1/FCS
            buf_hex = buf + buf2.hex()  # Append the rest of the frame
            buf_hex = buf_hex[2: len(buf_hex)]  # Remove the SOF byte
    except ValueError:
        buf_hex = []
    return buf_hex


# Finds the length table NV Regions
def sys_osal_nv_length(port, item_id):
    length = 0
    item_id = "%0.4X" % int(item_id, 16)
    command = [0x02, 0x21, 0x13]
    message = chr(0xFE).encode('latin-1')
    for x in range(len(command)):
        message += (chr(command[x]).encode('latin-1'))
    message = frame_append(message, item_id)
    message += chr(fcs_calc(message.hex())).encode('latin-1')
    port.write(message)

    buf_hex = read_response(port)
    if len(buf_hex) != 0:
        fcs_value = buf_hex[len(buf_hex) - 2:len(buf_hex)]
        buf_hex = buf_hex[0: -2]
        length = buf_hex[6: 8]
        if int(hex(fcs_calc(buf_hex)), 16) != int(fcs_value, 16):
            error = True
    return length


# Finds the length of non-table NV Regions
def sys_nv_length(port, nv_region, item_id, sub_id):
    length = 0
    item_id = "%0.4X" % int(item_id, 16)
    sub_id = "%0.4X" % int(sub_id, 16)
    command = [0x05, 0x21, 0x32, 0x01]
    message = chr(0xFE).encode('latin-1')
    for x in range(len(command)):
        message += (chr(command[x]).encode('latin-1'))
    message = frame_append(message, item_id)
    message = frame_append(message, sub_id)
    message += chr(fcs_calc(message.hex())).encode('latin-1')
    port.write(message)

    buf_hex = read_response(port)
    if len(buf_hex) != 0:
        fcs_value = buf_hex[len(buf_hex) - 2:len(buf_hex)]
        buf_hex = buf_hex[0: -2]
        length = buf_hex[6: 8]
        if int(hex(fcs_calc(buf_hex)), 16) != int(fcs_value, 16):
            error = True
    return length


def sys_nv_read(self, port, nv_region, item_id, sys_id, sub_id, length, entries, c_file, width, clone):
    wrote = False
    # Read the contents of each of the table entries
    sub_id = int(str(sub_id), 16)

    for y in range(sub_id, int(entries)):
        sub_id = "%0.4X" % int(hex(y), 16)
        sys_id = "%0.2X" % int(sys_id, 16)
        item_id = "%0.4X" % int(item_id, 16)
        length = "%0.2X" % int(length, 16)
        command = [0x08, 0x21, 0x33, int(sys_id, 16)]
        message = chr(0xFE).encode('latin-1')  # SOF
        for x in range(len(command)):
            message += (chr(command[x]).encode('latin-1'))  # Header
        message = frame_append(message, item_id)  # Item ID
        message = frame_append(message, sub_id)  # Sub ID
        message += chr(0x00).encode('latin-1')  # Offset (Byte 1)
        message += chr(0x00).encode('latin-1')  # Offset (Byte 2)
        message += chr(int(length, 16)).encode('latin-1')  # Length
        message += chr(fcs_calc(message.hex())).encode('latin-1')  # FCS
        port.write(message)  # Send the message to the device

        buf_hex = read_response(port)
        if len(buf_hex) != 0:
            status = buf_hex[6:8]
            if status == '00':
                fcs_value = buf_hex[len(buf_hex) - 2:len(buf_hex)]  # Isolate the FCS value
                buf_hex = buf_hex[0: -2]  # Remove FCS value from the rest of the frame
                data = buf_hex[10: 10 + int(buf_hex[8:10], 16) * 2]  # Isolate the data
                reversal = "".join(reversed([data[i:i + 2] for i in range(0, len(data), 2)]))
                buf_hex = buf_hex.replace(data, reversal)  # Replace the data content in the correct order
                if int(hex(fcs_calc(buf_hex)), 16) == int(fcs_value, 16) and reversal != '':
                    # Write the NV Region, ID, LEN, and Value to a text file
                    if clone:
                        c_file.write((nv_region + "*").ljust(width + 5) + '%5s %5s %5s %5s %5s' %
                            (": " + "0x" + item_id + "       :", "0x" + sys_id +"       :",
                             "0x" + sub_id + "      :", "0x" + buf_hex[8:10] +
                            "        :", reversal + '\n'))
                    else:
                        c_file.write(nv_region.ljust(width + 5) + '%5s %5s %5s %5s' %
                                     (": " + "0x" + item_id + "       :", "0x" + sys_id + "      :",
                                      "0x" + sub_id + "      :",
                                      "0x" + buf_hex[8:10] +
                                      "        :", reversal + '\n'))
                    wrote = True
                else:
                    write_func(self, "Incorrect FCS value for " + " " + nv_region)
    if wrote:
        write_func(self, "Success: Data Read for " + " " + nv_region)
    else:
        write_func(self, 'Failure: Could not read from:' + " " + nv_region)
    return


def sys_nv_update(self, port, nv_region, item_id, sys_id, sub_id, length, value):
    item_id = "%0.4X" % int(item_id, 16)
    sys_id = "%0.2X" % int(sys_id, 16)
    sub_id = "%0.4X" % int(sub_id, 16)
    length = "%0.4X" % int(length, 16)
    command = [int(length, 16) + 6, 0x21, 0x35, int(sys_id, 16)]
    message = chr(0xFE).encode('latin-1')
    for x in range(len(command)):
        message += (chr(command[x]).encode('latin-1'))
    message = frame_append(message, item_id)
    message = frame_append(message, sub_id)
    message += chr(int(length, 16)).encode('latin-1')  # LEN
    reversed_mes = "".join(reversed([value[i:i + 2] for i in range(0, len(value), 2)]))  # Reverse the bytes in data
    for x in range(0, len(reversed_mes), 2):
        message += chr(int(reversed_mes[x:x + 2], 16)).encode('latin-1')
    message += chr(fcs_calc(message.hex())).encode('latin-1')  # FCS
    port.write(message)

    buf_hex = read_response(port)
    wrote = False
    if len(buf_hex) != 0:
        status = buf_hex[6:8]
        if status == '00':
            fcs_value = buf_hex[len(buf_hex) - 2:len(buf_hex)]  # Isolate FCS Value
            buf_hex = buf_hex[0: -2]  # Remove FCS
            if int(hex(fcs_calc(buf_hex)), 16) == int(fcs_value, 16):  # Verify FCS Value
                wrote = True
        else:
            write_func(self, 'Failure: Could not write to:' + " " + nv_region + " (" + buf_hex[2:8] + ")")
    else:
        write_func(self, 'Failure: Could not write to:' + " " + nv_region + " (Empty Return)")
    return wrote


def sys_osal_nv_read(self, port, nv_region, item_id, c_file, width, clone):
    item_id = "%0.4X" % int(item_id, 16)
    command = [0x03, 0x21, 0x08]
    message = chr(0xFE).encode('latin-1')
    for x in range(len(command)):
        message += (chr(command[x]).encode('latin-1'))
    message = frame_append(message, item_id)
    message += chr(0x00).encode('latin-1')
    message += chr(fcs_calc(message.hex())).encode('latin-1')
    port.write(message)

    buf_hex = read_response(port)
    if len(buf_hex) != 0:
        status = buf_hex[6:8]
        if status == '00':
            fcs_value = buf_hex[len(buf_hex) - 2:len(buf_hex)]  # Isolate the FCS value
            buf_hex = buf_hex[0: -2]  # Remove FCS value from the rest of the frame
            data = buf_hex[10: 10 + int(buf_hex[8:10], 16) * 2]  # Isolate the data
            # Reverse the bytes in the data
            data = data.strip()
            reversal = "".join(reversed([data[i:i + 2] for i in range(0, len(data), 2)]))
            buf_hex = buf_hex.replace(data, reversal)  # Replace the data content in the correct order
            if int(hex(fcs_calc(buf_hex)), 16) == int(fcs_value, 16):  # Check if the FCS value is correct
                # Write the NV Region, ID, LEN, and Value to a text file
                reversal = reversal.strip()
                if clone:
                    c_file.write((nv_region + "*").ljust(width + 5) + '%5s %5s %2s' % (
                        ": " + "0x" + item_id + "                                  :", "0x" + buf_hex[8:10] + "        :",
                        reversal + '\n'))
                else:
                    c_file.write(nv_region.ljust(width + 5) + '%5s %5s %2s' % (
                        ": " + "0x" + item_id + "                                  :", "0x" + buf_hex[8:10] + "        :",
                        reversal + '\n'))
                write_func(self, 'Success: Data Read for' + " " + nv_region)
            else:
                write_func(self, "Incorrect FCS value for" + " " + nv_region)
        if status == '01':
            write_func(self, 'Failure: Please re-try the operation' + " " + nv_region + " " + "Message Received was "
                   + buf_hex)
        if status == '02':
            write_func(self, 'Failure: Could not read' + " " + nv_region + " (" + buf_hex[2:8] + ")")
    else:
        write_func(self, 'Failure: Could not read' + " " + nv_region + "(Empty Return)")
    return


def sys_osal_nv_write(self, port, nv_region, item_id, length, value):
    if length == '0xf7':
        length = '0xDB'
        value = value[:-56]  # Remove an extra byte (UART buffer limitation)
    item_id = "%0.4X" % int(item_id, 16)
    length = "%0.2X" % int(length, 16)
    command = [int(length, 16) + 4, 0x21, 0x09]
    message = chr(0xFE).encode('latin-1')
    for x in range(len(command)):
        message += (chr(command[x]).encode('latin-1'))
    message = frame_append(message, item_id)
    message += chr(0x00).encode('latin-1')  # Offset
    message += chr(int(length, 16)).encode('latin-1')  # LEN
    # Reverse the bytes in the data
    reversed_mes = "".join(reversed([value[i:i + 2] for i in range(0, len(value), 2)]))
    for x in range(0, len(reversed_mes), 2):
        message += chr(int(reversed_mes[x:x + 2], 16)).encode('latin-1')
    message += chr(fcs_calc(message.hex())).encode('latin-1')  # FCS
    port.write(message)  # Send the message to the device

    wrote = False
    buf_hex = read_response(port)
    if len(buf_hex) != 0:
        status = buf_hex[6:8]
        if status == '00':
            fcs_value = buf_hex[len(buf_hex) - 2:len(buf_hex)]  # Isolate FCS Value
            buf_hex = buf_hex[0: -2]  # Remove FCS
            if int(hex(fcs_calc(buf_hex)), 16) == int(fcs_value, 16):  # Verify FCS Value
                wrote = True
                write_func(self, 'Success: Wrote' + " " + nv_region + " " + 'to Memory')
        else:
            write_func(self, 'Failure: Could not write to:' + " " + nv_region + " (" + buf_hex[2:8] + ")")
    else:
        write_func(self, 'Failure: Could not write to:' + " " + nv_region + " (Empty Return)")
    return wrote


def sys_osal_nv_item_init(self, port, nv_region, item_id):
    item_id = "%0.4X" % int(item_id, 16)
    if item_id == '0021':
        item_len = "%0.4X" % int('0x0074', 16)
    if item_id == '0042':
        item_len = "%0.4X" % int('0x0142', 16)
    command = [0x05, 0x21, 0x07]
    message = chr(0xFE).encode('latin-1')
    for x in range(len(command)):
        message += (chr(command[x]).encode('latin-1'))
    message = frame_append(message, item_id)
    message = frame_append(message, item_len)
    message += chr(0x00).encode('latin-1')  # InitLen
    message += chr(fcs_calc(message.hex())).encode('latin-1')  # FCS
    port.write(message)  # Send the message to the device

    wrote = False
    buf_hex = read_response(port)
    if len(buf_hex) != 0:
        status = buf_hex[6:8]
        if status == '09' or status == '00':
            fcs_value = buf_hex[len(buf_hex) - 2:len(buf_hex)]  # Isolate FCS Value
            buf_hex = buf_hex[0: -2]  # Remove FCS
            if int(hex(fcs_calc(buf_hex)), 16) == int(fcs_value, 16):  # Verify FCS Value
                wrote = True
    return wrote


def app_cnf_bdb_start_commissioning(self, port, output):
    com_mode = ['Network Formation']
    command = [0x01, 0x2F, 0x05, 0x04]  # Initialize the Network
    message = chr(0xFE).encode('latin-1')
    for x in range(len(command)):
        message += (chr(command[x]).encode('latin-1'))
    message += chr(fcs_calc(message.hex())).encode('latin-1')
    port.write(message)

    buf_hex = read_response(port)
    if len(buf_hex) != 0:
        status = buf_hex[6:8]
        if status == '00':
            fcs_value = buf_hex[len(buf_hex) - 2:len(buf_hex)]  # Isolate FCS Value
            buf_hex = buf_hex[0: -2]  # Remove FCS
            if int(hex(fcs_calc(buf_hex)), 16) == int(fcs_value, 16):  # Verify FCS Value
                write_func(self, 'Success: Formed the Network')
        else:
            write_func(self, 'Failure: Could not form the Network')
    else:
        write_func(self, 'Failure: Could not form the Network')
    return


def bdb_start_initialization(self, port):
    com_mode = ['Initialization']
    command = [0x01, 0x2F, 0x05, 0x00]  # Initialize the Network
    message = chr(0xFE).encode('latin-1')
    for x in range(len(command)):
        message += (chr(command[x]).encode('latin-1'))
    message += chr(fcs_calc(message.hex())).encode('latin-1')
    port.write(message)

    buf_hex = read_response(port)
    if len(buf_hex) != 0:
        status = buf_hex[6:8]
        if status == '00':
            fcs_value = buf_hex[len(buf_hex) - 2:len(buf_hex)]  # Isolate FCS Value
            buf_hex = buf_hex[0: -2]  # Remove FCS
            if int(hex(fcs_calc(buf_hex)), 16) == int(fcs_value, 16):  # Verify FCS Value
                write_func(self, 'Success: Initialized the Network')
        else:
            write_func(self, 'Failure: Did not initialize the Network')
    else:
        write_func(self, 'Failure: Did not Initialize the Network')
    return


def sys_reset(port):
    command = [0x01, 0x41, 0x00, 0x01]
    message = chr(0xFE).encode('latin-1')
    for x in range(len(command)):
        message += (chr(command[x]).encode('latin-1'))
    message += chr(fcs_calc(message.hex())).encode('latin-1')
    port.write(message)
    return


def zdo_startup_from_app(self, port):
    command = [0x02, 0x25, 0x40, 0x00, 0x00]
    message = chr(0xFE).encode('latin-1')
    for x in range(len(command)):
        message += (chr(command[x]).encode('latin-1'))
    message += chr(fcs_calc(message.hex())).encode('latin-1')
    port.write(message)

    buf_hex = read_response(port)
    if len(buf_hex) != 0:
        status = buf_hex[6:8]
        if status == '00':
            fcs_value = buf_hex[len(buf_hex) - 2:len(buf_hex)]  # Isolate FCS Value
            buf_hex = buf_hex[0: -2]  # Remove FCS
            if int(hex(fcs_calc(buf_hex)), 16) == int(fcs_value, 16):  # Verify FCS Value
                write_func(self, 'Success: Reset the device')
        else:
            write_func(self, 'Failure: Did not reset the network')
    else:
        write_func(self, 'Failure: Did not reset the network')
    return


def sys_ping(port):
    valid = 0
    command = [0x00, 0x21, 0x01]
    message = chr(0xFE).encode('latin-1')
    for x in range(len(command)):
        message += (chr(command[x]).encode('latin-1'))
    message += chr(fcs_calc(message.hex())).encode('latin-1')  # FCS
    port.write(message)

    try:
        buf_hex = read_response(port)
        if len(buf_hex) != 0:
            fcs_value = buf_hex[len(buf_hex) - 2:len(buf_hex)]  # Isolate the FCS value
            buf_hex = buf_hex[0: -2]  # Remove FCS value from the rest of the frame
            if int(hex(fcs_calc(buf_hex)), 16) == int(fcs_value, 16):  # Check if the FCS value is correct
                valid = 1
    except ValueError:
        valid = 0
    return valid


def serial_ports():
    if sys.platform.startswith('win'):
        ports = list(serial.tools.list_ports.comports())
        app_ports = []
        error = False
        # Filter the available ports as TI Application Ports
        for p in ports:
            if p.manufacturer == 'Texas Instruments Incorporated':
                if p.hwid[-1] == '0':
                    try:
                        ser = serial.Serial(port=p.device, baudrate=115200, parity=serial.PARITY_NONE,
                                            stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=1)
                        if sys_ping(ser) == 1:
                            app_ports.append(p.device)
                        ser.close()
                    except IOError:
                        error = True
    elif sys.platform.startswith('darwin'):
        ports= serial.tools.list_ports.comports()
        app_ports=[]
        ti_ports=[]
        for p in ports:
            if p.manufacturer == 'Texas Instruments' or p.manufacturer == 'Texas Instruments Incorporated':
                ti_ports.append(p.device)
        ti_ports = list(dict.fromkeys(ti_ports))
        for p in ti_ports:
            try:
                ser=serial.Serial(port=p, baudrate=115200, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE,
                                  bytesize=serial.EIGHTBITS, timeout=.05)
                if sys_ping(ser) == 1:
                    if p.find('usbmodemL') != -1:
                        p = p.strip('/dev/cu.usbmodemL')
                        app_ports.append(p)
                ser.close()
            except (OSError, serial.SerialException):
                pass
    else:
        raise EnvironmentError('Unsupported platform')
    return app_ports
