#madebyr

import tkinter as tk
from tkinter import filedialog, simpledialog, messagebox
from PIL import Image, ImageTk
import piexif
import os
import random
import time
import threading


class ImageViewer:
    def __init__(self, root):
        self.SUPPORTED_EXTENSIONS = (".png", ".jpg", ".jpeg", ".gif", ".bmp", ".JPG", ".JPEG", ".BMP", ".GIF", ".webp", ".WEBP")
        self.button_style = {"font": ("consolas", 10), "fg": "white", "bg": "#3c3c3c", "relief": "flat"}
		
        #self.image_loading_queue = Queue(maxsize=5)
        self.switch_timestamps = []

        self.root = root
        self.root.geometry("800x600")  # Initial window size
        self.root.title("Jestur Viewer")
        self.root.configure(bg="#808080")  # Set window background color
        self.root.attributes("-topmost", True)  # Make the window stay on top

        self.image_folder = ""
        self.image_files = []
        self.current_image_index = 0

        self.main_frame = tk.Frame(self.root, bg="#808080")
        self.main_frame.pack()

        self.select_folder_button = tk.Button(self.main_frame, text="Select Folder", command=self.select_folder)
        self.select_folder_button.configure(**self.button_style)
        self.select_folder_button.pack(side=tk.LEFT, padx=5, pady=10)

        self.set_timer_button = tk.Button(self.main_frame, text="Timer", command=self.set_timer_interval)
        self.set_timer_button.configure(**self.button_style)
        self.set_timer_button.pack(side=tk.LEFT, padx=5, pady=10)

        self.start_button = tk.Button(self.main_frame, text="Start", command=self.start_pause_slideshow)
        self.start_button.configure(font=self.button_style["font"], bg="#909090", fg="#707070", relief=tk.FLAT)
        self.start_button.pack(side=tk.LEFT, padx=5, pady=10)

        self.mirror_button = tk.Button(self.main_frame, text=">|<", command=self.toggle_mirror_image)
        self.mirror_button.configure(**self.button_style)
        self.mirror_button.pack(side=tk.LEFT, padx=5, pady=10)
        
        self.greyscale_button = tk.Button(self.main_frame, text="B/W", command=self.toggle_greyscale)
        self.greyscale_button.configure(**self.button_style)
        self.greyscale_button.pack(side=tk.LEFT, padx=5, pady=10)
        
		
        self.random_next_image_var = tk.BooleanVar(value=False) # Checkbox variable
        self.random_next_image_checkbox = tk.Checkbutton(self.main_frame, text="Random", variable=self.random_next_image_var, command=self.toggle_colors)
        self.random_next_image_checkbox.configure(**self.button_style, bd=2)
        self.random_next_image_checkbox.pack(side=tk.LEFT, padx=5, pady=10)
		
		#image label
        self.canvas = tk.Canvas(self.root)
        self.is_mirrored = False
        self.is_greyscale = False
        self.is_paused = True
		
        self.timer_label = tk.Label(self.root, text="5", **self.button_style, anchor="ne") #fg="white", bg="black", font=("arial", 12), bd=1, relief=tk.RIDGE)
        self.timer_label.configure(font=("consolas",17))
        self.timer_label.place(relx=1, anchor="ne", x=-0, y=0)

        self.timer_interval = 5000  # Default time interval in milliseconds (5 seconds)
        self.set_timer_interval = 5000  # Store the set timer interval
        self.timer = None  # Timer ID
		
        self.window_size = (self.root.winfo_width(), self.root.winfo_height())
        self.resize_timer = None

        self.root.bind("<Right>", self.next_image)
        self.root.bind("<Left>", self.previous_image)
        self.root.bind("<space>", self.start_pause_slideshow)
        self.root.bind("<Configure>", self.update_image_size)
		
    def toggle_colors(self):
        if self.random_next_image_var.get():
            self.random_next_image_checkbox.configure(fg="#2c2c2c", bg="#d8d8d8")
        else:
            self.random_next_image_checkbox.configure(**self.button_style)

    def select_folder(self):
        self.image_folder = filedialog.askdirectory()
        if self.image_folder:
            image_files = os.listdir(self.image_folder) # Get all files in the selected folder
            self.image_files = [file for file in image_files if file.endswith(self.SUPPORTED_EXTENSIONS)] # Filter image files
            
            if len(self.image_files) > 0:
                self.current_image_index = 0
                self.canvas.pack(fill=tk.BOTH, expand=True)
                self.select_folder_button.pack(side=tk.LEFT, padx=5, pady=5)
                self.root.title("Jesturing in " + self.image_folder)
                self.canvas.config(bg="#808080", highlightthickness=0)
                self.display_image()   
                self.start_button.config(**self.button_style)
            else:
                messagebox.showinfo("No Image Files", "The selected folder does not contain any image files.")
                self.image_folder = ""
                self.root.title("Jestur")

    def set_timer_interval(self):
        self.root.attributes("-topmost", False)
        interval = simpledialog.askinteger("Set Timer Interval", "How many seconds?")        
        if interval:
            if interval < 1:  # Check if interval is less than 1 second
                interval = 1
            self.timer_interval = interval * 1000  # Convert to milliseconds
            self.set_timer_interval = self.timer_interval  # Store the set timer interval
        self.root.lift()  # Bring the main window to the top
        self.root.focus_force()  # Give focus to the main window
        self.root.attributes("-topmost", True)

    def start_pause_slideshow(self, event=None):
        if self.image_folder == "":
            messagebox.showinfo("You haven't picked a folder", "Please select an image folder first.")
        else:
            if self.timer is None:  # Check if timer is not running
                self.display_image()
                self.start_button.config(text="Pause")
                self.is_paused = False  # Set paused flag to False
                self.start_timer()
            else:
                if self.is_paused:  # Check if the slideshow is paused
                    self.start_button.config(text="Pause")
                    self.is_paused = False  # Set paused flag to False
                    self.start_timer()
                else:
                    self.pause_timer()
                    self.start_button.config(text="Start")
                    self.is_paused = True  # Set paused flag to True
    
    def pause_timer(self):
        if self.timer is not None:
            self.root.after_cancel(self.timer)
            self.timer = None

    def start_timer(self):
        if self.image_folder != "" and not self.is_paused:
            self.update_timer()
            self.timer = self.root.after(1000, self.start_timer)

    def update_timer(self):
        seconds_left = int(self.timer_interval / 1000)
        self.timer_label.config(text=f"{seconds_left}")
        self.timer_interval -= 1000

        if self.timer_interval < 0:
            self.timer_interval = self.set_timer_interval  # Use the stored set timer interval
            self.next_image()
			
    def toggle_mirror_image(self):
        self.is_mirrored = not self.is_mirrored
        self.display_image()

    def toggle_greyscale(self):
        self.is_greyscale = not self.is_greyscale
        self.display_image()			
		
    def load_image(self, image_path):
        image = Image.open(image_path)
        
        # Check if the image has EXIF data
        if "exif" in image.info:
            exif_data = piexif.load(image.info["exif"])
            if piexif.ImageIFD.Orientation in exif_data["0th"]:
                orientation = exif_data["0th"][piexif.ImageIFD.Orientation]
                if orientation == 3:
                    image = image.rotate(180, expand=True)
                elif orientation == 6:
                    image = image.rotate(-90, expand=True)
                elif orientation == 8:
                    image = image.rotate(90, expand=True)
    
        if self.is_greyscale:
            image = image.convert("L")
    
        if self.is_mirrored:
            image = image.transpose(Image.FLIP_LEFT_RIGHT)
    
        aspect_ratio = image.width / image.height
        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()
        max_width = min(canvas_width, int(aspect_ratio * canvas_height))
        max_height = min(canvas_height, int(canvas_width / aspect_ratio))
        scale_factor = min(max_width / image.width, max_height / image.height)
        new_width = int(image.width * scale_factor)
        new_height = int(image.height * scale_factor)
    
        if new_width > 0 and new_height > 0:
            resized_image = image.resize((new_width, new_height), Image.BICUBIC)
            self.photo = ImageTk.PhotoImage(resized_image)
            self.canvas.delete("all")
            self.canvas.create_image(canvas_width // 2, canvas_height // 2, image=self.photo)

    def display_image(self):
        if self.image_folder != "" and len(self.image_files) > 0:
            if self.update_switch_timestamps():
                # Show the text of the image name instead of loading the actual image
                image_name = self.image_files[self.current_image_index]
                self.canvas.delete("all")  # Clear the canvas
                self.canvas.create_text(
                    self.canvas.winfo_width() // 2, 
                    self.canvas.winfo_height() // 2,
                    text=image_name,
                    fill="white"
                )
                # Schedule the image load with a delay
                if self.timer is not None:
                    self.root.after_cancel(self.timer) # Cancel existing timer to avoid conflicts
                self.timer = self.root.after(500, self.load_image_delayed) # Set a new timer to load the image after 500ms

            else:
                # Load the image normally if not quick-switching
                image_path = os.path.join(self.image_folder, self.image_files[self.current_image_index])
                threading.Thread(target=self.load_image, args=(image_path,)).start()
                
    def update_image_size(self, event=None):
        current_width = self.root.winfo_width()
        current_height = self.root.winfo_height()
    
        if (current_width, current_height) != self.window_size:
            self.window_size = (current_width, current_height)
            self.canvas.config(width=current_width, height=current_height)
            self.buffer_forsize()
			
    def buffer_forsize(self, event=None):
        if self.resize_timer:
            self.root.after_cancel(self.resize_timer)
        self.resize_timer = self.root.after(500, self.display_image)

    def update_switch_timestamps(self):
        current_time = time.time()
        self.switch_timestamps.append(current_time)
        self.switch_timestamps = [t for t in self.switch_timestamps if current_time - t <= 2]
        if len(self.switch_timestamps) > 6:
            return True  # Too many updates in a short time period
        return False

    def load_image_delayed(self):
        # This method will be called after the 500ms delay if no keypress happens
        if self.timer is not None:
            self.root.after_cancel(self.timer)  # Cancel the existing timer
        self.timer = None  # Clear the timer ID
        image_path = os.path.join(self.image_folder, self.image_files[self.current_image_index])
        threading.Thread(target=self.load_image, args=(image_path,)).start()
        
        if not self.is_paused:
            self.timer = self.set_timer_interval
            self.root.after(500, self.start_timer)
            
        #self.timer = self.root.after(500, lambda:self.start_pause_slideshow() if not self.is_paused else None)#keycode
        #self.timer = self.root.after(500, lambda:self.start_timer if not self.is_paused else None)
        
    def next_image(self, event=None):
        #if self.timer is not None:
            # Cancel the scheduled image loading if the timer is set
        #    self.root.after_cancel(self.timer)
        #    self.timer = None
    
        if self.image_folder != "":
            if self.random_next_image_var.get(): # Check if random next image is selected
                self.current_image_index = random.randint(0, len(self.image_files) - 1) # Randomly select the next image index
            else:
                self.current_image_index = (self.current_image_index + 1) % len(self.image_files) # Linearly select the next image index
    
            # Display the image (or its name tag if switching quickly)
            self.display_image()
            # Reset the timer interval to the stored set timer interval
            self.timer_interval = self.set_timer_interval
            

    def previous_image(self, event=None):
        #if self.timer is not None:
            # Cancel the scheduled image loading if the timer is set
        #    self.root.after_cancel(self.timer)
        #    self.timer = None
        if self.image_folder != "":
            if self.random_next_image_var.get(): # Check if random next image is selected
                self.current_image_index = random.randint(0, len(self.image_files) - 1) # Randomly select the next image index
            else:
                self.current_image_index = (self.current_image_index - 1) % len(self.image_files)
            self.display_image()
            self.timer_interval = self.set_timer_interval  # Use the stored set timer interval
            
if __name__ == "__main__":
    root = tk.Tk()
    image_viewer = ImageViewer(root)
    root.mainloop()

turn this code into a Mermaid diagram graph that explains the code flow as well as possible