# Copyright (C) 2023 Deforum LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

# Contact the authors: https://deforum.github.io/

import os
import pandas as pd
import cv2
import numpy as np
import numexpr
import gc
import random
import PIL
import time
from PIL import Image, ImageOps
from .generate import generate, isJson
from .noise import add_noise
from .animation import anim_frame_warp
from .animation_key_frames import DeformAnimKeys, LooperAnimKeys, ControlNetKeys
from .video_audio_utilities import get_frame_name, get_next_frame, render_preview
from .depth import DepthModel
from .colors import maintain_colors
from .parseq_adapter import ParseqAdapter
from .seed import next_seed
from .image_sharpening import unsharp_mask
from .load_images import get_mask, load_img, load_image, get_mask_from_file
from .hybrid_video import (
    hybrid_generation, hybrid_composite, get_matrix_for_hybrid_motion, get_matrix_for_hybrid_motion_prev, get_flow_for_hybrid_motion, get_flow_for_hybrid_motion_prev, image_transform_ransac,
    image_transform_optical_flow, get_flow_from_images, abs_flow_to_rel_flow, rel_flow_to_abs_flow)
from .save_images import save_image
from .composable_masks import compose_mask_with_check
from .settings import save_settings_from_animation_run
from .deforum_controlnet import unpack_controlnet_vids, is_controlnet_enabled
from .subtitle_handler import init_srt_file, write_frame_subtitle, format_animation_params
from .resume import get_resume_vars
from .masks import do_overlay_mask
from .prompt import prepare_prompt
from modules.shared import opts, cmd_opts, state, sd_model
from modules import lowvram, devices, sd_hijack
from .RAFT import RAFT

from deforum_api import JobStatusTracker

#Deforumation_mediator imports/settings and other libraries needed by Deforumation
import pickle
import json
import types, copy
from .deforum_mediator import mediator_getValue, mediator_setValue, mediator_set_anim_args
from modules.processing import StableDiffusionProcessingImg2Img
#End settings

#Deforumation_stuff
deforumation_cadence_scheduling_manifest=""
#End settings

def get_now_cadence(now_frame):
    relevant_frames_cadence_modulation = json.loads(deforumation_cadence_scheduling_manifest)
    cadence_now = -1
    for frame_number, cadence_value in relevant_frames_cadence_modulation.items():
        if int(now_frame) >= int(frame_number):
            cadence_now = cadence_value
        else:
            return cadence_now

#Planted a "copy" for Deforumation convenience
def get_resume_vars_d(folder, timestring, cadence,startframe=-1):
    DEBUG_MODE = opts.data.get("deforum_debug_mode_enabled", False)
    # count previous frames
    frame_count = 0
    if startframe != -1:
        frame_count = startframe
    else:
        for item in os.listdir(folder):
            # don't count txt files or mp4 files
            if ".txt" in item or ".mp4" in item:
                pass
            else:
                filename = item.split("_")
                # other image file types may be supported in the future,
                # so we just count files containing timestring
                # that don't contain the depth keyword (depth maps are saved in same folder)
                if timestring in filename and "depth" not in filename:
                    frame_count += 1
                    # add this to debugging var
                    if DEBUG_MODE:
                        print(f"\033[36mResuming:\033[0m File: {filename}")

    print(f"\033[36mResuming:\033[0m Current frame count: {frame_count}")

    # get last frame from frame count corrected for any trailing cadence frames
    #last_frame = frame_count - (frame_count % cadence)

    # calculate previous actual frame
    #prev_frame = last_frame - cadence

    # calculate next actual frame
    #next_frame = last_frame - 1
    if (frame_count < 1): # or ((frame_count-cadence-1) <= 0):
        turbo_prev_image, turbo_prev_frame_idx = None, 0
        turbo_next_image, turbo_next_frame_idx = None, 0
        # initialize vars
        color_match_sample = None
        start_frame = 0
        prev_frame = 0
        next_frame = 0
        prev_img = None
        next_img = None
    else:
        prev_frame = frame_count -1 # - cadence -1
        next_frame = prev_frame + 1

        # get prev_img/next_img from prev/next frame index (files start at 0, so subtract 1 for index var)
        path = os.path.join(folder, f"{timestring}_{prev_frame:09}.png")
        prev_img = cv2.imread(path)
        path = os.path.join(folder, f"{timestring}_{next_frame:09}.png")
        next_img = cv2.imread(path)

    # report resume last/next in console
    print(f"\033[36mResuming:\033[0m Last frame: {prev_frame} - Next frame: {next_frame} ")

    # returns:
    #   last frame count, accounting for cadence
    #   next frame count, accounting for cadence
    #   prev frame's image cv2 BGR
    #   next frame's image cv2 BGR
    return prev_frame, next_frame, prev_img, next_img

