import serial
import time
import random
from typing import List


PORT_NAME = ''

# Check if the received data frame is valid


def check_buffer(buffer, formatted_time) -> bool:
    if (len(buffer) != 18):
        print(f"{formatted_time} received less than 18 bytes -> ", list(buffer))
        return False
    check = calculate_checksum(buffer)
    if (check != buffer[16]):
        print(
            f"{formatted_time} Checksum error", buffer[16], check, " -> ", list(buffer))
        return False
    if (buffer[15] != 0x81 and buffer[15] != 0x82 and buffer[15] != 0x83):
        print(
            f"{formatted_time} Invalid function code", buffer[15], " -> ", list(buffer))
        return False
    print(
        f"{formatted_time} Received: ", list(buffer))
    return True


def calculate_checksum(buffer: List[int]) -> int:
    sum_odd = sum(buffer[i] for i in range(1, 16, 2))
    sum_even = sum(buffer[i] for i in range(2, 15, 2))
    return ((sum_odd & 0x0f) | (sum_even << 4 & 0x0f))


data_frame_one: List[int] = [
    0x3a,  # Fixed start
    0x02, 0x03, 0x00, 0x00, 0x01, 0x20, 0x00, 0x30,
    0x1e, 0x00, 0x1a, 0x1d, 0x00, 0x02,
    0x01,  # Function code
    0x00,  # Calculated automatically so do not touch
    0x95  # Fixed end
]

data_frame_two: List[int] = [
    0x3a,  # Fixed start
    0x00, 0xc8, 0x1c, 0x00, 0x00, 0x87, 0xa0, 0x00,
    0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x02,  # Function code
    0x00,  # Calculated automatically so do not touch
    0x95  # Fixed end
]

if __name__ == "__main__":

    # open serial port and clear the buffer
    serial_port = serial.Serial(PORT_NAME, 9600, bytesize=serial.EIGHTBITS,
                                parity=serial.PARITY_EVEN, stopbits=serial.STOPBITS_TWO, timeout=1)
    serial_port.reset_input_buffer()

    print("Running (waiting for initialization message)")
    established_connection = False
    start_time = time.time()

    while 1:
        current_time = time.time()
        if (current_time - start_time > 30):
            # if no message received for 30 seconds, reset the connection
            if (established_connection):
                print(
                    f"{formatted_time} simulating connection lost after 30 seconds of inactivity (waiting for initialization message)")
            established_connection = False

        try:
            local_current_time = time.localtime()
            formatted_time = f"{local_current_time.tm_hour}:{local_current_time.tm_min}:{local_current_time.tm_sec} ->"

            # read -------------------
            if (serial_port.in_waiting > 0):
                # reset the timer if a message is received (connection is alive)
                start_time = time.time()
                time.sleep(0.05)
                result = serial_port.read_all()

                if (not check_buffer(result, formatted_time)):
                    continue

                if (not established_connection):
                    established_connection = True
                    serial_port.write(bytes(result))
                    print(
                        f"{formatted_time} initialization message sent successfully")
                    continue

                # --- random values for data frame with function code 0x01 ---
                electric_heating_status = 1 + 16 + 32
                compressor_frequency = random.randint(1, 255)
                machine_curent_value = random.randint(
                    6, 9)  # can be from 4 to 20
                error_1 = 1 + 8 + 128
                error_2 = 2 + 8 + 32 + 64
                error_3 = 16 + 128
                error_1 = 0
                error_2 = 0
                error_3 = 0
                status_flag_1 = 1
                return_water_temp = random.randint(20, 60)
                water_tank_temp = random.randint(20, 60)
                water_supply_pipe_temp = random.randint(20, 60)
                solar_collector_temp = random.randint(10, 70)
                outdoor_ambient_temp = random.randint(-20, 40) * 2 + 60
                data_frame_one[2] = compressor_frequency
                data_frame_one[3] = error_1
                data_frame_one[4] = error_2
                data_frame_one[5] = machine_curent_value
                data_frame_one[6] = return_water_temp
                data_frame_one[7] = status_flag_1
                data_frame_one[8] = electric_heating_status
                data_frame_one[9] = water_tank_temp
                data_frame_one[11] = water_supply_pipe_temp
                data_frame_one[12] = solar_collector_temp
                data_frame_one[13] = error_3
                data_frame_one[14] = outdoor_ambient_temp

                # --- random values for data frame with function code 0x02 ---
                outdoor_coil_temp = random.randint(20, 40) * 2 + 60
                exhaust_gas_temp = random.randint(40, 70)
                return_air_temp = random.randint(30, 50) * 2 + 30
                throttling_temp = random.randint(50, 60) * 2 + 30
                error_4 = 0
                data_frame_two[2] = outdoor_coil_temp
                data_frame_two[3] = exhaust_gas_temp
                data_frame_two[6] = return_air_temp
                data_frame_two[7] = throttling_temp
                data_frame_two[8] = error_4

                # --- spaghetti ---
                # respond to the request by sending data frame with function code 0x81, 0x82 or 0x83
                if (result[15] == 0x81):
                    # Handle the case when user select automatic selection of the operating mode
                    # in this case, the operating mode is set to heating
                    if (result[1] == 1 or result[1] == 32 or result[1] == 64):
                        data_frame_one[1] = 4
                    else:
                        data_frame_one[1] = result[1]

                    data_frame_one[16] = calculate_checksum(data_frame_one)
                    # write -------------------
                    serial_port.write(bytes(data_frame_one))
                    print(f"{formatted_time} Data frame sent successfully")

                elif (result[15] == 0x82):
                    data_frame_two[16] = calculate_checksum(data_frame_two)
                    # write -------------------
                    serial_port.write(bytes(data_frame_two))
                    print(f"{formatted_time} Data frame sent successfully")

                elif (result[15] == 0x83):
                    data_frame_one[16] = calculate_checksum(data_frame_one)
                    serial_port.write(bytes(data_frame_one))
                    print(f"{formatted_time} Data frame sent successfully")

        except Exception:
            print("Error")
            raise
