import sys              # NOQA: E402
import os
import cv2
import logging
import json
import pika
import queue
import requests
import time

from .CaptureThread import CaptureThread
from .ControllThread import ControllThread
from .MicroWaveUDPThread import MicroWaveUDPThread

# add python path of src to sys.path
src_path = os.path.join(__file__, *(['..'] * 2))
src_path = os.path.abspath(src_path)
sys.path.insert(0, src_path)

from protos import aerial_pb2 as pb


IMAGE_WIDTH = 3840
IMAGE_HEIGHT = 2880
CAMERA_COUNT = 4

class Config:
    """
    config class
    """
    def __init__(self, config_file: str):
        self.interval: float = 10
        self.resolution = '4k'

        self.clean_lens_start_angle = 10
        self.clean_lens_end_angle = 90
        self.clean_lens_num = 2
        self.clean_lens_period = 1200
        self.clean_self_start_angle = 10
        self.clean_self_end_angle = 55
        self.clean_self_num = 2
        self.clean_self_period = 240

        # load config file
        with open(config_file, 'r') as f:
            self.config = json.load(f)
            self.device_id = self.config['Device_ID']
            self.interval = self.config['Captue_Image_para']['capture_interval']
            self.resolution = self.config['Captue_Image_para']['resolution']

            Clean_Brush_para = self.config['Clean_Brush_para']
            self.clean_lens_start_angle = Clean_Brush_para['clean_lens_start_angle']
            self.clean_lens_end_angle = Clean_Brush_para['clean_lens_end_angle']
            self.clean_lens_num = Clean_Brush_para['clean_lens_num']
            self.clean_lens_period = Clean_Brush_para['clean_lens_period']
            self.clean_self_start_angle = Clean_Brush_para['clean_self_start_angle']
            self.clean_self_end_angle = Clean_Brush_para['clean_self_end_angle']
            self.clean_self_num = Clean_Brush_para['clean_self_num']
            self.clean_self_period = Clean_Brush_para['clean_self_period']
            f.close()
        return