def render_animation(args, anim_args, video_args, parseq_args, loop_args, controlnet_args, root):
    cnu = {}
    global deforumation_cadence_scheduling_manifest
    #Deforumation_initialization
    usingDeforumation = True
    cadence_was_one = False
    original_optical_flow_cadence = anim_args.optical_flow_cadence
    original_optical_flow_redo_generation = anim_args.optical_flow_redo_generation
    #End settings
    if not cadence_was_one:
        print("Cadence was not one")
    if opts.data.get("deforum_save_gen_info_as_srt", False):  # create .srt file and set timeframe mechanism using FPS
        srt_filename = os.path.join(args.outdir, f"{root.timestring}.srt")
        srt_frame_duration = init_srt_file(srt_filename, video_args.fps)

    if anim_args.animation_mode in ['2D', '3D']:
        # handle hybrid video generation
        if anim_args.hybrid_composite != 'None' or anim_args.hybrid_motion in ['Affine', 'Perspective', 'Optical Flow']:
            print("Hybrid shit")
            args, anim_args, inputfiles = hybrid_generation(args, anim_args, root)
            # path required by hybrid functions, even if hybrid_comp_save_extra_frames is False
            hybrid_frame_path = os.path.join(args.outdir, 'hybridframes')
            print("animation_mode")
        # initialize prev_flow
        if anim_args.hybrid_motion == 'Optical Flow':
            prev_flow = None

        if loop_args.use_looper:
            print("Using Guided Images mode: seed_behavior will be set to 'schedule' and 'strength_0_no_init' to False")
            if args.strength == 0:
                raise RuntimeError("Strength needs to be greater than 0 in Init tab")
            args.strength_0_no_init = False
            args.seed_behavior = "schedule"
            if not isJson(loop_args.init_images):
                raise RuntimeError("The images set for use with keyframe-guidance are not in a proper JSON format")

    # handle controlnet video input frames generation
    if is_controlnet_enabled(controlnet_args):
        unpack_controlnet_vids(args, anim_args, controlnet_args)
    if usingDeforumation:
        mediator_setValue("deforum_interrupted", 0) #A new genearation, reset interrupt flag
        CnSchKeys = ControlNetKeys(anim_args, controlnet_args)
        for cnIndex in range(5): #Save in order to be able to restore later (Deforumation might destroy them)
            keys = [
                "enabled", "module", "model", "weight", "resize_mode", "control_mode", "low_vram", "pixel_perfect",
                "processor_res", "threshold_a", "threshold_b", "guidance_start", "guidance_end"
            ]
            currCnIndex = cnIndex+1
            cnu[f'cn_{currCnIndex}_weight'] = getattr(controlnet_args, f'cn_{currCnIndex}_weight')
            cnu[f'cn_{currCnIndex}_guidance_start'] = getattr(controlnet_args, f'cn_{currCnIndex}_guidance_start')
            cnu[f'cn_{currCnIndex}_guidance_end'] = getattr(controlnet_args, f'cn_{currCnIndex}_guidance_end')
            cnu[f'cn_{currCnIndex}_threshold_a'] = getattr(controlnet_args, f'cn_{currCnIndex}_threshold_a')
            cnu[f'cn_{currCnIndex}_threshold_b'] = getattr(controlnet_args, f'cn_{currCnIndex}_threshold_b')

    #Let the deforum mediator get the anim_args and the args values
    if usingDeforumation:
        mediator_set_anim_args(anim_args, args, root)

    #Deforumation has a chance to overwrite the keys values, if it is using parseq
    if usingDeforumation:
        print("Made for Deforumation version: 0.7.0")
        print("------------------------------------")
        if int(mediator_getValue("use_parseq").strip().strip('\n')) == 1:
            #parseq_adapter.use_parseq = 1

            temp_parseq_manifest = parseq_args.parseq_manifest
            parseq_args.parseq_manifest = str(mediator_getValue("parseq_manifest").strip())

            #keys = ParseqAnimKeys(parseq_args, anim_args, video_args)
            #keys = parseq_adapter.anim_keys
            #parseq_args.parseq_manifest = temp_parseq_manifest

            print("Using Parseq through Deforumation.")
        else:
            #print("should_use_deforumation_timestring:" + str(mediator_getValue("should_use_deforumation_timestring").strip().strip('\n')))
            if int(mediator_getValue("should_use_deforumation_timestring").strip().strip('\n')) == 1:
                #Deforum is forced to start from the frame specified by deforum
                frame_idx = int(mediator_getValue("start_frame").strip().strip('\n'))
                mediator_setValue("total_recall_relive", frame_idx)
            args.seed = int(mediator_getValue("seed").strip().strip('\n'))
            if args.seed == -1:

                args.seed = random.randint(0, 2**32 - 1)

    # initialise Parseq adapter
    parseq_adapter = ParseqAdapter(parseq_args, anim_args, video_args, controlnet_args, loop_args)

    # expand key frame strings to values
    keys = DeformAnimKeys(anim_args, args.seed) if not parseq_adapter.use_parseq else parseq_adapter.anim_keys
    loopSchedulesAndData = LooperAnimKeys(loop_args, anim_args, args.seed) if not parseq_adapter.use_parseq else parseq_adapter.looper_keys

    # create output folder for the batch
    os.makedirs(args.outdir, exist_ok=True)
    print(f"Saving animation frames to:{args.outdir}")

    # save settings.txt file for the current run
    save_settings_from_animation_run(args, anim_args, parseq_args, loop_args, controlnet_args, video_args, root)

    # resume from timestring
    if anim_args.resume_from_timestring:
        root.timestring = anim_args.resume_timestring

    # Always enable pseudo-3d with parseq. No need for an extra toggle:
    # Whether it's used or not in practice is defined by the schedules
    if parseq_adapter.use_parseq:
        anim_args.flip_2d_perspective = True

        # expand prompts out to per-frame
    #if use_parseq and keys.manages_prompts():
    if parseq_adapter.manages_prompts():
        prompt_series = keys.prompts
    else:
        prompt_series = pd.Series([np.nan for a in range(anim_args.max_frames)])
        for i, prompt in root.animation_prompts.items():
            if str(i).isdigit():
                prompt_series[int(i)] = prompt
            else:
                prompt_series[int(numexpr.evaluate(i))] = prompt
        prompt_series = prompt_series.ffill().bfill()

    # check for video inits
    using_vid_init = anim_args.animation_mode == 'Video Input'

    # load depth model for 3D
    predict_depths = (anim_args.animation_mode == '3D' and anim_args.use_depth_warping) or anim_args.save_depth_maps
    predict_depths = predict_depths or (anim_args.hybrid_composite and anim_args.hybrid_comp_mask_type in ['Depth', 'Video Depth'])
    predict_depths = predict_depths and not args.motion_preview_mode
    if predict_depths:
        keep_in_vram = opts.data.get("deforum_keep_3d_models_in_vram")

        device = ('cpu' if cmd_opts.lowvram or cmd_opts.medvram else root.device)
        depth_model = DepthModel(root.models_path, device, root.half_precision, keep_in_vram=keep_in_vram, depth_algorithm=anim_args.depth_algorithm, Width=args.W, Height=args.H,
                                 midas_weight=anim_args.midas_weight)

        # depth-based hybrid composite mask requires saved depth maps
        if anim_args.hybrid_composite != 'None' and anim_args.hybrid_comp_mask_type == 'Depth':
            anim_args.save_depth_maps = True
    else:
        depth_model = None
        anim_args.save_depth_maps = False

    raft_model = None
    load_raft = (anim_args.optical_flow_cadence == "RAFT" and int(anim_args.diffusion_cadence) > 1) or \
                (anim_args.hybrid_motion == "Optical Flow" and anim_args.hybrid_flow_method == "RAFT") or \
                (anim_args.optical_flow_redo_generation == "RAFT")
    load_raft = load_raft and not args.motion_preview_mode
    if load_raft:
        print("Loading RAFT model...")
        raft_model = RAFT()

    use_deforumation_cadence_scheduling = 0
    # state for interpolating between diffusion steps
    if usingDeforumation: #Should we Connect to the Deforumation websocket server to write the current resume frame properties?
        if int(mediator_getValue("should_use_deforumation_cadence").strip().strip('\n')) == 1:
            turbo_steps = 1 if using_vid_init else int(mediator_getValue("cadence").strip().strip('\n'))
        else:
            turbo_steps = 1 if using_vid_init else int(anim_args.diffusion_cadence)
        if turbo_steps == 1:
            cadence_was_one = True
        #if int(mediator_getValue("use_deforumation_cadence_scheduling").strip().strip('\n')) == 1:
        #    use_deforumation_cadence_scheduling = 1
        #    deforumation_cadence_scheduling_manifest = str(mediator_getValue("deforumation_cadence_scheduling_manifest").strip().strip('\n'))
        #    print("Deforumation cadence scheduling will be used:\n"
        #          "---------------------------------------------\n"
        #          + str(deforumation_cadence_scheduling_manifest) + "\n")
    else:
        turbo_steps = 1 if using_vid_init else int(anim_args.diffusion_cadence)

    turbo_prev_image, turbo_prev_frame_idx = None, 0
    turbo_next_image, turbo_next_frame_idx = None, 0

    # initialize vars
    prev_img = None
    color_match_sample = None
    start_frame = 0
    frame_idx = start_frame
    # resume animation (requires at least two frames - see function)
    if anim_args.resume_from_timestring:
        # determine last frame and frame to start on
        print("Resume From TimeString!")
        if int(mediator_getValue("should_use_deforumation_timestring").strip().strip('\n')) == 1:
            #Deforum is forced to start from the frame specified by deforum
            frame_idx = int(mediator_getValue("start_frame").strip().strip('\n'))
            ######>>>>>>
            mediator_setValue("total_recall_relive", frame_idx)
            if int(mediator_getValue("should_use_deforumation_cadence").strip().strip('\n')) == 1:
                turbo_steps = int(mediator_getValue("cadence").strip().strip('\n'))
                print("Cadence value read from Deforumation:"+str(turbo_steps))
            else:
                print("Cadence value read from Deforum:"+str(turbo_steps))
                turbo_steps = int(anim_args.diffusion_cadence)

            #frame_idx = frame_idx - (frame_idx%turbo_steps)

            prev_frame, next_frame, prev_img, next_img = get_resume_vars_d(
                folder=args.outdir,
                timestring=root.timestring,
                cadence=turbo_steps,
                startframe=frame_idx
            )
            # set up turbo step vars
            if turbo_steps > 1:
                turbo_prev_image, turbo_prev_frame_idx = prev_img, prev_frame
                turbo_next_image, turbo_next_frame_idx = next_img, next_frame
            # advance start_frame to next frame
            if prev_img is None:
                root.init_sample = None
                root.initial_info = None
                root.first_frame = None
                start_frame = 0
                frame_idx = 0
                print("Trying to close StableDiffusionProcessingImg2Img")
                p = StableDiffusionProcessingImg2Img(sd_model=sd_model,outpath_samples = opts.outdir_samples or opts.outdir_img2img_samples, )
                p.close()
            else:
                start_frame = frame_idx
            mediator_setValue("total_recall_relive", frame_idx)
            args.seed = int(mediator_getValue("seed").strip().strip('\n'))
            state.job_count = anim_args.max_frames

        else:
            prev_frame, next_frame, prev_img, next_img = get_resume_vars(
                folder=args.outdir,
                timestring=root.timestring,
                cadence=turbo_steps
            )

            # set up turbo step vars
            if turbo_steps > 1:
                turbo_prev_image, turbo_prev_frame_idx = prev_img, prev_frame
                turbo_next_image, turbo_next_frame_idx = next_img, next_frame

        # advance start_frame to next frame
        #start_frame = next_frame + 1

    if usingDeforumation: #Should we Connect to the Deforumation websocket server to write the current resume frame properties?
        mediator_setValue("total_recall_relive", frame_idx)
        mediator_setValue("should_resume", 0)
        print("DEFORUM, SETTING OUTDIR:"+args.outdir)
        mediator_setValue("frame_outdir", args.outdir)
        print("DEFORUM, SETTING RESUME OR TIME-STRING:"+str(root.timestring))
        mediator_setValue("resume_timestring", root.timestring)

        #Should we use parseq? (use_deforumation_cadence_scheduling = parseq should be used)
        if int(mediator_getValue("use_deforumation_cadence_scheduling").strip().strip('\n')) == 1:
            use_deforumation_cadence_scheduling = 1
            deforumation_cadence_scheduling_manifest = str(mediator_getValue("deforumation_cadence_scheduling_manifest").strip().strip('\n'))
            print("Deforumation cadence scheduling will be used:\n"
                  "---------------------------------------------\n"
                  + str(deforumation_cadence_scheduling_manifest) + "\n")
            turbo_steps = get_now_cadence(frame_idx)
            print("Starting on frame:"+str(frame_idx)+", cadence:"+str(turbo_steps)+", should be used.")

    previous_turbo_steps = turbo_steps #usingDeforumation

    # reset the mask vals as they are overwritten in the compose_mask algorithm
    mask_vals = {}
    noise_mask_vals = {}

    mask_vals['everywhere'] = Image.new('1', (args.W, args.H), 1)
    noise_mask_vals['everywhere'] = Image.new('1', (args.W, args.H), 1)

    mask_image = None

    if args.use_init and args.init_image != None and args.init_image != '':
        _, mask_image = load_img(args.init_image,
                                 args.init_image_box,
                                 shape=(args.W, args.H),
                                 use_alpha_as_mask=args.use_alpha_as_mask)
        mask_vals['video_mask'] = mask_image
        noise_mask_vals['video_mask'] = mask_image

    # Grab the first frame masks since they wont be provided until next frame
    # Video mask overrides the init image mask, also, won't be searching for init_mask if use_mask_video is set
    # Made to solve https://github.com/deforum-art/deforum-for-automatic1111-webui/issues/386
    if anim_args.use_mask_video:
        args.mask_file = get_mask_from_file(get_next_frame(args.outdir, anim_args.video_mask_path, frame_idx, True), args)
        root.noise_mask = get_mask_from_file(get_next_frame(args.outdir, anim_args.video_mask_path, frame_idx, True), args)

        mask_vals['video_mask'] = get_mask_from_file(get_next_frame(args.outdir, anim_args.video_mask_path, frame_idx, True), args)
        noise_mask_vals['video_mask'] = get_mask_from_file(get_next_frame(args.outdir, anim_args.video_mask_path, frame_idx, True), args)
    elif mask_image is None and args.use_mask:
        mask_vals['video_mask'] = get_mask(args)
        noise_mask_vals['video_mask'] = get_mask(args)  # TODO?: add a different default noisc mask

    # get color match for 'Image' color coherence only once, before loop
    if anim_args.color_coherence == 'Image':
        color_match_sample = load_image(anim_args.color_coherence_image_path)
        color_match_sample = color_match_sample.resize((args.W, args.H), PIL.Image.LANCZOS)
        color_match_sample = cv2.cvtColor(np.array(color_match_sample), cv2.COLOR_RGB2BGR)

    # Webui
    state.job_count = anim_args.max_frames
    last_preview_frame = 0

    while frame_idx < anim_args.max_frames:
        state.job = f"frame {frame_idx + 1}/{anim_args.max_frames}"
        state.job_no = frame_idx + 1

        if state.skipped:
            print("\n** PAUSED **")
            state.skipped = False
            while not state.skipped:
                time.sleep(0.1)
            print("** RESUMING **")
        if usingDeforumation: #Should we Connect to the Deforumation websocket server and get is_paused_rendering?
            ispaused = 0
            while int(mediator_getValue("is_paused_rendering").strip().strip('\n')) == 1:
                if ispaused == 0:
                    print("\n** PAUSED **")
                    ispaused = 1
                time.sleep(0.5)
            if ispaused:
                print("** RESUMING **")
                start_frame = int(mediator_getValue("start_frame").strip().strip('\n'))
                if int(mediator_getValue("should_use_total_recall").strip().strip('\n')):
                    mediator_setValue("total_recall_relive", start_frame)

            if int(mediator_getValue("should_use_deforumation_cadence").strip().strip('\n')) == 1:
                turbo_steps = int(mediator_getValue("cadence").strip().strip('\n'))
            # resume animation (requires at least two frames - see function)
            # determine last frame and frame to start on
            # get last frame from frame count corrected for any trailing cadence frames
            should_use_deforumation_cadence_schedule = int(mediator_getValue("use_deforumation_cadence_scheduling").strip().strip('\n'))
            if using_vid_init:
                turbo_steps = 1

            elif should_use_deforumation_cadence_schedule and use_deforumation_cadence_scheduling == 0:
                use_deforumation_cadence_scheduling = 1
                deforumation_cadence_scheduling_manifest = str(mediator_getValue("deforumation_cadence_scheduling_manifest").strip().strip('\n'))
                print("Deforumation cadence scheduling was not used but will now be used!\n"
                      "------------------------------------------------------------------")
                turbo_steps = get_now_cadence(frame_idx)
                print("get_now_cadence() returned" + str(turbo_steps))
                if int(mediator_getValue("should_use_deforumation_cadence").strip().strip('\n')) == 1:
                    turbo_steps = int(mediator_getValue("cadence").strip().strip('\n'))
            elif should_use_deforumation_cadence_schedule and use_deforumation_cadence_scheduling == 1:
                turbo_steps = get_now_cadence(frame_idx)
                print("Deforumation cadence scheduling conntineously used, cadence:" + str(turbo_steps))
            elif int(mediator_getValue("should_use_deforumation_cadence").strip().strip('\n')) == 1:
                turbo_steps = int(mediator_getValue("cadence").strip().strip('\n'))
                #print("Cadence value read from Deforumation:"+str(turbo_steps))
                mediator_setValue("deforum_cadence", turbo_steps)
            else:
                print("Cadence value read from Deforum:"+str(turbo_steps))
                turbo_steps = int(anim_args.diffusion_cadence)
                mediator_setValue("deforum_cadence", turbo_steps)
                mediator_setValue("cadence", turbo_steps)

            shouldResume = int(mediator_getValue("should_resume").strip().strip('\n'))  #should_resume should be set when third party chooses another frame (rewinding forward, etc), it doesn't need to happen in paused mode
            if (previous_turbo_steps != turbo_steps) or (shouldResume == 1):
                #print("frame_idx is:"+str(frame_idx))
                #print("previous_turbo_steps is:"+str(previous_turbo_steps))
                #print("turbo_steps is:"+str(turbo_steps))
                #print("That would oldly have taken us to:"+str(frame_idx - previous_turbo_steps - (frame_idx%turbo_steps)))
                #print("But staying on:"+str(frame_idx))

                if shouldResume == 1: #If shouldResume == 1, then third party has choosen to jump to a non continues frame
                    #print("Gott the start_frame:"+str(start_frame))
                    #print("Was at frame:"+str(frame_idx))
                    mediator_setValue("should_resume", 0)

                start_frame = int(mediator_getValue("start_frame").strip().strip('\n'))

                if frame_idx != start_frame or previous_turbo_steps != turbo_steps:
                    frame_idx = start_frame  #- (start_frame%turbo_steps)
                    previous_turbo_steps = turbo_steps

                    #if (previous_turbo_steps != turbo_steps)
                    #    frame_idx = turbo_next_frame_idx - previous_turbo_steps + turbo_steps

                    #frame_idx = frame_idx - previous_turbo_steps #Undo the last render
                    #frame_idx = frame_idx - previous_turbo_steps - (frame_idx%turbo_steps)
                    #frame_idx = frame_idx - (frame_idx%turbo_steps)
                    #print("frame_idx is:"+str(frame_idx))
                    #print("previous_turbo_steps is:"+str(previous_turbo_steps))
                    #print("turbo_steps is:"+str(turbo_steps))
                    #print("That would oldly have taken us to:"+str(frame_idx - previous_turbo_steps - (frame_idx%turbo_steps)))
                    #frame_idx = turbo_next_frame_idx - previous_turbo_steps + turbo_steps
                    #print("But staying on:"+str(frame_idx))

                    prev_frame, next_frame, prev_img, next_img = get_resume_vars_d(
                        folder=args.outdir,
                        timestring=root.timestring,
                        cadence=turbo_steps,
                        startframe=frame_idx
                    )
                    # set up turbo step vars
                    if turbo_steps > 1:
                        turbo_prev_image, turbo_prev_frame_idx = prev_img, prev_frame
                        turbo_next_image, turbo_next_frame_idx = next_img, next_frame
                    # advance start_frame to next frame
                    if prev_img is None:
                        root.init_sample = None
                        root.initial_info = None
                        root.first_frame = None
                        start_frame = 0
                        frame_idx = 0
                        print("Trying to close StableDiffusionProcessingImg2Img")
                        p = StableDiffusionProcessingImg2Img(sd_model=sd_model,outpath_samples = opts.outdir_samples or opts.outdir_img2img_samples, )
                        p.close()

                    mediator_setValue("total_recall_relive", frame_idx)
                    args.seed = int(mediator_getValue("seed").strip().strip('\n'))
                    if args.seed == -1:
                        args.seed = random.randint(0, 2**32 - 1)
                        print("Using random seed:"+str(args.seed))

                    state.job_count = anim_args.max_frames
                    last_preview_frame = frame_idx
                    state.job = f"frame {frame_idx + 1}/{anim_args.max_frames}"
                    state.job_no = frame_idx + 1

            mediator_setValue("deforum_cadence", turbo_steps)
            previous_turbo_steps = turbo_steps #usingDeforumation
            #print("previous_turbo_steps:"+str(previous_turbo_steps))
            #print("turbo_steps:" + str(turbo_steps))
            #print("turbo_prev_frame_idx:" + str(turbo_prev_frame_idx))
            #print("turbo_next_frame_idx:" + str(turbo_next_frame_idx))
            #print("start_frame:" + str(start_frame))
            #print("frame_idx:" + str(frame_idx))

            #else:
            #    donothing = 0

        print(f"\033[36mAnimation frame: \033[0m{frame_idx}/{anim_args.max_frames}  ")

        noise = keys.noise_schedule_series[frame_idx]
        if usingDeforumation: #Should we Connect to the Deforumation websocket server and get seed_changed == new seed?
            if int(mediator_getValue("seed_changed").strip().strip('\n')):
                print("Seed has been changed!")
                args.seed = int(mediator_getValue("seed").strip().strip('\n'))
                mediator_setValue("seed_changed", 0)
                if args.seed == -1:
                    args.seed = random.randint(0, 2**32 - 1)
                print("To:"+str(args.seed))
        if usingDeforumation:
            if (int(mediator_getValue("should_use_deforumation_strength").strip().strip('\n')) == 1):
                strength = float(mediator_getValue("strength").strip().strip('\n'))
                mediator_setValue("deforum_strength", strength)
            else:
                strength = keys.strength_schedule_series[frame_idx]
                mediator_setValue("deforum_strength", strength)
                mediator_setValue("strength", strength)
        else:
            strength = keys.strength_schedule_series[frame_idx]
        if usingDeforumation:
            if (int(mediator_getValue("should_use_deforumation_cfg").strip().strip('\n')) == 1):
                scale = float(mediator_getValue("cfg").strip().strip('\n'))
                mediator_setValue("deforum_cfg", scale)
            else:
                scale = keys.cfg_scale_schedule_series[frame_idx]
                mediator_setValue("deforum_cfg", scale)
                mediator_setValue("cfg", scale)
        else: #if usingDeforumation == False or connectedToServer == False: #If we are not using Deforumation, go with the values in Deforum GUI (or if we can't connect to the Deforumation server).
            scale = keys.cfg_scale_schedule_series[frame_idx]

        contrast = keys.contrast_schedule_series[frame_idx]
        kernel = int(keys.kernel_schedule_series[frame_idx])
        sigma = keys.sigma_schedule_series[frame_idx]
        amount = keys.amount_schedule_series[frame_idx]
        threshold = keys.threshold_schedule_series[frame_idx]
        cadence_flow_factor = keys.cadence_flow_factor_schedule_series[frame_idx]
        redo_flow_factor = keys.redo_flow_factor_schedule_series[frame_idx]
        hybrid_comp_schedules = {
            "alpha": keys.hybrid_comp_alpha_schedule_series[frame_idx],
            "mask_blend_alpha": keys.hybrid_comp_mask_blend_alpha_schedule_series[frame_idx],
            "mask_contrast": keys.hybrid_comp_mask_contrast_schedule_series[frame_idx],
            "mask_auto_contrast_cutoff_low": int(keys.hybrid_comp_mask_auto_contrast_cutoff_low_schedule_series[frame_idx]),
            "mask_auto_contrast_cutoff_high": int(keys.hybrid_comp_mask_auto_contrast_cutoff_high_schedule_series[frame_idx]),
            "flow_factor": keys.hybrid_flow_factor_schedule_series[frame_idx]
        }
        scheduled_sampler_name = None
        scheduled_clipskip = None
        scheduled_noise_multiplier = None
        scheduled_ddim_eta = None
        scheduled_ancestral_eta = None

        mask_seq = None
        noise_mask_seq = None

        if usingDeforumation: #Should we Connect to the Deforumation websocket server to get CFG values?
            args.steps = int(mediator_getValue("steps").strip().strip('\n'))
            mediator_setValue("deforum_steps", args.steps)
        else: #If we are not using Deforumation, go with the values in Deforum GUI (or if we can't connect to the Deforumation server).
            if anim_args.enable_steps_scheduling and keys.steps_schedule_series[frame_idx] is not None:
                args.steps = int(keys.steps_schedule_series[frame_idx])
                mediator_setValue("deforum_steps", args.steps)
                mediator_setValue("steps", args.steps)
        if anim_args.enable_sampler_scheduling and keys.sampler_schedule_series[frame_idx] is not None:
            scheduled_sampler_name = keys.sampler_schedule_series[frame_idx].casefold()
        if anim_args.enable_clipskip_scheduling and keys.clipskip_schedule_series[frame_idx] is not None:
            scheduled_clipskip = int(keys.clipskip_schedule_series[frame_idx])

        if usingDeforumation:
            if int(mediator_getValue("should_use_deforumation_noise").strip().strip('\n')) == 1:
                scheduled_noise_multiplier = float(mediator_getValue("noise_multiplier").strip().strip('\n'))
                mediator_setValue("deforum_noise_multiplier", scheduled_noise_multiplier)
                if scheduled_noise_multiplier == 0:
                    scheduled_noise_multiplier = None
                    print("Noise multiplier scheduling, Disabled")
            else:
                if anim_args.enable_noise_multiplier_scheduling and keys.noise_multiplier_schedule_series[frame_idx] is not None:
                    scheduled_noise_multiplier = float(keys.noise_multiplier_schedule_series[frame_idx])
                    mediator_setValue("deforum_noise_multiplier", scheduled_noise_multiplier)
                    mediator_setValue("noise_multiplier", scheduled_noise_multiplier)
        elif anim_args.enable_noise_multiplier_scheduling and keys.noise_multiplier_schedule_series[frame_idx] is not None:
                scheduled_noise_multiplier = float(keys.noise_multiplier_schedule_series[frame_idx])

        if anim_args.enable_ddim_eta_scheduling and keys.ddim_eta_schedule_series[frame_idx] is not None:
            scheduled_ddim_eta = float(keys.ddim_eta_schedule_series[frame_idx])
        if anim_args.enable_ancestral_eta_scheduling and keys.ancestral_eta_schedule_series[frame_idx] is not None:
            scheduled_ancestral_eta = float(keys.ancestral_eta_schedule_series[frame_idx])
        if args.use_mask and keys.mask_schedule_series[frame_idx] is not None:
            mask_seq = keys.mask_schedule_series[frame_idx]
        if anim_args.use_noise_mask and keys.noise_mask_schedule_series[frame_idx] is not None:
            noise_mask_seq = keys.noise_mask_schedule_series[frame_idx]

        if args.use_mask and not anim_args.use_noise_mask:
            noise_mask_seq = mask_seq

        depth = None

        if anim_args.animation_mode == '3D' and (cmd_opts.lowvram or cmd_opts.medvram):
            # Unload the main checkpoint and load the depth model
            lowvram.send_everything_to_cpu()
            sd_hijack.model_hijack.undo_hijack(sd_model)
            devices.torch_gc()
            if predict_depths: depth_model.to(root.device)

        if turbo_steps == 1 and opts.data.get("deforum_save_gen_info_as_srt"):
            params_to_print = opts.data.get("deforum_save_gen_info_as_srt_params", ['Seed'])
            params_string = format_animation_params(keys, prompt_series, frame_idx, params_to_print)
            write_frame_subtitle(srt_filename, frame_idx, srt_frame_duration, f"F#: {frame_idx}; Cadence: false; Seed: {args.seed}; {params_string}")
            params_string = None

        if usingDeforumation: #Should we Connect to the Deforumation websocket server to get CFG values?
            should_use_deforumation_cadence_schedule = int(mediator_getValue("use_deforumation_cadence_scheduling").strip().strip('\n'))
            if using_vid_init:
                turbo_steps = 1

            elif should_use_deforumation_cadence_schedule and use_deforumation_cadence_scheduling == 0:
                use_deforumation_cadence_scheduling = 1
                deforumation_cadence_scheduling_manifest = str(mediator_getValue("deforumation_cadence_scheduling_manifest").strip().strip('\n'))
                print("Deforumation cadence scheduling was not used but will now be used!\n"
                      "------------------------------------------------------------------")
                turbo_steps = get_now_cadence(frame_idx)
                print("get_now_cadence() returned" + str(turbo_steps))
                if int(mediator_getValue("should_use_deforumation_cadence").strip().strip('\n')) == 1:
                    turbo_steps = int(mediator_getValue("cadence").strip().strip('\n'))
            elif should_use_deforumation_cadence_schedule and use_deforumation_cadence_scheduling == 1:
                turbo_steps = get_now_cadence(frame_idx)
                print("Deforumation cadence scheduling conntineously used, cadence:" + str(turbo_steps))
            elif int(mediator_getValue("should_use_deforumation_cadence").strip().strip('\n')) == 1:
                turbo_steps = int(mediator_getValue("cadence").strip().strip('\n'))
                #print("Cadence value read from Deforumation:"+str(turbo_steps))
                mediator_setValue("deforum_cadence", turbo_steps)
            else:
                #print("Cadence value read from Deforum:"+str(turbo_steps))
                turbo_steps = int(anim_args.diffusion_cadence)
                mediator_setValue("deforum_cadence", turbo_steps)
                mediator_setValue("cadence", turbo_steps)

            if previous_turbo_steps != turbo_steps:
                #print("frame_idX is:"+str(frame_idx))
                #print("previous_turbo_steps is:"+str(previous_turbo_steps))
                #print("turbo_steps is:"+str(turbo_steps))
                #print("That would oldly have taken us to:"+str(frame_idx - previous_turbo_steps - (frame_idx%turbo_steps)))
                frame_idx = turbo_next_frame_idx - previous_turbo_steps + turbo_steps
                #print("But staying on:"+str(frame_idx))

                prev_frame, next_frame, prev_img, next_img = get_resume_vars_d(
                    folder=args.outdir,
                    timestring=root.timestring,
                    cadence=turbo_steps,
                    startframe=frame_idx
                )
                # set up turbo step vars
                if turbo_steps > 1:
                    turbo_prev_image, turbo_prev_frame_idx = prev_img, prev_frame
                    turbo_next_image, turbo_next_frame_idx = next_img, next_frame
                # advance start_frame to next frame
                if prev_img is None:
                    root.init_sample = None
                    root.initial_info = None
                    root.first_frame = None
                start_frame = 0

                mediator_setValue("total_recall_relive", frame_idx)
                args.seed = int(mediator_getValue("seed").strip().strip('\n'))
                state.job_count = anim_args.max_frames

                state.job_count = anim_args.max_frames
                last_preview_frame = frame_idx
                state.job = f"frame {frame_idx + 1}/{anim_args.max_frames}"
                state.job_no = frame_idx + 1
            #else:
            #    print("****************** Warning: You are using total recall, and therefore no forced Rewind to match Cadence will be done! ******************")
            #    print("previous_turbo_steps was:"+str(previous_turbo_steps))
            #    print("and now turbo_steps:"+str(turbo_steps))

            mediator_setValue("deforum_cadence", turbo_steps)
            previous_turbo_steps = turbo_steps #usingDeforumation
            #print("previous_turbo_steps:"+str(previous_turbo_steps))

        if turbo_steps > 1:
            tween_frame_start_idx = max(start_frame, frame_idx - turbo_steps) #Maybe need to fix .png image #usingDeforumation
            cadence_flow = None
            if frame_idx < 0:
                print("frame_idx pushed into negative, so adjusting frame_idx to 0.")
                frame_idx = 0
                tween_frame_start_idx = 0

            if usingDeforumation:
                if int(mediator_getValue("should_use_total_recall").strip().strip('\n')):
                    #mediator_setValue("total_recall_relive", frame_idx)
                    mediator_setValue("total_recall_relive", tween_frame_start_idx)
                    print("Total recall (before tween_creation) at frame " + str(tween_frame_start_idx))
                should_use_optical_flow = int(mediator_getValue("should_use_optical_flow").strip().strip('\n'))
                if should_use_optical_flow != 1:
                    anim_args.optical_flow_cadence = "None"
                    anim_args.optical_flow_redo_generation = "None"
                    print("Disabling optical flow")
                else:
                    anim_args.optical_flow_cadence = original_optical_flow_cadence
                    anim_args.optical_flow_redo_generation = original_optical_flow_redo_generation

                    cadence_flow_factor = int(mediator_getValue("cadence_flow_factor").strip().strip('\n'))
                    redo_flow_factor = int(mediator_getValue("generation_flow_factor").strip().strip('\n'))
                    print("Enabling optical flow")

            for tween_frame_idx in range(tween_frame_start_idx, frame_idx):
                #If controlnet is being used, get the values from Deforumation
                if usingDeforumation:
                    if int(mediator_getValue("should_use_total_recall").strip().strip('\n')):
                        print("Total recall (inside tween_creation) at frame " + str(tween_frame_idx))
                        #mediator_setValue("total_recall_relive", frame_idx)
                    mediator_setValue("total_recall_relive", tween_frame_idx)

                    if is_controlnet_enabled(controlnet_args):
                        for cnIndex in range(5):
                            currCnIndex = cnIndex+1
                            cn_udcn = int(mediator_getValue("cn_udcn"+str(cnIndex+1)).strip().strip('\n'))
                            if cn_udcn == 1:
                                setattr(controlnet_args, f'cn_{currCnIndex}_weight', "0:(" + str(mediator_getValue("cn_weight"+str(cnIndex+1)).strip().strip('\n')) + ")" )
                                #print(str(getattr(controlnet_args, f'cn_{currCnIndex}_weight')))
                                setattr(controlnet_args, f'cn_{currCnIndex}_guidance_start', "0:(" + str(mediator_getValue("cn_stepstart"+str(cnIndex+1)).strip().strip('\n')) + ")" )
                                #print(str(getattr(controlnet_args, f'cn_{currCnIndex}_guidance_start')))
                                setattr(controlnet_args, f'cn_{currCnIndex}_guidance_end', "0:(" + str(mediator_getValue("cn_stepend"+str(cnIndex+1)).strip().strip('\n')) + ")" )
                                #print(str(getattr(controlnet_args, f'cn_{currCnIndex}_guidance_end')))
                                setattr(controlnet_args, f'cn_{currCnIndex}_threshold_a', int(mediator_getValue("cn_lowt"+str(cnIndex+1)).strip().strip('\n')))
                                #print(str(getattr(controlnet_args, f'cn_{currCnIndex}_threshold_a')))
                                setattr(controlnet_args, f'cn_{currCnIndex}_threshold_b', int(mediator_getValue("cn_hight"+str(cnIndex+1)).strip().strip('\n')))
                                #print(str(getattr(controlnet_args, f'cn_{currCnIndex}_threshold_b')))
                            else:
                                setattr(controlnet_args, f'cn_{currCnIndex}_weight', cnu[f'cn_{currCnIndex}_weight'])
                                setattr(controlnet_args, f'cn_{currCnIndex}_guidance_start', cnu[f'cn_{currCnIndex}_guidance_start'])
                                setattr(controlnet_args, f'cn_{currCnIndex}_guidance_end', cnu[f'cn_{currCnIndex}_guidance_end'])
                                setattr(controlnet_args, f'cn_{currCnIndex}_threshold_a', cnu[f'cn_{currCnIndex}_threshold_a'])
                                setattr(controlnet_args, f'cn_{currCnIndex}_threshold_b', cnu[f'cn_{currCnIndex}_threshold_b'])
                                mediator_setValue("cn_weight"+str(cnIndex+1), getattr(CnSchKeys, f"cn_{currCnIndex}_weight_schedule_series")[frame_idx])
                                mediator_setValue("cn_stepstart"+str(cnIndex+1),getattr(CnSchKeys, f"cn_{currCnIndex}_guidance_start_schedule_series")[frame_idx])
                                mediator_setValue("cn_stepend"+str(cnIndex+1), getattr(CnSchKeys, f"cn_{currCnIndex}_guidance_end_schedule_series")[frame_idx])
                                mediator_setValue("cn_lowt"+str(cnIndex+1), getattr(controlnet_args, f"cn_{currCnIndex}_threshold_a"))
                                mediator_setValue("cn_hight"+str(cnIndex+1), getattr(controlnet_args, f"cn_{currCnIndex}_threshold_b"))

                #print("!Inside tween for loop!")
                # update progress during cadence
                state.job = f"frame {tween_frame_idx + 1}/{anim_args.max_frames}"
                state.job_no = tween_frame_idx + 1
                # cadence vars
                tween = float(tween_frame_idx - tween_frame_start_idx + 1) / float(frame_idx - tween_frame_start_idx)
                advance_prev = turbo_prev_image is not None and tween_frame_idx > turbo_prev_frame_idx
                advance_next = tween_frame_idx > turbo_next_frame_idx

                # optical flow cadence setup before animation warping
                if anim_args.animation_mode in ['2D', '3D'] and anim_args.optical_flow_cadence != 'None':
                    if keys.strength_schedule_series[tween_frame_start_idx] > 0:
                        if cadence_flow is None and turbo_prev_image is not None and turbo_next_image is not None:
                            cadence_flow = get_flow_from_images(turbo_prev_image, turbo_next_image, anim_args.optical_flow_cadence, raft_model) / 2
                            print("Creating: turbo_next_image")
                            turbo_next_image = image_transform_optical_flow(turbo_next_image, -cadence_flow, 1)

                if opts.data.get("deforum_save_gen_info_as_srt"):
                    params_to_print = opts.data.get("deforum_save_gen_info_as_srt_params", ['Seed'])
                    params_string = format_animation_params(keys, prompt_series, tween_frame_idx, params_to_print)
                    write_frame_subtitle(srt_filename, tween_frame_idx, srt_frame_duration, f"F#: {tween_frame_idx}; Cadence: {tween < 1.0}; Seed: {args.seed}; {params_string}")
                    params_string = None

                print(f"Creating in-between {'' if cadence_flow is None else anim_args.optical_flow_cadence + ' optical flow '}cadence frame: {tween_frame_idx}; tween:{tween:0.2f};")

                if depth_model is not None:
                    assert (turbo_next_image is not None)
                    depth = depth_model.predict(turbo_next_image, anim_args.midas_weight, root.half_precision)

                if advance_prev:
                    turbo_prev_image, _ = anim_frame_warp(turbo_prev_image, args, anim_args, keys, tween_frame_idx, depth_model, depth=depth, device=root.device, half_precision=root.half_precision)
                if advance_next:
                    turbo_next_image, _ = anim_frame_warp(turbo_next_image, args, anim_args, keys, tween_frame_idx, depth_model, depth=depth, device=root.device, half_precision=root.half_precision)

                # hybrid video motion - warps turbo_prev_image or turbo_next_image to match motion
                if tween_frame_idx > 0:
                    if anim_args.hybrid_motion in ['Affine', 'Perspective']:
                        if anim_args.hybrid_motion_use_prev_img:
                            matrix = get_matrix_for_hybrid_motion_prev(tween_frame_idx - 1, (args.W, args.H), inputfiles, prev_img, anim_args.hybrid_motion)
                            if advance_prev:
                                turbo_prev_image = image_transform_ransac(turbo_prev_image, matrix, anim_args.hybrid_motion)
                            if advance_next:
                                turbo_next_image = image_transform_ransac(turbo_next_image, matrix, anim_args.hybrid_motion)
                        else:
                            matrix = get_matrix_for_hybrid_motion(tween_frame_idx - 1, (args.W, args.H), inputfiles, anim_args.hybrid_motion)
                            if advance_prev:
                                turbo_prev_image = image_transform_ransac(turbo_prev_image, matrix, anim_args.hybrid_motion)
                            if advance_next:
                                turbo_next_image = image_transform_ransac(turbo_next_image, matrix, anim_args.hybrid_motion)
                    if anim_args.hybrid_motion in ['Optical Flow']:
                        if anim_args.hybrid_motion_use_prev_img:
                            flow = get_flow_for_hybrid_motion_prev(tween_frame_idx - 1, (args.W, args.H), inputfiles, hybrid_frame_path, prev_flow, prev_img, anim_args.hybrid_flow_method, raft_model,
                                                                   anim_args.hybrid_flow_consistency, anim_args.hybrid_consistency_blur, anim_args.hybrid_comp_save_extra_frames)
                            if advance_prev:
                                turbo_prev_image = image_transform_optical_flow(turbo_prev_image, flow, hybrid_comp_schedules['flow_factor'])
                            if advance_next:
                                turbo_next_image = image_transform_optical_flow(turbo_next_image, flow, hybrid_comp_schedules['flow_factor'])
                            prev_flow = flow
                        else:
                            flow = get_flow_for_hybrid_motion(tween_frame_idx - 1, (args.W, args.H), inputfiles, hybrid_frame_path, prev_flow, anim_args.hybrid_flow_method, raft_model,
                                                              anim_args.hybrid_flow_consistency, anim_args.hybrid_consistency_blur, anim_args.hybrid_comp_save_extra_frames)
                            if advance_prev:
                                turbo_prev_image = image_transform_optical_flow(turbo_prev_image, flow, hybrid_comp_schedules['flow_factor'])
                            if advance_next:
                                turbo_next_image = image_transform_optical_flow(turbo_next_image, flow, hybrid_comp_schedules['flow_factor'])
                            prev_flow = flow

                # do optical flow cadence after animation warping
                if cadence_flow is not None:
                    print("Cadence flow exists")
                    cadence_flow = abs_flow_to_rel_flow(cadence_flow, args.W, args.H)
                    cadence_flow, _ = anim_frame_warp(cadence_flow, args, anim_args, keys, tween_frame_idx, depth_model, depth=depth, device=root.device, half_precision=root.half_precision)
                    cadence_flow_inc = rel_flow_to_abs_flow(cadence_flow, args.W, args.H) * tween
                    if advance_prev:
                        turbo_prev_image = image_transform_optical_flow(turbo_prev_image, cadence_flow_inc, cadence_flow_factor)
                    if advance_next:
                        turbo_next_image = image_transform_optical_flow(turbo_next_image, cadence_flow_inc, cadence_flow_factor)

                turbo_prev_frame_idx = turbo_next_frame_idx = tween_frame_idx
                if turbo_prev_image is not None and tween < 1.0:
                    img = turbo_prev_image * (1.0 - tween) + turbo_next_image * tween
                else:
                    img = turbo_next_image

                # intercept and override to grayscale
                if anim_args.color_force_grayscale:
                    img = cv2.cvtColor(img.astype(np.uint8), cv2.COLOR_BGR2GRAY)
                    img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

                    # overlay mask
                if args.overlay_mask and (anim_args.use_mask_video or args.use_mask):
                    img = do_overlay_mask(args, anim_args, img, tween_frame_idx, True)

                # get prev_img during cadence
                prev_img = img

                # current image update for cadence frames (left commented because it doesn't currently update the preview)
                # state.current_image = Image.fromarray(cv2.cvtColor(img.astype(np.uint8), cv2.COLOR_BGR2RGB))

                # saving cadence frames
                filename = f"{root.timestring}_{tween_frame_idx:09}.png"
                cv2.imwrite(os.path.join(args.outdir, filename), img)
                if usingDeforumation: #Should we Connect to the Deforumation websocket server to tell 3:d parties what frame we are on currently?
                    shouldResume = int(mediator_getValue("should_resume").strip().strip('\n'))  #If the user pushed "Set current image" in Deforumation, we can't just overwrite the new start_frame
                    if not shouldResume:
                        mediator_setValue("start_frame", tween_frame_idx)
                    mediator_setValue("saved_frame_params", tween_frame_idx)

                if anim_args.save_depth_maps:
                    depth_model.save(os.path.join(args.outdir, f"{root.timestring}_depth_{tween_frame_idx:09}.png"), depth)

        # get color match for video outside of prev_img conditional
        hybrid_available = anim_args.hybrid_composite != 'None' or anim_args.hybrid_motion in ['Optical Flow', 'Affine', 'Perspective']
        if anim_args.color_coherence == 'Video Input' and hybrid_available:
            if int(frame_idx) % int(anim_args.color_coherence_video_every_N_frames) == 0:
                prev_vid_img = Image.open(os.path.join(args.outdir, 'inputframes', get_frame_name(anim_args.video_init_path) + f"{frame_idx:09}.jpg"))
                prev_vid_img = prev_vid_img.resize((args.W, args.H), PIL.Image.LANCZOS)
                color_match_sample = np.asarray(prev_vid_img)
                color_match_sample = cv2.cvtColor(color_match_sample, cv2.COLOR_RGB2BGR)

        # after 1st frame, prev_img exists
        if prev_img is not None:
            # apply transforms to previous frame
            prev_img, depth = anim_frame_warp(prev_img, args, anim_args, keys, frame_idx, depth_model, depth=None, device=root.device, half_precision=root.half_precision)

            # do hybrid compositing before motion
            if anim_args.hybrid_composite == 'Before Motion':
                args, prev_img = hybrid_composite(args, anim_args, frame_idx, prev_img, depth_model, hybrid_comp_schedules, root)

            # hybrid video motion - warps prev_img to match motion, usually to prepare for compositing
            if anim_args.hybrid_motion in ['Affine', 'Perspective']:
                if anim_args.hybrid_motion_use_prev_img:
                    matrix = get_matrix_for_hybrid_motion_prev(frame_idx - 1, (args.W, args.H), inputfiles, prev_img, anim_args.hybrid_motion)
                else:
                    matrix = get_matrix_for_hybrid_motion(frame_idx - 1, (args.W, args.H), inputfiles, anim_args.hybrid_motion)
                prev_img = image_transform_ransac(prev_img, matrix, anim_args.hybrid_motion)
            if anim_args.hybrid_motion in ['Optical Flow']:
                if anim_args.hybrid_motion_use_prev_img:
                    flow = get_flow_for_hybrid_motion_prev(frame_idx - 1, (args.W, args.H), inputfiles, hybrid_frame_path, prev_flow, prev_img, anim_args.hybrid_flow_method, raft_model,
                                                           anim_args.hybrid_flow_consistency, anim_args.hybrid_consistency_blur, anim_args.hybrid_comp_save_extra_frames)
                else:
                    flow = get_flow_for_hybrid_motion(frame_idx - 1, (args.W, args.H), inputfiles, hybrid_frame_path, prev_flow, anim_args.hybrid_flow_method, raft_model,
                                                      anim_args.hybrid_flow_consistency, anim_args.hybrid_consistency_blur, anim_args.hybrid_comp_save_extra_frames)
                prev_img = image_transform_optical_flow(prev_img, flow, hybrid_comp_schedules['flow_factor'])
                prev_flow = flow

            # do hybrid compositing after motion (normal)
            if anim_args.hybrid_composite == 'Normal':
                args, prev_img = hybrid_composite(args, anim_args, frame_idx, prev_img, depth_model, hybrid_comp_schedules, root)

            # apply color matching
            if anim_args.color_coherence != 'None':
                if color_match_sample is None:
                    color_match_sample = prev_img.copy()
                else:
                    prev_img = maintain_colors(prev_img, color_match_sample, anim_args.color_coherence)

            # intercept and override to grayscale
            if anim_args.color_force_grayscale:
                prev_img = cv2.cvtColor(prev_img, cv2.COLOR_BGR2GRAY)
                prev_img = cv2.cvtColor(prev_img, cv2.COLOR_GRAY2BGR)

            # apply scaling
            contrast_image = (prev_img * contrast).round().astype(np.uint8)
            # anti-blur
            if amount > 0:
                contrast_image = unsharp_mask(contrast_image, (kernel, kernel), sigma, amount, threshold, mask_image if args.use_mask else None)
            # apply frame noising
            if args.use_mask or anim_args.use_noise_mask:
                root.noise_mask = compose_mask_with_check(root, args, noise_mask_seq, noise_mask_vals, Image.fromarray(cv2.cvtColor(contrast_image, cv2.COLOR_BGR2RGB)))

            if usingDeforumation: #Should we Connect to the Deforumation websocket server to get CFG values?
                if int(mediator_getValue("should_use_deforumation_noise").strip().strip('\n')) == 1:
                    deforumation_perlin_octaves = int(mediator_getValue("perlin_octaves").strip().strip('\n'))
                    deforumation_perlin_persistence = float(mediator_getValue("perlin_persistence").strip().strip('\n'))
                    noised_image = add_noise(contrast_image, noise, args.seed, anim_args.noise_type,
                                             (anim_args.perlin_w, anim_args.perlin_h, deforumation_perlin_octaves, deforumation_perlin_persistence),
                                             root.noise_mask, args.invert_mask)
                    mediator_setValue("deforum_perlin_octaves", deforumation_perlin_octaves)
                    mediator_setValue("deforum_perlin_persistence", deforumation_perlin_persistence)

                else:
                    noised_image = add_noise(contrast_image, noise, args.seed, anim_args.noise_type,
                                             (anim_args.perlin_w, anim_args.perlin_h, anim_args.perlin_octaves, anim_args.perlin_persistence),
                                             root.noise_mask, args.invert_mask)
                    mediator_setValue("deforum_perlin_octaves", anim_args.perlin_octaves)
                    mediator_setValue("deforum_perlin_persistence", anim_args.perlin_persistence)
                    mediator_setValue("perlin_octaves", anim_args.perlin_octaves)
                    mediator_setValue("perlin_persistence", anim_args.perlin_persistence)
            else:
                noised_image = add_noise(contrast_image, noise, args.seed, anim_args.noise_type,
                                         (anim_args.perlin_w, anim_args.perlin_h, anim_args.perlin_octaves, anim_args.perlin_persistence),
                                         root.noise_mask, args.invert_mask)

            # use transformed previous frame as init for current
            args.use_init = True
            root.init_sample = Image.fromarray(cv2.cvtColor(noised_image, cv2.COLOR_BGR2RGB))
            args.strength = max(0.0, min(1.0, strength))

        args.scale = scale

        # Pix2Pix Image CFG Scale - does *nothing* with non pix2pix checkpoints
        args.pix2pix_img_cfg_scale = float(keys.pix2pix_img_cfg_scale_series[frame_idx])

        # grab prompt for current frame
        if usingDeforumation: #Should we Connect to the Deforumation websocket server to get CFG values?
            if (int(mediator_getValue("should_use_deforumation_prompt_scheduling").strip().strip('\n')) == 1): #Should we use manual or deforum's strength scheduling?
                deforumation_positive_prompt = str(mediator_getValue("positive_prompt"))
                deforumation_negative_prompt = str(mediator_getValue("negative_prompt"))
                args.prompt = deforumation_positive_prompt + "--neg "+ deforumation_negative_prompt
            else:
                args.prompt = prompt_series[frame_idx]
                pos_prompt, neg_prompt = args.prompt.strip().split("--neg")
                mediator_setValue("positive_prompt", pos_prompt)
                mediator_setValue("negative_prompt", neg_prompt)

        else: #If we are not using Deforumation, go with the values in Deforum GUI (or if we can't connect to the Deforumation server).
            args.prompt = prompt_series[frame_idx]

        #if args.seed_behavior == 'schedule' or use_parseq:
        if args.seed_behavior == 'schedule' or parseq_adapter.manages_seed():
            args.seed = int(keys.seed_schedule_series[frame_idx])

        if anim_args.enable_checkpoint_scheduling:
            args.checkpoint = keys.checkpoint_schedule_series[frame_idx]
        else:
            args.checkpoint = None

        # SubSeed scheduling
        if anim_args.enable_subseed_scheduling:
            root.subseed = int(keys.subseed_schedule_series[frame_idx])
            root.subseed_strength = float(keys.subseed_strength_schedule_series[frame_idx])

        #if use_parseq:
        if parseq_adapter.manages_seed():
            anim_args.enable_subseed_scheduling = True
            root.subseed = int(keys.subseed_schedule_series[frame_idx])
            root.subseed_strength = keys.subseed_strength_schedule_series[frame_idx]

        # set value back into the prompt - prepare and report prompt and seed
        args.prompt = prepare_prompt(args.prompt, anim_args.max_frames, args.seed, frame_idx)

        # grab init image for current frame
        if using_vid_init:
            init_frame = get_next_frame(args.outdir, anim_args.video_init_path, frame_idx, False)
            print(f"Using video init frame {init_frame}")
            args.init_image = init_frame
            args.strength = max(0.0, min(1.0, strength))
        if anim_args.use_mask_video:
            args.mask_file = get_mask_from_file(get_next_frame(args.outdir, anim_args.video_mask_path, frame_idx, True), args)
            root.noise_mask = get_mask_from_file(get_next_frame(args.outdir, anim_args.video_mask_path, frame_idx, True), args)

            mask_vals['video_mask'] = get_mask_from_file(get_next_frame(args.outdir, anim_args.video_mask_path, frame_idx, True), args)

        if args.use_mask:
            args.mask_image = compose_mask_with_check(root, args, mask_seq, mask_vals, root.init_sample) if root.init_sample is not None else None  # we need it only after the first frame anyway

        # setting up some arguments for the looper
        loop_args.imageStrength = loopSchedulesAndData.image_strength_schedule_series[frame_idx]
        loop_args.blendFactorMax = loopSchedulesAndData.blendFactorMax_series[frame_idx]
        loop_args.blendFactorSlope = loopSchedulesAndData.blendFactorSlope_series[frame_idx]
        loop_args.tweeningFrameSchedule = loopSchedulesAndData.tweening_frames_schedule_series[frame_idx]
        loop_args.colorCorrectionFactor = loopSchedulesAndData.color_correction_factor_series[frame_idx]
        loop_args.use_looper = loopSchedulesAndData.use_looper
        loop_args.imagesToKeyframe = loopSchedulesAndData.imagesToKeyframe

        if 'img2img_fix_steps' in opts.data and opts.data["img2img_fix_steps"]:  # disable "with img2img do exactly x steps" from general setting, as it *ruins* deforum animations
            opts.data["img2img_fix_steps"] = False
        if scheduled_clipskip is not None:
            opts.data["CLIP_stop_at_last_layers"] = scheduled_clipskip
        if scheduled_noise_multiplier is not None:
            opts.data["initial_noise_multiplier"] = scheduled_noise_multiplier
        if scheduled_ddim_eta is not None:
            opts.data["eta_ddim"] = scheduled_ddim_eta
        if scheduled_ancestral_eta is not None:
            opts.data["eta_ancestral"] = scheduled_ancestral_eta

        if anim_args.animation_mode == '3D' and (cmd_opts.lowvram or cmd_opts.medvram):
            if predict_depths: depth_model.to('cpu')
            devices.torch_gc()
            lowvram.setup_for_low_vram(sd_model, cmd_opts.medvram)
            sd_hijack.model_hijack.hijack(sd_model)

        optical_flow_redo_generation = anim_args.optical_flow_redo_generation if not args.motion_preview_mode else 'None'

        if usingDeforumation:
            mediator_setValue("total_recall_relive", frame_idx)
            if is_controlnet_enabled(controlnet_args):
                for cnIndex in range(5):
                    currCnIndex = cnIndex+1
                    isCnActive = mediator_getValue("cn_udcn"+str(cnIndex+1)).strip().strip('\n')
                    #print("ControlNet " + str(currCnIndex) + " has activeValue:" + str(isCnActive))
                    cn_udcn = int(isCnActive)
                    if cn_udcn == 1:
                        #print("ControlNet " + str(currCnIndex) + " should use Deforumation values.")
                        #print("Got weight from mediator:" + str(float(mediator_getValue("cn_weight"+str(cnIndex+1)))))
                        setattr(controlnet_args, f'cn_{currCnIndex}_weight', "0:(" + str(mediator_getValue("cn_weight"+str(cnIndex+1)).strip().strip('\n')) + ")" )
                        setattr(controlnet_args, f'cn_{currCnIndex}_guidance_start', "0:(" + str(mediator_getValue("cn_stepstart"+str(cnIndex+1)).strip().strip('\n')) + ")" )
                        setattr(controlnet_args, f'cn_{currCnIndex}_guidance_end', "0:(" + str(mediator_getValue("cn_stepend"+str(cnIndex+1)).strip().strip('\n')) + ")" )
                        setattr(controlnet_args, f'cn_{currCnIndex}_threshold_a', int(mediator_getValue("cn_lowt"+str(cnIndex+1)).strip().strip('\n')))
                        setattr(controlnet_args, f'cn_{currCnIndex}_threshold_b', int(mediator_getValue("cn_hight"+str(cnIndex+1)).strip().strip('\n')))
                    else:
                        setattr(controlnet_args, f'cn_{currCnIndex}_weight', cnu[f'cn_{currCnIndex}_weight'])
                        setattr(controlnet_args, f'cn_{currCnIndex}_guidance_start', cnu[f'cn_{currCnIndex}_guidance_start'])
                        setattr(controlnet_args, f'cn_{currCnIndex}_guidance_end', cnu[f'cn_{currCnIndex}_guidance_end'])
                        setattr(controlnet_args, f'cn_{currCnIndex}_threshold_a', cnu[f'cn_{currCnIndex}_threshold_a'])
                        setattr(controlnet_args, f'cn_{currCnIndex}_threshold_b', cnu[f'cn_{currCnIndex}_threshold_b'])
                        #print("ControlNet " + str(currCnIndex) + " should use Deforum values.")
                        #print("Got weight:" + str(getattr(CnSchKeys, f"cn_{currCnIndex}_weight_schedule_series")[frame_idx]))
                        mediator_setValue("cn_weight"+str(cnIndex+1), getattr(CnSchKeys, f"cn_{currCnIndex}_weight_schedule_series")[frame_idx])
                        mediator_setValue("cn_stepstart"+str(cnIndex+1),getattr(CnSchKeys, f"cn_{currCnIndex}_guidance_start_schedule_series")[frame_idx])
                        mediator_setValue("cn_stepend"+str(cnIndex+1), getattr(CnSchKeys, f"cn_{currCnIndex}_guidance_end_schedule_series")[frame_idx])
                        mediator_setValue("cn_lowt"+str(cnIndex+1), getattr(controlnet_args, f"cn_{currCnIndex}_threshold_a"))
                        mediator_setValue("cn_hight"+str(cnIndex+1), getattr(controlnet_args, f"cn_{currCnIndex}_threshold_b"))

        # optical flow redo before generation
        if optical_flow_redo_generation != 'None' and prev_img is not None and strength > 0:
            print(f"Optical flow redo is diffusing and warping using {optical_flow_redo_generation} optical flow before generation.")
            stored_seed = args.seed
            args.seed = random.randint(0, 2 ** 32 - 1)
            #disposable_image = generate(args, keys, anim_args, loop_args, controlnet_args, root, frame_idx, sampler_name=scheduled_sampler_name)
            disposable_image = generate(args, keys, anim_args, loop_args, controlnet_args, root, parseq_adapter, frame_idx, sampler_name=scheduled_sampler_name)
            if disposable_image != None:
                disposable_image = cv2.cvtColor(np.array(disposable_image), cv2.COLOR_RGB2BGR)
                disposable_flow = get_flow_from_images(prev_img, disposable_image, optical_flow_redo_generation, raft_model)
                disposable_image = cv2.cvtColor(disposable_image, cv2.COLOR_BGR2RGB)
                disposable_image = image_transform_optical_flow(disposable_image, disposable_flow, redo_flow_factor)
                args.seed = stored_seed
                root.init_sample = Image.fromarray(disposable_image)
                del (disposable_image, disposable_flow, stored_seed)
                gc.collect()

        # diffusion redo
        if int(anim_args.diffusion_redo) > 0 and prev_img is not None and strength > 0 and not args.motion_preview_mode:
            stored_seed = args.seed
            for n in range(0, int(anim_args.diffusion_redo)):
                print(f"Redo generation {n + 1} of {int(anim_args.diffusion_redo)} before final generation")
                args.seed = random.randint(0, 2 ** 32 - 1)
                #disposable_image = generate(args, keys, anim_args, loop_args, controlnet_args, root, frame_idx, sampler_name=scheduled_sampler_name)
                disposable_image = generate(args, keys, anim_args, loop_args, controlnet_args, root, parseq_adapter, frame_idx, sampler_name=scheduled_sampler_name)
                disposable_image = cv2.cvtColor(np.array(disposable_image), cv2.COLOR_RGB2BGR)
                # color match on last one only
                if n == int(anim_args.diffusion_redo):
                    disposable_image = maintain_colors(prev_img, color_match_sample, anim_args.color_coherence)
                args.seed = stored_seed
                root.init_sample = Image.fromarray(cv2.cvtColor(disposable_image, cv2.COLOR_BGR2RGB))
            del (disposable_image, stored_seed)
            gc.collect()

        # generation
        #image = generate(args, keys, anim_args, loop_args, controlnet_args, root, frame_idx, sampler_name=scheduled_sampler_name)

        image = generate(args, keys, anim_args, loop_args, controlnet_args, root, parseq_adapter, frame_idx, sampler_name=scheduled_sampler_name)
        #mediator_setValue("saved_frame_params", frame_idx)
        if image is None:
            break

        # do hybrid video after generation
        if frame_idx > 0 and anim_args.hybrid_composite == 'After Generation':
            image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
            args, image = hybrid_composite(args, anim_args, frame_idx, image, depth_model, hybrid_comp_schedules, root)
            image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))

        # color matching on first frame is after generation, color match was collected earlier, so we do an extra generation to avoid the corruption introduced by the color match of first output
        if frame_idx == 0 and (anim_args.color_coherence == 'Image' or (anim_args.color_coherence == 'Video Input' and hybrid_available)):
            image = maintain_colors(cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR), color_match_sample, anim_args.color_coherence)
            image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
        elif color_match_sample is not None and anim_args.color_coherence != 'None' and not anim_args.legacy_colormatch:
            image = maintain_colors(cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR), color_match_sample, anim_args.color_coherence)
            image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))

        # intercept and override to grayscale
        if anim_args.color_force_grayscale:
            image = ImageOps.grayscale(image)
            image = ImageOps.colorize(image, black="black", white="white")

        # overlay mask
        if args.overlay_mask and (anim_args.use_mask_video or args.use_mask):
            image = do_overlay_mask(args, anim_args, image, frame_idx)

        # on strength 0, set color match to generation
        if ((not anim_args.legacy_colormatch and not args.use_init) or (anim_args.legacy_colormatch and strength == 0)) and not anim_args.color_coherence in ['Image', 'Video Input']:
            color_match_sample = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)

        opencv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
        if not using_vid_init:
            prev_img = opencv_image

        if turbo_steps > 1:
            turbo_prev_image, turbo_prev_frame_idx = turbo_next_image, turbo_next_frame_idx
            turbo_next_image, turbo_next_frame_idx = opencv_image, frame_idx
            frame_idx += turbo_steps
        else:
            filename = f"{root.timestring}_{frame_idx:09}.png"
            save_image(image, 'PIL', filename, args, video_args, root)
            if usingDeforumation: #Should we Connect to the Deforumation websocket server to tell 3:d parties what frame_idx we are on currently?
                shouldResume = int(mediator_getValue("should_resume").strip().strip('\n'))  #If the user pushed "Set current image" in Deforumation, we can't just overwrite the new start_frame

                if not shouldResume:
                    mediator_setValue("start_frame", frame_idx)
                mediator_setValue("saved_frame_params", frame_idx)

            if anim_args.save_depth_maps:
                if cmd_opts.lowvram or cmd_opts.medvram:
                    lowvram.send_everything_to_cpu()
                    sd_hijack.model_hijack.undo_hijack(sd_model)
                    devices.torch_gc()
                    depth_model.to(root.device)
                depth = depth_model.predict(opencv_image, anim_args.midas_weight, root.half_precision)
                depth_model.save(os.path.join(args.outdir, f"{root.timestring}_depth_{frame_idx:09}.png"), depth)
                if cmd_opts.lowvram or cmd_opts.medvram:
                    depth_model.to('cpu')
                    devices.torch_gc()
                    lowvram.setup_for_low_vram(sd_model, cmd_opts.medvram)
                    sd_hijack.model_hijack.hijack(sd_model)
            frame_idx += 1

        state.current_image = image
        if usingDeforumation:
            if not int(mediator_getValue("seed_changed").strip().strip('\n')):
                mediator_setValue("seed", args.seed)

            if int(mediator_getValue("should_use_total_recall").strip().strip('\n')):
                mediator_setValue("total_recall_relive", frame_idx)
                print("Total recall (last to get seed) at frame " + str(frame_idx))
                args.seed = int(mediator_getValue("seed").strip().strip('\n'))
                print("Using seed:" + str())
            else:
                args.seed = next_seed(args, root)
        else:
            args.seed = next_seed(args, root)

        last_preview_frame = render_preview(args, anim_args, video_args, root, frame_idx, last_preview_frame)

        JobStatusTracker().update_phase(root.job_id, phase="GENERATING", progress=frame_idx/anim_args.max_frames)
    #Restore Deforum CN Properties
    for cnIndex in range(5):
        currCnIndex = cnIndex+1
        setattr(controlnet_args, f'cn_{currCnIndex}_weight', cnu[f'cn_{currCnIndex}_weight'] )
        setattr(controlnet_args, f'cn_{currCnIndex}_guidance_start', cnu[f'cn_{currCnIndex}_guidance_start'])
        setattr(controlnet_args, f'cn_{currCnIndex}_guidance_end', cnu[f'cn_{currCnIndex}_guidance_end'])
        setattr(controlnet_args, f'cn_{currCnIndex}_threshold_a', cnu[f'cn_{currCnIndex}_threshold_a'])
        setattr(controlnet_args, f'cn_{currCnIndex}_threshold_b', cnu[f'cn_{currCnIndex}_threshold_b'])

    if usingDeforumation:
        mediator_setValue("deforum_interrupted", 1)

    if predict_depths and not keep_in_vram:
        depth_model.delete_model()  # handles adabins too

    if load_raft:
        raft_model.delete_model()


