# -*- coding: utf-8 -*-
import time

import cv2
import subprocess as sp

import ffmpeg
import numpy as np
from loguru import logger
from exception.CustomerException import ServiceException
from enums.ExceptionEnum import ExceptionType


class Cv2Util():

    def __init__(self, pullUrl, pushUrl=None, orFilePath=None, aiFilePath=None, requestId=None):
        self.pullUrl = pullUrl
        self.pushUrl = pushUrl
        self.orFilePath = orFilePath
        self.aiFilePath = aiFilePath
        self.cap = None
        self.p = None
        self.or_video_file = None
        self.ai_video_file = None
        self.fps = None
        self.width = None
        self.height = None
        self.wh = None
        self.all_frames = None
        self.bit_rate = None
        self.pull_p = None
        self.requestId = requestId
        self.p_push_retry_num = 0

    '''
        获取视频信息
    '''

    def get_video_info(self):
        try:
            if self.pullUrl is None:
                logger.error("拉流地址不能为空, requestId:{}", self.requestId)
                raise ServiceException(ExceptionType.PULL_STREAM_URL_EXCEPTION.value[0],
                                       ExceptionType.PULL_STREAM_URL_EXCEPTION.value[1])
            probe = ffmpeg.probe(self.pullUrl)
            # 视频大小
            # format = probe['format']
            # size = int(format['size'])/1024/1024
            video_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'video'), None)
            if video_stream is None:
                logger.error("根据拉流地址未获取到视频流, requestId:{}", self.requestId)
                return
            width = video_stream.get('width')
            height = video_stream.get('height')
            nb_frames = video_stream.get('nb_frames')
            fps = video_stream.get('r_frame_rate')
            # duration = video_stream.get('duration')
            bit_rate = video_stream.get('bit_rate')
            if width:
                self.width = int(width)
            if height:
                self.height = int(height)
            if width is not None and height is not None:
                self.wh = int(width * height * 3)
            if nb_frames:
                self.all_frames = int(nb_frames)
            if fps:
                up, down = str(fps).split('/')
                self.fps = int(eval(up) / eval(down))
            # if duration:
            #    self.duration = float(video_stream['duration'])
            if bit_rate:
                self.bit_rate = int(bit_rate) / 1000
            logger.info("视频信息, width:{}|height:{}|fps:{}|all_frames:{}|bit_rate:{}, requestId:{}", self.width,
                        self.height, self.fps, self.all_frames, self.bit_rate, self.requestId)
        except ServiceException as s:
            logger.exception("获取视频信息异常: {}, requestId:{}", s.msg, self.requestId)
            raise s
        except Exception as e:
            logger.exception("获取视频信息异常：{}, requestId:{}", e, self.requestId)

    '''
        拉取视频
    '''

    def build_pull_p(self):
        try:
            if self.width is None or self.height is None:
                return
            if self.pull_p:
                logger.info("重试, 关闭拉流管道, requestId:{}", self.requestId)
                self.pull_p.stdout.close()
                self.pull_p.terminate()
                self.pull_p.wait()
            if self.pullUrl is None:
                logger.error("拉流地址不能为空, requestId:{}", self.requestId)
                raise ServiceException(ExceptionType.PULL_STREAM_URL_EXCEPTION.value[0],
                                       ExceptionType.PULL_STREAM_URL_EXCEPTION.value[1])
            # command = ['ffmpeg',
            #            # '-b:v', '3000k',
            #            '-i', self.pullUrl,
            #            '-f', 'rawvideo',
            #            '-vcodec', 'rawvideo',
            #            '-pix_fmt', 'bgr24',
            #            # '-s', "{}x{}".format(int(width), int(height)),
            #            '-an',
            #            '-']
            aa = {'loglevel': 'error', 'c:v': 'h264_cuvid'}
            process = (
                ffmpeg
                    .input(self.pullUrl, **aa)
                    .output('pipe:', format='rawvideo', pix_fmt='bgr24', loglevel='error')
                    .overwrite_output()
                    .global_args('-an')
                    .run_async(pipe_stdout=True)
            )
            # self.pull_p = sp.Popen(command, stdout=sp.PIPE, stderr=sp.PIPE)
            self.pull_p = process
        except ServiceException as s:
            logger.exception("构建拉流管道异常: {}, requestId:{}", s, self.requestId)
            raise s
        except Exception as e:
            logger.exception("构建拉流管道异常：{}, requestId:{}", e, self.requestId)

    def checkconfig(self):
        if self.fps is None or self.width is None or self.height is None:
            return True
        return False

    def read(self):
        result = None
        try:
            # if self.pull_p is None:
            #     logger.error("拉流管道为空, requestId:{}", self.requestId)
            #     raise ServiceException(ExceptionType.PULL_PIPELINE_INIT_EXCEPTION.value[0],
            #                            ExceptionType.PULL_PIPELINE_INIT_EXCEPTION.value[1])
            in_bytes = self.pull_p.stdout.read(self.wh)
            if in_bytes is not None and len(in_bytes) > 0:
                result = (np.frombuffer(in_bytes, np.uint8).reshape([int(self.height), int(self.width), 3]))
                result = cv2.resize(result, (int(self.width / 2), int(self.height / 2)), interpolation=cv2.INTER_LINEAR)
        except ServiceException as s:
            logger.exception("读流异常: {}, requestId:{}", s, self.requestId)
        except Exception as e:
            logger.exception("读流异常：{}, requestId:{}", e, self.requestId)
        return result

    def close(self):
        if self.pull_p:
            if self.pull_p.stdout:
                self.pull_p.stdout.close()
            self.pull_p.terminate()
            self.pull_p.wait()
            logger.info("关闭拉流管道完成, requestId:{}", self.requestId)
        if self.p:
            if self.p.stdin:
                self.p.stdin.close()
            self.p.terminate()
            self.p.wait()
            # self.p.communicate()
            # self.p.kill()
            logger.info("关闭管道完成, requestId:{}", self.requestId)
        if self.or_video_file:
            self.or_video_file.release()
            logger.info("关闭原视频写入流完成, requestId:{}", self.requestId)
        if self.ai_video_file:
            self.ai_video_file.release()
            logger.info("关闭AI视频写入流完成, requestId:{}", self.requestId)

    # 构建 cv2
    # def build_cv2(self):
    #     try:
    #         if self.cap is not None:
    #             logger.info("重试, 关闭cap, requestId:{}", self.requestId)
    #             self.cap.release()
    #         if self.p is not None:
    #             logger.info("重试, 关闭管道, requestId:{}", self.requestId)
    #             self.p.stdin.close()
    #             self.p.terminate()
    #             self.p.wait()
    #         if self.pullUrl is None:
    #             logger.error("拉流地址不能为空, requestId:{}", self.requestId)
    #             raise ServiceException(ExceptionType.PULL_STREAM_URL_EXCEPTION.value[0],
    #                                    ExceptionType.PULL_STREAM_URL_EXCEPTION.value[1])
    #         if self.pushUrl is None:
    #             logger.error("推流地址不能为空, requestId:{}", self.requestId)
    #             raise ServiceException(ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[0],
    #                                    ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[1])
    #         self.cap = cv2.VideoCapture(self.pullUrl)
    #         if self.fps is None or self.fps == 0:
    #             self.fps = int(self.cap.get(cv2.CAP_PROP_FPS))
    #         if self.width is None or self.width == 0:
    #             self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    #         if self.height is None or self.height == 0:
    #             self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    #         command = ['/usr/bin/ffmpeg',
    #                    # '-y',  # 不经过确认，输出时直接覆盖同名文件。
    #                    '-f', 'rawvideo',
    #                    '-vcodec', 'rawvideo',
    #                    '-pix_fmt', 'bgr24',  # 显示可用的像素格式
    #                    # '-s', "{}x{}".format(self.width * 2, self.height),
    #                    '-s', "{}x{}".format(int(self.width), int(self.height/2)),
    #                    # '-r', str(15),
    #                    '-i', '-',  # 指定输入文件
    #                    '-g', '25',
    #                    '-b:v', '3000k',
    #                    '-tune', 'zerolatency',  # 加速编码速度
    #                    '-c:v', 'libx264',  # 指定视频编码器
    #                    '-sc_threshold', '0',
    #                    '-pix_fmt', 'yuv420p',
    #                    '-an',
    #                    '-preset', 'ultrafast',  # 指定输出的视频质量，会影响文件的生成速度，有以下几个可用的值 ultrafast,
    #                    # superfast, veryfast, faster, fast, medium, slow, slower, veryslow。
    #                    '-f', 'flv',
    #                    self.pushUrl]
    #         # 管道配置
    #         logger.info("fps:{}|height:{}|width:{}|requestId:{}", self.fps, self.height, self.width, self.requestId)
    #         self.p = sp.Popen(command, stdin=sp.PIPE)
    #     except ServiceException as s:
    #         logger.exception("构建cv2异常: {}, requestId:{}", s, self.requestId)
    #         raise s
    #     except Exception as e:
    #         logger.exception("初始化cv2异常：{}, requestId:{}", e, self.requestId)

    # 构建 cv2
    def build_p(self):
        try:
            if self.p:
                logger.info("重试, 关闭管道, requestId:{}", self.requestId)
                if self.p.stdin:
                    self.p.stdin.close()
                self.p.terminate()
                self.p.wait()
                # self.p.communicate()
                # self.p.kill()
            if self.pushUrl is None:
                logger.error("推流地址不能为空, requestId:{}", self.requestId)
                raise ServiceException(ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[0],
                                       ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[1])
            command = ['ffmpeg',
                       # '-loglevel', 'debug',
                       '-y',  # 不经过确认，输出时直接覆盖同名文件。
                       '-f', 'rawvideo',
                       '-vcodec', 'rawvideo',
                       '-pix_fmt', 'bgr24',
                       '-thread_queue_size', '16',
                       # '-s', "{}x{}".format(self.width * 2, self.height),
                       '-s', "{}x{}".format(int(self.width), int(self.height / 2)),
                       '-r', str(self.fps),
                       '-i', '-',  # 指定输入文件
                       '-g', str(self.fps),
                       # '-maxrate', '15000k',
                       # '-profile:v', 'high',
                       # '-b:v', '4000k',
                       # '-crf', '18',
                       '-rc:v', 'vbr',
                       '-cq:v', '30',
                       '-qmin', '30',
                       '-qmax', '30',
                       '-c:v', 'h264_nvenc',  #
                       # '-bufsize', '4000k',
                       # '-c:v', 'libx264',  # 指定视频编码器
                       # '-tune', 'zerolatency',  # 加速编码速度
                       # '-sc_threshold', '0',
                       '-pix_fmt', 'yuv420p',
                       "-an",
                       # '-flvflags', 'no_duration_filesize',
                       '-preset', 'fast',  # 指定输出的视频质量，会影响文件的生成速度，有以下几个可用的值 ultrafast,
                       # superfast, veryfast, faster, fast, medium, slow, slower, veryslow。
                       '-f', 'flv',
                       self.pushUrl]
            # command = 'ffmpeg -loglevel debug -y -f rawvideo -vcodec rawvideo -pix_fmt bgr24' +\
            #            ' -s ' +  "{}x{}".format(int(self.width), int(self.height/2))\
            #            + ' -i - ' + '-g ' + str(self.fps)+\
            #            ' -b:v 6000k -tune zerolatency -c:v libx264 -pix_fmt yuv420p -preset ultrafast'+\
            #            ' -f flv ' + self.pushUrl

            # kwargs = {'format': 'rawvideo',
            #           # 'vcodec': 'rawvideo',
            #           'pix_fmt': 'bgr24',
            #           's': '{}x{}'.format(int(self.width), int(self.height/2))}
            # out = {
            #     'r': str(self.fps),
            #     'g': str(self.fps),
            #     'b:v': '5500k', # 恒定码率
            #     # 'maxrate': '15000k',
            #     # 'crf': '18',
            #     'bufsize': '5500k',
            #     'tune': 'zerolatency', # 加速编码速度
            #     'c:v': 'libx264',  # 指定视频编码器
            #     'sc_threshold': '0',
            #     'pix_fmt': 'yuv420p',
            #     # 'flvflags': 'no_duration_filesize',
            #     'preset': 'medium',  # 指定输出的视频质量，会影响文件的生成速度，有以下几个可用的值 ultrafast,
            #     # superfast, veryfast, faster, fast, medium, slow, slower, veryslow。
            #     'format': 'flv'}
            # 管道配置
            # process2 = (
            #     ffmpeg
            #         .input('pipe:', **kwargs)
            #         .output(self.pushUrl, **out)
            #         .global_args('-y', '-an')
            #         .overwrite_output()
            #         .run_async(pipe_stdin=True)
            # )
            logger.info("fps:{}|height:{}|width:{}|requestId:{}", self.fps, self.height, self.width, self.requestId)
            self.p = sp.Popen(command, stdin=sp.PIPE, shell=False)
            # self.p = process2
        except ServiceException as s:
            logger.exception("构建p管道异常: {}, requestId:{}", s, self.requestId)
            raise s
        except Exception as e:
            logger.exception("初始化p管道异常：{}, requestId:{}", e, self.requestId)

    def push_stream(self, frame):
        if self.p is None:
            self.build_p()
        try:
            self.p.stdin.write(frame.tostring())
        except Exception as ex:
            logger.exception("推流进管道异常：{}, requestId: {}", ex, self.requestId)
            current_retry_num = 0
            while True:
                try:
                    if self.p_push_retry_num > 20:
                        logger.info("推流失败重试次数过多, 请检查相关配置信息, 当前重试次数: {}, requestId: {}",
                                    self.p_push_retry_num, self.requestId)
                        current_retry_num = 4
                        break
                    self.p_push_retry_num += 1
                    time.sleep(10)
                    self.build_p()
                    self.p.stdin.write(frame.tostring())
                    logger.info("构建p管道重试成功, 当前重试次数: {}, requestId: {}", current_retry_num,
                                self.requestId)
                    break
                except Exception as e:
                    current_retry_num += 1
                    logger.exception("构建p管道异常：{}, 开始重试, 当前重试次数：{}, requestId: {}", e,
                                     current_retry_num, self.requestId)
                    if current_retry_num > 3:
                        logger.exception("构建p管道重试失败：{}, requestId: {}", e, self.requestId)
                        break
            if current_retry_num > 3:
                raise ServiceException(ExceptionType.PUSH_STREAM_URL_E_EXCEPTION.value[0],
                                       ExceptionType.PUSH_STREAM_URL_E_EXCEPTION.value[1])

    def video_write(self, or_frame, ai_frame):
        try:
            self.build_write()
            if or_frame is not None and len(or_frame) > 0:
                self.or_video_file.write(or_frame)
            if ai_frame is not None and len(ai_frame) > 0:
                self.ai_video_file.write(ai_frame)
        except Exception as ex:
            ai_retry_num = 0
            while True:
                try:
                    if or_frame is not None and len(or_frame) > 0:
                        self.or_video_file.write(or_frame)
                    if ai_frame is not None and len(ai_frame) > 0:
                        self.ai_video_file.write(ai_frame)
                    logger.info("重新写入离线分析后视频到本地, 当前重试次数: {}, requestId: {}", ai_retry_num,
                                self.requestId)
                    break
                except Exception as e:
                    ai_retry_num += 1
                    logger.exception("重新写入离线分析后视频到本地：{}, 开始重试, 当前重试次数：{}, requestId: {}", e,
                                     ai_retry_num, self.requestId)
                    if ai_retry_num > 3:
                        logger.exception("重新写入离线分析后视频到本地,重试失败：{}, requestId: {}", e, self.requestId)
                        raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
                                               ExceptionType.SERVICE_INNER_EXCEPTION.value[1])

    def build_write(self):
        try:
            if self.fps is None or self.width is None or self.height is None:
                raise ServiceException(ExceptionType.VIDEO_CONFIG_EXCEPTION.value[0],
                                       ExceptionType.VIDEO_CONFIG_EXCEPTION.value[1])
            if self.orFilePath is not None and self.or_video_file is None:
                self.or_video_file = cv2.VideoWriter(self.orFilePath, cv2.VideoWriter_fourcc(*'mp4v'), self.fps,
                                                     (int(self.width / 2), int(self.height / 2)))
                if self.or_video_file is None:
                    raise ServiceException(ExceptionType.OR_WRITE_OBJECT_EXCEPTION.value[0],
                                           ExceptionType.OR_WRITE_OBJECT_EXCEPTION.value[1])
            if self.aiFilePath is not None and self.ai_video_file is None:
                self.ai_video_file = cv2.VideoWriter(self.aiFilePath, cv2.VideoWriter_fourcc(*'mp4v'), self.fps,
                                                     (int(self.width), int(self.height / 2)))
                if self.ai_video_file is None:
                    raise ServiceException(ExceptionType.AI_WRITE_OBJECT_EXCEPTION.value[0],
                                           ExceptionType.AI_WRITE_OBJECT_EXCEPTION.value[1])
        except ServiceException as s:
            logger.exception("构建文件写对象异常: {}, requestId:{}", s, self.requestId)
            raise s
        except Exception as e:
            logger.exception("构建文件写对象异常: {}, requestId:{}", e, self.requestId)
            raise e

    def video_merge(self, frame1, frame2):
        # frameLeft = cv2.resize(frame1, (int(self.width / 2), int(self.height / 2)), interpolation=cv2.INTER_LINEAR)
        # frameRight = cv2.resize(frame2, (int(self.width / 2), int(self.height / 2)), interpolation=cv2.INTER_LINEAR)
        # frame_merge = np.hstack((frameLeft, frameRight))
        frame_merge = np.hstack((frame1, frame2))
        return frame_merge

    def getP(self):
        if self.p is None:
            logger.error("获取管道为空, requestId:{}", self.requestId)
            raise ServiceException(ExceptionType.PULL_PIPELINE_INIT_EXCEPTION.value[0],
                                   ExceptionType.PULL_PIPELINE_INIT_EXCEPTION.value[1])
        return self.p

    def getCap(self):
        if self.cap is None:
            logger.error("获取cv2为空, requestId:{}", self.requestId)
            raise ServiceException(ExceptionType.CV2_IS_NULL_EXCEPTION.value[0],
                                   ExceptionType.CV2_IS_NULL_EXCEPTION.value[1])
        return self.cap

    def getOrVideoFile(self):
        if self.or_video_file is None:
            logger.error("获取原视频写入对象为空, requestId:{}", self.requestId)
            raise ServiceException(ExceptionType.OR_WRITE_OBJECT_EXCEPTION.value[0],
                                   ExceptionType.OR_WRITE_OBJECT_EXCEPTION.value[1])
        return self.or_video_file

    def getAiVideoFile(self):
        if self.ai_video_file is None:
            logger.error("获取AI视频写入对象为空, requestId:{}", self.requestId)
            raise ServiceException(ExceptionType.AI_WRITE_OBJECT_EXCEPTION.value[0],
                                   ExceptionType.AI_WRITE_OBJECT_EXCEPTION.value[1])
        return self.ai_video_file