class AcquisitionMain:
    """
    acquisition main class
    """
    def __init__(self,
                 rabbitmq_host: str, 
                 rabbitmq_user: str, 
                 rabbitmq_password: str,
                 rabbitmq_queue: str, 
                 device_id: str, 
                 api_url: str,
                 udp_server_host: str,
                 config: Config) -> None:
        """
        init AcquisitionMain class
        Args:
            rabbitmq_host: rabbitmq server host
            rabbitmq_user: rabbitmq user
            rabbitmq_password: rabbitmq password
            rabbitmq_queue: rabbitmq upload queue
            device_id: device id
            api_url: server api url
            udp_server_host: udp server host for micro wave, localhost for testing or 192.168.2.65 for production
            config: config object             
        """
        self.rabbitmq_host: str = rabbitmq_host
        self.udp_server_host: str = udp_server_host
        self.rabbitmq_user: str = rabbitmq_user
        self.rabbitmq_password: str = rabbitmq_password
        self.rabbitmq_queue: str = rabbitmq_queue
        self.device_id: str = device_id
        self.message_send: int = 0
        self.api_url: str = api_url
        self.config = config
        self.image_quality: float = 95
        self.cameras: list = []
        self.msg_queue = queue.Queue()
        # thread members
        self._controll_thread = None
        self._capture_thread = None
        self._udp_thread = None

    def _start_udp_thread(self):
        """start udp thread"""
        if self._udp_thread is None:
            self._udp_thread = MicroWaveUDPThread((self.udp_server_host, 9911), self.msg_queue)
            self._udp_thread.setDaemon(True)
            self._udp_thread.start()

    def _stop_udp_thread(self):
        """stop udp thread"""
        if not self._udp_thread is None:
            self._udp_thread.stop()
            self._udp_thread = None

    def _start_capture_thread(self):
        """start camera capture thread"""
        if self._capture_thread is None:
            self._open_cameras()
            if len(self.cameras) == 0:
                print("No camera available")
                return
            self._capture_thread = CaptureThread(
                self.cameras, self.msg_queue, self.config.interval, self.image_quality)
            self._capture_thread.setDaemon(True)
            self._capture_thread.start()

    def _stop_capture_thread(self):
        """stop camera capture thread"""
        if not self._capture_thread is None:
            self._capture_thread.stop()
            self._capture_thread = None

    def _start_controll_thread(self):
        """start controll thread"""
        if self._controll_thread is None:
            self._controll_thread = ControllThread(
                self.rabbitmq_host, self.rabbitmq_user, self.rabbitmq_password, self.device_id + "_callback",
                self.config)
            self._controll_thread.setDaemon(True)
            self._controll_thread.start()

    def _stop_controll_thread(self):
        """stop controll thread"""
        if not self._controll_thread is None:
            self._controll_thread.stop()
            self._controll_thread = None

    def _connect_message_queue(self) -> None:
        """connect message queue of rabbitmq"""
        credentials = pika.PlainCredentials(self.rabbitmq_user, self.rabbitmq_password)
        parameters = pika.ConnectionParameters(
            credentials=credentials, host=self.rabbitmq_host)
        # 连接队列服务器
        self._connection = pika.BlockingConnection(parameters)
        self.channel = self._connection.channel()
        self.channel.queue_declare(queue='', exclusive=True)

    def _close_message_queue(self) -> None:
        """close message queue of rabbitmq"""
        self._connection.close()

    def _set_logging(self):
        """set logging"""
        logging.basicConfig(level=logging.INFO,
                            format='%(asctime)s %(levelname)s %(message)s',
                            datefmt='%Y-%m-%d %H:%M:%S')
        self.logger = logging.getLogger(__name__)

    def _send_data(self, req: pb.RequestCommand) -> None:
        """send data to rabbitmq"""
        req.device_id = self.device_id
        body = req.SerializeToString()
        self.channel.basic_publish(
            exchange='',
            routing_key=self.rabbitmq_queue,
            body=body)
        self.message_send += 1
        return

    def _call(self, req: pb.RequestCommand, timeout: int = 10) -> pb.ResponseCommand:
        """call server api"""
        req.device_id = self.device_id
        self.response = None
        body = req.SerializeToString()
        response = requests.post(
            self.api_url,
            data=body,
            headers={'Content-Type': 'application/x-protobuf'},
            timeout=timeout)
        resp = pb.ResponseCommand()
        resp.ParseFromString(response.content)
        return resp

    def _send_power_on_message(self) -> pb.ResponseCommand:
        """send power on message"""
        print("send power on message")
        req = pb.RequestCommand()
        req.sys_command = pb.SysCommand.SYS_COMMAND
        req.sub_command = pb.SubCommand.POWER_ON_SUB_COMMAND
        return self._call(req)

    def _send_power_off_message(self) -> pb.ResponseCommand:
        """send power off message"""
        print("send power off message")
        req = pb.RequestCommand()
        req.sys_command = pb.SysCommand.SYS_COMMAND
        req.sub_command = pb.SubCommand.POWER_OFF_SUB_COMMAND
        return self._call(req)

    def _open_cameras(self):
        """
        Test the ports and returns a tuple with the available ports and the ones that are working.
        """
        self.cameras = []
        self.working_ports = []
        for dev_port in range(CAMERA_COUNT):
            camera = cv2.VideoCapture(dev_port, cv2.CAP_DSHOW)
            camera.set(cv2.CAP_PROP_FRAME_WIDTH, IMAGE_WIDTH)
            camera.set(cv2.CAP_PROP_FRAME_HEIGHT, IMAGE_HEIGHT)
            camera.set(cv2.CAP_PROP_FPS, 1)
            if camera.isOpened():
                is_reading, img = camera.read()
                w = camera.get(3)
                h = camera.get(4)
                if is_reading:
                    print("Port %s is working and reads images (%s x %s)" %
                          (dev_port, h, w))
                    self.working_ports.append(dev_port)
                    self.cameras.append(camera)

    def _main_loop(self):
        # main loop of the acquisition main program
        self._connect_message_queue()
        while True:
            try:
                if not self.msg_queue.empty():
                    msg = self.msg_queue.get()
                    self._send_data(msg)
                    print(time.asctime(), "send message")
                time.sleep(0.1)
            except Exception as e:
                break
        self._close_message_queue()
        self._stop_udp_thread()
        self._stop_capture_thread()
        self._stop_controll_thread()
        print("Stopped")
        
    def start(self) -> None:
        """start all threads"""
        self._start_controll_thread()
        self._start_capture_thread()
        self._start_udp_thread()
        self._main_loop()