#
# @lc app=leetcode id=208 lang=python3
#
# [208] Implement Trie (Prefix Tree)
#
# https://leetcode.com/problems/implement-trie-prefix-tree/description/
#
# algorithms
# Medium (40.34%)
# Likes:    1838
# Dislikes: 35
# Total Accepted:    199.8K
# Total Submissions: 494.6K
# Testcase Example:  '["Trie","insert","search","search","startsWith","insert","search"]\n[[],["apple"],["apple"],["app"],["app"],["app"],["app"]]'
#
# Implement a trie with insert, search, and startsWith methods.
#
# Example:
#
#
# Trie trie = new Trie();
#
# trie.insert("apple");
# trie.search("apple");   // returns true
# trie.search("app");     // returns false
# trie.startsWith("app"); // returns true
# trie.insert("app");
# trie.search("app");     // returns true
#
#
# Note:
#
#
# You may assume that all inputs are consist of lowercase letters a-z.
# All inputs are guaranteed to be non-empty strings.
#
#
#
from collections import defaultdict

class TrieNode:
    def __init__(self):
        self.childs = defaultdict(TrieNode)
        self.isWord = False

class Trie:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.root = TrieNode()

    def insert(self, word: str) -> None:
        """
        Inserts a word into the trie.
        """
        r = self.root
        for ch in word:
            r = r.childs[ch]
        r.isWord = True

    def search(self, word: str) -> bool:
        """
        Returns if the word is in the trie.
        """
        r = self.root
        for ch in word:
            if ch not in r.childs:
                return False
            else:
                r = r.childs[ch]
        return r.isWord

    def startsWith(self, prefix: str) -> bool:
        """
        Returns if there is any word in the trie that starts with the given prefix.
        """
        r = self.root
        for ch in prefix:
            if ch not in r.childs:
                return False
            else:
                r = r.childs[ch]
        return True

