import tkinter as tk
from tkinter import ttk, messagebox
from PIL import Image, ImageTk, UnidentifiedImageError
from bs4 import BeautifulSoup
import io
import requests
import threading
from functools import partial
import tempfile
import os
import re
from urllib.parse import quote 

# Setup basic caching mechanism
class ImageCache:
    def __init__(self):
        self.cache_dir = tempfile.gettempdir()
        self.cache = {}

    def get_from_cache(self, url):
        if url in self.cache:
            return Image.open(self.cache[url])
        else:
            try:
                response = requests.get(url, timeout=5)
                image = Image.open(io.BytesIO(response.content))

                # Sanitize the filename
                filename = os.path.basename(url)
                filename = re.sub(r'[\/*?:"<>|]', "", filename)  # Removes problematic characters

                # Ensure a valid extension is added
                file_ext = os.path.splitext(filename)[1]
                if not file_ext.lower() in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff']:
                    filename += '.jpg'
                
                cache_file_path = os.path.join(self.cache_dir, filename)

                image.save(cache_file_path)
                self.cache[url] = cache_file_path  # Cache the path of the saved image
                return image
            except Exception as e:
                raise e

cache = ImageCache()

root = tk.Tk()
root.title("Image Searcher")

input_var = tk.StringVar()
input_entry = ttk.Entry(root, textvariable=input_var, width=50)
input_entry.grid(row=0, column=0, padx=10, pady=10)

image_frames = []
for i in range(5):
    frame = ttk.Frame(root, width=100, height=100)
    frame.grid(row=i+1, column=0, padx=5, pady=5, sticky="ew")
    label = ttk.Label(frame)
    label.pack(expand=True, fill=tk.BOTH)
    frame.label = label  # Dynamically add label reference to frame for easy access
    image_frames.append(frame)

def show_original(url, event=None):
    try:
        top = tk.Toplevel(root)
        top.title("Original Image")

        image = cache.get_from_cache(url)  # Use the cache to load image
        image = image.resize((400, 400))  # Resize to a larger view
        photo_image = ImageTk.PhotoImage(image)

        label = tk.Label(top, image=photo_image)
        label.image = photo_image  # Keep a reference!
        label.pack()

        top.transient(root)  # Set to be a transient window of root
        top.grab_set()  # Modal behavior
    except Exception as e:
        messagebox.showerror("Error", f"Error displaying original image: {e}")

def fetch_and_resize_image(url):
    try:
        image = cache.get_from_cache(url)
        image = image.resize((100, 100))
        return ImageTk.PhotoImage(image)
    except Exception as e:
        messagebox.showerror("Error", f"Error processing image: {e}")
        return None

def search():
    # Clear images and unbind events
    for frame in image_frames:
        frame.label.config(image=None)
        frame.unbind("<Button-1>")
    
    search_term = input_var.get()
    # Ensure the search term is URL encoded to handle spaces, etc.
    google_url = "https://www.google.com/search?q=" + quote(search_term) + "&source=lnms&tbm=isch"
    
    try:
        response = requests.get(google_url, timeout=5)  # Include timeout for the request
    except Exception as e:
        messagebox.showerror("Error", f"Error making request to Google: {e}")
        return
    
    soup = BeautifulSoup(response.text, 'html.parser')
    
    image_urls = []
    for img in soup.find_all("img"):
        try:
            img_url = img['src']
            if img_url.startswith("http"): 
                image_urls.append(img_url)
                if len(image_urls) >= 5:
                    break
        except KeyError:
            continue

    if not image_urls:
        messagebox.showinfo("Info", f"No images found for '{search_term}'.")
        return

    for i, url in enumerate(image_urls):
        photo_image = fetch_and_resize_image(url)
        if photo_image:
            frame = image_frames[i]
            frame.label.config(image=photo_image)
            frame.label.image = photo_image  # Keep reference!
            frame.bind("<Button-1>", partial(show_original, url))

def start_search():
    threading.Thread(target=search, daemon=True).start()

search_button = ttk.Button(root, text="Search", command=start_search)
search_button.grid(row=0, column=1, padx=10, pady=10)

root.mainloop()



write a 2.0 version of this, improve everything if possible