import argparse
import os
import random
import time

import orjson
import gc

os.environ["OMP_NUM_THREADS"] = "1"
os.environ["OPENBLAS_NUM_THREADS"] = "1"
os.environ["MKL_NUM_THREADS"] = "1"
os.environ["VECLIB_MAXIMUM_THREADS"] = "1"
os.environ["NUMEXPR_NUM_THREADS"] = "1"

from PIL import Image, ImageDraw, ImageChops, ImageFilter
from enum import Enum
import sys
import numpy as np
from pathlib import Path
import torch
import glob
from dataclasses import dataclass, field, astuple
import string
from concurrent.futures import ThreadPoolExecutor
import facechecker
import webuiapi
import functools
import SSIM_PIL
from moviepy.video.io.ImageSequenceClip import ImageSequenceClip

FILE = Path(__file__).resolve()
ROOT = FILE.parents[0]  # yolov5 strongsort root directory
WEIGHTS = ROOT / 'weights'

if str(ROOT) not in sys.path:
    sys.path.append(str(ROOT))  # add ROOT to PATH
if str(ROOT / 'yolov5') not in sys.path:
    sys.path.append(str(ROOT / 'yolov5'))  # add yolov5 ROOT to PATH
if str(ROOT / 'trackers' / 'strong_sort') not in sys.path:
    sys.path.append(str(ROOT / 'trackers' / 'strong_sort'))  # add strong_sort ROOT to PATH

if str(ROOT / 'rife') not in sys.path:
    sys.path.append(str(ROOT / 'trackers' / 'strong_sort'))

if r"TikTokDiffusion\rife" not in sys.path:
    sys.path.append(r"TikTokDiffusion\rife")

if r"rife" not in sys.path:
    sys.path.append(r"rife")

ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  # relative

import RIFE
from yolov5.models.common import DetectMultiBackend
from yolov5.utils.dataloaders import LoadImages
from yolov5.utils.general import (LOGGER, Profile, check_img_size, non_max_suppression, scale_boxes, check_requirements,
                                   print_args, scale_segments)
from yolov5.utils.torch_utils import select_device
from utils.segment.general import masks2segments, process_mask, process_mask_native
from trackers.multi_tracker_zoo import create_tracker