# Your Trie object will be instantiated and called as such:
# obj = Trie()
# obj.insert(word)
# param_2 = obj.search(word)
# param_3 = obj.startsWith(prefix)


from datetime import datetime

import icalendar
import pytz
from babel.dates import get_timezone
from sqlalchemy import Date, Time, or_
from sqlalchemy.sql import cast
from werkzeug.datastructures import MultiDict, OrderedMultiDict

from indico.core.config import config
from indico.core.db import db
from indico.core.errors import IndicoError
from indico.modules.auth import Identity
from indico.modules.rb.models.locations import Location
from indico.modules.rb.models.reservations import ConflictingOccurrences, RepeatFrequency, RepeatMapping, Reservation
from indico.modules.rb.models.rooms import Room
from indico.modules.rb.util import rb_check_user_access
from indico.modules.users import User
from indico.util.date_time import utc_to_server
from indico.web.http_api import HTTPAPIHook
from indico.web.http_api.metadata import ical
from indico.web.http_api.responses import HTTPAPIError
from indico.web.http_api.util import get_query_parameter

class RoomBookingHookBase(HTTPAPIHook):
    GUEST_ALLOWED = False

    def _getParams(self):
        super()._getParams()
        self._fromDT = utc_to_server(self._fromDT.astimezone(pytz.utc)).replace(tzinfo=None) if self._fromDT else None
        self._toDT = utc_to_server(self._toDT.astimezone(pytz.utc)).replace(tzinfo=None) if self._toDT else None
        self._occurrences = _yesno(get_query_parameter(self._queryParams, ['occ', 'occurrences'], 'no'))

    def _has_access(self, user):
        return config.ENABLE_ROOMBOOKING and rb_check_user_access(user)

