import cv2 as cv
from system.shared import LastErrorHolder
import imutils
import numpy as np
import datetime


class MotionDetectorBase(LastErrorHolder):
    """
    Base class for motion detection support
    """
    def __init__(self):
        LastErrorHolder.__init__(self)
        # previous frame
        self.prevFrame = None

        # DTS (date & time) of moment when last motion was detected
        self.motionDetectionDts = None

        self.resizeBeforeDetect = True

        self.multiFrameDetection = False

    def preprocessInputFrame(self, newFrame):
        if self.resizeBeforeDetect:
            return imutils.resize(newFrame, width=500, height=500)

        return newFrame.copy()

    def checkMotionDetected(self, frame):
        """
        Checks that motion detected.

        :param frame: new frame from camera
        :return: True when motion detected, otherwise False
        """
        return False

    def updateMotionDetectionDts(self):
        self.motionDetectionDts = datetime.datetime.utcnow()


class MotionDetectorV1(MotionDetectorBase):
    def __init__(self):
        MotionDetectorBase.__init__(self)
        self.threshold = 8

    def motionDetected(self, new_frame):
        frame = self.preprocessInputFrame(new_frame)

        gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
        gray = cv.GaussianBlur(gray, (21, 21), 0)

        if self.prevFrame is None:
            self.prevFrame = gray
            return False

        # compute the absolute difference between the current frame and
        # first frame
        frameDelta = cv.absdiff(gray, self.prevFrame)
        thresh = cv.threshold(frameDelta, 25, 255, cv.THRESH_BINARY)[1]

        # dilate the thresholded image to fill in holes, then find contours
        # on thresholded image
        thresh = cv.dilate(thresh, None, iterations=2)
        (cnts, _, _) = cv.findContours(thresh.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

        height = np.size(gray, 0)
        width = np.size(gray, 1)
        nb = height * width

        qty = 0
        for c in cnts:
            a = cv.boundingRect(c)

            (x, y, w, h) = a
            s = w * h

            pcs = (float(s) / float(nb)) * 100

            if pcs < self.threshold:
                continue

            # cv.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

            qty += 1
            break

        # cv.imshow("frame", frame)
        self.prevFrame = gray

        ret = (qty > 0)
        if ret:
            self.updateMotionDetectionDts()

        return ret


class MotionDetectorV2(MotionDetectorBase):
    def __init__(self):
        MotionDetectorBase.__init__(self)
        self.threshold = 1

    def motionDetected(self, new_frame):
        frame = self.preprocessInputFrame(new_frame)

        gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
        gray = cv.GaussianBlur(gray, (21, 21), 0)

        if self.prevFrame is None:
            self.prevFrame = gray
            return False

        frameDiff = cv.absdiff(gray, self.prevFrame)

        # kernel = np.ones((5, 5), np.uint8)

        opening = cv.morphologyEx(frameDiff, cv.MORPH_OPEN, None)  # noqa
        closing = cv.morphologyEx(frameDiff, cv.MORPH_CLOSE, None)  # noqa

        ret1, th1 = cv.threshold(frameDiff, 10, 255, cv.THRESH_BINARY)

        height = np.size(th1, 0)
        width = np.size(th1, 1)

        nb = cv.countNonZero(th1)

        avg = (nb * 100) / (height * width)  # Calculate the average of black pixel in the image

        self.prevFrame = gray

        # cv.DrawContours(currentframe, self.currentcontours, (0, 0, 255), (0, 255, 0), 1, 2, cv.CV_FILLED)
        # cv.imshow("frame", current_frame)

        ret = avg > self.threshold   # If over the ceiling trigger the alarm

        if ret:
            self.updateMotionDetectionDts()

        return ret


class MotionDetectorV3(MotionDetectorBase):
    def __init__(self):
        MotionDetectorBase.__init__(self)

        self.threshold = 1000
        self.prevPrevFrame = None

    def diffImg(self, t0, t1, t2):
        d1 = cv.absdiff(t2, t1)
        d2 = cv.absdiff(t1, t0)
        return cv.bitwise_and(d1, d2)

    def motionDetected(self, new_frame):
        frame = self.preprocessInputFrame(new_frame)

        gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
        gray = cv.GaussianBlur(gray, (11, 11), 0)

        if self.prevPrevFrame is None:
            self.prevPrevFrame = gray
            return False

        if self.prevFrame is None:
            self.prevFrame = gray
            return False

        cv.normalize(gray, gray, 0, 255, cv.NORM_MINMAX)

        frameDiff = self.diffImg(self.prevPrevFrame, self.prevFrame, gray)
        ret1, th1 = cv.threshold(frameDiff, 10, 255, cv.THRESH_BINARY)

        cv.dilate(th1, None, iterations=15)
        cv.erode(th1, None, iterations=1)

        delta_count = cv.countNonZero(th1)

        cv.imshow("frame_th1", th1)

        self.prevPrevFrame = self.prevFrame
        self.prevFrame = gray

        ret = delta_count > self.threshold

        if ret:
            self.updateMotionDetectionDts()

        return ret


class MotionDetector(MotionDetectorBase):
    def __init__(self):
        MotionDetectorBase.__init__(self)

        self.threshold = 1500
        self.prevPrevFrame = None

    def diffImg(self, t0, t1, t2):
        if not self.multiFrameDetection:
            return cv.absdiff(t2, t1)

        d1 = cv.absdiff(t2, t1)
        d2 = cv.absdiff(t1, t2)
        return cv.bitwise_and(d1, d2)

    def motionDetected(self, new_frame):
        frame = self.preprocessInputFrame(new_frame)

        gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
        gray = cv.GaussianBlur(gray, (11, 11), 0)

        if (self.multiFrameDetection) and (self.prevPrevFrame is None):
            self.prevPrevFrame = gray
            return False

        if self.prevFrame is None:
            self.prevFrame = gray
            return False

        cv.normalize(gray, gray, 0, 255, cv.NORM_MINMAX)

        frameDiff = self.diffImg(self.prevPrevFrame, self.prevFrame, gray)
        ret1, th1 = cv.threshold(frameDiff, 10, 255, cv.THRESH_BINARY)

        th1 = cv.dilate(th1, None, iterations=8)
        th1 = cv.erode(th1, None, iterations=4)

        delta_count = cv.countNonZero(th1)

        if self.multiFrameDetection:
            self.prevPrevFrame = self.prevFrame

        self.prevFrame = gray
        if delta_count < self.threshold:
            return False

        if self.multiFrameDetection:
            self.prevPrevFrame = self.prevFrame

        self.prevFrame = gray
        self.updateMotionDetectionDts()
        return True


class MotionDetectorV3Traced(MotionDetectorBase):
    def __init__(self):
        MotionDetectorBase.__init__(self)

        self.threshold = 1500
        self.prevPrevFrame = None

        self.produceContoursFrame = False
        self.contoursFrame = None

        self.productDiffFrame1 = False
        self.diffFrame1 = None

        self.productDiffFrame2 = False
        self.diffFrame2 = None

    def diffImg(self, t0, t1, t2):
        if not self.multiFrameDetection:
            return cv.absdiff(t2, t1)

        d1 = cv.absdiff(t2, t1)
        d2 = cv.absdiff(t1, t2)
        return cv.bitwise_and(d1, d2)

    def motionDetected(self, new_frame):
        frame = self.preprocessInputFrame(new_frame)

        gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
        gray = cv.GaussianBlur(gray, (11, 11), 0)

        if (self.multiFrameDetection) and (self.prevPrevFrame is None):
            self.prevPrevFrame = gray
            return False

        if self.prevFrame is None:
            self.prevFrame = gray
            return False

        cv.normalize(gray, gray, 0, 255, cv.NORM_MINMAX)

        frameDiff = self.diffImg(self.prevPrevFrame, self.prevFrame, gray)
        ret1, th1 = cv.threshold(frameDiff, 10, 255, cv.THRESH_BINARY)

        if self.productDiffFrame1:
            self.diffFrame1 = th1.copy()

        th1 = cv.dilate(th1, None, iterations=8)
        th1 = cv.erode(th1, None, iterations=4)

        if self.productDiffFrame2:
            self.diffFrame2 = th1.copy()

        delta_count = cv.countNonZero(th1)

        if self.multiFrameDetection:
            self.prevPrevFrame = self.prevFrame

        self.prevFrame = gray
        if delta_count < self.threshold:
            return False

        if self.produceContoursFrame:
            self.contoursFrame = frame.copy()

            im2, contours, hierarchy = cv.findContours(th1, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
            for c in contours:
                cv.drawContours(self.contoursFrame, [c], 0, (0, 0, 255), 2)

            # (x, y, w, h) = cv.boundingRect(c)
            # cv.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

        if self.multiFrameDetection:
            self.prevPrevFrame = self.prevFrame

        self.prevFrame = gray

        self.updateMotionDetectionDts()
        return True


class MotionDetectorV4(MotionDetectorBase):
    def __init__(self):
        MotionDetectorBase.__init__(self)

        self.threshold = 10
        self.prevPrevFrame = None

    def diffImg(self, t0, t1, t2):
        d1 = cv.absdiff(t2, t1)
        d2 = cv.absdiff(t1, t0)
        return cv.bitwise_and(d1, d2)

    def motionDetected(self, new_frame):
        frame = self.preprocessInputFrame(new_frame)

        gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
        gray = cv.GaussianBlur(gray, (11, 11), 0)

        if self.prevPrevFrame is None:
            self.prevPrevFrame = gray
            return False

        if self.prevFrame is None:
            self.prevFrame = gray
            return False

        cv.normalize(gray, gray, 0, 255, cv.NORM_MINMAX)

        frameDiff = self.diffImg(self.prevPrevFrame, self.prevFrame, gray)
        ret1, th1 = cv.threshold(frameDiff, 10, 255, cv.THRESH_BINARY)

        cv.dilate(th1, None, iterations=4)
        cv.erode(th1, None, iterations=2)

        totalArea = 0
        im2, contours, hierarchy = cv.findContours(th1, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
        for c in contours:
            totalArea += cv.contourArea(c)
            cv.drawContours(frame, [c], 0, (0, 0, 255), 2)

        if totalArea < self.threshold:
            return False

        cv.imshow("frame_th1", frame)

        self.prevPrevFrame = self.prevFrame
        self.prevFrame = gray
        self.updateMotionDetectionDts()

        return True