@torch.no_grad()
def run(
        output_path,
        default_save_folder,
        fps,
        sampler,
        frame_gen_all,
        frame_gen_auto,
        cfg,
        height,
        width,
        seed,
        denoising,
        pipe,
        frame_gen_even,
        restore_faces,
        server,
        audio_source,
        control_net_model,
        control_net_lowvram,
        control_net_processor,
        source: Path,
        yolo_weights=WEIGHTS / 'yolov5x-seg.pt',  # model.pt path(s),
        reid_weights=WEIGHTS / 'osnet_x0_25_msmt17.pt',  # model.pt path,
        tracking_method='strongsort',
        imgsz=(640, 640),  # inference size (height, width)
        conf_thres=0.5,  # confidence threshold
        iou_thres=0.5,  # NMS IOU threshold
        max_det=1000,  # maximum detections per image
        device='',  # cuda device, i.e. 0 or 0,1,2,3 or cpu
        save_crop=False,  # save cropped prediction boxes
        classes=0,  # filter by class: --class 0, or --class 0 2 3
        agnostic_nms=False,  # class-agnostic NMS
        augment=False,  # augmented inference
        visualize=False,  # visualize features
        half=False,  # use FP16 half-precision inference
        dnn=False,  # use OpenCV DNN for ONNX inference
        retina_masks=True,
        debug=False,
        skip_if_exists="",
        check_faces=False,
        prompt_by_body=False,
        steps=20,
        save_format=".png",
        stable_diffusion_folder="",
        face_match_sensitivity=1,
        func_prompt_list=None,
        ignore_if_no_prompt=True,
        fallback=None,
        loopback=1,
        inject_last_image=dict,
        variation_scaling=0.0,
        seed_scaling=False,
        ssim_retry=1,
        ssim_threshold=0.87,
        max_person_size=150,
        skip_ratio=.80,
        max_frame_skip=2,
        use_control_net=False,
        negative_prompt="",
):

    print(inject_last_image)
    org_max_person_size = max_person_size
    max_person_size = org_max_person_size*org_max_person_size
    source = str(source)
    source_folder = os.path.dirname(source)
    dataset = []
    name_prompt = func_prompt_list[0] if len(func_prompt_list[0]) < 20 else func_prompt_list[0][:20]+"..."
    id_set = set()

    if check_faces: #this makes the prompts work from right to left which is how prompt to body works and is more intuitive
        func_prompt_list = list(reversed(func_prompt_list))

    if skip_if_exists:
        save_folder = os.path.join(output_path, skip_if_exists)
    else:
        save_folder = default_save_folder

    class SaveType(Enum):
        DEBUG_IMAGE = "debug"
        UNFOUND_IMAGE = "unfound"
        FOUND_IMAGE = "found"
        FACES = "faces"

    class PersonJob:
        class_save_format = save_format
        class_full_save_folder_path = stable_diffusion_folder

        def __init__(self, org_image_path):
            self.org_image_path = org_image_path
            self.org_image_array = None
            self.segment = None

            filename = os.path.splitext(os.path.basename(self.org_image_path))[0]
            safe_filename = name_cleaner(filename)

            self.save_name_no_ext = safe_filename
            self.full_save_path_with_ext = os.path.join(save_folder, self.save_name_no_ext+f"{save_format}")
            self.mask_with_alpha = None
            self.masked_image = None
            self.prompt = None
            self.generated_times = 0
            self.found_face = False
            self.label = None
            self.face_index = None
            self.face = None
            self.track_label = None

        def save_debug_image(self, folder: SaveType, count=0):
            debug_folder = os.path.join(save_folder, "debugging", folder.value)

            try:
                os.makedirs(debug_folder)
            except FileExistsError:
                pass

            save_full_file_name = os.path.join(debug_folder, self.save_name_no_ext + f"_{count}_{folder.value}.jpg")

            if folder == SaveType.FACES:
                if type(self.face) == Image:
                    self.face.save(save_full_file_name)
            elif folder == SaveType.FOUND_IMAGE:
                if self.label is not None:
                    dir_name, filename = os.path.split(save_full_file_name)
                    label_path = os.path.join(dir_name, self.prompt if self.prompt is not None else "No Prompt", filename)

                    try:
                        os.makedirs(os.path.dirname(label_path))
                    except FileExistsError:
                        pass

                    self.get_masked_image().convert("RGB").save(label_path)
                else:
                    self.get_masked_image().convert("RGB").save(save_full_file_name)
            else:
                self.get_masked_image().convert("RGB").save(save_full_file_name)

            print(f"saving {folder.value} image to {save_full_file_name}")

        def get_org_image(self):
            return Image.open(self.org_image_path)

        def get_masked_image(self, invert=True, get_hole=False):
            def get_mask_or_alpha(mode="RGB"):
                mask_image_alpha = Image.new(mode, self.get_org_image().size, color=(0, 0, 0, 0))
                draw = ImageDraw.Draw(mask_image_alpha, mode)
                draw.polygon(self.segment.segment, fill=(255, 255, 255))
                return mask_image_alpha

            if get_hole:
                return_mask = ImageChops.invert(get_mask_or_alpha("RGBA"))
            elif invert:
                mask_image_alpha = get_mask_or_alpha("RGBA")
                return_mask = self.get_org_image().convert("RGBA")
                return_mask.alpha_composite(ImageChops.invert(mask_image_alpha))
            else:
                mask_image = get_mask_or_alpha()
                return_mask = mask_image
            return return_mask

    @dataclass
    class PersonList:
        persons: list[PersonJob] = field(default_factory=list)
        found_persons: list[PersonJob] = field(default_factory=list)
        unfound_persons: list[PersonJob] = field(default_factory=list)
        group_by_image_path_list: list[list[PersonJob]] = field(default_factory=list)
        generated_images: list[PersonJob] = field(default_factory=list)

        def get_found_persons(self):
            self.found_persons = []
            self.unfound_persons = []

            for loop_person in self.persons:
                if loop_person.found_face:
                    self.found_persons.append(loop_person)
                else:
                    self.unfound_persons.append(loop_person)

        def save_faces(self, save_type: SaveType):
            pool = ThreadPoolExecutor()
            if save_type == SaveType.UNFOUND_IMAGE:
                list_of_persons = self.unfound_persons

            if save_type == SaveType.DEBUG_IMAGE:
                list_of_persons = self.persons

            if save_type == SaveType.FOUND_IMAGE:
                list_of_persons = self.found_persons

            workers = []
            for count, save_person in enumerate(list_of_persons):
                workers.append(pool.submit(save_person.save_debug_image, save_type, count))

            for worker in workers:
                worker.result()

        def get_prompts(self):
            def get_missing():
                if not check_faces and not prompt_by_body:
                    return
                if check_faces:
                    loop_times = range(2)
                else:
                    loop_times = range(1)

                for times in loop_times:
                    for person_count, person in enumerate(self.persons):
                        if check_faces:
                            checker = person.found_face is False and times != 0
                        else:
                            checker = person.track_label is None
                        person.track_label = person.segment.id

                        if checker:  # this means we found a segmentation map but there was no track_label so we dont know what prompt to give it
                            searching_for_box = True
                            current_frame = person.segment.frame_idx
                            before_count = person_count
                            after_count = person_count
                            run_times = 0

                            while searching_for_box:
                                run_times += 1
                                compare_list = []
                                after_count += 1

                                if current_frame != 0:
                                    before_count -= 1

                                    try:
                                        before = self.persons[before_count]
                                        if before.segment.frame_idx < current_frame:
                                            compare_list.append(before)
                                    except IndexError:
                                        pass

                                try:
                                    after = self.persons[after_count]
                                    if after.segment.frame_idx > current_frame:
                                        compare_list.append(after)
                                except IndexError:
                                    pass

                                if compare_list:
                                    for _, compare_person in enumerate(compare_list):
                                        if searching_for_box is False:
                                            continue

                                        if prompt_by_body:
                                            if compare_person.segment.id is None:
                                                continue

                                        bounding_boxes = zip(person.segment.bounding_box,
                                                             compare_person.segment.bounding_box)
                                        all_difference = 0
                                        for box in bounding_boxes:
                                            all_difference += abs(box[0] - box[1])
                                        if all_difference <= 100:
                                            if times == 0:
                                                person.track_label = compare_person.segment.id
                                                if person.track_label > len(func_prompt_list):
                                                    person.prompt = fallback
                                                else:
                                                    person.prompt = func_prompt_list[int(person.track_label) - 1]
                                                    print(person.prompt)
                                            else:
                                                if not compare_person.found_face:
                                                    continue

                                                person.label = compare_person.label
                                                if person.label > len(func_prompt_list):
                                                    person.prompt = fallback
                                                else:
                                                    if person.label+1 > len(func_prompt_list):
                                                        person.prompt = fallback
                                                    else:
                                                        person.prompt = func_prompt_list[int(person.label)]
                                                        person.found_face = True
                                                        print(person.prompt)

                                            searching_for_box = False

                                        elif person.track_label is not None:
                                            if person.track_label > len(func_prompt_list):
                                                person.prompt = fallback
                                            else:
                                                person.prompt = func_prompt_list[int(person.track_label) - 1]
                                                print(person.prompt)

                                if run_times >= 15:
                                    if not searching_for_box:
                                        continue

                                    print(f"searched {run_times} for match and didnt find")
                                    break

                            if person.prompt is None:
                                print(f"None person on frame {person.segment.frame_idx}")
                        elif prompt_by_body:
                            if person.track_label > len(func_prompt_list):
                                person.prompt = fallback
                            else:
                                person.prompt = func_prompt_list[int(person.track_label) - 1]
                                print(person.prompt)

            if check_faces:
                self.persons = facechecker.get_face_identities(self.persons, face_match_sensitivity)
                self.get_found_persons()

                for person in self.found_persons:
                    try:
                        person.prompt = func_prompt_list[int(person.label)]
                    except IndexError:
                        person.prompt = fallback

                get_missing()
                self.get_found_persons()
                if debug:
                    person_list.save_faces(SaveType.UNFOUND_IMAGE)
                    person_list.save_faces(SaveType.FOUND_IMAGE)
                    print("Finished Saving Debug Images")

            elif not prompt_by_body:
                for person in self.persons:
                    person.prompt = func_prompt_list[0]

                self.group_by_image_path()
                return

            get_missing()
            self.group_by_image_path()

        def group_by_image_path(self):
            person_images = {}
            for person in self.persons:
                found_path = person_images.get(person.org_image_path)
                if found_path is None:
                    person_images[person.org_image_path] = [person]
                else:
                    person_images[person.org_image_path].append(person)

            self.group_by_image_path_list = [value for value in person_images.values()]

        def add_to_generate_list(self, generated_class:PersonJob):
            generated_class.generated_times += 1
            self.generated_images.append(generated_class)

    @dataclass
    class SegmentCoord:
        segment: np.array
        frame_idx: int
        bounding_box: field(default_factory=list)
        path: str
        id = None
        dif = None

    @dataclass
    class SegmentListClass:
        segments: field(default_factory=list)

        def sort_segments(self):
            self.segments = sorted(self.segments, key=lambda x: x.path)

    person_list = PersonList()
    segment_list = SegmentListClass(list())
    host, port = str(server).split(":")
    api = webuiapi.WebUIApi(host=host, port=port)
    unfound_masks = []

    def name_cleaner(name_to_clean:str):
        valid_chars = f"-_.(), {string.ascii_letters}{string.digits}"
        safe_filename = "".join(letter for letter in name_to_clean if letter in valid_chars)
        return safe_filename

    def check_if_masks_exists_and_save_masks(list_of_files: list, current_image_segments=None):
        for _, image_path in enumerate(list_of_files):
            current_image_path = image_path[1] if type(image_path) == tuple else image_path
            not_found = True
            masks_folder = os.path.join(output_path, "masks")
            mask_filename = name_cleaner(current_image_path)
            mask_filename = os.path.join(masks_folder, mask_filename + f".json")

            if not os.path.exists(masks_folder):
                try:
                    os.makedirs(masks_folder)

                except FileExistsError:
                    pass

            else:
                if os.path.exists(mask_filename):
                    with open(mask_filename, "rb") as mask_file:
                        segments_tuple = orjson.loads(mask_file.read())

                    for segment_item in segments_tuple:
                        current_segment = SegmentCoord(segment=np.array(segment_item.get("segment"), dtype=np.float32),
                                                       frame_idx=segment_item.get("frame_idx"),
                                                       bounding_box=segment_item.get("bounding_box"),
                                                       path=segment_item.get("path"))
                        current_segment.id = segment_item.get("id")
                        current_segment.dif = segment_item.get("dif")
                        for value in astuple(current_segment):
                            if value is None and current_segment.frame_idx !=0:
                                break

                        segment_list.segments.append(current_segment)
                        id_set.add(current_segment.id)
                        not_found = False
            if current_image_segments is not None:
                with open(mask_filename, "wb") as mask_file:
                    mask_file.write(orjson.dumps(tuple(current_image_segments), option=orjson.OPT_SERIALIZE_NUMPY))

            if not not_found:
                print(f"found mask for {current_image_path} so no new mask will be generated for it\n")
            else:
                unfound_masks.append(current_image_path)

    check_mask_pool = ThreadPoolExecutor()
    check_mask_workers = []

    for check_image_path in enumerate(glob.glob(source+"\*")):
        check_mask_workers.append(check_mask_pool.submit(check_if_masks_exists_and_save_masks, [check_image_path]))

    for check_mask_worker in check_mask_workers:
        check_mask_worker.result()

    # Load model
    if unfound_masks:
        device = select_device(device)
        is_seg = '-seg' in str(yolo_weights)
        model = DetectMultiBackend(yolo_weights, device=device, dnn=dnn, data=None, fp16=half)
        stride, names, pt = model.stride, model.names, model.pt
        imgsz = check_img_size(imgsz, s=stride)  # check image size
        dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt)
        nr_sources = 1
        segment_list.sort_segments()
        dataset.files = sorted(unfound_masks)
        dataset.nf = len(unfound_masks)
        dataset.count = len(unfound_masks)

        # Create as many strong sort instances as there are video sources
        tracker_list = []
        for i in range(nr_sources):
            tracker = create_tracker(tracking_method, reid_weights, device, half)
            tracker_list.append(tracker, )
            if hasattr(tracker_list[i], 'model'):
                if hasattr(tracker_list[i].model, 'warmup'):
                    tracker_list[i].model.warmup()
        outputs = [None] * nr_sources

        seen, windows, dt = 0, [], (Profile(), Profile(), Profile(), Profile())
        curr_frames, prev_frames = [None] * nr_sources, [None] * nr_sources

    for frame_idx, (path, im, im0s, vid_cap, s) in enumerate(dataset):
            with dt[0]:
                im = torch.from_numpy(im).to(device)
                im = im.half() if half else im.float()  # uint8 to fp16/32
                im /= 255.0  # 0 - 255 to 0.0 - 1.0
                if len(im.shape) == 3:
                    im = im[None]  # expand for batch dim

            # Inference
            with dt[1]:

                if is_seg:
                    pred, proto = model(im, augment=augment, visualize=visualize)[:2]
                else:
                    pred = model(im, augment=augment, visualize=visualize)

            # Apply NMS
            with dt[2]:
                if is_seg:
                    pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det, nm=32)
                else:
                    pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)

            # Process detections
            for i, det in enumerate(pred):  # detections per image
                seen += 1
                p, im0, _ = path, im0s.copy(), getattr(dataset, 'frame', 0)
                p = Path(p)  # to Path
                curr_frames[i] = im0

                s += '%gx%g ' % im.shape[2:]  # print string
                imc = im0.copy() if save_crop else im0  # for save_crop

                if hasattr(tracker_list[i], 'tracker') and hasattr(tracker_list[i].tracker, 'camera_update'):
                    if prev_frames[i] is not None and curr_frames[i] is not None:  # camera motion compensation
                        tracker_list[i].tracker.camera_update(prev_frames[i], curr_frames[i])

                if det is not None and len(det):
                    if is_seg:
                        # scale bbox first the crop masks
                        if retina_masks:
                            det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()  # rescale boxes to im0 size
                            masks = process_mask_native(proto[i], det[:, 6:], det[:, :4], im0.shape[:2])  # HWC
                        else:
                            masks = process_mask(proto[i], det[:, 6:], det[:, :4], im.shape[2:], upsample=True)  # HWC
                            det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()  # rescale boxes to im0 size

                        # Segments
                        segments = [
                            scale_segments(im0.shape if retina_masks else im.shape[2:], x, im0.shape, normalize=False)
                            for x in reversed(masks2segments(masks))]

                        current_image_segments = []
                        for j, segment in enumerate(segments):
                            segmentation_map = segment

                            # Find the minimum and maximum x and y values
                            min_x = min(x for x, y in segmentation_map)
                            max_x = max(x for x, y in segmentation_map)
                            min_y = min(y for x, y in segmentation_map)
                            max_y = max(y for x, y in segmentation_map)
                            area = (max_x - min_x) * (max_y - min_y)

                            if area <= max_person_size:
                                print(f"Skipping detected person on frame {frame_idx} as they are below the size limit of {org_max_person_size}")
                                continue

                            # Print the bounding box coordinates
                            current_segment = SegmentCoord(segment=segment, frame_idx=frame_idx, bounding_box=(min_x, min_y, max_x, max_y), path=path)
                            segment_list.segments.append(current_segment)
                            current_image_segments.append(current_segment)
                            if frame_idx == 0:
                                current_segment.id = None

                    else:
                        det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()  # rescale boxes to im0 size

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

                    # pass detections to strongsort
                    with dt[3]:
                        outputs[i] = tracker_list[i].update(det.cpu(), im0)

                    # draw boxes for visualization

                    if len(outputs[i]) > 0:
                        for j, (output) in enumerate(outputs[i]):
                            bboxes = output[0:4]
                            id = output[4]
                            cls = output[5]
                            conf = output[6]

                            for a, segment in enumerate(current_image_segments):
                                left_dif = abs(segment.bounding_box[0] - bboxes[0])
                                top_dif = abs(segment.bounding_box[1] - bboxes[1])
                                bot_dif = abs(segment.bounding_box[2] - bboxes[2])
                                right_dif = abs(segment.bounding_box[3] - bboxes[3])
                                all_dif = left_dif + top_dif + bot_dif + right_dif
                                segment.dif = all_dif

                            smallest = None
                            for segment in current_image_segments:
                                if smallest is None:
                                    smallest = segment.dif
                                else:
                                    if segment.dif < smallest:
                                        smallest = segment.dif

                            for segment in current_image_segments:
                                if smallest == segment.dif:
                                    segment.id = id
                                    id_set.add(id)

                    if current_image_segments:
                        check_if_masks_exists_and_save_masks([path], current_image_segments)
                else:
                    pass

            try:
                LOGGER.info(f"{s}{'' if len(det) else '(no detections), '}{sum([dt.dt for dt in dt]) * 1E3:.1f}ms")
            except AttributeError:
                print(LOGGER.info(f"{s} (no detections)"))

    try:
        del model
        del dt
        del pred
        torch.cuda.empty_cache()
        gc.collect()
    except UnboundLocalError:
        print("no model to delete")
        pass

    id_list = [id for id in id_set if id is not None]
    id_dict = {}

    for count, id_to_change in enumerate(id_list, 1):  #normalizing ids so that we can use them as indexes later
        if id_to_change is None:
            continue

        id_dict.update({id_to_change: count})

    for segment in segment_list.segments:
        person = PersonJob(segment.path)

        person.segment = segment

        person.segment.id = id_dict[person.segment.id] if segment.id is not None else None

        if debug or check_faces:
            person.masked_image = True

        person_list.persons.append(person)

    person_list.persons = sorted(person_list.persons, key=lambda x:x.org_image_path)
    segment_list.sort_segments()
    person_list.get_prompts() #assigns prompts to people and groups images by the path to the file
    ssim_persons = []
    last_generated_image_for_injection = None

    def save_generated_img(image: Image, full_path_to_save: str):
        save_img_dir = os.path.dirname(full_path_to_save)
        if not os.path.exists(save_img_dir):
            os.makedirs(save_img_dir)
        try:
            try:
                image.save(full_path_to_save)
            except PermissionError:
                time.sleep(.1)
                save_generated_img(image, full_path_to_save)
                print(f"Error Saving: {full_path_to_save} retrying")
        except OSError:
            pass

        print(f"Saved image to {full_path_to_save}")
        ssim_persons.append({"image": image, "path": full_path_to_save})
        func_last_generated_image_for_injection = image
        return func_last_generated_image_for_injection

    def get_similar_frames(files_to_check):
        func_skip_set = set()
        for count, file in enumerate(files_to_check):
            if file in func_skip_set:
                continue

            skip_list = []
            frames_count = 0

            current_image = Image.open(file)
            for sub_count, sub_file in enumerate(files_to_check):
                if frames_count >= max_frame_skip:
                    skip_list.pop(-1)
                    for pathfile in skip_list:
                        func_skip_set.add(pathfile)
                    break

                if sub_count <= count:
                    continue

                next_image = Image.open(sub_file)
                sub_similar_ratio = SSIM_PIL.compare_ssim(current_image, next_image)

                if sub_similar_ratio*100 > skip_ratio:
                    frames_count += 1
                    skip_list.append(sub_file)
                else:
                    if skip_list:
                        skip_list.pop(-1)
                        for pathfile in skip_list:
                            func_skip_set.add(pathfile)
                    break
        return func_skip_set

    def save_video(path_to_images, framerate, filename, generated_images_folder=""):

        filename = name_cleaner(filename)
        if not framerate:
            framerate = 30

        non_generated_images = glob.glob(path_to_images+"\*.png")
        generated_images = glob.glob(path_to_images+"\generated_imgs\*.png")

        if generated_images_folder:
            generated_images_folder = glob.glob(generated_images_folder+"\*.png")

        if not generated_images_folder:
            images = non_generated_images
        else:
            images = sorted(non_generated_images+generated_images_folder, key=lambda x: os.path.basename(x))

        sequence = ImageSequenceClip(images, framerate)
        sequence_save_folder = os.path.join(output_path, "videos")

        if not os.path.exists(sequence_save_folder):
            os.makedirs(sequence_save_folder)

        save_filename = os.path.join(sequence_save_folder, filename+f" {random.random()}.mp4")

        sequence.write_videofile(save_filename, framerate, audio=audio_source)

    def skip_list_split(list_to_split, sections=5):
        list_length = len(list_to_split)
        section_size = list_length // sections
        lists = []
        loop_range = range(sections)
        loop_range_size = len(loop_range)
        for section in loop_range:
            section += 1
            slice_amt = section * section_size

            if section == loop_range_size:
                lists.append(list_to_split[slice_amt - section_size:])
            else:
                lists.append(list_to_split[slice_amt - section_size:slice_amt + max_frame_skip + 1])
        return tuple(lists)

    skip_set = set()
    files_in_source_dir = list(glob.glob(source + "\*.png"))

    if frame_gen_auto:
        print(f"Checking for frames to skip using a ratio of {skip_ratio}")
        skip_workers = []
        skip_pool = ThreadPoolExecutor()
        skip_lists = skip_list_split(files_in_source_dir, 6)

        for skip_list in skip_lists:
            res = skip_pool.submit(get_similar_frames, skip_list)
            skip_workers.append(res)

        for skip_worker in skip_workers:
            skip_set.update(skip_worker.result())

        print(f"Found {len(skip_set)} similar image(s) to skip")

    total_jobs = 0
    for count_job in person_list.group_by_image_path_list:
        total_jobs += len(count_job)
    total_jobs = total_jobs-len(skip_set)
    org_job_length = total_jobs-len(skip_set)
    done_jobs = 0
    generated_list = {}
    last_time = 0
    for personjob_list_count, personjob_list in enumerate(person_list.group_by_image_path_list):

        if frame_gen_all or frame_gen_even:
            if personjob_list_count % 2 != 0:
                continue

        ssim_loop = range(ssim_retry) if personjob_list_count != 0 else range(1)
        best_ssim = 0
        variation = 0
        skip_this_frame = False
        #hack need to change later

        if skip_set:
            for set_personjob in personjob_list:
                if set_personjob.org_image_path in skip_set:
                    skip_this_frame = True
                    break

        if skip_if_exists:
            for check_personjob in personjob_list:
                if os.path.exists(check_personjob.full_save_path_with_ext):
                    print(f"file: {check_personjob.full_save_path_with_ext} exists so it will be skipped. And no new image will be generated")
                    skip_this_frame = True
                    break

        if skip_this_frame:
            done_jobs += len(personjob_list)
            remaining_jobs = total_jobs-done_jobs
            print(f"{remaining_jobs} jobs remaining")

            for personjob in personjob_list:
                if generated_list.get(personjob.prompt) is None:
                    generated_list[personjob.prompt] = 1
                else:
                    generated_list[personjob.prompt] += 1
            continue

        for ssim_amount in ssim_loop:
            ssim_amount += 1
            doing_ssim = ssim_amount > 1

            if doing_ssim and variation_scaling != 0:
                variation = variation_scaling

            if doing_ssim and len(ssim_persons) > 1:
                main_image = ssim_persons[0]["image"]
                last_generated_image_to_compare = ssim_persons[-1]["image"]
                ssim_difference = SSIM_PIL.compare_ssim(main_image, last_generated_image_to_compare)
                print(f"Compared Image 1 to Image {len(ssim_persons)} and they were {round(ssim_difference, 3)*100}% similar")

                if ssim_difference >= ssim_threshold/100:
                    print(f"This is above the similarity threhsold of {ssim_threshold}% that was set so this Image has been selected")
                    last_generated_image_for_injection = save_generated_img(last_generated_image_to_compare, ssim_persons[-1]["path"])

                    done_jobs += 1
                    remaining_jobs = total_jobs - done_jobs
                    print(f"{remaining_jobs} jobs remaining")

                    ssim_persons = []
                    break
                else:
                    print(f"This is below the similarity threhsold of {ssim_threshold} that was set so will keep searching {ssim_amount}/{ssim_retry} times")
                    done_jobs -= 1
                    if ssim_difference > best_ssim:
                        best_ssim = ssim_difference
                        best_ssim_image = last_generated_image_to_compare
                        best_ssim_image_path = ssim_persons[-1]["path"]

            if ssim_amount == ssim_retry and doing_ssim:
                print(f"Reached search limit {ssim_amount}/{ssim_retry} limit choosing most similar image in batch which was {round(best_ssim, 3)*100}% similar")
                last_generated_image_for_injection = save_generated_img(best_ssim_image, best_ssim_image_path)

                done_jobs += 1
                remaining_jobs = total_jobs - done_jobs
                print(f"{remaining_jobs} jobs remaining")

                ssim_persons = []
                break

            completed_images = []

            for person_count, personjob in enumerate(personjob_list, 1):
                if personjob.prompt is None:
                    if ignore_if_no_prompt:
                        continue
                    else:
                        completed_images = []
                        for sub_personjob in personjob_list:
                            sub_personjob.generated_times = 0
                        break

                for loop_times in range(loopback):
                    loop_times = loop_times
                    loop_times += 1
                    print_prompt = personjob.prompt if len(personjob.prompt) < 60 else personjob.prompt[:60]+"..."

                    if loop_times > 1:
                        print(f"generating prompt: '{print_prompt}' for person {person_count} again since loopback is {loopback} current: {loop_times}/{loopback}")

                    if loopback > 1 and loop_times > 1 and person_list.generated_images:
                        if not use_control_net:
                            gen_image = image.images[-1]
                        else:
                            gen_image = image.images[0]

                    elif inject_last_image and last_generated_image_for_injection is not None:
                        last_generated_image = last_generated_image_for_injection
                        last_generated_image = last_generated_image.filter(ImageFilter.GaussianBlur(inject_last_image["Blur"]))
                        mask = ImageChops.invert(personjob.get_masked_image(get_hole=True))
                        last_generated_image = Image.composite(last_generated_image, mask, mask).convert("RGBA")
                        last_generated_image_array = np.array(last_generated_image)
                        opaque = last_generated_image_array[:, :, 3] == 255
                        opacity = inject_last_image["Opacity"]
                        last_generated_image_array[opaque, 3] = opacity * 255
                        last_generated_image = Image.fromarray(last_generated_image_array).convert("RGBA")
                        blended_image = Image.alpha_composite(personjob.get_org_image().convert("RGBA"), last_generated_image)
                        gen_image = blended_image
                        print(f"Injecting previous image into generation at blur {inject_last_image['Blur']} and opacity {inject_last_image['Opacity']}")

                    else:
                        gen_image = personjob.get_org_image()

                    if loop_times <= 1:
                        print(f"generating prompt: '{print_prompt}' for person {person_count}")
                    api_ssim_amount = ssim_amount if seed_scaling else 0

                    if pipe.poll():
                        instruction = pipe.recv()
                        if instruction == "STOP":
                            return False

                    prompt = personjob.prompt
                    gen_times = generated_list.get(prompt)

                    transition_frames = 30
                    mid_transition_frames = 15

                    if loop_times <= 1:
                        print(f"generating prompt: '{print_prompt}' for person {person_count}")

                    if last_time != 0:
                        cur_time = time.perf_counter()
                        runtime = cur_time-last_time
                        print(runtime)
                        total_time = round(runtime*total_jobs)
                        print("")
                        print(f"estimated time remaining {total_time} seconds")
                        print("")

                    last_time = time.perf_counter()
                    image = api.img2img(images=[gen_image],
                                        prompt=prompt,
                                        mask_image=personjob.get_masked_image(False),
                                        inpainting_fill=1,
                                        cfg_scale=cfg,
                                        denoising_strength=denoising,
                                        inpaint_full_res=True,
                                        steps=steps,
                                        sampler_index=sampler,
                                        restore_faces=restore_faces,
                                        negative_prompt=negative_prompt,
                                        height=height,
                                        width=width,
                                        seed=seed+loop_times+api_ssim_amount,
                                        subseed=ssim_amount,
                                        subseed_strength=variation,
                                        use_control_net=use_control_net,
                                        controlnet_model=control_net_model,
                                        controlnet_module=control_net_processor,
                                        controlnet_lowvram=control_net_lowvram
                                        )

                    person_list.add_to_generate_list(personjob)

                    if loop_times == loopback:

                        done_jobs += 1
                        remaining_jobs = total_jobs - done_jobs
                        print(f"{remaining_jobs} jobs remaining")

                        if use_control_net:
                            image.images = [image.images[0]]

                        completed_images.append([image.images[-1], personjob.get_masked_image(get_hole=True)])

            if len(completed_images) == 1:
                last_generated_image_for_injection = save_generated_img(completed_images[0][0], personjob_list[0].full_save_path_with_ext)

            elif len(completed_images) > 1:
                final_image = functools.reduce(lambda x, y: Image.composite(x[0] if type(x) is list else x, y[0], y[1]), completed_images)
                last_generated_image_for_injection = save_generated_img(final_image, personjob_list[0].full_save_path_with_ext)
            print("")

    generated_images_save_folder = os.path.join(save_folder, "generated_imgs")
    if frame_gen_auto or frame_gen_even:

        if frame_gen_auto:
            print(f"Generating {len(skip_set)} skipped frames")

        RIFE.frame_generator(path_of_images=save_folder, save_folder=generated_images_save_folder)
        save_video(path_to_images=save_folder, filename=name_prompt, framerate=fps, generated_images_folder=generated_images_save_folder)

    elif frame_gen_all:
        RIFE.frame_generator(path_of_images=save_folder, save_folder=generated_images_save_folder)
        RIFE.frame_generator(path_of_images=generated_images_save_folder, save_folder=generated_images_save_folder)
        save_video(path_to_images=generated_images_save_folder, filename=name_prompt, framerate=fps)

    else:
        save_video(path_to_images=save_folder, filename=name_prompt, framerate=fps)

    print(f"Saving video to {os.path.join(output_path, 'videos')}")
    return True


