import os
import sys
from pathlib import Path
FILE = Path(__file__).resolve()
ROOT = str(FILE.parents[0])
sys.path.insert(0, ROOT)
import argparse
import torch
import torch.backends.cudnn as cudnn
from models.common import DetectMultiBackend
from utils.datasets import IMG_FORMATS, VID_FORMATS, LoadImages, LoadStreams
from utils.general import (LOGGER, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2,
                           increment_path, non_max_suppression, print_args, scale_coords, strip_optimizer, xyxy2xywh)
from utils.plots import Annotator, colors, save_one_box
from utils.torch_utils import select_device, time_sync
from com_ineuron_apparel.com_ineuron_utils import utils

class DetectorYolov5():

    def __init__(self, filename,model_n):
        if model_n=="yolov5n":
            print("\nThe model selected is yolov5n\n")
            self.weights="yolov5n.pt"
        elif model_n=="yolov5s":
            print("\nThe model selected is yolov5s\n")
            self.weights="yolov5s.pt"
        elif model_n=="yolov5m":
            print("\nThe model selected is yolov5m\n")
            self.weights="yolov5m.pt"
        elif model_n=="yolov5l":
            print("\nThe model selected is yolov5l\n")
            self.weights="yolov5l.pt"
        elif model_n=="yolov5x":
            print("\nThe model selected is yolov5x\n")
            self.weights="yolov5x.pt"
        else:
            print("PLEASE SELECT THE YOLOv5 MODEL")
        self.source=filename
        self.data='utils/yolo_utils/coco128.yaml'
        self.imgsz=(416, 416)  # inference size (height, width)
        self.conf_thres=0.5  # confidence threshold
        self.iou_thres=0.45  # NMS IOU threshold
        self.max_det=1000  # maximum detections per image
        self.device=''  # cuda device, i.e. 0 or 0,1,2,3 or cpu
        self.view_img=False  # show results
        self.save_txt=False  # save results to *.txt
        self.save_conf=False  # save confidences in --save-txt labels
        self.save_crop=False  # save cropped prediction boxes
        self.nosave=False  # do not save images/videos
        self.classes=None  # filter by class: --class 0, or --class 0 2 3
        self.agnostic_nms=False  # class-agnostic NMS
        self.augment=False  # augmented inference
        self.visualize=False  # visualize features
        self.update=False  # update all models
        self.project=ROOT+"/com_ineuron_apparel/predictor_yolo_detector/inference/output" # save results to project/name
        self.name='output_image.jpg'  # save results to project/name
        self.exist_ok=False  # existing project/name ok, do not increment
        self.line_thickness=1  # bounding box thickness (pixels)
        self.hide_labels=False  # hide labels
        self.hide_conf=False  # hide confidences
        self.half=False  # use FP16 half-precision inference
        self.dnn=False  # use OpenCV DNN for ONNX inference
        self.pred_counter=0
        self.bboxes=[]
        

    def run(self):
        weights= self.weights
        source=self.source
        data=self.data
        imgsz=self.imgsz
        conf_thres=self.conf_thres
        iou_thres=self.iou_thres
        max_det=self.max_det
        device=self.device
        view_img= self.view_img
        save_txt= self.save_txt
        save_conf=self.save_conf
        save_crop= self.save_crop
        nosave=self.nosave
        classes=self.classes
        agnostic_nms=self.agnostic_nms
        augment=self.augment
        visualize=self.visualize
        update=self.update
        project=self.project
        name=self.name
        exist_ok=self.exist_ok
        line_thickness=self.line_thickness
        hide_labels=self.hide_labels
        hide_conf=self.hide_conf
        half=self.half
        dnn=self.dnn

        source = str(source)
        in_img = cv2.imread(source)
        cv2.imwrite('inputImage.jpg', in_img)

        save_img = not nosave and not source.endswith('.txt')  # save inference images
        is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
        is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
        webcam = source.isnumeric() or source.endswith('.txt') or (is_url and not is_file)
        if is_url and is_file:
            source = check_file(source)  # download

        # Directories
        #Saquib commented below
        # save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)  # increment run
        save_dir=Path(f"{project}")
        (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir

        # Load model
        device = select_device(device)
        model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
        stride, names, pt = model.stride, model.names, model.pt
        imgsz = check_img_size(imgsz, s=stride)  # check image size

        # Dataloader
        if webcam:
            view_img = check_imshow()
            cudnn.benchmark = True  # set True to speed up constant image size inference
            dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt)
            bs = len(dataset)  # batch_size
        else:
            dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt)
            bs = 1  # batch_size
        vid_path, vid_writer = [None] * bs, [None] * bs

        # Run inference
        model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))  # warmup
        dt, seen = [0.0, 0.0, 0.0], 0
        for path, im, im0s, vid_cap, s in dataset:
            t1 = time_sync()
            im = torch.from_numpy(im).to(device)
            im = im.half() if model.fp16 else im.float()  # uint8 to fp16/32
            im /= 255  # 0 - 255 to 0.0 - 1.0
            if len(im.shape) == 3:
                im = im[None]  # expand for batch dim
            t2 = time_sync()
            dt[0] += t2 - t1

            # Inference
            visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
            pred = model(im, augment=augment, visualize=visualize)
            t3 = time_sync()
            dt[1] += t3 - t2

            # NMS
            pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
            dt[2] += time_sync() - t3

            # Process predictions
            for i, det in enumerate(pred):  # per image
                seen += 1
                if webcam:  # batch_size >= 1
                    p, im0, frame = path[i], im0s[i].copy(), dataset.count
                    s += f'{i}: '
                else:
                    p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)

                p = Path(p)  # to Path
                # Saquib commented below
                # save_path = str(save_dir / p.name)  # im.jpg
                save_path = str(save_dir / name)  # im.jpg
                txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}')  # im.txt
                s += '%gx%g ' % im.shape[2:]  # print string
                gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # normalization gain whwh
                imc = im0.copy() if save_crop else im0  # for save_crop
                annotator = Annotator(im0, line_width=line_thickness, example=str(names))
                if len(det):
                    # Rescale boxes from img_size to im0 size
                    det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round()

                    # Print results
                    for c in det[:, -1].unique():
                        n = (det[:, -1] == c).sum()  # detections per class
                        s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  # add to string

                    # Write results
                    
                    for *xyxy, conf, cls in reversed(det):
                        if save_txt:  # Write to file
                            xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh
                            line = (cls, *xywh, conf) if save_conf else (cls, *xywh)  # label format
                            with open(txt_path + '.txt', 'a') as f:
                                f.write(('%g ' * len(line)).rstrip() % line + '\n')

                        if save_img or save_crop or view_img:  # Add bbox to image
                            c = int(cls)  # integer class
                            self.pred_counter+=1
                            label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}')
                            self.bboxes.append(xyxy)
                            annotator.box_label(xyxy, label, color=colors(c, True))
                            if save_crop:
                                save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True)
                        
                # Stream results
                im0 = annotator.result()
                if view_img:
                    cv2.imshow(str(p), im0)
                    cv2.waitKey(1)  # 1 millisecond

                # Save results (image with detections)
                if save_img:
                    if dataset.mode == 'image':
                        cv2.imwrite(save_path, im0)
                        # cv2.imwrite("predicted_output_image.jpg", im0)
                    else:  # 'video' or 'stream'
                        if vid_path[i] != save_path:  # new video
                            vid_path[i] = save_path
                            if isinstance(vid_writer[i], cv2.VideoWriter):
                                vid_writer[i].release()  # release previous video writer
                            if vid_cap:  # video
                                fps = vid_cap.get(cv2.CAP_PROP_FPS)
                                w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                                h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                            else:  # stream
                                fps, w, h = 30, im0.shape[1], im0.shape[0]
                            save_path = str(Path(save_path).with_suffix('.mp4'))  # force *.mp4 suffix on results videos
                            vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
                        vid_writer[i].write(im0)

            # Print time (inference-only)
            LOGGER.info(f'{s}Done. ({t3 - t2:.3f}s)')

        
        # Print results
        t = tuple(x / seen * 1E3 for x in dt)  # speeds per image
        LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}' % t)
        if save_txt or save_img:
            s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else ''
            LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}{s}")
        if update:
            strip_optimizer(weights)  # update model (to fix SourceChangeWarning)

    def detect_action(self):
        with torch.no_grad():
            self.run()
        print(f"Number of Prediction= {self.pred_counter}")
        print(self.bboxes)
        # print(type(self.bboxes.tolist()))
        bgr_image = cv2.imread(ROOT+"/com_ineuron_apparel/predictor_yolo_detector/inference/output/output_image.jpg")
        # cv2.imwrite(ROOT+'/predicted_output_image.jpg', bgr_image)
        cv2.imwrite('predicted_output_image.jpg', bgr_image)
        opencodedbase64 = utils.encodeImageIntoBase64('predicted_output_image.jpg')
        result = {"image": opencodedbase64.decode('utf-8')}
        return result
