"""

Image module for geopyv.

"""
import logging
import cv2
import numpy as np
import geopyv as gp
import os
from geopyv.object import Object

log = logging.getLogger(__name__)


class Image(Object):
    def __init__(self, filepath=None, border=20):
        super().__init__(object_type="Image")
        """

        Image class for geopyv Image object.

        Parameters
        ----------
        filepath : str
            Path to image.
        border : int
            Bi-quintic B-spline interpolation border. Defaults to a value of 20.


        Attributes
        ----------
        image_gs : `numpy.ndarray` (Nx, Ny)
            2D array containing grayscale image data of type `int`, generated by OpenCV
            function :py:func:`~cv2.cvtColor`.
        QCQT : `numpy.ndarray` (6*Nx, 6*Ny)
            2D array containing precomputed bi-quintic B-spline interpolation matrices
            of type `float`, computed by :meth:`~_get_QCQT`.

        """
        log.debug("Initialising geopyv Image object at {}.".format(filepath))
        if filepath is None:
            directory = os.getcwd()
            dialog = gp.gui.selectors.image.ImageSelector()
            filepath = dialog.get_path(directory, "Select image")
        elif os.path.exists(filepath):
            self.filepath = filepath
            self.border = np.int_(border)
            self._get_image_gs()
            self._get_QCQT()
            log.debug("Initialised geopyv Image object at {}.".format(filepath))
        else:
            log.error("Image does not exist at the path supplied:\n{}".format(filepath))
            raise FileExistsError(
                "Image does not exist at the path supplied:\n{}".format(filepath)
            )

    def _get_image_gs(self):
        r"""

        Private method to convert colour image to grayscale image for image processing
        purposes using OpenCV functions `imread` and `cvtColor`, and apply Gaussian
        prefilter with 5x5 kernel after Pan et al. (2013), assuming
        :math:`\sigma_{x} = \sigma_{y} = 0.3 \cdot (0.5 \cdot (k-1)-1) + 0.8 = 1.1`.

        """
        image = cv2.imread(self.filepath, cv2.IMREAD_COLOR)
        self.image_gs = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        self.image_gs = cv2.GaussianBlur(
            self.image_gs, ksize=(5, 5), sigmaX=1.1, sigmaY=1.1
        )

    def _get_C(self):
        r"""

        Private method to calculate the bi-quintic B-spline coefficient matrix
        :math:`\mathbf{C}` for image intensity interpolation.

        """
        # Pad the grayscale image.
        image_gs_pad = cv2.copyMakeBorder(
            self.image_gs,
            self.border,
            self.border,
            self.border,
            self.border,
            cv2.BORDER_REPLICATE,
        )

        # Define the quintic B-spline kernel.
        k = np.array(
            [1 / 120, 13 / 60, 11 / 20, 13 / 60, 1 / 120, 0],
            order="F",
        )

        # Create initial C array.
        C = np.zeros(
            (np.shape(image_gs_pad)[0], np.shape(image_gs_pad)[1]),
            dtype=complex,
            order="F",
        )

        # Work through rows first.
        # Setup kernel vector.
        kernel_x = np.zeros((np.shape(image_gs_pad)[1]), dtype=complex)
        kernel_x[0:3] = k[3:]
        kernel_x[-3:] = k[0:3]
        kernel_x = np.fft.fft(kernel_x)

        # FFT across rows.
        for i in np.arange(0, np.shape(image_gs_pad)[0], 1):
            C[i, :] = np.fft.ifft(np.fft.fft(image_gs_pad[i, :]) / kernel_x)

        # Now work through columns.
        # Setup kernel vector.
        kernel_y = np.zeros((np.shape(image_gs_pad)[0]), dtype=complex)
        kernel_y[0:3] = k[3:]
        kernel_y[-3:] = k[0:3]
        kernel_y = np.fft.fft(kernel_y)

        # FFT along columns.
        for j in np.arange(0, np.shape(image_gs_pad)[1], 1):
            C[:, j] = np.fft.ifft(np.fft.fft(C[:, j]) / kernel_y)

        # Discard the imaginary part.
        C = np.real(C)

        return C

    def _get_QCQT(self):
        r"""

        Private method to precompute the :math:`\mathbf{Q} \cdot
        \mathbf{C}_\left(i-2:i+3, j-2:j+3\right) \cdot \mathbf{Q^T}`
        matrix for the image for use in the bi-quintic B-spline image
        intensity interpolation process.

        """
        # Create QK array.
        Q = np.asarray(
            (
                [1 / 120, 13 / 60, 11 / 20, 13 / 60, 1 / 120, 0],
                [-1 / 24, -5 / 12, 0, 5 / 12, 1 / 24, 0],
                [1 / 12, 1 / 6, -1 / 2, 1 / 6, 1 / 12, 0],
                [-1 / 12, 1 / 6, 0, -1 / 6, 1 / 12, 0],
                [1 / 24, -1 / 6, 1 / 4, -1 / 6, 1 / 24, 0],
                [-1 / 120, 1 / 24, -1 / 12, 1 / 12, -1 / 24, 1 / 120],
            ),
            np.float64,
            order="F",
        )

        # Create transpose of Q array.
        QT = np.ascontiguousarray(np.transpose(Q), np.float64)

        # Call C++ extension to perform computations.
        C = self._get_C()
        self.QCQT = gp._image_extensions._QCQT(
            Q, QT, C, self.image_gs.shape, self.border
        )
