import os
import numpy as np
import cv2
#import matplotlib.pyplot as plt
from imutils import contours
import csv
import time
from datetime import datetime
from datetime import timedelta
from pathlib import Path



DIGITS_LOOKUP = {
    (1, 1, 1, 0, 0, 0, 1, 1, 1): 0,
    (1, 0, 1, 1, 1, 1, 1, 0, 1): 1,
    (1, 1, 1, 1, 1, 1, 1, 0, 1): 1,
    (1, 0, 1, 1, 1, 1, 1, 1, 1): 1,
    (1, 1, 1, 1, 1, 1, 1, 1, 1): 1,
    (1, 0, 1, 0, 1, 0, 1, 0, 1): 2,
    (1, 0, 1, 0, 1, 0, 0, 1, 1): 3,
    (0, 1, 1, 0, 1, 0, 0, 1, 0): 4,
    (1, 1, 0, 0, 1, 0, 0, 1, 1): 5,
    (1, 1, 0, 0, 1, 0, 1, 1, 1): 6,
    (1, 0, 1, 0, 0, 0, 0, 1, 0): 7,
    (1, 1, 1, 0, 1, 0, 1, 1, 1): 8,
    (1, 1, 1, 0, 1, 0, 0, 1, 1): 9,
}

lastWriteGas = datetime.now()
lastWriteElectricity = datetime.now()
lastWriteWater = datetime.now()

csvPath = "..//..//..//var//www//html//"
imagePath = "..//..//..//var//www//html//ocr//"

fileNameSamples = os.path.join(os.path.dirname(__file__), 'glyphSamples.data')
fileNameResponses = os.path.join(os.path.dirname(__file__), 'glyphResponses.data')

samples = np.loadtxt(fileNameSamples,np.float32)
responses = np.loadtxt(fileNameResponses,np.float32)
responses = responses.reshape((responses.size,1))

model = cv2.ml.KNearest_create()
model.train(samples,cv2.ml.ROW_SAMPLE,responses)

def writeToCSV(path, device, stringWrite):
    fileCSV = os.path.join(os.path.dirname(__file__), path)
    filePath = Path(fileCSV)
    if filePath.is_file():
        with open(fileCSV, 'a', newline='') as f:
            writer = csv.writer(f, delimiter=',')
            t = datetime.now().isoformat(sep=" ", timespec="seconds")
            writer.writerows([(t, device, stringWrite)])
    else:
        with open(fileCSV, 'w', newline='') as f:
            writer = csv.writer(f, delimiter=',')
            t = datetime.now().isoformat(sep=" ", timespec="seconds")
            writer.writerows([(t, device, stringWrite)])


