import json
import struct
import time
import urllib

import cv2
import numpy as np

import paths
from cv_cam import OpenCVFFMPEGCaptureWrapper, CvUrlListDetector


class BirdwingCaptureWrapper(OpenCVFFMPEGCaptureWrapper):
    ID = "BIRDWING"
    ANTI_FLOOD_SLEEP = 0.01

    def get_auth_from_urls_file(self, cap_id):
        for line_words in BirdwingListDetector.load_camera_urls_file(full_string=True):
            if line_words and line_words[0] == cap_id:
                if len(line_words) > 4:
                    self.host, _, self.client_id, self.client_secret, self.auth_code = line_words[:5]
                    break
                else:
                    self.logger.warning('Invalid line in urls file: ' + " ".join(line_words))
        else:
            self.access_token = None
            self.operational = False

    def request_access_token(self):
        args = {
            "response_type": "token",
            "client_id": self.client_id,
            "client_secret": self.client_secret,
            "auth_code": self.auth_code,
            "context": "camera",
        }
        url = "http://" + self.cap_id + "/auth/?" + urllib.parse.urlencode(args)
        try:
            response = urllib.request.urlopen(url).read()
            return json.loads(response).get("access_token", "")
        except urllib.error.HTTPError as e:
            self.logger.info("HTTPError on getting access_token: " + str(e))
        except Exception as e:
            self.logger.exception("Exception on getting access_token: " + str(e))
        return ""

    def frame_loop(self):
        while not self.stop_flag:
            if self.fails > self.MAX_FAILS:
                self.logger.error(
                    f"Reached max failed count for {self.cap_id}. Stopping"
                )
                self.fails = 0
                break
            if self.frame_consumed:
                self.update_frame()
                self.frame_consumed = False
            else:
                time.sleep(self.ANTI_FLOOD_SLEEP)
            time.sleep(self.RETRY_SLEEP)
        self.deactivation_cleanup()

    def update_frame(self):
        try:
            self.access_token = self.request_access_token()
            url = f"http://{self.host}/camera?token=" + self.access_token
            frame_data = urllib.request.urlopen(url).read()
            width, height, image_format = struct.unpack("!III", frame_data[4:16])
        except Exception as e:
            self.fails += 1
            self.logger.error("Frame request or parsing error: " + str(e))
        else:
            if width and height and image_format:
                try:
                    self.operational = True
                    self.fails = 0
                    if image_format == 1:
                        image = np.frombuffer(frame_data[16:], dtype=np.uint8).reshape(
                            (height, width, 2)
                        )
                        image = cv2.cvtColor(image, cv2.COLOR_YUV2BGR_YUY2)
                        with self.frame_lock:
                            self.frame = image
                    elif image_format == 2:
                        image = np.frombuffer(frame_data[16:], dtype=np.uint8)
                        with self.frame_lock:
                            self.frame = cv2.imdecode(image, cv2.IMREAD_UNCHANGED)
                    else:
                        self.logger.warning(
                            "Failed to get camera frame (wrong type):"
                            + str(image_format)
                        )
                except Exception as e:
                    self.logger.exception(str(e))
                    self.fails += 1
            else:
                self.logger.warning("Received an empty camera frame")


class BirdwingListDetector(CvUrlListDetector):
    CAPTURE_WRAPPER_CLASS = BirdwingCaptureWrapper
    IS_DEFAULT_FOR_EMPTY_CLASS = False
    WORDS_PER_LINE_IN_URL_FILE = (5,)
    FILES = (paths.CAMERA_URLS_FILE, paths.USER_CAMERA_URLS_FILE)
