import os
import shutil
import xml.etree.cElementTree as et
from pathlib import Path
import cv2


tag_size = 'size'
tag_object = 'object'
tag_name = 'name'
tag_bndbox = 'bndbox'
name_class_map = {'Truck': 1, 'Car': 2, 'Taxi': 2}  # 1 Вантажівка, 2 Машина легкова
forbidden_classes = ['Motorcycle']
default_class = 1
image_extensions = ['jpg', 'png', 'jpeg']


def convert(folder, dest_folder, read_annotations, ann_format):
    dest_images_dir = os.path.join(dest_folder, 'images')
    dest_labels_dir = os.path.join(dest_folder, 'labels')
    os.makedirs(dest_images_dir, exist_ok=True)
    os.makedirs(dest_labels_dir, exist_ok=True)

    for f in os.listdir(folder):
        if not f[-3:] in image_extensions:
            continue
        im = cv2.imread(os.path.join(folder, f))
        height = im.shape[0]
        width = im.shape[1]

        label = f'{Path(f).stem}.{ann_format}'
        try:
            with open(os.path.join(folder, label), 'r') as label_file:
                text = label_file.read()
                lines = read_annotations(width, height, text)
        except ValueError as val_err:
            print(f'Image {f} annotations could not be converted. Error: {val_err}')
            continue
        except Exception as e:
            print(f'Error conversion for {f}. Error: {e}')

        shutil.copy(os.path.join(folder, f), os.path.join(dest_images_dir, f))
        with open(os.path.join(dest_labels_dir, f'{Path(label).stem}.txt'), 'w') as new_label_file:
            new_label_file.writelines(lines)
            new_label_file.close()
        print(f'Image {f} has been processed successfully')


def minmax2yolo(width, height, xmin, xmax, ymin, ymax):
    c_w = (xmax - xmin) / width
    c_h = (ymax - ymin) / height
    c_x = xmin / width + c_w / 2
    c_y = ymin / height + c_h / 2
    return round(c_x, 5), round(c_y, 5), round(c_w, 5), round(c_h, 5)


def read_pascal_voc(width, height, s):
    root = et.fromstring(s)
    lines = []
    for node_object in root.findall(tag_object):
        class_num = default_class
        c_x = c_y = c_w = c_h = 0
        for node_object_ch in node_object:
            if node_object_ch.tag == tag_name:
                key = node_object_ch.text
                if key in name_class_map:
                    class_num = name_class_map[key]
                else:
                    if key in forbidden_classes:
                        class_num = -1
                        continue
                    else:
                        class_num = default_class
            if node_object_ch.tag == tag_bndbox:
                bbox_dict = {bbox_ch.tag: bbox_ch.text for bbox_ch in node_object_ch}
                c_x, c_y, c_w, c_h = minmax2yolo(width, height,
                                                 int(bbox_dict['xmin']),
                                                 int(bbox_dict['xmax']),
                                                 int(bbox_dict['ymin']),
                                                 int(bbox_dict['ymax']))
        if class_num == -1:
            continue
        if c_x > 1 or c_y > 1 or c_w > 1 or c_h > 1:
            print('Values are out of bounds')
        else:
            if c_x != 0 and c_y != 0 and c_w != 0 and c_h != 0:
                lines.append(f'{class_num} {c_x} {c_y} {c_w} {c_h}\n')
    return lines


def read_bbox_oriented(width, height, s):
    yolo_lines = []
    lines = s.split('\n', )
    for line in lines:
        if line == '':
            continue
        vals = line.split(' ')
        if len(vals) != 14:
            raise ValueError('wrong format')
        xmin = min(int(vals[6]), int(vals[7]), int(vals[8]), int(vals[9]))
        xmax = max(int(vals[6]), int(vals[7]), int(vals[8]), int(vals[9]))
        ymin = min(int(vals[10]), int(vals[11]), int(vals[12]), int(vals[13]))
        ymax = max(int(vals[10]), int(vals[11]), int(vals[12]), int(vals[13]))
        c_x, c_y, c_w, c_h = minmax2yolo(width, height, xmin, xmax, ymin, ymax)
        if c_x > 1 or c_y > 1 or c_w > 1 or c_h > 1:
            print('Values are out of bounds')
        else:
            yolo_lines.append(f'2 {c_x} {c_y} {c_w} {c_h}\n')
    return yolo_lines


def rename_images(folder):
    for f in os.listdir(folder):
        shutil.move(os.path.join(folder, f), os.path.join(folder, f[:-7] + '.png'))


if __name__ == '__main__':
    convert('/azaion/datasets/others/cars_height', '/azaion/datasets/converted', read_bbox_oriented, 'txt')
    convert('/azaion/datasets/others/cars', '/azaion/datasets/converted', read_pascal_voc, 'xml')