@HTTPAPIHook.register
class RoomHook(RoomBookingHookBase):
    # e.g. /export/room/CERN/23.json
    TYPES = ('room',)
    RE = r'(?P<location>[\w\s]+)/(?P<idlist>\w+(?:-[\w\s]+)*)'
    DEFAULT_DETAIL = 'rooms'
    MAX_RECORDS = {
        'rooms': 500,
        'reservations': 100
    }
    VALID_FORMATS = ('json', 'jsonp', 'xml')

    def _getParams(self):
        super()._getParams()
        self._location = self._pathParams['location']
        self._ids = list(map(int, self._pathParams['idlist'].split('-')))
        if self._detail not in {'rooms', 'reservations'}:
            raise HTTPAPIError('Invalid detail level: %s' % self._detail, 400)

    def export_room(self, user):
        loc = Location.query.filter_by(name=self._location, is_deleted=False).first()
        if loc is None:
            return

        # Retrieve rooms
        rooms_data = list(Room.get_with_data(filters=[Room.id.in_(self._ids), Room.location_id == loc.id]))

        # Retrieve reservations
        reservations = None
        if self._detail == 'reservations':
            reservations = OrderedMultiDict(_export_reservations(self, True, False, [
                Reservation.room_id.in_(x['room'].id for x in rooms_data)
            ]))

        for result in rooms_data:
            yield _serializable_room(result, reservations)