#!/usr/bin/env python3
# Atcoder necoak answer
# https://atcoder.jp/contests/diverta2019/tasks/diverta2019_a
import itertools

intext = input()
[n, k] = map(int, intext.split())

int_list = [v for v in range(1, n+1)]
nk_combis = itertools.combinations(int_list, k)
ans_nk_combis_cnt = 0

for nk_combi in nk_combis:
    # nk_list = list(nk_combi)

    prev_item = None
    is_renzoku = True

    for nk_item in nk_combi:
        if (prev_item != None) and (prev_item + 1 != nk_item):
            is_renzoku = False
            break
        prev_item = nk_item

    if is_renzoku:
        ans_nk_combis_cnt += 1

print(ans_nk_combis_cnt)


import logging
import json
import requests

from random import randint
from time import sleep

from django.conf import settings
import lxml.html
from urllib.parse import parse_qs, urlparse, unquote, urlencode

from crawl_engine.spiders.ya_search import YaSearch
from crawl_engine.spiders.rss_spider import RSSFeedParser
from crawl_engine.spiders.search_parser import SearchParser, ParserError

logger = logging.getLogger(__name__)

def xpath_first(item, path):
    """ method for safelly get first element """
    items = item.xpath(path)
    return items[0] if len(items) > 0 else ""

