"""For HA sensor components."""
import asyncio
import logging
import re
from time import time
from typing import Callable, Optional, Coroutine

import async_timeout

from .core import FFMPEG_STDOUT, HAFFmpegWorker

_LOGGER = logging.getLogger(__name__)


class SensorNoise(HAFFmpegWorker):
    """Implement a noise detection on a autio stream."""

    STATE_NONE = 0
    STATE_NOISE = 1
    STATE_END = 2
    STATE_DETECT = 3

    def __init__(self, ffmpeg_bin: str, callback: Callable):
        """Init noise sensor."""
        super().__init__(ffmpeg_bin)

        self._callback = callback
        self._peak = -30
        self._time_duration = 1
        self._time_reset = 2

    def set_options(
        self, time_duration: int = 1, time_reset: int = 2, peak: int = -30
    ) -> None:
        """Set option parameter for noise sensor."""
        self._time_duration = time_duration
        self._time_reset = time_reset
        self._peak = peak

    def open_sensor(
        self,
        input_source: str,
        output_dest: Optional[str] = None,
        extra_cmd: Optional[str] = None,
    ) -> Coroutine:
        """Open FFmpeg process for read autio stream.

        Return a coroutine.
        """
        command = ["-vn", "-filter:a", f"silencedetect=n={self._peak}dB:d=1"]

        # run ffmpeg, read output
        return self.start_worker(
            cmd=command,
            input_source=input_source,
            output=output_dest,
            extra_cmd=extra_cmd,
            pattern="silence",
        )

    async def _worker_process(self) -> None:
        """This function processing data."""
        state = self.STATE_DETECT
        timeout = self._time_duration

        self._loop.call_soon(self._callback, False)

        re_start = re.compile("silence_start")
        re_end = re.compile("silence_end")

        # process queue data
        while True:
            try:
                _LOGGER.debug("Reading State: %d, timeout: %s", state, timeout)
                with async_timeout.timeout(timeout):
                    data = await self._queue.get()
                timeout = None
                if data is None:
                    self._loop.call_soon(self._callback, None)
                    return
            except asyncio.TimeoutError:
                _LOGGER.debug("Blocking timeout")
                # noise
                if state == self.STATE_DETECT:
                    # noise detected
                    self._loop.call_soon(self._callback, True)
                    state = self.STATE_NOISE

                elif state == self.STATE_END:
                    # no noise
                    self._loop.call_soon(self._callback, False)
                    state = self.STATE_NONE

                timeout = None
                continue

            if re_start.search(data):
                if state == self.STATE_NOISE:
                    # stop noise detection
                    state = self.STATE_END
                    timeout = self._time_reset
                elif state == self.STATE_DETECT:
                    # reset if only a peak
                    state = self.STATE_NONE
                continue

            if re_end.search(data):
                if state == self.STATE_NONE:
                    # detect noise begin
                    state = self.STATE_DETECT
                    timeout = self._time_duration
                elif state == self.STATE_END:
                    # back to noise status
                    state = self.STATE_NOISE
                continue

            _LOGGER.warning("Unknown data from queue!")


class SensorMotion(HAFFmpegWorker):
    """Implement motion detection with ffmpeg scene detection."""

    STATE_NONE = 0
    STATE_REPEAT = 1
    STATE_MOTION = 2

    MATCH = r"\d,.*\d,.*\d,.*\d,.*\d,.*\w"

    def __init__(self, ffmpeg_bin: str, callback: Callable):
        """Init motion sensor."""
        super().__init__(ffmpeg_bin)

        self._callback = callback
        self._changes = 10
        self._time_reset = 60
        self._time_repeat = 0
        self._repeat = 0

    def set_options(
        self,
        time_reset: int = 60,
        time_repeat: int = 0,
        repeat: int = 0,
        changes: int = 10,
    ) -> None:
        """Set option parameter for noise sensor."""
        self._time_reset = time_reset
        self._time_repeat = time_repeat
        self._repeat = repeat
        self._changes = changes

    async def open_sensor(
        self, input_source: str, extra_cmd: Optional[str] = None
    ) -> Coroutine:
        """Open FFmpeg process a video stream for motion detection.

        Return a coroutine.
        """
        command = [
            "-an",
            "-filter:v",
            f"select=gt(scene\\,{self._changes / 100})",
        ]

        # run ffmpeg, read output
        return await self.start_worker(
            cmd=command,
            input_source=input_source,
            output="-f framemd5 -",
            extra_cmd=extra_cmd,
            pattern=self.MATCH,
            reading=FFMPEG_STDOUT,
        )

    async def _worker_process(self) -> None:
        """This function processing data."""
        state = self.STATE_NONE
        timeout = None

        self._loop.call_soon(self._callback, False)

        # for repeat feature
        re_frame = 0
        re_time = 0

        re_data = re.compile(self.MATCH)

        # process queue data
        while True:
            try:
                _LOGGER.debug("Reading State: %d, timeout: %s", state, timeout)
                with async_timeout.timeout(timeout):
                    data = await self._queue.get()
                if data is None:
                    self._loop.call_soon(self._callback, None)
                    return
            except asyncio.TimeoutError:
                _LOGGER.debug("Blocking timeout")
                # reset motion detection
                if state == self.STATE_MOTION:
                    state = self.STATE_NONE
                    self._loop.call_soon(self._callback, False)
                    timeout = None
                # reset repeate state
                if state == self.STATE_REPEAT:
                    state = self.STATE_NONE
                    timeout = None
                continue

            frames = re_data.search(data)
            if frames:
                # repeat not used
                if self._repeat == 0 and state == self.STATE_NONE:
                    state = self.STATE_MOTION
                    self._loop.call_soon(self._callback, True)
                    timeout = self._time_reset

                # repeat feature is on / first motion
                if state == self.STATE_NONE:
                    state = self.STATE_REPEAT
                    timeout = self._time_repeat
                    re_frame = 0
                    re_time = time()

                elif state == self.STATE_REPEAT:
                    re_frame += 1

                    # REPEAT ready?
                    if re_frame >= self._repeat:
                        state = self.STATE_MOTION
                        self._loop.call_soon(self._callback, True)
                        timeout = self._time_reset
                    else:
                        past = time() - re_time
                        timeout -= past

                    # REPEAT time down
                    if timeout <= 0:
                        _LOGGER.debug("Reset repeat to none")
                        state = self.STATE_NONE
                        timeout = None

                continue

            _LOGGER.warning("Unknown data from queue!")