@HTTPAPIHook.register
class RoomNameHook(RoomBookingHookBase):
    # e.g. /export/roomName/CERN/pump.json
    GUEST_ALLOWED = True
    TYPES = ('roomName', )
    RE = r'(?P<location>[\w\s]+)/(?P<room_name>[\w\s/\-]+)'
    DEFAULT_DETAIL = 'rooms'
    MAX_RECORDS = {
        'rooms': 500
    }
    VALID_FORMATS = ('json', 'jsonp', 'xml')

    def _getParams(self):
        super()._getParams()
        self._location = self._pathParams['location']
        self._room_name = self._pathParams['room_name']

    def _has_access(self, user):
        # Access to RB data (no reservations) is public
        return config.ENABLE_ROOMBOOKING

    def export_roomName(self, user):
        loc = Location.query.filter_by(name=self._location, is_deleted=False).first()
        if loc is None:
            return

        search_str = f'%{self._room_name}%'
        rooms_data = Room.get_with_data(
            filters=[
                Room.location_id == loc.id,
                or_(Room.name.ilike(search_str),
                    Room.verbose_name.ilike(search_str))
            ])

        for result in rooms_data:
            yield _serializable_room(result)

@HTTPAPIHook.register
class ReservationHook(RoomBookingHookBase):
    # e.g. /export/reservation/CERN.json
    TYPES = ('reservation', )
    RE = r'(?P<loclist>[\w\s]+(?:-[\w\s]+)*)'
    DEFAULT_DETAIL = 'reservations'
    MAX_RECORDS = {
        'reservations': 100
    }
    VALID_FORMATS = ('json', 'jsonp', 'xml', 'ics')

    @property
    def serializer_args(self):
        return {'ical_serializer': _ical_serialize_reservation}

    def _getParams(self):
        super()._getParams()
        self._locations = self._pathParams['loclist'].split('-')

    def export_reservation(self, user):
        locations = Location.query.filter(Location.name.in_(self._locations), ~Location.is_deleted).all()
        if not locations:
            return

        for room_id, reservation in _export_reservations(self, False, True):
            yield reservation

