# -*- coding: utf-8 -*-

# TODO: Replace slice by spinalcordtoolbox.image.Slicer

import abc
import logging
import math

import numpy as np
from scipy import ndimage
from nibabel.nifti1 import Nifti1Image

from spinalcordtoolbox.image import Image, split_img_data
from spinalcordtoolbox.resampling import resample_nib
from spinalcordtoolbox.centerline.core import ParamCenterline, get_centerline

logger = logging.getLogger(__name__)


class Slice(object):
    """Abstract class representing slicing applied to >=1 volumes for the purpose
    of generating ROI slices.

    Typically, the first volumes are images, while the last volume is a segmentation, which is used as overlay on the
    image, and/or to retrieve the center of mass to center the image on each QC mosaic square.

    For convenience, the volumes are all brought in the SAL reference frame.

    Functions with the suffix `_slice` gets a slice cut in the desired axis at the
    "i" position of the data of the 3D image. While the functions with the suffix
    `_dim` gets the size of the desired dimension of the 3D image.

    IMPORTANT: Convention for orientation is "SAL"

    """

    __metaclass__ = abc.ABCMeta

    def __init__(self, images, p_resample=0.6):
        """
        :param images: list of 3D or 4D volumes to be separated into slices.
        """
        logger.info('Resample images to {}x{} mm'.format(p_resample, p_resample))
        self._images = list()  # 3d volumes
        self._4d_images = list()  # 4d volumes
        self._image_seg = None  # for cropping
        self._absolute_paths = list()  # Used because change_orientation removes the field absolute_path
        image_ref = None  # first pass: we don't have a reference image to resample to
        for i, image in enumerate(images):
            img = image.copy()
            self._absolute_paths.append(img.absolutepath)  # change_orientation removes the field absolute_path
            img.change_orientation('SAL')
            if p_resample:
                if i == len(images) - 1 and img.dim[3] == 1:
                    # Last volume, if it is 3d, corresponds to a segmentation, therefore use linear interpolation here
                    type_img = 'seg'
                else:
                    # Otherwise it's an image: use spline interpolation
                    type_img = 'im'
                img_r = self._resample_slicewise(img, p_resample, type_img=type_img, image_ref=image_ref)
            else:
                img_r = img.copy()
            if img_r.dim[3] == 1:   # If image is 3D, nt = 1
                self._images.append(img_r)
                image_ref = self._images[0]  # 2nd and next passes: we resample any image to the space of the first one
            else:
                self._4d_images.append(img_r)
                # image_ref = self._4d_images[0]  # img_dest is not covered for 4D volumes in resample_nib()

    @staticmethod
    def axial_slice(data, i):
        return data[i, :, :]

    @staticmethod
    def axial_dim(image):
        nx, ny, nz, nt, px, py, pz, pt = image.dim
        return nx

    @staticmethod
    def axial_aspect(image):
        nx, ny, nz, nt, px, py, pz, pt = image.dim
        return py / pz

    @staticmethod
    def sagittal_slice(data, i):
        return data[:, :, int(i)]

    @staticmethod
    def sagittal_dim(image):
        nx, ny, nz, nt, px, py, pz, pt = image.dim
        return nz

    @staticmethod
    def sagittal_aspect(image):
        nx, ny, nz, nt, px, py, pz, pt = image.dim
        return px / py

    @staticmethod
    def coronal_slice(data, i):
        return data[:, i, :]

    @staticmethod
    def coronal_dim(image):
        nx, ny, nz, nt, px, py, pz, pt = image.dim
        return ny

    @staticmethod
    def coronal_aspect(image):
        nx, ny, nz, nt, px, py, pz, pt = image.dim
        return px / pz

    @abc.abstractmethod
    def get_aspect(self, image):
        return

    @staticmethod
    def crop(matrix, x, y, width, height):
        """Crops the matrix to width and height from the center

        Select the size of the matrix if the calculated crop `width` or `height`
        are larger than the size of the matrix.

        TODO : Move this into the Axial class

        :param matrix: Array representation of the image
        :param x: The center of the crop area in the x axis
        :param y: The center of the crop area in the y axis
        :param width: The width from the center
        :param height: The height from the center
        :returns: cropped matrix
        """
        if width * 2 > matrix.shape[0]:
            width = matrix.shape[0] // 2
        if height * 2 > matrix.shape[1]:
            height = matrix.shape[1] // 2

        if x < width:
            x = width
        if y < height:
            y = height

        start_row = x - width
        end_row = start_row + width * 2

        start_col = y - height
        end_col = start_col + height * 2

        return matrix[start_row:end_row, start_col:end_col]

    @staticmethod
    def add_slice(matrix, i, column, size, patch):
        """Adds a slice to the canvas containing all the slices

        TODO : Move this to the Axial class

        :param matrix: input/output "big canvas"
        :param i: slice position
        :param column: number of columns in mosaic
        :param size: