import numpy as np
import cv2
import fitz # PyMuPDF is installed
import os
import json
import pathlib

#list_of_img_formats = ['.pdf', '.jpeg', '.jpg', '.bmp', '.png', '.dwg']
list_of_img_formats = ['.pdf', '.jpeg', '.jpg', '.bmp', '.png']

class NumpyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, np.ndarray):
            return obj.tolist()
        return json.JSONEncoder.default(self, obj)

def create_list_of_pdf(path):
    lst = os.listdir(path)
    return [x for x in lst if '.pdf' in x]


def create_list_of_files_in_formats(path):
    lst = os.listdir(path)

    out_list = []
    for file_name in lst:
        for img_format in list_of_img_formats:
            if img_format in file_name:
                out_list.append(file_name)

    return out_list


def create_list_of_img(path):
    lst = os.listdir(path)
    list_of_formats = ['.pdf']

    return [x for x in lst if '.pdf' in x]

def replce_format_from_file_name(file_name):

    for f in list_of_img_formats:
        file_name = file_name.replace(f, '')

    return file_name



def load_image(file_name):
    '''
    read image in .pdf or .png
    :param file_name:
    :return:
    '''
    # from library:
    if '.pdf' in file_name:
        return read_pdf(file_name)
    elif ('.jpeg' in file_name) or ('.jpg' in file_name) or ('.png' in file_name) or ('.bmp' in file_name):
        return cv2.imread(file_name)


def read_pdf(pdf_file_name):
    '''

    :param pdf_file_name:
    :param out_folder_name:
    :return:
    '''
    # pages = convert_from_path(pdf_file, dpi=200)[0]
    # pages.save('out.jpg', 'JPEG')

    doc = fitz.open(pdf_file_name)
    # split pages
    for i, page in enumerate(doc.pages()):
        # print(i)
        zoom = 1
        mat = fitz.Matrix(zoom, zoom)
        pix = page.getPixmap(matrix=mat)
        imgData = pix.getImageData('png')

    # save image from opencv
    nparr = np.frombuffer(imgData, np.uint8)
    img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)

    return img

def image_transform2find_contours(img):
    '''

    :param img:
    :return:
    '''
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    gray_smoothed = cv2.GaussianBlur(gray, (3, 3), 0)

    # contour recognition
    edged = cv2.Canny(gray, 10, 250)

    # create and apply a closure
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))
    closed = cv2.morphologyEx(edged, cv2.MORPH_CLOSE, kernel)

    # inversion to initial state (white background)
    out_img = cv2.bitwise_not(closed)

    return out_img

def save_test_image(out_folder_name, img_file_name, img, prefix):
    '''
    to ctrate a test image
    :param out_folder_name:
    :param img_file_name:
    :param img:
    :param prefix:
    :return:
    '''
    file_name = '%s%s_%s.jpg' % (out_folder_name, prefix, replce_format_from_file_name(img_file_name))

    cv2.imwrite(file_name, img)


def choose_big_conturs(contours, noise_removal_threshold):
    '''
    find the contours 'bigger' then the minimal area in pixel (see noise_removal_threshold)

    :param contours:
    :param noise_removal_threshold:
    :param show_fig:
    :param img:
    :return:
    '''
    max_area = 1000000

    contours2 = []
    for contour in contours:
        area = cv2.contourArea(contour)

        if (area > noise_removal_threshold) and (area < max_area):
            contours2.append(contour)

    return contours2


def draw_conturs(contours, img, draw_img, save_img):
    '''

    :param contours:
    :param img:
    :param draw_img:
    :param save_img:
    :return:
    '''

    total = 0
    for c in contours:
        # contour tops > 2
        if len(c) > 2:
            cv2.drawContours(img, [c], -1, (0, 0, 255), 4)
            total += 1

    # sowing the output image
    print('Здесь %i комнат' % total)
    if draw_img == True:
        image_show(img)

    if save_img == True:
        cv2.imwrite('output_p.jpg', img)

def image_show(image):
    '''

    :param image:
    :return:
    '''
    cv2.imshow("Image", image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

def return_contours_as_poligon(contours, k_approxPolyDP):
    '''
    to smooth the contour and
    to save contours to dictionary with coordinates
    {N_room : [[x1, y1], [x2, y2], ..], ..}
    :param contours:
    :param img:
    :return: {1: array([[1271, 1313], .. [1271, 1485]], 2: ..}
    '''
    #k_approxPolyDP = 0.02

    i_cont = 0
    corner_coordinates, contours_out = {}, []
    for contur in contours:
        i_cont += 1

        # approximate (smooth) the contour
        peri = cv2.arcLength(contur, True)
        approx_contur = cv2.approxPolyDP(contur, k_approxPolyDP * peri, True)

        #apply Convex Hull
        hull_contur = cv2.convexHull(approx_contur)

        corner_coordinates.update({i_cont : [xx[0] for xx in hull_contur]})
        contours_out.append(approx_contur)

    return corner_coordinates, contours_out

def draw_main_out_figure(img, corner_coordinates, out_folder_name, img_file_name, show_process):
    '''

    :return:
    '''

    for i_room, contur in corner_coordinates.items():
        cont_color = (np.random.randint(0, 255), np.random.randint(0, 255), np.random.randint(0, 255))

        show_img_with_dots_from_conturs(img, contur, i_room, cont_color, show_process)

    prefix = 'rooms'
    cv2.imwrite('%s%s_%s.jpg' % (out_folder_name, prefix, replce_format_from_file_name(img_file_name)), img)

def show_img_with_dots_from_conturs(img, contur, i_cont, color, showing_img):
    '''
    out figure with patterns locations we found
    :param img:
    :param list_of_cord: [[1271 1313], [1212 1314], ..  [1271 1485]]
    :return:
    '''
    font = cv2.FONT_HERSHEY_SIMPLEX

    # Radius of circle, Blue color in BGR, Line thickness of -1 px
    radius, thickness = 10, 5
    fontScale, fontthickness = 1, 2  # fontScale

    for coordinate in contur:
        # center coordinates and radius
        cv2.circle(img, (coordinate[0], coordinate[1]), radius, color, thickness)
        cv2.putText(img, '%i'%i_cont, (coordinate[0]+10, coordinate[1]-10),
                    font, fontScale, color, fontthickness, cv2.LINE_AA)

    if showing_img == True:
        image_show(img)


def save_out_dict(data, file_name):
    '''

    :param dict:
    :param file_name:
    :return:
    '''
    json_dump = json.dumps(data, cls=NumpyEncoder)
    file_name = replce_format_from_file_name(file_name)
    pathlib.Path(file_name).write_text(json_dump, encoding="utf-8")


def path_abs_make(path_in):
    THIS_FOLDER = os.path.dirname(os.path.abspath(__file__))
    return os.path.join(THIS_FOLDER, path_in)


def gaussian_blurring(img, k):
    # k = 3, 7, 11..
    img_blur = cv2.GaussianBlur(img, (k, k), 0)

    return img_blur