def xpath_first_text(item, path):
    """ method for safelly get first element text_content() """
    item = xpath_first(item, path)
    return item.text_content() if item != "" else ""

class GoogleGeneralParser(SearchParser):
    """
    BAse class for parsing google search results
    """
    def _parse_href(self, href):
        """
        Revise this method
        """
        # Method for normalising Google's result link. Receives the link, parse it and gets the direct
        # url from querystring. E.g.: google's result links look's like:
        # https://www.google.com/url?q=some+query&url=http://foo.bar.url
        # so we should get only q parameter from google's querystring
        try:
            url = parse_qs(urlparse(href).query)['q'][0]
            return unquote(url)
        except KeyError:
            return href

    def _get_tree(self, url, params):
        sleep(randint(1, 2))
        r = requests.get(url, params)
        tree = lxml.html.fromstring(r.text)

        if "Google" not in tree.findtext('.//title'):
            logger.info("I can't find Google in page title")

        return tree

class GoogleParser(GoogleGeneralParser):

    def run(self):
        base_url = 'https://google.com/search'
        result = []
        for count in range(0, self.depth):
            tree = self._get_tree(base_url, {'q': self.search_query, 'start': count * 10, 'sa': 'N'})

            for item in tree.xpath("//div[@class='g']"):
                result.append(self._new_article(
                    self._parse_href(xpath_first(item, ".//a/@href")),
                    xpath_first_text(item, ".//a"),
                    xpath_first_text(item, ".//span[@class='st']")
                ))

        return result

class GoogleScholarParser(GoogleGeneralParser):
    """
    Class for fetching results from google's scholar.google.com.
    Search query example: https://scholar.google.com.ua/scholar?start=30&q=robotics+ai&start=30
    start parameter responsible for results pagination
    :return: seed_links
    """
    def run(self):
        base_url = 'https://scholar.google.com/scholar'
        result = []
        for count in range(0, self.depth):
            tree = self._get_tree(base_url, {'q': self.search_query, 'start': count * 10, 'sa': 'N'})

            for item in tree.xpath("//div[@class='gs_ri']"):
                if (len(item.xpath(".//a/@href")) == 0) or (len(item.xpath(".//div[@class='gs_rs']")) == 0):
                    continue
                result.append(self._new_article(
                    self._parse_href(xpath_first(item, ".//a/@href")),
                    xpath_first_text(item, ".//a"),
                    xpath_first_text(item, ".//div[@class='gs_rs']")
                ))

        return result

class GoogleNewsParser(RSSFeedParser):
    """
    Gets results from google's news.google.com and parses all them by feedparser
    Search query example: https://news.google.com/news/feeds?output=rss&q=banking&num=30.
    num parameter responsible for the number of returned entries (max=30, default=30)
    output parameter responsible for response format (ATOM or RSS)
    :return: seed_links
    """
    def __init__(self, search_query='', depth=1, options={}):
        params = urlencode({'q': search_query, 'output': 'rss', 'num': depth * 10})
        url = 'https://news.google.com/news?%s' % params
        super(GoogleNewsParser, self).__init__(url)

    def _parse_href(self, href):
        """
        Revise this method
        """
        # Method for normalising Google's result link. Receives the link, parse it and gets the direct
        # url from querystring. E.g.: google's result links look's like:
        # https://www.google.com/url?q=some+query&url=http://foo.bar.url
        # so we should get only q parameter from google's querystring
        try:
            url = parse_qs(urlparse(href).query)['url'][0]
            return unquote(url)
        except KeyError:
            return href

    def _new_article(self, url, title, text):
        return super(GoogleNewsParser, self)._new_article(self._parse_href(url), title, text)

class GoogleCseParser(SearchParser):
    """
    Another method for getting google's search results but from CSE (custom search engine):
    https://developers.google.com/custom-search/json-api/v1/using_rest.
    Using Requests library
    :return: seed_links list
    """

    def __init__(self, search_query, depth, options):
        # Check search depth value if it higher than 10
        # redefine it to display only 100 first results
        # see: https://developers.google.com/custom-search/json-api/v1/using_rest
        if depth > 10:
            logger.warn("Only the first 100 results will be displayed, due to CSE search depth limit.")
            depth = 10

        # We store options at JSONField. So we need to load them.
        options = json.loads(options) if options else None

        super(GoogleCseParser, self).__init__(search_query, depth, options)

    def run(self):
        base_url = 'https://www.googleapis.com/customsearch/v1'
        result = []

        for count in range(0, self.depth):
            params = {
                'key': settings.GOOGLE_TRANSLATE_API_KEY,
                'cx': settings.CSE_ID,
                'q': self.search_query,
                'safe': 'medium',
                'start': count * 10 + 1
            }
            if self.options:
                params.update(self.options)

            r = requests.get(base_url, params=params)
            data = json.loads(r.text)
            if 'items' not in data:
                return []

            for item in data['items']:
                result.append(self._new_article(
                    item['link'],
                    item['title'],
                    item['snippet']
                ))

        return result

class GoogleBlogsParser(GoogleGeneralParser):

    def run(self):

        base_url = 'https://google.com/search'
        result = []
        for count in range(0, self.depth):
            tree = self._get_tree(base_url, {'q': self.search_query, 'start': count * 10, 'tbm': 'nws', 'tbs': 'nrt:b'})

            for item in tree.xpath("//div[@class='g']"):
                result.append(self._new_article(
                    self._parse_href(xpath_first(item, ".//a/@href")),
                    xpath_first_text(item, ".//a"),
                    xpath_first_text(item, ".//div[@class='st']")
                ))

        return result

class BingParser(SearchParser):

    def run(self):
        base_url = 'https://www.bing.com/search'
        result = []
        for count in range(0, self.depth):
            r = requests.get(base_url, {'q': self.search_query, 'first': count * 10 + 1})

            tree = lxml.html.fromstring(r.text)

            if "Bing" not in tree.findtext('.//title'):
                logger.info("I can't find Bing in page title")

            for item in tree.xpath("//li[contains(@class, 'b_algo')]"):
                result.append(self._new_article(
                    xpath_first(item, ".//a/@href"),
                    xpath_first_text(item, ".//a"),
                    xpath_first_text(item, ".//p")
                ))

        return result

class YandexParser(SearchParser):

    def run(self):
        search = YaSearch(settings.YANDEX_API_USER, settings.YANDEX_API_KEY, 'com')
        results = search.search(self.search_query)
        if results.error is not None:
            raise ParserError(results.error.description)

        return [self._new_article(i.url, i.title, i.snippet) for i in results.items]

class SocialParser(SearchParser):

    def run(self):
        base_url = 'https://api.social-searcher.com/v2/search'
        r = requests.get(base_url,
                         {'q': self.search_query,
                          'key': settings.SOCIAL_SEARCHER_API_KEY,
                          'limit': self.depth * 10}, verify=False)
        posts = json.loads(r.text).get('posts')
        if posts is None:
            raise ParserError('Social search returned empty result')
        return [self._new_article(
                    post.get('url'),
                    post.get('network') + ' - @' + post.get('user', {}).get('name'),
                    post.get('text'))
                for post in posts]

SEARCH_PARSERS = {
    'google': GoogleParser,
    'google_scholar': GoogleScholarParser,
    'google_news': GoogleNewsParser,
    'google_cse': GoogleCseParser,
    'google_blogs': GoogleBlogsParser,
    'bing': BingParser,
    'yandex': YandexParser,
    'social': SocialParser,
}

def get_search_parser(search_query, engine, depth=5, options=None):
    """ Init serach parser by name """
    if engine not in SEARCH_PARSERS:
        raise NameError('Search engine %s not found' % engine)
    return SEARCH_PARSERS[engine](search_query, depth, options)


from django.shortcuts import render
from django.http import HttpResponse, HttpResponseRedirect
from .forms import registrationForm
from .models import cust_details
from django.core.urlresolvers import reverse
from django.template import loader, Context
# from django.templatetags.static import static
from django.views import generic
from django.conf import settings
from django.core.files.storage import FileSystemStorage

def display(request):
    # return HttpResponseRedirect("bankWebApp/demo.html")
    t = loader.get_template('bankWebApp/demo.html')
    return HttpResponse(t.render())

def registration(request):
    if request.method == 'POST':

        form = registrationForm(data=request.POST,files=request.FILES)
        # myfile = request.FILES['cust_picture']
        if form.is_valid():
            form.save()
            # cust_account_id =request.POST.get('cust_account_id')
            # cust_name =request.POST.get('cust_name')
            # cust_address =request.POST.get('cust_address')
            # cust_dob =request.POST.get('cust_dob')
            # cust_email =request.POST.get('cust_email')
            # cust_balance =request.POST.get('cust_balance')
            # cust_picture=request.POST.get('cust_picture')
            # print(cust_picture)
            # registrationObj=cust_details(cust_account_id=cust_account_id,cust_name=cust_name,cust_address=cust_address,cust_dob=cust_dob,cust_email=cust_email,cust_balance=cust_balance,cust_picture=cust_picture)
            # registrationObj.save()

            return HttpResponseRedirect(reverse("login"))
        else:
            print(form.errors)
            print("Invalid form")
            # return HttpResponseRedirect(reverse('registration'))
    else:
        form=registrationForm()

    return render(request,"bankWebApp/Form.html", {"form": form})

def login(request):
    t = loader.get_template('bankWebApp/login.html')
    return HttpResponse(t.render())

class DetailView(generic.DetailView):
    model = cust_details
    pk_url_kwarg = 'detail'
    template_name = 'bankWebApp/details.html'

    # /home/monish/WEEK-7[MONISH]/My Banksrcsrc/home/monish/WEEK-7[MONISH]/My Bank/src


import numpy as np
import cv2

image = cv2.imread("Assets/InputImage.bmp");
w, h = image.shape[:2];

xNew = int(w * 512/ 220);
yNew = int(h * 512 / 260);

xScale = xNew/(w-1);
yScale = yNew/(h-1);

newImage = np.zeros([xNew, yNew, 3]);

for i in range(xNew-1):
   for j in range(yNew-1):
       newImage[i + 1, j + 1]= image[1 + int(i / xScale),1 + int(j / yScale)]

cv2.imwrite('Output/Im_NNI.jpg', newImage)


 # -*- coding: utf-8 -*-

import matplotlib.pyplot as pl
import pandas as pd
from math import pi

from pybemt.solver import Solver

s = Solver('tmotor28.ini')

df, sections = s.run_sweep('rpm', 20, 1000.0, 3200.0)

ax = df.plot(x='rpm', y='T')
ax2 = df.plot(x='rpm', y='P')

df_exp = pd.read_csv("tmotor28_data.csv", delimiter=';')

df_exp.plot(x='RPM',y='T(N)',style='o',ax=ax)
pl.figure(1)
pl.ylabel('Thrust (N)')
pl.legend(('BEMT','Experiment'))
df_exp.plot(x='RPM',y='P(W)',style='o',ax=ax2)
pl.figure(2)
pl.ylabel('Power (W)')
pl.legend(('BEMT','Experiment'))

pl.show()


import sys

import numpy
from tqdm import tqdm

class NelderMead:
    x_points = None
    y_points = None
    min_element = -1
    max_element = 1
    end_method = 'max_iter'
    end_cond = None
    dim = None
    alpha = 1
    beta = 0.5
    gamma = 2
    func = None
    h_index, g_index, l_index = None, None, None
    x_center = None
    x_reflected = None

    def __init__(self, min_element=-1, max_element=1):
        self.min_element = min_element
        self.max_element = max_element
        self.end_method = 'max_iter'
        self.alpha = 1
        self.beta = 0.5
        self.gamma = 2
        self.method_types = [0, 0, 0, 0]
        self.types_list = []
        self.cost_list = []

    def set_params(self, alpha, beta, gamma):
        self.alpha = alpha
        self.beta = beta
        self.gamma = gamma

    def set_end_method(self, end_method):
        self.end_method = end_method

    def optimize(self, func, dim, end_cond):
        # func - оптимизируемая функция, должна принимать numpy.array соотвесвтующей размерности в качесвте параметра
        # dim - размерность функции
        # end_method - условие останова
        # 'variance' - дисперсия набора значений функции симплкса должна быть меньше end_cond
        # 'max_iter' - остановка при достижении end_cond итераций
        self.func = func
        self.dim = dim
        self.x_points = self.create_points()  # Создаем точки
        self.y_points = numpy.array(list(map(func, self.x_points)))  # Вычисляем значение функции
        # в созданых точках
        if self.end_method == 'max_iter':  # Если условием выхода является достижение некого числа итераций
            for _ in tqdm(range(end_cond)):
                method_type = self.iteration()
                self.types_list.append(method_type)
                self.cost_list.append(numpy.min(self.y_points))
                self.method_types[method_type] += 1

        elif self.end_method == 'variance':  # Условие выхода - дисперсия значений функции не больше заданной величины
            var = end_cond + 1
            while var >= end_cond:
                method_type = self.iteration()
                self.method_types[method_type] += 1
                var = NelderMead.variance(self.y_points)

        else:
            print('Error in end_method param')
            return -1

        _, _, l_index = self.find_points()  # Определяем точку с нименьшим значением функции
        return self.x_points[l_index]

    def iteration(self):
        self.h_index, self.g_index, self.l_index = self.find_points()  # Находим точки h, g и l
        self.x_center = self.calculate_center()  # Вычисляем центр масс
        self.x_reflected = self.calculate_reflected_point()  # Вычисляем отраженную
        # точку
        y_reflected = self.func(self.x_reflected)
        # Далее мы делаем ряд действий, в зависимости от соотношения между значениями функции в найденных точках
        # Объяснять подробно нет смысла, так что смотри просто "Метод Нелдера - Мида" в вики
        if y_reflected < self.y_points[self.l_index]:
            method_type = 0
            x_stretch = self.calculate_stretched_point()
            y_stretch = self.func(x_stretch)
            if y_stretch < self.y_points[self.l_index]:
                self.x_points[self.h_index] = x_stretch
                self.y_points[self.h_index] = y_stretch
            else:
                self.x_points[self.h_index] = self.x_reflected
                self.y_points[self.h_index] = y_reflected

        elif y_reflected <= self.y_points[self.g_index]:
            method_type = 1
            self.x_points[self.h_index] = self.x_reflected
            self.y_points[self.h_index] = y_reflected

        else:
            if y_reflected < self.y_points[self.h_index]:
                self.x_points[self.h_index] = self.x_reflected
                self.y_points[self.h_index] = y_reflected

            x_compress = self.calculate_compressed_point()
            y_compress = self.func(x_compress)
            if y_compress < self.y_points[self.h_index]:
                method_type = 2
                self.x_points[self.h_index] = x_compress
                self.y_points[self.h_index] = y_compress
            else:
                method_type = 3
                self.compress_simplex()
                self.y_points = numpy.array(list(map(self.func, self.x_points)))
        return method_type

    def create_points(self):
        points = []
        for i in range(self.dim + 1):  # Создаем массив точек размера dim + 1 (так требуется по методу)
            points.append(numpy.random.uniform(self.min_element, self.max_element, self.dim))
        return numpy.array(points)

    def find_points(self):
        # В данном методе мы находим три точки: h - точка с наибользим значением оптимизируемой функции,
        # g - следующая за точкой с наибольишм значение, l - с наименьшим значением.
        # Далее мы задаем начальные парамтры...
        h_point = -sys.maxsize
        g_point = -sys.maxsize
        l_point = sys.maxsize
        h_index = 0
        g_index = 0
        l_index = 0
        # ...и проводим стандарнтый поиск.
        for index, item in enumerate(self.y_points):
            if item > h_point:
                g_point = h_point
                h_point = item
                g_index = h_index
                h_index = index
            elif (item > g_point) and (item != h_point):
                g_point = item
                g_index = index
            if item < l_point:
                l_point = item
                l_index = index

        return h_index, g_index, l_index

    def calculate_center(self):
        # Вычисляем "центр масс" всех точек, за исключением h
        sum_data = 0
        n = len(self.x_points) - 1
        for index, item in enumerate(self.x_points):
            if index != self.h_index:
                sum_data += item

        return sum_data / n

    def calculate_reflected_point(self):
        # В данной функции выполняется отражение точки h относительно центра масс
        x_h = self.x_points[self.h_index]
        x_reflected = ((1 + self.alpha) * self.x_center) - (self.alpha * x_h)
        return x_reflected

    def calculate_stretched_point(self):
        # В данной функции выполняется растяжение в направлении, соединяющим h, center и reflected
        x_stretch = (self.gamma * self.x_reflected) + ((1 - self.gamma) * self.x_center)
        return x_stretch

    def calculate_compressed_point(self):
        x_h = self.x_points[self.h_index]
        # В данной функции выполняется сжатие к center
        x_compress = (self.beta * x_h) + ((1 - self.beta) * self.x_center)
        return x_compress

    def compress_simplex(self):
        # В данной функции происходит схатие всего симплекса к l
        x_l = self.x_points[self.l_index]
        for index, x_i in enumerate(self.x_points):
            self.x_points[index] = 0.5 * (x_i + x_l)

    def return_method_types(self):
        return self.method_types

    def return_types_list(self):
        return self.types_list

    def return_cost_list(self):
        return self.cost_list

    @staticmethod
    def average(data):
        # Вычисление среднего значения
        return sum(data) / len(data)

    @staticmethod
    def variance(data):
        # Вычисление дисперсии
        mean_data = NelderMead.average(data)
        sum_var = 0
        for item in data:
            sum_var += (item - mean_data) ** 2
        return sum_var / len(data)


from django.core.validators import MaxValueValidator, MinValueValidator
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ObjectDoesNotExist
from courses.models import Content
from django.utils import timezone
from django.urls import reverse
from operator import itemgetter
from users.models import User
from django.db import models
import ast

class Exam(models.Model):

    user = models.ForeignKey(User, default=None, on_delete=models.CASCADE)
    content = models.OneToOneField(
        Content, blank=True, null=True, default=None, on_delete=models.SET_DEFAULT
    )
    name = models.CharField(
        ("Exam name"),
        max_length=150,
        unique=False,
        blank=False,
        help_text=("Required. 150 characters or fewer."),
    )
    description = models.TextField()
    approved = models.DecimalField(
        ("minimun points to approved"),
        default=None,
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0), MaxValueValidator(100)],
        blank=False,
        null=False,
        help_text=(
            "Put here the minimum points necessary to pass this exam. Max 5 digits: 3 for en integer part and 2 for decimal part"
        ),
        error_messages={
            "blank": ("you must provied the minimun points."),
            "null": ("you must provied the minimun points."),
        },
    )
    create_date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return "Quiz: " + self.name

    def save(self, *args, **kwargs):
        questionlistForms = kwargs.pop("questionlist", None)
        if kwargs.pop("update", None):
            Question.objects.filter(exam=self).delete()
        super(Exam, self).save(*args, **kwargs)
        if questionlistForms:
            for questionForm in questionlistForms:
                question_instance = questionForm.save(commit=False)
                question_instance.exam = self
                question_instance.save(
                    answerlist=questionForm.answersForms(),
                    familyInstance={"Exam": self},
                )

    def get_owner(self):
        return self.user.get_full_name()

    def get_questions_instances(self):
        questions = Question.objects.filter(exam=self)
        numbers_of_questions = [index + 1 for index in range(questions.count())]

        return zip(numbers_of_questions, questions)

    def get_take_url(self):
        # return reverse("courses:course-home-week-content", kwargs={'id':self.course.id, 'idModule':self.module.id, 'idContent':self.id})
        # return reverse("exams:exam-take", kwargs={'id':self.content.belongs_to_the_course().id, 'idModule':self.content.belongs_to_the_module().id, 'idContent':self.content.id, 'idExam':self.id})
        return reverse("exams:exam-take", kwargs={"idExam": self.id})

    def get_detail_url(self):
        return reverse("exams:exam-detail", kwargs={"id": self.id})

    def get_edit_url(self):
        return reverse("exams:exam-update", kwargs={"id": self.id})

    def get_delete_url(self):
        return reverse("exams:exam-delete", kwargs={"id": self.id})

    def get_evaluated_url(self):
        return reverse("exams:exam-evaluated", kwargs={"idExam": self.id})

    def evaluate(self, questions):
        if not questions:
            raise "they must provide the questions with their answers to evaluate"
        for question in questions.values():
            question["Answers"] = sorted(question["Answers"], key=itemgetter(0))
        questions_instances = [
            question for _, question in self.get_questions_instances()
        ]
        if len(questions_instances) != len(questions):
            raise "must provide all questions associated with this exam"
        validation = {}
        for question_instance, question in zip(questions_instances, questions.values()):
            validation["Questionid"] = question_instance.id
            answers_instances = question_instance.get_answers_instances()
            if len(answers_instances) != len(question["Answers"]):
                raise "must provide all answers associated with this exam"
            question["validate"] = True
            for answer_instance, answer in zip(answers_instances, question["Answers"]):
                if answer_instance.correct_answer != answer[1]:
                    question["validate"] = False
                    continue
            del question["Answers"]
            question["points"] = (
                float(question_instance.question_value)
                if question["validate"]
                else float(0)
            )
        questions["approved"] = float(self.approved)
        return questions

