"""SC-Controller - Daemon - CemuHookUDP motion provider.

Accepts all connections from clients and sends data captured
by 'cemuhook' actions to them.
"""

import logging
import os
import socket
from ctypes import c_bool, c_char_p, c_float, c_int, c_size_t, create_string_buffer
from datetime import datetime, timedelta
from enum import IntEnum
from threading import Thread
from time import sleep
from typing import TYPE_CHECKING

from scc.tools import find_library

if TYPE_CHECKING:
	from ctypes import CDLL

log = logging.getLogger("CemuHook")

BUFFER_SIZE = 1024
IP = "127.0.0.1"
PORT = 26760


class MessageType(IntEnum):
	DSUC_VERSIONREQ = 0x100000
	DSUS_VERSIONRSP = 0x100000
	DSUC_LISTPORTS = 0x100001
	DSUS_PORTINFO = 0x100001
	DSUC_PADDATAREQ = 0x100002
	DSUS_PADDATARSP = 0x100002


class CemuhookServer:
	C_DATA_T = c_float * 6
	timeout = timedelta(seconds=1)

	def __init__(self, daemon):
		self._lib: CDLL = find_library("libcemuhook")
		self._lib.cemuhook_data_received.argtypes = [c_int, c_char_p, c_int, c_char_p, c_size_t]
		self._lib.cemuhook_data_received.restype = None
		self._lib.cemuhook_feed.argtypes = [c_int, c_int, CemuhookServer.C_DATA_T]
		self._lib.cemuhook_feed.restype = None
		self._lib.cemuhook_socket_enable.argtypes = []
		self._lib.cemuhook_socket_enable.restype = c_bool
		self.last_signal: datetime = datetime.now()

		if not self._lib.cemuhook_socket_enable():
			raise OSError("cemuhook_socket_enable failed")

		self.socket: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
		self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

		poller = daemon.get_poller()
		daemon.poller.register(self.socket.fileno(), poller.POLLIN, self.on_data_received)

		server_port = int(os.getenv("SCC_SERVER_PORT") or PORT)
		server_ip = os.getenv("SCC_SERVER_IP") or IP
		self.socket.bind((server_ip, server_port))
		log.info("Created CemuHookUDP Motion Provider")

		Thread(target=self._keepalive).start()

	def _keepalive(self):
		while True:
			if datetime.now() - self.last_signal >= self.timeout:
				# feed all zeroes to indicate the gyro has not changed
				self.feed((0.0, 0.0, 0.0, 0.0, 0.0, 0.0))
			sleep(1)

	def on_data_received(self, fd: int, event_type):
		if fd != self.socket.fileno():
			return
		message, (ip, port) = self.socket.recvfrom(BUFFER_SIZE)
		buffer = create_string_buffer(BUFFER_SIZE)
		self._lib.cemuhook_data_received(fd, ip.encode("utf-8"), port, message, len(message), buffer)

	def feed(self, data):
		self.last_signal = datetime.now()
		c_data = CemuhookServer.C_DATA_T()
		# log.debug(data)
		c_data[0:6] = data[0:6]
		# log.debug(list(c_data))
		self._lib.cemuhook_feed(self.socket.fileno(), 0, c_data)
