import logging
from math import floor

import cv2
import numpy as np

from scipy.ndimage import center_of_mass
from skimage.transform import rescale
from skimage.util import img_as_float32
from image_registration import chi2_shift

log = logging.getLogger(__name__)

def _template_match(image, template, metric):
    template = img_as_float32(template)
    image = img_as_float32(image)
    
    # If image is WxH and templ is wxh  , then result is (W-w)+1, (H-h)+1 .
    w, h = template.shape[::-1]
    corrmap = cv2.matchTemplate(image, template, method=metric)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(corrmap)
    if metric in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
        top_left = min_loc
        max_corr = min_val
    else:
        top_left = max_loc
        max_corr = max_val
    # This is the location in the image where the match has occurred.
    # I need the shift in the template needed to get into alignment (so the inverse!)
    # Need to take the initial location in the image and diff the updated location.
    matched_x = top_left[0] + w//2
    matched_y = top_left[1] + h//2
    assert max_corr == np.max(corrmap)
    return matched_x, matched_y, max_corr, corrmap

def pattern_match_chi2(image, template, usfac=16):
    assert image.shape == template.shape

    # Swapped so that we get the adjustment to the image to match the template
    # like the other matchers.
    dx, dy, err_x, err_y = chi2_shift(image, template, return_error=True, upsample_factor=usfac, boundary='constant')
    shape_y, shape_x = np.array(template.shape)//2
    err =  (err_x + err_y) / 2.
    return shape_x-dx, shape_y-dy, err, None


def pattern_match_autoreg(image, template, subpixel_size=5, metric=cv2.TM_CCOEFF_NORMED, upsampling=16):
    """
    Call an arbitrary pattern matcher using a subpixel approach where a center of gravity using
    the correlation coefficients are used for subpixel alignment.
    
    Parameters
    ----------
    template : ndarray
               The input search template used to 'query' the destination
               image
    image : ndarray
            The image or sub-image to be searched
    subpixel_size : int
                    An odd integer that defines the window size used to compute
                    the moments
    max_scaler : float
                 The percentage offset to apply to the delta between the maximum
                 correlation and the maximum edge correlation.
    metric : object
             The function to be used to perform the template based matching
             Options: {cv2.TM_CCORR_NORMED, cv2.TM_CCOEFF_NORMED, cv2.TM_SQDIFF_NORMED}
             In testing the first two options perform significantly better with Apollo data.

    Returns
    -------
    x : float
        The x offset
    y : float
        The y offset
    max_corr : float
               The strength of the correlation in the range [-1, 1].
    """

    x, y, max_corr, corrmap = _template_match(image, template, metric)
   
    maxy, maxx = np.unravel_index(corrmap.argmax(), corrmap.shape)
    area = corrmap[maxy - subpixel_size:maxy + subpixel_size + 1,
                   maxx - subpixel_size:maxx + subpixel_size + 1]

    # # If the area is not square and large enough, this method should fail
    # if area.shape != (subpixel_size+2, subpixel_size+2):
    #     raise Exception("Max correlation is too close to the boundary.")
    #     # return None, None, 0, None

    cmass = center_of_mass(area)

    subpixel_y_shift = subpixel_size - 1 - cmass[0]
    subpixel_x_shift = subpixel_size - 1 - cmass[1]
    
    # Apply the subpixel shift to the whole pixel shifts computed above
    y += subpixel_y_shift
    x += subpixel_x_shift
    return x, y, max_corr, corrmap

def pattern_match(image, template, upsampling=8, metric=cv2.TM_CCOEFF_NORMED):
    """
    Call an arbitrary pattern matcher using a subpixel approach where the template and image
    are upsampled using a third order polynomial.

    This function assumes that the image center (0,0) and template center (0,0) are the a priori
    coordinates. This function returns the offset to the center of the template such that the 
    template is brought into alignment with the image.

    For example, if the image to be searched appears as follow:

    0, 0, 0, 0, 0, 0, 0, 1, 0
    0, 0, 0, 0, 0, 0, 0, 1, 0
    1, 1, 1, 0, 0, 0, 0, 1, 0
    0, 1, 0, 0, 0, 0, 0, 0, 0
    0, 1, 0, 0, 0, 0, 0, 0, 0
    0, 0, 0, 0, 0, 0, 0, 0, 0
    0, 0, 0, 0, 0, 0, 0, 0, 0
    0, 0, 0, 0, 0, 0, 0, 0, 0
    0, 0, 0, 0, 0, 0, 1, 1, 1
    0, 1, 1, 1, 0, 0, 1, 0, 1
    0, 1, 0, 1, 0, 0, 1, 0, 1
    0, 1, 1, 1, 0, 0, 1, 0, 1
    0, 0, 0, 0, 0, 0, 1, 1, 1

    and the template image is: 

    1, 1, 1
    0, 1, 0
    0, 1, 0 

    the expected shift from the center of the search image to have template image align is -3 in
    the y-direction and -3 in the x-direction. Conversely, if the search image is:

    1, 1, 1
    1, 0, 1
    1, 0, 1
    1, 0, 1
    1, 1, 1         

    the expected shift is 4 in the y-direction and 3 in the x-direction.

    Parameters
    ----------
    template : ndarray
               The input search template used to 'query' the destination
               image
    image : ndarray
            The image or sub-image to be searched
    upsampling : int
                 The multiplier to upsample the template and image.
    func : object
           The function to be used to perform the template based matching
           Options: {cv2.TM_CCORR_NORMED, cv2.TM_CCOEFF_NORMED, cv2.TM_SQDIFF_NORMED}
           In testing the first two options perform significantly better with Apollo data.

    Returns
    -------
    x : float
        The x offset of the template center the image center, the inverse of this shift will 
        need to be applied to the template as a correction.
    y : float
        The y offset of the template center to the image center, the inverse of this shift
        will need to be applied to the template as a correction.
    max_corr : float
               The strength of the correlation in the range [-1, 1].
    result : ndarray
             (m,n) correlation matrix showing the correlation for all tested coordinates. The
             maximum correlation is reported where the upper left hand corner of the template
             maximally correlates with the image.
    """
    if upsampling < 1:
        raise ValueError

    # Fit a 3rd order polynomial to upsample the images
    if upsampling != 1:
        u_template = rescale(template, upsampling, order=3, mode='edge')
        u_image = rescale(image, upsampling, order=3, mode='edge')
    else:
        u_template = template
        u_image = image
 
    # new_x, new_y is the updated center location in the image
    new_x, new_y, max_corr, corrmap = _template_match(u_image, u_template, metric)
    new_x /= upsampling
    new_y /= upsampling

    return new_x, new_y, max_corr, corrmap
