import socket
import ssl
import time

import cv2
import numpy as np

import paths
from cv_cam import OpenCVFFMPEGCaptureWrapper, CvUrlListDetector


class BambulabAP1CaptureWrapper(OpenCVFFMPEGCaptureWrapper):

    ID = 'BAMB_AP1'

    USERNAME = b'bblp'
    PORT = 6000
    TIMEOUT = 10
    ON_ERROR_SLEEP = 1
    ANTI_FLOOD_SLEEP = 1/60
    READ_CHUNK = 1024
    INACTIVE_LOOP_TIME = 3
    JPEG_START_MAGIC = bytes.fromhex("ff d8 ff e0")
    JPEG_END_MAGIC = bytes.fromhex("ff d9")
    LEN_JPEG_END_MAGIC = len(JPEG_END_MAGIC)

    DEBUG_OUTPUT_FILE = None 
    # DEBUG_OUTPUT_FILE = "/tmp/p1.jpeg"


    def __init__(self, parent, cap_id):
        for line_words in BambulabAP1ListDetector.load_camera_urls_file(full_string=True):
            if len(line_words) == 3:
                line_cap_id, _, access_code = line_words
                if line_cap_id == cap_id:
                    self.auth_msg = self.form_auth_msg(access_code)
                    break
        else:
            self.auth_msg = None
        if self.auth_msg:
            super().__init__(parent, cap_id)
        else:
            self.operational = False

    def form_auth_msg(self, access_code):
        access_code = access_code.encode()
        return b'@\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + \
               self.USERNAME + b'\x00' * (32 - len(self.USERNAME)) + \
               access_code + b'\x00' * (32 - len(access_code))

    def frame_loop(self):
        ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        while not self.stop_flag:
            try:
                self.logger.info('Connecting to camera socket...')
                with socket.create_connection((self.cap_id, self.PORT), self.TIMEOUT) as sock:
                    with ctx.wrap_socket(sock, server_hostname=self.cap_id) as ssock:
                        ssock.sendall(self.auth_msg)
                        buf = bytearray()
                        jpeg_start_found = False #TODO check is that is not to late to set this flag
                        while not self.stop_flag:
                            self.active = True
                            try:
                                received = ssock.recv(self.READ_CHUNK)
                            except socket.error:
                                self.logger.error('Camera socket read error')
                                received = None
                            if not received:
                                self.logger.error('Connection lost')
                                time.sleep(self.ON_ERROR_SLEEP)
                                self.operational = False
                                break
                            self.operational = True #TODO check is that is not to late to set this flag
                            buf += received
                            if not jpeg_start_found:
                                index = buf.find(self.JPEG_START_MAGIC)
                                if index >= 0:
                                    jpeg_start_found = True
                                    buf = buf[index:]
                                time.sleep(self.ANTI_FLOOD_SLEEP)
                            index = buf.find(self.JPEG_END_MAGIC)
                            if index >= 0:
                                jpeg_start_found = False
                                with self.frame_lock:
                                    self.frame = cv2.imdecode(np.asarray((buf[:index + self.LEN_JPEG_END_MAGIC]), \
                                            dtype=np.uint8), cv2.IMREAD_UNCHANGED)
                                buf = buf[index + self.LEN_JPEG_END_MAGIC:]
                                if self.DEBUG_OUTPUT_FILE:
                                    print(".", end="", flush=True)
                                    with open(self.DEBUG_OUTPUT_FILE, 'wb') as f:
                                        f.write(self.frame)
                self.operational = False
            except socket.error:
                self.logger.error('No connection to camera socket')
                time.sleep(self.ON_ERROR_SLEEP)
                self.operational = False
                break
            except Exception as e:
                self.logger.exception(f'{e} in frame loop. Trace: ')
        self.active = False
        self.operational = False


class BambulabAP1ListDetector(CvUrlListDetector):

    CAPTURE_WRAPPER_CLASS = BambulabAP1CaptureWrapper
    IS_DEFAULT_FOR_EMPTY_CLASS = False
    FILES = (paths.CAMERA_URLS_FILE, paths.USER_CAMERA_URLS_FILE)