@HTTPAPIHook.register
class BookRoomHook(HTTPAPIHook):
    PREFIX = 'api'
    TYPES = ('roomBooking',)
    RE = r'bookRoom'
    GUEST_ALLOWED = False
    VALID_FORMATS = ('json', 'xml')
    COMMIT = True
    HTTP_POST = True

    def _getParams(self):
        super()._getParams()
        self._fromDT = utc_to_server(self._fromDT.astimezone(pytz.utc)).replace(tzinfo=None) if self._fromDT else None
        self._toDT = utc_to_server(self._toDT.astimezone(pytz.utc)).replace(tzinfo=None) if self._toDT else None
        if not self._fromDT or not self._toDT or self._fromDT.date() != self._toDT.date():
            raise HTTPAPIError('from/to must be on the same day')
        elif self._fromDT >= self._toDT:
            raise HTTPAPIError('to must be after from')
        elif self._fromDT < datetime.now():
            raise HTTPAPIError('You cannot make bookings in the past')

        username = get_query_parameter(self._queryParams, 'username')
        if not username:
            raise HTTPAPIError('No username provided')
        users = User.query.join(User.identities).filter(~User.is_deleted, Identity.identifier == username).all()
        if not users:
            raise HTTPAPIError('Username does not exist')
        elif len(users) != 1:
            raise HTTPAPIError(f'Ambiguous username ({len(users)} users found)')
        user = users[0]

        self._params = {
            'room_id': get_query_parameter(self._queryParams, 'roomid'),
            'reason': get_query_parameter(self._queryParams, 'reason'),
            'booked_for': user,
            'from': self._fromDT,
            'to': self._toDT
        }
        missing = [key for key, val in self._params.items() if not val]
        if missing:
            raise HTTPAPIError('Required params missing: {}'.format(', '.join(missing)))
        self._room = Room.get(self._params['room_id'])
        if not self._room:
            raise HTTPAPIError('A room with this ID does not exist')

    def _has_access(self, user):
        if not config.ENABLE_ROOMBOOKING or not rb_check_user_access(user):
            return False
        if self._room.can_book(user):
            return True
        elif self._room.can_prebook(user):
            raise HTTPAPIError('The API only supports direct bookings but this room only allows pre-bookings.')
        return False

    def api_roomBooking(self, user):
        data = MultiDict({
            'start_dt': self._params['from'],
            'end_dt': self._params['to'],
            'repeat_frequency': RepeatFrequency.NEVER,
            'repeat_interval': 0,
            'room_id': self._room.id,
            'booked_for_user': self._params['booked_for'],
            'booking_reason': self._params['reason']
        })
        try:
            reservation = Reservation.create_from_data(self._room, data, user)
        except ConflictingOccurrences:
            raise HTTPAPIError('Failed to create the booking due to conflicts with other bookings')
        except IndicoError as e:
            raise HTTPAPIError(f'Failed to create the booking: {e}')
        db.session.add(reservation)
        db.session.flush()
        return {'reservationID': reservation.id}