def getDisplay(image, boxSize, C, offset, limit, highlight, mode):
    """offset - upravovatelna hodnota - procentualni oriznuti hranic horni, dolni, leva, prava"""
    thresh = cv2.adaptiveThreshold(image, 255,cv2.ADAPTIVE_THRESH_MEAN_C, mode, boxSize, C)# prevedeni na binarni obraz
    cnts,_ = cv2.findContours(thresh,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) #nalezeni kontur
    cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
    for cnt in cnts:
        (_x, _y, w, h) = cv2.boundingRect(cnt)
        if cv2.contourArea(cnt) < limit:
            cv2.drawContours(thresh, [cnt], 0, (0,0,0), -10) #odstraneni kontur s velikosti mensi jak 100
        elif cv2.contourArea(cnt) > limit and highlight:
            cv2.drawContours(thresh, [cnt], 0, (255,255,255), 10)# zvyrayneni zbyvajicich
    cnts,_ = cv2.findContours(thresh,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    for cnt in cnts:
        (x, y, w, h) = cv2.boundingRect(cnt)
        if w<h:
            cv2.drawContours(thresh, [cnt], 0, (0,0,0), -10) #smazani objektu jejichz sirka je mensi jak delka a tedy tvarove neodpovidaji displeji meraku
    cnts,_ = cv2.findContours(thresh,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    cnts = sorted(cnts, key=cv2.contourArea, reverse=True)#srovnani zbylych objektu od nejvetsiho po nejmensi. Nejvetsi zbyly objekt je hledany displej

    cntSort = cnts[0]
    maxX = 0
    minY, minX = thresh.shape[:2]
    maxY = 0
    length = cntSort.size/2
    length = int(length)

    for each in range(length):
        if cntSort[each, 0][0] > maxX:
            maxX = cntSort[each, 0][0]
        if cntSort[each, 0][0] < minX:
            minX = cntSort[each, 0][0]
        if cntSort[each, 0][1] > maxY:
            maxY = cntSort[each, 0][1]
        if cntSort[each, 0][1] < minY:
            minY = cntSort[each, 0][1]

    #dalsi uprava hranicnich bodu vyriznuteho displeje
    cropMinY = minY+((maxY-minY)*offset[0])#0.1
    cropMinY = cropMinY.astype(int)
    cropMaxY = maxY - ((maxY-minY)*offset[1])#0.2
    cropMaxY = cropMaxY.astype(int)

    cropMinX = minX +((maxX-minX)*offset[2])#0
    cropMinX = cropMinX.astype(int)
    cropMaxX = maxX - ((maxX-minX)*offset[3])#0.2
    cropMaxX = cropMaxX.astype(int)

    return cropMaxX, cropMinX , cropMaxY, cropMinY

def getDigitObjects(image, boxSize, C, limitValues, mode):
    """limit values= stanoveni veelikosti nejmensich kontru branych v potaz, pomer stran kontury aby byla ignorovana(w>procenta vysky), obtahovani kontur ano, ne"""
    threshGray = cv2.adaptiveThreshold(image, 255,cv2.ADAPTIVE_THRESH_MEAN_C, mode, boxSize, C)# 69,14
  

    cnts,_ = cv2.findContours(threshGray,cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    for cnt in cnts:
        (_x, _y, w, h) = cv2.boundingRect(cnt)
        if cv2.contourArea(cnt) < limitValues[0] or w>limitValues[1]*h:
            cv2.drawContours(threshGray, [cnt], 0, (0,0,0), -10)
        elif cv2.contourArea(cnt) > limitValues[0] and limitValues[2] == 1:
            cv2.drawContours(threshGray, [cnt], 0, (255,255,255), 8)
    cnts,_ = cv2.findContours(threshGray,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
   
    return cnts, threshGray


def getCleanDigits(cnts, displayH, displayW, limits):
    """limits = minimalni sirka kontury, maximalni sirka v procentech celkove sirky obrazy, maximalni a minimalni vyska v procentech clkove vysky obrazu"""
    digitCnts = []
    numbers=''

    for cnt in cnts:
        (_x, _y, w, h) = cv2.boundingRect(cnt)#ziskani hranicnich rozmeru kontury     
        if (w > limits[0] and w <= displayW*limits[1]) and (h >= displayH*limits[2] and h<= displayH*limits[3]):#cokoliv splnujici tyto rozmery je s velkou pravdepodobnosti cislo    
            digitCnts.append(cnt)
  
    try:
        digitCnts, _box = contours.sort_contours(digitCnts, method="left-to-right")#srovnani nalezenych objektu tak podle jejich pozice
    except ValueError:
        numbers = 'No read error'
    return digitCnts, numbers

def knnRecognition(digitCnts, img): 
    lastPosX = 0
    lastWidth = 0
    stringResult = ''
    for cnt in digitCnts:
        (x, y, w, h) = cv2.boundingRect(cnt)

        if x > (lastPosX+1.5*lastWidth): #pokud je znak prilis blizko tomu predchozimu, tak jde pravdepodobne jen o nejaký odlesk a lze to ignorovat
            lastPosX = x
            lastWidth = w

            glyph = img[y:y+h,x:x+w]
         
            glyphSmall = cv2.resize(glyph,(10,10))
            glyphSmall = glyphSmall.reshape((1,100))
            glyphSmall = np.float32(glyphSmall)
            _retval, results, _neigh_resp, _dists = model.findNearest(glyphSmall, k = 1)
            stringResult = stringResult + str(int((results[0][0])))
    return stringResult

def recognizeGasDigits(file_path, boxSize, C):
    #file_name = os.path.join(os.path.dirname(__file__), file_path)
    img = cv2.imread(file_path, cv2.IMREAD_GRAYSCALE)
    noise = cv2.fastNlMeansDenoising(img,None,16.0,7,21) 
    
    #obsahuje nastavitelne limity
    maxX, minX, maxY, minY = getDisplay(noise, boxSize, C, [0.1, 0.25, 0.10, 0.20], 150, False, cv2.THRESH_BINARY)
    
    cropImgGray = noise[minY:maxY,minX:maxX]
    (displayH, displayW) = cropImgGray.shape[:2]

    thresh = cv2.adaptiveThreshold(cropImgGray, 255,cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, boxSize, C)
    cnts,_ = cv2.findContours(thresh,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

    areaMax = 0
    for cnt in cnts:
        (x, y, w, h) = cv2.boundingRect(cnt)#ziskani hranicnich rozmeru kontury
        
        if w > displayW*0.50 and (h >= displayH*0.05 and h<= displayH*0.90) and cv2.contourArea(cnt) > areaMax:#cokoliv splnujici tyto rozmery je s velkou pravdepodobnosti cislo    
            areaMax = cv2.contourArea(cnt)
            minX = x
            minY = y
            maxX = x + w
            maxY = y + h
    #obsahuje nastavitelne limity
    cnts, digitImg = getDigitObjects(cropImgGray[minY:maxY,minX:maxX], boxSize, C, [150, 1.5, 0], cv2.THRESH_BINARY)
    numbers = ''
    try:
        cnts, _box = contours.sort_contours(cnts, method="left-to-right")#srovnani nalezenych objektu tak podle jejich pozice
    except ValueError:
        numbers = 'No read error'

    (x, y, w, h) = cv2.boundingRect(cnts[4])
    if w > 20:
        contour = [np.array([[x+w-10,y],[x+w-10,y+h],[x+w+10,y+h],[x+w+10, y]], dtype=np.int32)]
        cv2.drawContours(digitImg,[contour[0]],0,(0,0,0),-2)
    #obsahuje nastavitelne limity
    digitCnts, numbers = getCleanDigits(cnts, displayH, displayW, [10, 0.3, 0.3, 0.95])

    if numbers == '':
        numbers = knnRecognition(digitCnts, digitImg)
    print(numbers)
    return numbers    

def recognizeWaterDigits(file_path, boxSize, C): #water.jpg 15, 7
    """identifying digits in picture of water meter"""
    #file_name = os.path.join(os.path.dirname(__file__), file_path)
    img = cv2.imread(file_path, cv2.IMREAD_GRAYSCALE)
    noise = cv2.fastNlMeansDenoising(img,None,16.0,7,21) 
    #obsahuje nastavitelne limity
    maxX, minX, maxY, minY = getDisplay(noise, boxSize, C, [0.15, 0.35, 0.15, 0.35], 150, True, cv2.THRESH_BINARY_INV)

    cropImgGray = noise[minY:maxY,minX:maxX]
    (displayH, displayW) = cropImgGray.shape[:2]
    #obsahuje nastavitelne limity
    cnts, digitImg = getDigitObjects(cropImgGray, boxSize, C, [120, 1.5, 0], cv2.THRESH_BINARY_INV)
    #obsahuje nastavitelne limity
    digitCnts, numbers = getCleanDigits(cnts, displayH, displayW, [10, 0.3, 0.10, 0.60])
    
    if numbers == '':
        numbers = knnRecognition(digitCnts, digitImg)
    print(numbers)
    return numbers



def recognizeElectricityDigits(file_path, boxSize, C): #electricity.jpg, 64 , 14
    """identifying digits in picture of electricity meter"""
    #file_name = os.path.join(os.path.dirname(__file__), file_path)
    img = cv2.imread(file_path, cv2.IMREAD_GRAYSCALE)
    #obsahuje nastavitelne limity
    maxX, minX, maxY, minY = getDisplay(img, boxSize, C*-1, [0.1, 0.2, 0, 0], 150, True, cv2.THRESH_BINARY)

    cropImgGray = img[minY:maxY,minX:maxX]
    (displayH, displayW) = cropImgGray.shape[:2]

    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(15,15))
    cl1 = clahe.apply(cropImgGray)
    #obsahuje nastavitelne limity
    cnts, digitImg = getDigitObjects(cl1, boxSize, C, [400, 1, 1], cv2.THRESH_BINARY_INV)
    #obsahuje nastavitelne limity
    digitCnts, numbers = getCleanDigits(cnts, displayH, displayW, [20, 1, 0.55, 0.95])

    segment = []
    outputRow = []
    if numbers == '':
        for cnt in digitCnts:
            (x, y, w, h) = cv2.boundingRect(cnt)

            single = digitImg[y:y+h,x:x+w] #vyriznuti jednoho samotneho cisla z ciselniku

            segmentPos = []#vytvareni pole s identifikovanymi polohami segmentu pro dane cislo
            segmentPos.append([0, h*0.16, w*0.33, w*0.66, 0])#horni segment
            segmentPos.append([h*0.15, h*0.40, 0, w*0.33, 0])#levy horni segment
            segmentPos.append([h*0.15, h*0.40, w*0.66, w, 0])#pravy horni segment
            segmentPos.append([h*0.15, h*0.40, w*0.33, w*0.66, 0])#horni prostredni segment
            segmentPos.append([h*0.45, h*0.55, w*0.33, w*0.66, 0])#prostredni segment
            segmentPos.append([h*0.60, h*0.84, w*0.33, w*0.66, 0])#spodni prostredni segment
            segmentPos.append([h*0.60, h*0.84, 0, w*0.33, 0])#levy spodni segment
            segmentPos.append([h*0.60, h*0.84, w*0.66, w, 0])#pravy spodni segment
            segmentPos.append([h*0.84, h, w*0.33, w*0.66, 0])#spodni segment


            segmentPos = np.asarray(segmentPos)
            segmentPos = segmentPos.astype(int)
            segment.append(segmentPos)

            for sPos in segmentPos: #pocitani aktivnich segmentu identifikovaneho cisla
                segSingle = single[sPos[0]:sPos[1], sPos[2]:sPos[3]]
                whiteSegmentArea = cv2.countNonZero(segSingle)
                totalSegmentArea = (sPos[1] - sPos[0]) * (sPos[3] - sPos[2]) #pokud je alespon pulka oblasti, ve ktere se nachazi segment bila, pak je segment aktivni

                if whiteSegmentArea/totalSegmentArea> 0.6:
                    sPos[4] = 1
                    
            matrix = np.asarray(segmentPos)
            matrix = matrix.astype(int)
            try:
                actualDigit = DIGITS_LOOKUP[tuple(matrix[:,-1])] #identifikace cisla podle kombinace aktivnich segmentu
                outputRow.append(actualDigit)
                numbers = numbers + str(actualDigit)
            except KeyError: #pokud kombinace segmentu neodpovidala zadne kombinaci ve vyctu, pak cislo nelze identifikovat
                actualDigit = "X"  
                outputRow.append(actualDigit) 
                numbers = numbers + str(actualDigit)
    print(numbers)
    return numbers

def checkDataAge(timestamp, data):
    if  datetime.now() - timestamp > timedelta(hours = 3):
        print(datetime.now() - timestamp)
        writeToCSV(csvPath + "data.csv", data, "No data received")
        timestamp = datetime.now()
    return timestamp


while True:
    time.sleep(10)
    fileImg= os.path.join(os.path.dirname(__file__), imagePath +"gas.jpg")
    file = Path(fileImg)
    if file.is_file():
        result = recognizeGasDigits(fileImg, 15, -5)
        writeToCSV(csvPath + "data.csv", "gas", result)
        lastWriteGas = datetime.now()
        os.remove(fileImg)

    fileImg= os.path.join(os.path.dirname(__file__), imagePath + "water.jpg")
    file = Path(fileImg)
    if file.is_file():
        result = recognizeWaterDigits(fileImg, 15, 7)
        writeToCSV(csvPath + "data.csv", "water", result)
        lastWriteWater = datetime.now()
        os.remove(fileImg)

    fileImg= os.path.join(os.path.dirname(__file__), imagePath + "electricity.jpg")
    file = Path(fileImg)
    if file.is_file():
        result = recognizeElectricityDigits(fileImg, 69, 14)
        writeToCSV(csvPath + "data.csv", "electricity", result)
        lastWriteElectricity = datetime.now()
        os.remove(fileImg)

    lastWriteElectricity = checkDataAge(lastWriteElectricity, "electricity")
    lastWriteGas = checkDataAge(lastWriteGas, "gas")
    lastWriteWater = checkDataAge(lastWriteWater, "water")