class Question(models.Model):
    exam = models.ForeignKey(Exam, default=None, on_delete=models.CASCADE)
    question = models.CharField(
        ("question"),
        max_length=600,
        blank=False,
        unique=False,
        help_text=("Required. 600 characters or fewer."),
        error_messages={"unique": ("A question with that name already exists."),},
    )
    correct_answers = models.IntegerField(
        ("number of correct answers for this question"),
        help_text=("number of correct answers for this question"),
        default=0,
    )
    question_value = models.DecimalField(
        ("points"),
        default=None,
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0), MaxValueValidator(100)],
        help_text=('Max 5 digits: same as "minimun points to approved"'),
        # help_text=('point value of the question. Max 5 digits: 3 for en integer part and 2 for decimal part'),
        blank=False,
        null=False,
        error_messages={
            "blank": ("you must provied the point value."),
            "null": ("you must provied the point value."),
        },
    )

    def __str__(self):
        return "Question: " + self.question

    def save(self, *args, **kwargs):
        answerlistForms = kwargs.pop("answerlist", None)
        familyInstance = kwargs.pop("familyInstance", None)
        super(Question, self).save(*args, **kwargs)
        if answerlistForms:
            for answerForm in answerlistForms:
                answer_instance = answerForm.save(commit=False)
                answer_instance.exam = familyInstance["Exam"]
                answer_instance.question = self
                answer_instance.save()

    def get_answers_instances(self):
        return Answer.objects.filter(question=self)

    def get_altAnswers_instances(self):
        return Answer.objects.filter(question=self).order_by("?")

class Answer(models.Model):
    exam = models.ForeignKey(Exam, default=None, on_delete=models.CASCADE)
    question = models.ForeignKey(Question, default=None, on_delete=models.CASCADE)
    answer = models.CharField(("answer"), max_length=600, blank=False,)
    correct_answer = models.BooleanField(
        default=False, help_text=("the answer is correct?."),
    )

    def __str__(self):
        return "answer for question, " + str(self.question_id)

class ExamUserRelations(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    exam = models.ForeignKey(Exam, on_delete=models.CASCADE)
    user_answer = models.TextField(default=None, blank=True, null=True)
    points = models.DecimalField(
        default=0,
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0), MaxValueValidator(100)],
    )
    number_of_try = models.IntegerField(
        default=0, validators=[MinValueValidator(0), MaxValueValidator(3)]
    )
    last_try = models.DateTimeField(default=None, blank=True, null=True)

    def __str__(self):
        return self.exam.name + ": " + self.user.get_full_name()

    def can_take_exam(self):
        if self.number_of_try >= 3:
            if self.time().days > 0 or self.time().seconds > (8 * 60 * 60):
                self.number_of_try = 0
                self.save()
                return True
            return False
        return True

    def get_user_answer(self):
        user_answer = ast.literal_eval(self.user_answer)
        del user_answer["approved"]
        for question in user_answer.values():
            question["id"] = int(question["id"])
        return user_answer

    def time(self):
        return timezone.now() - self.last_try

    def time_until_take(self):
        time = (
            timezone.timedelta(days=0, seconds=8 * 60 * 60, microseconds=0)
            - self.time()
        )
        return "%s hour(s) and %s minut(s)" % (
            time.seconds // 3600,
            (time.seconds // 60) % 60,
        )

    def try_exam(self, userQuestions):
        self.number_of_try += 1
        self.last_try = timezone.now()
        self.user_answer = userQuestions
        new_points = 0
        for key, question in userQuestions.items():
            if key != "approved":
                new_points += question["points"]
        if new_points > self.points:
            self.points = new_points
        self.save()


# This file preps data to be taken by svm and decision tree sorting

import word
import numpy
from csv import writer
import csv
import spacy
import json

PHRASE_LENGTH = 10
nlp = spacy.load("en_core_web_lg")
lemmatizer = nlp.get_pipe("lemmatizer")

chosen_verbs = {"speration":["split","part"], "body_movement":["roll","throw"],"filling":["cover","stuff"]}

#determine the overlapping percentage of two phrases
def phraseOverlap(phrase1, phrase2):
    if (phrase1 == (1000,-1)):
        return 0
    print("obj/subj ", phrase1, "judgedPhrase ", phrase2)
    length = phrase1[1]-phrase1[0] + 1#obj or subj
    #check if outside
    if( phrase1[1] < phrase2[0] ):
        return 0
    elif( phrase1[0] > phrase2[1]):
        return 0
    if( phrase1[0] < phrase2[0] ):
        length2 = (min(phrase1[1],phrase2[1])-phrase2[0]+1)
        if length2 <= 0:
            return 0
        else:
            return length2/length
    elif (phrase1[1] > phrase2[1] ):
        length2 = (max(phrase1[0], phrase2[0])-phrase1[0]+1)
        if length2 <= 0:
            return 0
        else:
            return length2/length

    else:
        return 1

def getPhrase(sentence, outputSetence):
    start,end = -1,-1
    for i in outputSetence:
        if i.token_text == "phraseTag":
            if start == -1:
                start = i.token_index
            else:
                end = i.token_index
    #print(start,end)
    return (start,end-2)

#getting frame of the word
def correctVerb(sentence, verb):
    verbPhrase = ""
    for i in range(verb[0], verb[1] + 1):
        verbPhrase += (sentence[i].token_text + " ")
    doc = nlp(verbPhrase)
    for w in doc:
        if w.lemma_ in chosen_verbs["speration"]:
            return 1
        elif w.lemma_ in chosen_verbs["body_movement"]:
            return 2
        elif w.lemma_ in chosen_verbs["filling"]:
            return 3
    return -1
def getVectors(sentence, phrase):
    i = 0
    sentenceVector = []
    padding = [0] * 303
    for j in range(phrase[0],phrase[1]+1):
        sentenceVector += (sentence[j].embedding + sentence[j].pos)
        i+=1
    while i < PHRASE_LENGTH:
        sentenceVector += padding
        i += 1
    return sentenceVector

def prepData(sentence, verb, outputSentence, subjs, objs, isAgent):
    frame = correctVerb(sentence, verb)
    if (frame == -1):
        return None,None
    else:
        phrase = getPhrase(sentence, outputSentence)
        isSubj = 0
        for subj in subjs:
            isSubj = max(isSubj, phraseOverlap(subj, phrase))
            print("isSubj",isSubj)

        isObj = 0
        #print("this is phrase ", phrase)
        #print("this is objs ", objs)
        for obj in objs:
            isObj = max(isObj, phraseOverlap(obj, phrase))
            print("isObj",isObj)
        if isAgent == "TRUE\n":
            isAgent = 1
        else:
            isAgent = 0
        row = [str(frame),str(isSubj),str(isObj),str(isAgent)]
        with open("data_frame.csv", 'a', newline='') as csvfile:
            csvwriter = csv.writer(csvfile)

            # writing the fields
            csvwriter.writerow(row)
        if phrase[1]-phrase[0]<=PHRASE_LENGTH:
            vec = getVectors(sentence, phrase)
            return vec, isAgent
        return None, None