# Copyright (c) 2016, Jeffrey Maggio and Joseph Bartelmo
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
# associated documentation files (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in all copies or substantial 
# portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import Tkinter as tk
from PIL import Image, ImageTk
import cv2
import sys
import numpy
import time
import matplotlib.path as matpath

class GlobalSurveyor(object):
    def __init__(self, root, ideal_image,  number_polys = 2, thresholds = (50, 200), polys = None):
        self.ideal_im = ideal_image
        self.parent = root
        self.done = False
        self.points = []
        self.warningColor = (128, 128, 0)
        self.errorColor = (255, 0, 0)

        self.number_polys = number_polys
        #self.number_sides = number_sides
        if polys == None:
            self.polys = []
        else:
            self.polys = polys

        self.thresh = thresholds
        self.ideal_counts = []
        self.calibrates_polygons()

    def calibrates_polygons(self):
        polys = self.define_polygons(self.ideal_im)
        ideal_kp = self.apply_fast(self.ideal_im)
        self.ideal_counts = self.keypoints_to_counts(ideal_kp)

    def _on_left_mouse(self, event):   # CITE THIS FROM STACKEXCHANGE
        # Mouse callback that gets called for every mouse event (i.e. moving, clicking, etc.)
        if self.done: # Nothing more to do
            return
        # Left click means adding a point at current position to the list of points
        print("Adding point #%d with position(%d,%d)" % (len(self.points), event.x, event.y))
        self.points.append((event.x, event.y))

    def _on_right_mouse(self, event):   # CITE THIS FROM STACKEXCHANGE
        if len(self.points) > 2:
            # Right click means we're done
            print("Completing polygon with %d points." % len(self.points))
            self.done = True

    def define_polygons(self, image):
        # use this to automate point picking and/or display point picker window in gui

        FINAL_LINE_COLOR = (255, 0, 0)
        WORKING_LINE_COLOR = (0, 0, 255)

        top = tk.Toplevel(self.parent)
        top.title("Select bounding points for the target area")
        print image.shape
        top.update_idletasks()
        width = image.shape[1]
        height = image.shape[0]
        x = (top.winfo_screenwidth() // 2) - (width // 2)
        y = (top.winfo_screenheight() // 2) - (height // 2)
        top.geometry('{}x{}+{}+{}'.format(width, height, x, y))
        
        #we are defaulting to the image size, i don't want to deal wiht resizing
        initial_im = tk.PhotoImage()
        image_label = tk.Label(top, image=initial_im)
        image_label.grid(row = 0, column = 0)
        image_label.bind("<Button-1>", self._on_left_mouse)
        image_label.bind("<Button-3>", self._on_right_mouse)
        top.grid()

        copy = image.copy()
        def display_image():
            imageFromArray = Image.fromarray(copy)
            try:
                tkImage = ImageTk.PhotoImage(image=imageFromArray)
                image_label.configure(image=tkImage)
            
                image_label._image_cache = tkImage  # avoid garbage collection

                top.update()
                return True
            except RuntimeError:
                print('Unable to update image frame. Assuming application has been killed unexpectidly.')
                return False

        for num in range(self.number_polys):
            if display_image() == False:
                top.destroy()
                return

            self.points = []
            self.done = False

            while (not self.done):
                if (len(self.points) > 0):
                    cv2.polylines(copy, numpy.asarray([self.points]), False, FINAL_LINE_COLOR, 1)

                imageFromArray = Image.fromarray(copy)
                if display_image() == False:
                    top.destroy()
                    return
            self.polys.append(self.points)
            print(self.polys)
            update = numpy.asarray(self.polys[num]).astype(numpy.int32)
            
            if (len(self.points) > 0):
                cv2.fillPoly(copy, [update], (0,0,255))
        display_image()

        time.sleep(0.5)
        top.destroy()

        return self.polys

    def apply_fast(self, image):
        fast = cv2.FastFeatureDetector()
        kp = fast.detect(image, None)

        return kp

    def keypoints_to_counts(self, im_keypoints): #polys = numpy.asarray[[[0,0],[0,ideal_im.shape[0]//2],[ideal_im.shape[0]//2,ideal_im.shape[1]],[ideal_im.shape[0]//2, 0]]]):
        #pass in im_keypoints and count how many are in each polygon
        #polygon points should also be an input

        im_keys = []
        for i in range(len(im_keypoints)):
            im_keys.append(im_keypoints[i].pt)

        # NOTE:: POLYS MUST BE IN X (COL), Y (ROW) FORMAT FOR FILLPOLY TO WORK

        # NOTE:: POLYS MUST ALSO BE OF DTYPE FLOAT
        
        data = self.polys
        polys = map(lambda data: map(lambda data: map(float, data), data), data)
        counts = []

        for poly in polys:
            # MATPLOTLIB PATH EXAMPLE GOES HERE
            bbPath = matpath.Path(poly)
            # sum boolean array to get number of kp within current poly
            try:
                truth_vector = bbPath.contains_points(im_keys)   # check input for contains_points. might only like tuples
            except:
                truth_vector = [1] * len(im_keys)
            counts.append(numpy.sum(truth_vector))

        return numpy.asarray(counts)

    def threshold(self, counts, thresh_tuple):
        # do the thresholding thing
        delta = abs(counts - self.ideal_counts)

        threshed_polys = numpy.where((delta >= thresh_tuple[0]) & (delta < thresh_tuple[1]))[0]   # ok to index like this because tuple represents vector
        
        # save which polys get marked true out so these can be used as overlay in fillpolys
        # this can be done by indexing into polys with truth vector saved here

        return threshed_polys.astype(int)   # a true/false vector of length polys

    def generate_output(self, image, threshed_polys, color, alpha = 0.5): #, colorAlert = (0,0,255)):
        # use polyfill to blend im_in and overlay (which is just the polygons we want colored in)
        colored_polys = []

        for threshed_poly in threshed_polys:
            colored_polys.append(numpy.array(self.polys[threshed_poly]).astype(numpy.int32))

        overlay = image.copy()
        im_out = image.copy()

        for cp in range(len(colored_polys)):
            cv2.fillPoly(overlay, [colored_polys[cp]], color)
        
        cv2.addWeighted(overlay, alpha, im_out, 1 - alpha, 0, im_out)

        return im_out

    def run_basic_fod(self, current_im):
        if current_im is None:
            return None
        # load ideal image
        im_in_kp = self.apply_fast(current_im)

        counts = self.keypoints_to_counts(im_keypoints = im_in_kp)

        warning_polys = self.threshold(counts, self.thresh)
        error_polys = self.threshold(counts, (self.thresh[1], sys.maxint))

        warning_overlay = self.generate_output(current_im, warning_polys, self.warningColor)
        return self.generate_output(warning_overlay, error_polys, self.errorColor)

def get_thresholds_widget(parent, values_list):
    top = tk.Toplevel(parent)
    top.title("Select your desired thresholds")
    top.update_idletasks()

    thresh1 = tk.IntVar()
    thresh2 = tk.IntVar()
    polys = tk.IntVar()

    #3 labels and 3 textbox selectors
    label1 = tk.Label(top, text = "Warning (Maybe an object)")
    label1.grid(row = 1, column = 0, sticky ="nsew")
    threshold1 = tk.Entry(top, textvariable=thresh1)
    threshold1.grid(row = 1, column = 1, sticky ="nsew")
    
    label2 = tk.Label(top, text = "Error (Most defintely an object)")
    label2.grid(row = 2, column = 0, sticky ="nsew")
    threshold2 = tk.Entry(top, textvariable = thresh2)
    threshold2.grid(row = 2, column = 1, sticky ="nsew")

    label3 = tk.Label(top, text = "Number of Polygons")
    label3.grid(row = 3, column = 0, sticky ="nsew")
    threshold3 = tk.Entry(top, textvariable = polys)
    threshold3.grid(row = 3, column = 1, sticky ="nsew")

    def returnVars():
        top.destroy()
        if len(values_list) != 3:
            values_list.append(thresh1.get())
            values_list.append(thresh2.get())
            values_list.append(polys.get())
        else:
            values_list[0] = thresh1.get()
            values_list[1] = thresh2.get()
            values_list[2] = polys.get()
    
    button = tk.Button(top, text = "Set Values", command = returnVars)
    button.grid(row = 4, column = 0, columnspan = 2, sticky ="nsew")

    threshold1.delete(0, "end")
    threshold2.delete(0, "end")
    threshold3.delete(0, "end")

    if len(values_list) == 3:
        threshold1.insert(0, values_list[0])
        threshold2.insert(0, values_list[1])
        threshold3.insert(0, values_list[2])
    else:
        threshold1.insert(0, 50)
        threshold2.insert(0, 200)
        threshold3.insert(0, 2)

    width = 300
    height = 100
    x = (top.winfo_screenwidth() // 2) - (width // 2)
    y = (top.winfo_screenheight() // 2) - (height // 2)
    top.geometry('{}x{}+{}+{}'.format(width, height, x, y))

    top.grid_columnconfigure(1, weight=1)
    top.grid_rowconfigure(0, weight=1)
    top.grid_rowconfigure(1, weight=1)
    top.grid()
    top.update()
    parent.wait_window(top)
    
if __name__=='__main__':
    root = tk.Tk()
    vals = []
    get_thresholds_widget(root, vals)
    print 'obtained values', vals
    get_thresholds_widget(root, vals)
    print 'obtained values', vals
    root.mainloop()
