import socket
import numpy as np
import ctypes
import data_structures
import threading
import queue
import os
import mmap

class DataRecorder:
    def __init__(self,
                 host="192.168.2.128",
                 port=1234,
                 packet_size=4096):


        # # TESTTTT
        # self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # print('SO_RCVBUF', self.s.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF))
        # self.s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 4 * 1024 * 1024)
        # print('SO_RCVBUF', self.s.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF))
        # self.s.settimeout(1)
        # self.s.bind(("", 1234))
        # data = np.arange(16, dtype=np.uint32)
        # data = data.tobytes()
        # self.s.sendto(data, (host, 1234))
        # self.s.close()
        # # TESTTTTT



        # UDP Socket for High Speed Data
        self.ip = host
        self.port = port
        self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        print('SO_RCVBUF', self.s.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF))
        self.s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 4 * 1024 * 1024)
        print('SO_RCVBUF', self.s.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF))
        self.s.settimeout(1)
        self.s.bind(("", port))
        # Need to send one udp message to set IP and port info inside FPGA
        data = np.arange(16, dtype=np.uint32)
        data = data.tobytes()
        self.s.sendto(data, (self.ip, self.port))


        self.max_packet_size = packet_size

        # Data Buffer
        # self.buffer = bytearray(512 * 1024 * 1024)
        self.buffer = mmap.mmap(-1, 512 * 1024 * 1024)
        self.buffer_view = memoryview(self.buffer)

        self.stop_event = threading.Event()

        self.fid = None
        self.write_to_disk = False
        self.write_offset = 0
        self.write_count = 0
        self.write_queue = queue.SimpleQueue()

    def start_recording(self, filename, write_to_disk=False):

        self.write_to_disk = write_to_disk

        if write_to_disk:
            self.write_offset = 0
            self.write_count = 0
            self.fid = os.open(filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC | os.O_DIRECT )

            self.write_queue = queue.SimpleQueue()
            self.write_data_thread = threading.Thread(target=self.write_data)
            self.write_data_thread.start()

        self.get_data_thread = threading.Thread(target=self.get_data)

        self.get_data_thread.start()

    def stop_recording(self):
        print('Stop Thread')
        self.stop_event.set()
        self.get_data_thread.join()
        print('Get Data Thread Joined')
        if self.write_to_disk:
            self.write_data_thread.join()
            print('Write Data Thread Joined')

    def write_data(self):

        write_chunk_size = 4 * 1024 * 1024
        buffer_view = memoryview(self.buffer)

        print('Waiting For Data to Write')
        while not self.stop_event.is_set():

            try:
                num_bytes = self.write_queue.get(timeout=1)

                self.write_count += num_bytes

                if self.write_count > write_chunk_size:
                    # print(self.write_offset)
                    # os.write(self.fid, self.buffer[self.write_offset:self.write_offset + write_chunk_size])
                    os.write(self.fid, buffer_view[self.write_offset:self.write_offset + write_chunk_size])
                    self.write_offset += write_chunk_size
                    self.write_count -= write_chunk_size
                    self.write_offset = self.write_offset % len(self.buffer)
            except queue.Empty:
                print('DR Queue Empty!', self.ip)


    def get_data(self):
        offset = 0

        print('Waiting For Data From Socket')
        while not self.stop_event.is_set():

            try:
                n = self.s.recv_into(self.buffer_view[offset:offset + self.max_packet_size])

                if self.write_to_disk:
                    self.write_queue.put(n)

                offset += n
                offset = offset % len(self.buffer)
                # print(offset)

            except socket.timeout:
                continue