def _export_reservations(hook, limit_per_room, include_rooms, extra_filters=None):
    """Export reservations.

    :param hook: The HTTPAPIHook instance
    :param limit_per_room: Should the limit/offset be applied per room
    :param include_rooms: Should reservations include room information
    """
    filters = list(extra_filters) if extra_filters else []
    if hook._fromDT and hook._toDT:
        filters.append(cast(Reservation.start_dt, Date) <= hook._toDT.date())
        filters.append(cast(Reservation.end_dt, Date) >= hook._fromDT.date())
        filters.append(cast(Reservation.start_dt, Time) <= hook._toDT.time())
        filters.append(cast(Reservation.end_dt, Time) >= hook._fromDT.time())
    elif hook._toDT:
        filters.append(cast(Reservation.end_dt, Date) <= hook._toDT.date())
        filters.append(cast(Reservation.end_dt, Time) <= hook._toDT.time())
    elif hook._fromDT:
        filters.append(cast(Reservation.start_dt, Date) >= hook._fromDT.date())
        filters.append(cast(Reservation.start_dt, Time) >= hook._fromDT.time())
    filters += _get_reservation_state_filter(hook._queryParams)
    occurs = [datetime.strptime(x, '%Y-%m-%d').date()
              for x in [_f for _f in get_query_parameter(hook._queryParams, ['occurs'], '').split(',') if _f]]
    data = []
    if hook._occurrences:
        data.append('occurrences')
    order = {
        'start': Reservation.start_dt,
        'end': Reservation.end_dt
    }.get(hook._orderBy, Reservation.start_dt)
    if hook._descending:
        order = order.desc()
    reservations_data = Reservation.get_with_data(*data, filters=filters, limit=hook._limit, offset=hook._offset,
                                                  order=order, limit_per_room=limit_per_room, occurs_on=occurs)
    for result in reservations_data:
        yield result['reservation'].room_id, _serializable_reservation(result, include_rooms)

def _serializable_room(room_data, reservations=None):
    """Serializable room data.

    :param room_data: Room data
    :param reservations: MultiDict mapping for room id => reservations
    """
    from indico.modules.rb.schemas import RoomLegacyAPISchema
    data = RoomLegacyAPISchema().dump(room_data['room'])
    data['_type'] = 'Room'
    if reservations is not None:
        data['reservations'] = reservations.getlist(room_data['room'].id)
    return data

def _serializable_room_minimal(room):
    """Serializable minimal room data (inside reservations).

    :param room: A `Room`
    """
    from indico.modules.rb.schemas import RoomLegacyMinimalAPISchema
    data = RoomLegacyMinimalAPISchema().dump(room)
    data['_type'] = 'Room'
    return data

def _serializable_reservation(reservation_data, include_room=False):
    """Serializable reservation (standalone or inside room).

    :param reservation_data: Reservation data
    :param include_room: Include minimal room information
    """
    from indico.modules.rb.schemas import ReservationLegacyAPISchema, ReservationOccurrenceLegacyAPISchema
    reservation = reservation_data['reservation']
    data = ReservationLegacyAPISchema().dump(reservation)
    data['_type'] = 'Reservation'
    data['repeatability'] = None
    if reservation.repeat_frequency:
        data['repeatability'] = RepeatMapping.get_short_name(*reservation.repetition)
    if include_room:
        data['room'] = _serializable_room_minimal(reservation_data['reservation'].room)
    if 'occurrences' in reservation_data:
        data['occurrences'] = ReservationOccurrenceLegacyAPISchema(many=True).dump(reservation_data['occurrences'])
    return data

def _ical_serialize_repeatability(data):
    start_dt_utc = data['startDT'].astimezone(pytz.utc)
    end_dt_utc = data['endDT'].astimezone(pytz.utc)
    week_days = 'MO TU WE TH FR SA SU'.split()
    recur = ical.vRecur()
    recur['until'] = end_dt_utc
    if data['repeat_frequency'] == RepeatFrequency.DAY:
        recur['freq'] = 'daily'
    elif data['repeat_frequency'] == RepeatFrequency.WEEK:
        recur['freq'] = 'weekly'
        recur['interval'] = data['repeat_interval']
    elif data['repeat_frequency'] == RepeatFrequency.MONTH:
        recur['freq'] = 'monthly'
        recur['byday'] = f'{start_dt_utc.day // 7}{week_days[start_dt_utc.weekday()]}'
    return recur

def _ical_serialize_reservation(cal, data, now):
    start_dt_utc = data['startDT'].astimezone(pytz.utc)
    end_dt_utc = datetime.combine(data['startDT'].date(), data['endDT'].timetz()).astimezone(pytz.utc)

    event = icalendar.Event()
    event.add('uid', 'indico-resv-%s@cern.ch' % data['id'])
    event.add('dtstamp', now)
    event.add('dtstart', start_dt_utc)
    event.add('dtend', end_dt_utc)
    event.add('url', data['bookingUrl'])
    event.add('summary', data['reason'])
    event.add('location', '{}: {}'.format(data['location'], data['room']['fullName']))
    event.add('description', data['reason'] + '\n\n' + data['bookingUrl'])
    if data['repeat_frequency'] != RepeatFrequency.NEVER:
        event.add('rrule', _ical_serialize_repeatability(data))
    cal.add_component(event)

def _add_server_tz(dt):
    if dt.tzinfo is None:
        return dt.replace(tzinfo=get_timezone(config.DEFAULT_TIMEZONE))
    return dt

def _yesno(value):
    return value.lower() in {'yes', 'y', '1', 'true'}

def _get_reservation_state_filter(params):
    cancelled = get_query_parameter(params, ['cxl', 'cancelled'])
    rejected = get_query_parameter(params, ['rej', 'rejected'])
    confirmed = get_query_parameter(params, ['confirmed'])
    archived = get_query_parameter(params, ['arch', 'archived', 'archival'])
    repeating = get_query_parameter(params, ['rec', 'recurring', 'rep', 'repeating'])
    booked_for = get_query_parameter(params, ['bf', 'bookedfor'])

    filters = []
    if cancelled is not None:
        filters.append(Reservation.is_cancelled == _yesno(cancelled))
    if rejected is not None:
        filters.append(Reservation.is_rejected == _yesno(rejected))
    if confirmed is not None:
        if confirmed == 'pending':
            filters.append(Reservation.is_pending)
        elif _yesno(confirmed):
            filters.append(Reservation.is_accepted)
        else:
            filters.append(~Reservation.is_accepted)
            filters.append(Reservation.is_rejected | Reservation.is_cancelled)
    if archived is not None:
        filters.append(Reservation.is_archived == _yesno(archived))
    if repeating is not None:
        if _yesno(repeating):
            filters.append(Reservation.repeat_frequency != 0)
        else:
            filters.append(Reservation.repeat_frequency == 0)
    if booked_for:
        like_str = '%{}%'.format(booked_for.replace('?', '_').replace('*', '%'))
        filters.append(Reservation.booked_for_name.ilike(like_str))
    return filters


import cv2
import numpy as np
from . import filters
from . import image_operations as img_o
from logging_framework import logging_setup as log

# ------------------------------   V GLOBAL VARIABLES V   ------------------------------

#blob detect parameters:
MAX_THRESHOLD = 200
MIN_THRESHOLD = 10
MAX_AREA = 5000
MIN_AREA = 10
MAX_CIRCULARITY = 1
MIN_CIRCULATIRY = 0
MAX_CONVEXITY = 1
MIN_CONVEXITY = 0
MAX_INETRTIA_RATIO = 1
MIN_INERTIA_RATIO = 0.0
BLOB_COLOR = 0

DEBUG = True #uncomment to use imshow etc.

# ------------------------------   ^ GLOBAL VARIABLES ^   ------------------------------

# ------------------------------   V FUNCTIONS V   ------------------------------

def init_blob_detector():
    # Setup SimpleBlobDetector parameters.
    params = cv2.SimpleBlobDetector_Params()

    # Change thresholds
    params.maxThreshold = MAX_THRESHOLD
    params.minThreshold = MIN_THRESHOLD

    params.filterByColor = True
    params.blobColor = BLOB_COLOR

    # Filter by Area.
    params.filterByArea = False
    params.maxArea = MAX_AREA
    #params.minArea = MIN_AREA

    # Filter by Circularity
    params.filterByCircularity = True
    #params.maxCircularity = MAX_CIRCULARITY
    params.minCircularity = MIN_CIRCULATIRY

    # Filter by Convexity
    params.filterByConvexity = True
    params.minConvexity = MIN_CONVEXITY
    #params.maxConvexity = MAX_CONVEXITY

    # Filter by Inertia
    params.filterByInertia = True
    #params.maxInertiaRatio = MAX_INERTIA_RATIO
    params.minInertiaRatio = MIN_INERTIA_RATIO

    detector = cv2.SimpleBlobDetector_create(params)    #create detector

    return detector

def draw_blobs(img, keyPoints, color, name='blobs'):

    imgKeyPoints = cv2.drawKeypoints(img, keyPoints, np.array([]), color, cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) #DRAW_MATCHES_FLAGS_RICH KEYPOINTS for cicles and not dots
    cv2.imshow(name, imgKeyPoints)                                                          #show img

def blob_detection(img):
    #function to detect blobs. Returns list of keypoints

    detector = init_blob_detector() #create detector with params for blobs

    keyPoints = detector.detect(img) #analyse and make list of blobs in picture

    if DEBUG:
        draw_blobs(img, keyPoints, (0,0,255))  #show img with red cicles if debug

    return keyPoints

# ------------------------------   ^ FUNCTIONS ^   ------------------------------

# ------------------------------   V TEST FUNCTION V   ------------------------------

def blob_detection_test_func():
    #just some function to test

    blob

# ------------------------------   ^ TEST FUNCTION ^   ------------------------------

if __name__ == "__main__":
    pass


import  itertools
import string
def quicksort(x):
      if len(x) < 2:
          return x
      else:
          pivot = x[0]
          less = [i for i in x[1:] if i <= pivot]
          greater = [i for i in x[1:] if i > pivot]
          return quicksort(less) + [pivot] + quicksort(greater)

def alfabet(m,n):
    return(itertools.combinations(m,n))

m = string.ascii_lowercase
n = 5
val= int(input("veldu tölu"))
svar = itertools.combinations(m,val)
cnt = 0

listi = []

for i in list(svar):
    listi.append("".join(i))
for x in listi:
    listi.reverse()
print(listi)
print(quicksort(listi))