from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict

import cv2

from app.constants import UPDATE_MOTION_DETECTION_STATUS_INTERVAL
from app.exceptions import OpenFileError
from app.loggers import processing as logger


class VideoProcessingStats:
    """
    Class for calculating statistics of video processing as FPS
    """

    def __init__(self, frames_number: int) -> None:
        self._frames_number = frames_number
        self._start_time = datetime.now()
        self._num_occurrences = 0

    def update(self) -> None:
        """
        Increase number of occurrences
        :return:
        """
        self._num_occurrences += 1
        if self._num_occurrences % 10 == 0:
            logger.debug(f"{self.fps:.2f} frames per second")

    @property
    def fps(self) -> float:
        """
        Calculate FPS based on number of occurrences and elapsed time
        :return:
        """
        return self._num_occurrences / self.elapsed_time.total_seconds()

    @property
    def occurrences(self) -> int:
        """
        Get number of occurrences
        :return:
        """
        return self._num_occurrences

    @property
    def elapsed_time(self) -> timedelta:
        """
        Get elapsed time since start
        :return:
        """
        return datetime.now() - self._start_time


class EstimatedTimeCalculator:
    """
    Class for calculating estimated time of video processing
    """

    def __init__(self, frame_numbers_by_file_index: Dict[int, int]) -> None:
        self._frame_numbers_by_file_index = frame_numbers_by_file_index.copy()
        self._remaining_frame_numbers_by_file_index = frame_numbers_by_file_index.copy()

    def set_processed_frames_number(
        self, file_index: int, current_frame_number: int
    ) -> None:
        """
        Set number of processed frames for file and calculate remaining frames number
        :param file_index: index of file in queue
        :param current_frame_number: number of processing frame
        :return:
        """
        remaining_frames_number = (
            self._frame_numbers_by_file_index[file_index] - current_frame_number
        )
        if remaining_frames_number < UPDATE_MOTION_DETECTION_STATUS_INTERVAL:
            remaining_frames_number = 0
        self._remaining_frame_numbers_by_file_index[
            file_index
        ] = remaining_frames_number

    def calculate(self, fps: float) -> timedelta:
        """
        Calculate estimated time of video processing
        :param fps: actual value of FPS
        :return: timedelta object with estimated time
        """
        remaining_frames_number = sum(
            self._remaining_frame_numbers_by_file_index.values()
        )
        return timedelta(seconds=remaining_frames_number / fps)


def get_video_frames_number(file_path: Path) -> int:
    """
    Get number of frames in video file
    :param file_path: path to video file
    :return: number of frames
    """
    data = cv2.VideoCapture(str(file_path))
    frames_number = int(data.get(cv2.CAP_PROP_FRAME_COUNT))
    data.release()
    if frames_number == 0:
        raise OpenFileError("Video file is corrupted or not supported")
    return frames_number
