import re
import os
import sys
import socket
from threading import Thread, Event
import subprocess
import time
from shutil import copyfile

try:
    import IDF
except ImportError:
    # this is a test case write with tiny-test-fw.
    # to run test cases outside tiny-test-fw,
    # we need to set environment variable `TEST_FW_PATH`,
    # then get and insert `TEST_FW_PATH` to sys path before import FW module
    test_fw_path = os.getenv("TEST_FW_PATH")
    if test_fw_path and test_fw_path not in sys.path:
        sys.path.insert(0, test_fw_path)
    import IDF

import DUT
import Utility

stop_sock_listener = Event()
stop_io_listener = Event()
sock = None
client_address = None
manual_test = False


def io_listener(dut1):
    global sock
    global client_address
    data = b''
    while not stop_io_listener.is_set():
        try:
            data = dut1.expect(re.compile(r"PacketOut:\[([a-fA-F0-9]+)\]"), timeout=5)
        except DUT.ExpectTimeout:
            continue
        if data != () and data[0] != b'':
            packet_data = data[0]
            print("Packet_data>{}<".format(packet_data))
            response = bytearray.fromhex(packet_data.decode())
            print("Sending to socket:")
            packet = ' '.join(format(x, '02x') for x in bytearray(response))
            print("Packet>{}<".format(packet))
            if client_address is not None:
                sock.sendto(response, ('127.0.0.1', 7777))


def sock_listener(dut1):
    global sock
    global client_address
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.settimeout(5)
    server_address = '0.0.0.0'
    server_port = 7771
    server = (server_address, server_port)
    sock.bind(server)
    try:
        while not stop_sock_listener.is_set():
            try:
                payload, client_address = sock.recvfrom(1024)
                packet = ' '.join(format(x, '02x') for x in bytearray(payload))
                print("Received from address {}, data {}".format(client_address, packet))
                dut1.write(str.encode(packet))
            except socket.timeout:
                pass
    finally:
        sock.close()
        sock = None


@IDF.idf_example_test(env_tag="Example_WIFI")
def lwip_test_suite(env, extra_data):
    global stop_io_listener
    global stop_sock_listener
    """
    steps: |
      1. Rebuilds test suite with esp32_netsuite.ttcn
      2. Starts listeners on stdout and socket
      3. Execute ttcn3 test suite
      4. Collect result from ttcn3
    """
    dut1 = env.get_dut("net_suite", "examples/system/network_tests")
    # check and log bin size
    binary_file = os.path.join(dut1.app.binary_path, "net_suite.bin")
    bin_size = os.path.getsize(binary_file)
    IDF.log_performance("net_suite", "{}KB".format(bin_size // 1024))
    IDF.check_performance("net_suite", bin_size // 1024)
    dut1.start_app()
    thread1 = Thread(target=sock_listener, args=(dut1, ))
    thread2 = Thread(target=io_listener, args=(dut1, ))
    if not manual_test:
        # Variables refering to esp32 ttcn test suite
        TTCN_SRC = 'esp32_netsuite.ttcn'
        TTCN_CFG = 'esp32_netsuite.cfg'
        # System Paths
        netsuite_path = os.getenv("NETSUITE_PATH")
        netsuite_src_path = os.path.join(netsuite_path, "src")
        test_dir = os.path.dirname(os.path.realpath(__file__))
        # Building the suite
        print("Rebuilding the test suite")
        print("-------------------------")
        # copy esp32 specific files to ttcn net-suite dir
        copyfile(os.path.join(test_dir, TTCN_SRC), os.path.join(netsuite_src_path, TTCN_SRC))
        copyfile(os.path.join(test_dir, TTCN_CFG), os.path.join(netsuite_src_path, TTCN_CFG))
        proc = subprocess.Popen(['bash', '-c', 'cd ' + netsuite_src_path + ' && source make.sh'],
                                cwd=netsuite_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        output = proc.stdout.read()
        print("Note: First build step we expect failure (titan/net_suite build system not suitable for multijob make)")
        print(output)
        proc = subprocess.Popen(['bash', '-c', 'cd ' + netsuite_src_path + ' && make'],
                                cwd=netsuite_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        print("Note: This time all dependencies shall be generated -- multijob make shall pass")
        output = proc.stdout.read()
        print(output)
        # Executing the test suite
        thread1.start()
        thread2.start()
        time.sleep(2)
        print("Executing the test suite")
        print("------------------------")
        proc = subprocess.Popen(['ttcn3_start', os.path.join(netsuite_src_path,'test_suite'), os.path.join(netsuite_src_path, TTCN_CFG)],
                                stdout=subprocess.PIPE)
        output = proc.stdout.read()
        print(output)
        print("Collecting results")
        print("------------------")
        verdict_stats = re.search('(Verdict statistics:.*)', output)
        if verdict_stats:
            verdict_stats = verdict_stats.group(1)
        else:
            verdict_stats = b""
        verdict = re.search('Overall verdict: pass', output)
        if verdict:
            print("Test passed!")
            Utility.console_log(verdict_stats, "green")
        else:
            Utility.console_log(verdict_stats, "red")
            raise ValueError('Test failed with: {}'.format(verdict_stats))
    else:
        try:
            # Executing the test suite
            thread1.start()
            thread2.start()
            time.sleep(2)
            while True:
                time.sleep(0.5)
        except KeyboardInterrupt:
            pass
    print("Executing done, waiting for tests to finish")
    print("-------------------------------------------")
    stop_io_listener.set()
    stop_sock_listener.set()
    thread1.join()
    thread2.join()


if __name__ == '__main__':
    print("Manual execution, please build and start ttcn in a separate console")
    manual_test = True
    lwip_test_suite()
