"""
Programmer  :   EOF
File        :   detector.py
E-mail      :   jasonleaster@163.com
Date        :   2016.01.18
"""

from config         import TRAINING_IMG_WIDTH
from config         import TRAINING_IMG_HEIGHT
from config         import HAAR_FEATURE_TYPE_I
from config         import HAAR_FEATURE_TYPE_II
from config         import HAAR_FEATURE_TYPE_III
from config         import HAAR_FEATURE_TYPE_IV
from config         import AB_TH
from config         import SEARCH_WIN_STEP

from image          import Image
from haarFeature    import Feature
from matplotlib     import pyplot
from adaboost       import getCachedAdaBoost
import pylab
import numpy
import math


class Detector:

    def __init__(self):
        pass


    def scanImgAtScale(self, model, image, scale):
        assert isinstance(image, numpy.ndarray)

        ImgHeight, ImgWidth = image.shape

        SEARCH_WIN_WIDTH  = int(TRAINING_IMG_WIDTH  * scale)
        SEARCH_WIN_HEIGHT = int(TRAINING_IMG_HEIGHT * scale)
        width     = ImgWidth - SEARCH_WIN_WIDTH - 10
        height    = ImgHeight - SEARCH_WIN_HEIGHT - 10
        step      = SEARCH_WIN_WIDTH/SEARCH_WIN_STEP
        subWinNum = (width/step + 1) * (height/step + 1)
        #print('subWinNum=', subWinNum) # 1746.3296398891966
        subImages = numpy.zeros(int(subWinNum), dtype = object)
        subWins   = numpy.zeros(int(subWinNum), dtype = object)

        idx = 0
        #print('step=', step)
        #print('subImages=', len(subImages))
        #print('width=', width) # 204
        for x in range(0, int(width), int(step)):
            #print(x)
            for y in range(0, int(height), int(step)):
                #print(y)
                #print(idx)
                subWins[idx]   = (x, y, SEARCH_WIN_WIDTH, SEARCH_WIN_HEIGHT)
                subImages[idx] = Image(Mat = image[y:y+SEARCH_WIN_HEIGHT, x:x+SEARCH_WIN_WIDTH])
                idx += 1
                if idx == int(subWinNum):
                    print('break 1')
                    break
            if idx == int(subWinNum):
                print('break 2')
                break
        assert idx <= subWinNum

        subImgNum = idx

        selFeatures = numpy.zeros(model.N, dtype=object)

        haar_scaled = Feature(SEARCH_WIN_WIDTH,   SEARCH_WIN_HEIGHT)
        haar_train  = Feature(TRAINING_IMG_WIDTH, TRAINING_IMG_HEIGHT)

        for n in range(model.N):
            selFeatures[n] = haar_train.features[ model.G[n].opt_dimension ] + tuple([model.G[n].opt_dimension])

        mat = numpy.zeros((haar_train.featuresNum, subImgNum), dtype=numpy.float16)

        for feature in selFeatures:
            (types, x, y, w, h, dim) = feature

            x = int(x * scale)
            y = int(y * scale)
            w = int(w * scale)
            h = int(h * scale)

            for i in range(subImgNum):
                if   types == HAAR_FEATURE_TYPE_I:
                    mat[dim][i] = haar_scaled.VecFeatureTypeI(subImages[i].vecImg, x, y, w, h)
                elif types == HAAR_FEATURE_TYPE_II:
                    mat[dim][i] = haar_scaled.VecFeatureTypeII(subImages[i].vecImg, x, y, w, h)
                elif types == HAAR_FEATURE_TYPE_III:
                    mat[dim][i] = haar_scaled.VecFeatureTypeIII(subImages[i].vecImg, x, y, w, h)
                elif types == HAAR_FEATURE_TYPE_IV:
                    mat[dim][i] = haar_scaled.VecFeatureTypeIV(subImages[i].vecImg, x, y, w, h)

        output = model.grade(mat)

        rectangle = []
        for i in range(len(output)):
            if output[i] > AB_TH:
                candidate = numpy.array(subWins[i])
                x, y, w, h = candidate
                rectangle.append((x, y, w, h, output[i]))

        return rectangle


    def scanImgOverScale(self, image):

        from config import DETECT_START
        from config import DETECT_END
        from config import DETECT_STEP
        from config import ADABOOST_CACHE_FILE
        from config import ADABOOST_LIMIT

        #print(ADABOOST_CACHE_FILE)
        model = getCachedAdaBoost(filename = ADABOOST_CACHE_FILE + str(0), limit = ADABOOST_LIMIT)

        rectangles = []

        for scale in numpy.arange(DETECT_START , DETECT_END, DETECT_STEP):
            rectangles += self.scanImgAtScale(model, image, scale)

        return self.optimalRectangle(rectangles)


    def optimalRectangle(self, rectangles):

        # number of rectangles
        numRec = len(rectangles)

        for i in range(numRec):
            for j in range(i+1, numRec):
                if rectangles[i][4] < rectangles[j][4]:
                    rectangles[i], rectangles[j] = \
                    tuple(rectangles[j]), tuple(rectangles[i])

        """
        (x1, y1, w1, h1) represent as the first  rectangle.
        (x2, y2, w2, h2) represent as the second rectangle.
        |-------> x
        | _______
        ||  1  __|____
        ||____|__|    |
        |     |____2__|
        \/ y
        """
        reduced = [i for i in range(numRec)]
        for i in range(numRec):
            x1, y1, w1, h1, score1 = rectangles[i]
            area_1 = w1 * h1
            for j in range(i+1, numRec):
                x2, y2, w2, h2, score2 = rectangles[j]
                area_2 = h2 * w2

                if( self.pointInRectangle((x2,      y2     ), rectangles[i]) or
                    self.pointInRectangle((x2 + w2, y2     ), rectangles[i]) or
                    self.pointInRectangle((x2,      y2 + h2), rectangles[i]) or
                    self.pointInRectangle((x2 + w2, y2 + h2), rectangles[i]) or
                    self.pointInRectangle((x2 + w2/2,y2    ), rectangles[i]) or
                    self.pointInRectangle((x2      ,y2+h2/2), rectangles[i]) or
                    self.pointInRectangle((x2 + w2, y2+h2/2), rectangles[i]) or
                    self.pointInRectangle((x2 + w2/2,y2+h2 ), rectangles[i]) ) is True:

                    reduced[j] = reduced[i]

                if( self.pointInRectangle((x1,      y1     ), rectangles[j]) or
                    self.pointInRectangle((x1 + w1, y1     ), rectangles[j]) or
                    self.pointInRectangle((x1,      y1 + h1), rectangles[j]) or
                    self.pointInRectangle((x1 + w1, y1 + h1), rectangles[j]) or
                    self.pointInRectangle((x1 + w1/2,y1    ), rectangles[j]) or
                    self.pointInRectangle((x1      ,y1+h1/2), rectangles[j]) or
                    self.pointInRectangle((x1 + w1, y1+h1/2), rectangles[j]) or
                    self.pointInRectangle((x1 + w1/2,y1+h1 ), rectangles[j]) ) is True:

                    reduced[j] = reduced[i]

        reducedRectangels = []
        for i in numpy.unique(reduced):
            reducedRectangels.append(rectangles[i])

        return reducedRectangels


    def pointInRectangle(self, point, rectangle):
        m, n = point
        x, y, w, h, _ = rectangle

        if((x < m and m < x + w) and (y < n and n < y +h)):
            return True
        else:
            return False


    def drawRectangle(self, image, x, y, width, height):
        assert isinstance(image, numpy.ndarray)

        if len(image.shape) != 2:
            Row, Col, _ = image.shape
            if x + width >= Col or y + height >= Row:
                return
            image[y:y+height, x-1:x+1, 0] = 0
            image[y:y+height, x-1:x+1, 1] = 255
            image[y:y+height, x-1:x+1, 2] = 0

            image[y:y+height, (x-1 + width):(x+1 + width), 0] = 0
            image[y:y+height, (x-1 + width):(x+1 + width), 1] = 255
            image[y:y+height, (x-1 + width):(x+1 + width), 2] = 0

            image[y-1:y+1, x:x+width, 0] = 0
            image[y-1:y+1, x:x+width, 1] = 255
            image[y-1:y+1, x:x+width, 2] = 0

            image[(y-1+height):(y+height+1), x:x+width, 0] = 0
            image[(y-1+height):(y+height+1), x:x+width, 1] = 255
            image[(y-1+height):(y+height+1), x:x+width, 2] = 0
        else:
            Row, Col = image.shape
            if x + width >= Col or y + height >= Row:
                return

            image[y:y+height, x-1:x+1  ] = 255
            image[y:y+height, x-1 + width:x+1 + width] = 255
            image[y-1:y+1   , x:x+width] = 255
            image[y-1+height:y+1+height, x:x+width] = 255

        pyplot.imshow(image)
        pylab.show()


    def showResult(self, image, rectangles):

        for rectangle in rectangles:
            x, y, width, height, score = rectangle

            print(rectangle)
            self.drawRectangle(image, x, y, width, height)
