Make it simpler to use and add in more balancing and stuff.

```
#!/usr/bin/env python3
"""
Enhanced SRVK Algorithm Implementation with Optimized and Feature-Rich GUI in Python

This script implements the Super RSA Versatile Key (SRVK) algorithm with an enhanced and optimized GUI.
The GUI allows users to interactively perform key generation, encryption, and decryption
with additional features such as batch processing, compression, detailed progress tracking, and logging.

Author: Keperman209
Date: LAST_UPDATED
"""

import os
import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog
from tkinter import ttk
import threading
import json
import gzip
import base64
import logging
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.ciphers import algorithms, modes, Cipher
from cryptography.hazmat.backends import default_backend
from cryptography.exceptions import InvalidTag

# ==================== Logging Configuration ====================
logging.basicConfig(
    filename='srvk_app.log',
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

# ==================== Encryption and Key Management Functions ====================

def generate_keys(rsa_key_size=2048, symmetric_key_size=256):
    """
    Generates RSA key pair and symmetric key.

    Parameters:
    - rsa_key_size (int): Size of the RSA key in bits (2048, 4096, or 8192)
    - symmetric_key_size (int): Size of the symmetric key in bits (128 or 256)

    Returns:
    - rsa_private_key: RSA private key object
    - rsa_public_key: RSA public key object
    - symmetric_key (bytes): Symmetric key bytes
    """
    logging.debug(f"Generating RSA keys with size {rsa_key_size} bits.")
    # Generate RSA key pair
    rsa_private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=rsa_key_size,
        backend=default_backend()
    )
    rsa_public_key = rsa_private_key.public_key()

    # Generate symmetric key
    if symmetric_key_size not in [128, 256]:
        raise ValueError("Symmetric key size must be 128 or 256 bits.")
    symmetric_key = os.urandom(symmetric_key_size // 8)  # Convert bits to bytes
    logging.debug("Symmetric key generated.")

    return rsa_private_key, rsa_public_key, symmetric_key

def srvk_encrypt(plaintext, rsa_public_key, symmetric_key=None, symmetric_key_size=256, block_size=1024*1024, key_rotation_interval=0, compress=False):
    """
    Encrypts the plaintext using the SRVK algorithm.

    Parameters:
    - plaintext (bytes): Data to encrypt
    - rsa_public_key: RSA public key object
    - symmetric_key (bytes): Optional; symmetric key to use (if None, a new key is generated)
    - symmetric_key_size (int): Size of the symmetric key in bits (128 or 256)
    - block_size (int): Size of data blocks to split plaintext into
    - key_rotation_interval (int): Number of blocks after which to rotate symmetric key (0 for no rotation)
    - compress (bool): Whether to compress the data before encryption

    Returns:
    - A dictionary containing:
        - 'encrypted_symmetric_keys': List of encrypted symmetric keys (base64 encoded)
        - 'cipher_blocks': List of cipher blocks (each block is a dictionary with 'ciphertext', 'nonce', 'tag', 'key_index')
        - 'compression': Boolean indicating if compression was applied
    """
    logging.debug("Starting encryption process.")
    # Compress data if required
    if compress:
        logging.debug("Compressing plaintext.")
        plaintext = gzip.compress(plaintext)

    # If no symmetric key is provided, generate one
    if symmetric_key is None:
        symmetric_key = os.urandom(symmetric_key_size // 8)  # Dynamically use the symmetric_key_size
        logging.debug("Symmetric key not provided. Generated new symmetric key.")

    encrypted_symmetric_keys = []
    cipher_blocks = []

    # Split plaintext into blocks
    blocks = [plaintext[i:i+block_size] for i in range(0, len(plaintext), block_size)]
    num_blocks = len(blocks)
    logging.debug(f"Plaintext split into {num_blocks} blocks.")

    current_symmetric_key = symmetric_key
    encrypted_symmetric_key = rsa_public_key.encrypt(
        current_symmetric_key,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )
    encrypted_symmetric_keys.append(base64.b64encode(encrypted_symmetric_key).decode())
    key_index = 0
    logging.debug("Initial symmetric key encrypted and stored.")

    for i, block in enumerate(blocks):
        # Key rotation
        if key_rotation_interval > 0 and i > 0 and i % key_rotation_interval == 0:
            logging.debug(f"Rotating symmetric key at block {i}.")
            # Rotate symmetric key
            current_symmetric_key = os.urandom(symmetric_key_size // 8)  # Use symmetric_key_size here
            encrypted_symmetric_key = rsa_public_key.encrypt(
                current_symmetric_key,
                padding.OAEP(
                    mgf=padding.MGF1(algorithm=hashes.SHA256()),
                    algorithm=hashes.SHA256(),
                    label=None
                )
            )
            encrypted_symmetric_keys.append(base64.b64encode(encrypted_symmetric_key).decode())
            key_index += 1

        # Encrypt the block with AES-GCM
        nonce = os.urandom(12)  # 96-bit nonce for GCM
        aes_cipher = Cipher(
            algorithms.AES(current_symmetric_key),
            modes.GCM(nonce),
            backend=default_backend()
        ).encryptor()

        ciphertext = aes_cipher.update(block) + aes_cipher.finalize()
        tag = aes_cipher.tag

        cipher_blocks.append({
            'ciphertext': base64.b64encode(ciphertext).decode(),
            'nonce': base64.b64encode(nonce).decode(),
            'tag': base64.b64encode(tag).decode(),
            'key_index': key_index  # Indicates which encrypted symmetric key to use
        })

        # Log progress at approximately every 10%
        if num_blocks >= 10:
            if (i+1) % max(1, num_blocks // 10) == 0:
                logging.debug(f"Encrypted {i+1}/{num_blocks} blocks.")

    logging.debug("Encryption process completed.")
    return {
        'encrypted_symmetric_keys': encrypted_symmetric_keys,
        'cipher_blocks': cipher_blocks,
        'compression': compress
    }

def srvk_decrypt(encrypted_data, rsa_private_key):
    """
    Decrypts the encrypted data using the SRVK algorithm.

    Parameters:
    - encrypted_data (dict): The encrypted data as returned by srvk_encrypt
    - rsa_private_key: RSA private key object

    Returns:
    - plaintext (bytes)
    """
    logging.debug("Starting decryption process.")
    encrypted_symmetric_keys = encrypted_data['encrypted_symmetric_keys']
    cipher_blocks = encrypted_data['cipher_blocks']
    compression = encrypted_data.get('compression', False)

    # Decrypt symmetric keys
    symmetric_keys = []
    for enc_key_b64 in encrypted_symmetric_keys:
        encrypted_symmetric_key = base64.b64decode(enc_key_b64)
        symmetric_key = rsa_private_key.decrypt(
            encrypted_symmetric_key,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )
        symmetric_keys.append(symmetric_key)
    logging.debug("All symmetric keys decrypted.")

    # Decrypt cipher blocks
    plaintext_blocks = []
    total_blocks = len(cipher_blocks)

    for idx, block in enumerate(cipher_blocks):
        key_index = block['key_index']
        symmetric_key = symmetric_keys[key_index]

        nonce = base64.b64decode(block['nonce'])
        tag = base64.b64decode(block['tag'])
        ciphertext = base64.b64decode(block['ciphertext'])

        # Decrypt the block with AES-GCM
        try:
            aes_cipher = Cipher(
                algorithms.AES(symmetric_key),
                modes.GCM(nonce, tag),
                backend=default_backend()
            ).decryptor()

            plaintext_block = aes_cipher.update(ciphertext) + aes_cipher.finalize()
            plaintext_blocks.append(plaintext_block)

            # Log progress at approximately every 10%
            if total_blocks >= 10:
                if (idx+1) % max(1, total_blocks // 10) == 0:
                    logging.debug(f"Decrypted {idx+1}/{total_blocks} blocks.")
        except InvalidTag:
            logging.error(f"Decryption failed at block {idx}. Invalid authentication tag.")
            raise ValueError(f"Decryption failed at block {idx}. Invalid authentication tag.")

    # Reassemble plaintext
    plaintext = b''.join(plaintext_blocks)

    # Decompress if needed
    if compression:
        logging.debug("Decompressing plaintext.")
        plaintext = gzip.decompress(plaintext)

    logging.debug("Decryption process completed.")
    return plaintext

def save_rsa_private_key(private_key, filepath, password=None):
    """
    Saves RSA private key to a file in PEM format.

    Parameters:
    - private_key: RSA private key object
    - filepath (str): Path to save the private key file
    - password (bytes): Optional; password to encrypt the private key
    """
    pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.BestAvailableEncryption(password) if password else serialization.NoEncryption()
    )

    with open(filepath, 'wb') as f:
        f.write(pem)
    logging.debug(f"Private key saved to {filepath}.")

def load_rsa_private_key(filepath, password=None):
    """
    Loads RSA private key from a PEM file.

    Parameters:
    - filepath (str): Path to the private key PEM file
    - password (bytes): Optional; password to decrypt the private key

    Returns:
    - private_key: RSA private key object
    """
    with open(filepath, 'rb') as f:
        pem_data = f.read()

    private_key = serialization.load_pem_private_key(
        pem_data,
        password=password,
        backend=default_backend()
    )
    logging.debug(f"Private key loaded from {filepath}.")
    return private_key

def save_rsa_public_key(public_key, filepath):
    """
    Saves RSA public key to a file in PEM format.

    Parameters:
    - public_key: RSA public key object
    - filepath (str): Path to save the public key file
    """
    pem = public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    )

    with open(filepath, 'wb') as f:
        f.write(pem)
    logging.debug(f"Public key saved to {filepath}.")

def load_rsa_public_key(filepath):
    """
    Loads RSA public key from a PEM file.

    Parameters:
    - filepath (str): Path to the public key PEM file

    Returns:
    - public_key: RSA public key object
    """
    with open(filepath, 'rb') as f:
        pem_data = f.read()

    public_key = serialization.load_pem_public_key(
        pem_data,
        backend=default_backend()
    )
    logging.debug(f"Public key loaded from {filepath}.")
    return public_key

# ==================== GUI Class ====================

class SRVKApp:
    def __init__(self, root):
        self.root = root
        self.root.title("SRVK Encryption/Decryption")
        self.root.geometry("800x700")
        self.root.configure(bg="#2e2e2e")

        # Apply a style
        self.style = ttk.Style()
        self.style.theme_use('clam')  # Options: 'clam', 'alt', 'default', 'classic'

        # Existing styles
        self.style.configure("TNotebook", background="#2e2e2e", borderwidth=0)
        self.style.configure("TNotebook.Tab", background="#444", foreground="white", padding=[10, 5])
        self.style.map("TNotebook.Tab", background=[("selected", "#2e2e2e")], foreground=[("selected", "white")])
        self.style.configure("TButton", padding=6, relief="flat", background="#444", foreground="white")
        self.style.map("TButton", background=[("active", "#555")])

        # Define custom styles
        self.style.configure("Custom.TCheckbutton", foreground="white", background="#2e2e2e")
        self.style.configure("Custom.TLabel", foreground="white", background="#2e2e2e")
        self.style.configure("Custom.TEntry", foreground="black", background="#ffffff")

        # Create a Notebook (tabbed interface)
        self.notebook = ttk.Notebook(root)
        self.notebook.pack(expand=True, fill='both', padx=10, pady=10)

        # Create Frames for each tab
        self.create_key_generation_tab()
        self.create_encryption_tab()
        self.create_decryption_tab()
        self.create_settings_tab()
        self.create_log_tab()
        self.create_status_bar()

    def create_key_generation_tab(self):
        self.key_gen_frame = ttk.Frame(self.notebook, padding=20)
        self.notebook.add(self.key_gen_frame, text='Key Generation')

        # RSA Key Size
        rsa_label = ttk.Label(self.key_gen_frame, text="RSA Key Size (bits):", style="Custom.TLabel")
        rsa_label.grid(row=0, column=0, sticky='W', pady=10)
        self.rsa_key_size_var = tk.IntVar(value=2048)
        self.rsa_key_size_combo = ttk.Combobox(self.key_gen_frame, textvariable=self.rsa_key_size_var, state='readonly', width=20)
        self.rsa_key_size_combo['values'] = (2048, 4096, 8192)
        self.rsa_key_size_combo.grid(row=0, column=1, pady=10, sticky='W')

        # Symmetric Key Size
        sym_label = ttk.Label(self.key_gen_frame, text="Symmetric Key Size (bits):", style="Custom.TLabel")
        sym_label.grid(row=1, column=0, sticky='W', pady=10)
        self.sym_key_size_var = tk.IntVar(value=256)
        self.sym_key_size_combo = ttk.Combobox(self.key_gen_frame, textvariable=self.sym_key_size_var, state='readonly', width=20)
        self.sym_key_size_combo['values'] = (128, 256)
        self.sym_key_size_combo.grid(row=1, column=1, pady=10, sticky='W')

        # Generate Keys Button
        self.generate_keys_button = ttk.Button(self.key_gen_frame, text="Generate Keys", command=self.generate_keys_thread)
        self.generate_keys_button.grid(row=2, column=0, columnspan=2, pady=20)

        # Progress Bar
        self.key_gen_progress = ttk.Progressbar(self.key_gen_frame, mode='determinate')
        self.key_gen_progress.grid(row=3, column=0, columnspan=2, sticky='EW', pady=10)

    def create_encryption_tab(self):
        self.encrypt_frame = ttk.Frame(self.notebook, padding=20)
        self.notebook.add(self.encrypt_frame, text='Encryption')

        # Public Key Selection
        pub_key_label = ttk.Label(self.encrypt_frame, text="RSA Public Key:", style="Custom.TLabel")
        pub_key_label.grid(row=0, column=0, sticky='W', pady=5)
        self.pub_key_path = tk.StringVar()
        self.pub_key_entry = ttk.Entry(self.encrypt_frame, textvariable=self.pub_key_path, width=50, state='readonly', style="Custom.TEntry")
        self.pub_key_entry.grid(row=0, column=1, pady=5, sticky='W')
        browse_pub_button = ttk.Button(self.encrypt_frame, text="Browse", command=self.browse_pub_key)
        browse_pub_button.grid(row=0, column=2, pady=5, padx=5)

        # Files to Encrypt (Allow multiple)
        file_enc_label = ttk.Label(self.encrypt_frame, text="File(s) to Encrypt:", style="Custom.TLabel")
        file_enc_label.grid(row=1, column=0, sticky='NW', pady=5)
        self.file_enc_paths = tk.StringVar()
        self.file_enc_listbox = tk.Listbox(
            self.encrypt_frame,
            listvariable=self.file_enc_paths,
            selectmode='multiple',
            width=50,
            height=5,
            bg="#1e1e1e",
            fg="#dcdcdc"
        )
        self.file_enc_listbox.grid(row=1, column=1, pady=5, sticky='W')
        browse_file_enc_button = ttk.Button(self.encrypt_frame, text="Browse", command=self.browse_files_to_encrypt)
        browse_file_enc_button.grid(row=1, column=2, pady=5, padx=5)

        # Compression Option
        self.compress_var = tk.BooleanVar(value=False)
        compress_check = ttk.Checkbutton(
            self.encrypt_frame,
            text="Compress before Encryption",
            variable=self.compress_var,
            style="Custom.TCheckbutton"
        )
        compress_check.grid(row=2, column=0, columnspan=2, sticky='W', pady=5)

        # Block Size
        block_size_label = ttk.Label(self.encrypt_frame, text="Block Size (bytes):", style="Custom.TLabel")
        block_size_label.grid(row=3, column=0, sticky='W', pady=5)
        self.block_size_var = tk.IntVar(value=1048576)
        self.block_size_entry = ttk.Entry(self.encrypt_frame, textvariable=self.block_size_var, width=20, style="Custom.TEntry")
        self.block_size_entry.grid(row=3, column=1, pady=5, sticky='W')

        # Key Rotation Interval
        key_rot_label = ttk.Label(self.encrypt_frame, text="Key Rotation Interval (blocks):", style="Custom.TLabel")
        key_rot_label.grid(row=4, column=0, sticky='W', pady=5)
        self.key_rot_var = tk.IntVar(value=0)
        self.key_rot_entry = ttk.Entry(self.encrypt_frame, textvariable=self.key_rot_var, width=20, style="Custom.TEntry")
        self.key_rot_entry.grid(row=4, column=1, pady=5, sticky='W')

        # Encrypt Button
        self.encrypt_button = ttk.Button(self.encrypt_frame, text="Encrypt", command=self.encrypt_thread)
        self.encrypt_button.grid(row=5, column=0, columnspan=3, pady=20)

        # Progress Bar with Percentage
        self.encrypt_progress = ttk.Progressbar(self.encrypt_frame, mode='determinate')
        self.encrypt_progress.grid(row=6, column=0, columnspan=3, sticky='EW', pady=10)

        self.encrypt_progress_label = ttk.Label(self.encrypt_frame, text="Progress: 0%", style="Custom.TLabel")
        self.encrypt_progress_label.grid(row=7, column=0, columnspan=3, sticky='E', pady=5)

    def create_decryption_tab(self):
        self.decrypt_frame = ttk.Frame(self.notebook, padding=20)
        self.notebook.add(self.decrypt_frame, text='Decryption')

        # Private Key Selection
        priv_key_label = ttk.Label(self.decrypt_frame, text="RSA Private Key:", style="Custom.TLabel")
        priv_key_label.grid(row=0, column=0, sticky='W', pady=5)
        self.priv_key_path = tk.StringVar()
        self.priv_key_entry = ttk.Entry(self.decrypt_frame, textvariable=self.priv_key_path, width=50, state='readonly', style="Custom.TEntry")
        self.priv_key_entry.grid(row=0, column=1, pady=5, sticky='W')
        browse_priv_button = ttk.Button(self.decrypt_frame, text="Browse", command=self.browse_priv_key)
        browse_priv_button.grid(row=0, column=2, pady=5, padx=5)

        # Encrypted Files to Decrypt (Allow multiple)
        file_dec_label = ttk.Label(self.decrypt_frame, text="Encrypted File(s):", style="Custom.TLabel")
        file_dec_label.grid(row=1, column=0, sticky='NW', pady=5)
        self.file_dec_paths = tk.StringVar()
        self.file_dec_listbox = tk.Listbox(
            self.decrypt_frame,
            listvariable=self.file_dec_paths,
            selectmode='multiple',
            width=50,
            height=5,
            bg="#1e1e1e",
            fg="#dcdcdc"
        )
        self.file_dec_listbox.grid(row=1, column=1, pady=5, sticky='W')
        browse_file_dec_button = ttk.Button(self.decrypt_frame, text="Browse", command=self.browse_files_to_decrypt)
        browse_file_dec_button.grid(row=1, column=2, pady=5, padx=5)

        # Decrypt Button
        self.decrypt_button = ttk.Button(self.decrypt_frame, text="Decrypt", command=self.decrypt_thread)
        self.decrypt_button.grid(row=2, column=0, columnspan=3, pady=20)

        # Progress Bar with Percentage
        self.decrypt_progress = ttk.Progressbar(self.decrypt_frame, mode='determinate')
        self.decrypt_progress.grid(row=3, column=0, columnspan=3, sticky='EW', pady=10)

        self.decrypt_progress_label = ttk.Label(self.decrypt_frame, text="Progress: 0%", style="Custom.TLabel")
        self.decrypt_progress_label.grid(row=4, column=0, columnspan=3, sticky='E', pady=5)

    def create_settings_tab(self):
        self.settings_frame = ttk.Frame(self.notebook, padding=20)
        self.notebook.add(self.settings_frame, text='Settings')

        # Default Save Directory
        save_dir_label = ttk.Label(self.settings_frame, text="Default Save Directory:", style="Custom.TLabel")
        save_dir_label.grid(row=0, column=0, sticky='W', pady=10)
        self.save_dir_var = tk.StringVar(value=os.getcwd())
        self.save_dir_entry = ttk.Entry(self.settings_frame, textvariable=self.save_dir_var, width=50, state='readonly', style="Custom.TEntry")
        self.save_dir_entry.grid(row=0, column=1, pady=10, sticky='W')
        browse_save_dir_button = ttk.Button(self.settings_frame, text="Browse", command=self.browse_save_directory)
        browse_save_dir_button.grid(row=0, column=2, pady=10, padx=5)

        # Log Level
        log_level_label = ttk.Label(self.settings_frame, text="Log Level:", style="Custom.TLabel")
        log_level_label.grid(row=1, column=0, sticky='W', pady=10)
        self.log_level_var = tk.StringVar(value='INFO')
        self.log_level_combo = ttk.Combobox(self.settings_frame, textvariable=self.log_level_var, state='readonly', width=20)
        self.log_level_combo['values'] = ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
        self.log_level_combo.grid(row=1, column=1, pady=10, sticky='W')

        # Apply Settings Button
        apply_settings_button = ttk.Button(self.settings_frame, text="Apply Settings", command=self.apply_settings)
        apply_settings_button.grid(row=2, column=0, columnspan=3, pady=20)

    def create_log_tab(self):
        self.log_frame = ttk.Frame(self.notebook, padding=20)
        self.notebook.add(self.log_frame, text='Logs')

        # Log Display
        self.log_text = tk.Text(self.log_frame, state='disabled', wrap='word', bg="#1e1e1e", fg="#dcdcdc")
        self.log_text.pack(expand=True, fill='both')

        # Load existing logs
        self.load_logs()

    def create_status_bar(self):
        self.status_var = tk.StringVar()
        self.status_var.set("Welcome to the SRVK Encryption/Decryption Tool.")
        self.status_bar = ttk.Label(self.root, textvariable=self.status_var, relief='sunken', anchor='w', background="#2e2e2e", foreground="white")
        self.status_bar.pack(side='bottom', fill='x')

    # ==================== Key Generation Handlers ====================

    def generate_keys_thread(self):
        """
        Initiates the key generation process. Collects user inputs in the main thread
        and starts a background thread for key generation.
        """
        # Step 1: Collect save paths and password in the main thread
        private_key_save_path = filedialog.asksaveasfilename(
            title="Save Private Key As",
            defaultextension=".pem",
            initialdir=self.save_dir_var.get(),
            filetypes=[("PEM files", "*.pem")]
        )

        if not private_key_save_path:
            messagebox.showwarning("Warning", "Private key save operation canceled.")
            logging.warning("Private key save operation was canceled by the user.")
            self.status_var.set("Private key save canceled.")
            return

        # Prompt for password
        password = simpledialog.askstring(
            "Private Key Encryption",
            "Enter password for private key encryption (leave blank for no password):",
            show='*'
        )
        if password:
            password_bytes = password.encode()
        else:
            password_bytes = None

        # Prompt for public key save path
        public_key_save_path = filedialog.asksaveasfilename(
            title="Save Public Key As",
            defaultextension=".pem",
            initialdir=self.save_dir_var.get(),
            filetypes=[("PEM files", "*.pem")]
        )

        if not public_key_save_path:
            messagebox.showwarning("Warning", "Public key save operation canceled.")
            logging.warning("Public key save operation was canceled by the user.")
            self.status_var.set("Public key save canceled.")
            return

        # Step 2: Start background thread for key generation and saving
        threading.Thread(
            target=self.generate_keys_gui,
            args=(private_key_save_path, password_bytes, public_key_save_path),
            daemon=True
        ).start()

    def generate_keys_gui(self, private_key_save_path, password_bytes, public_key_save_path):
        """
        Generates RSA and symmetric keys and saves them to the specified file paths.
        Runs in a background thread.

        Parameters:
        - private_key_save_path (str): Path to save the private key
        - password_bytes (bytes): Password for encrypting the private key (optional)
        - public_key_save_path (str): Path to save the public key
        """
        rsa_key_size = self.rsa_key_size_var.get()
        symmetric_key_size = self.sym_key_size_var.get()

        self.root.after(0, lambda: self.status_var.set("Generating keys..."))
        self.root.after(0, lambda: self.key_gen_progress.config(value=0))
        self.key_gen_progress.update()

        try:
            rsa_private_key, rsa_public_key, symmetric_key = generate_keys(
                rsa_key_size=rsa_key_size,
                symmetric_key_size=symmetric_key_size
            )
        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("Error", f"Key generation failed: {e}"))
            logging.exception("Key generation failed.")
            self.root.after(0, lambda: self.key_gen_progress.config(value=0))
            self.root.after(0, lambda: self.status_var.set("Key generation failed."))
            return

        self.root.after(0, lambda: self.key_gen_progress.config(value=50))
        self.key_gen_progress.update()

        try:
            # Save Private Key
            save_rsa_private_key(rsa_private_key, private_key_save_path, password=password_bytes)
            logging.info(f"Private key saved to {private_key_save_path}.")

            # Save Public Key
            save_rsa_public_key(rsa_public_key, public_key_save_path)
            logging.info(f"Public key saved to {public_key_save_path}.")

            self.root.after(0, lambda: self.key_gen_progress.config(value=100))
            self.root.after(0, lambda: self.status_var.set("Key generation completed successfully."))
            self.root.after(0, lambda: messagebox.showinfo("Success", "Keys generated and saved successfully."))

        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("Error", f"Failed to save keys: {e}"))
            logging.exception("Failed to save keys.")
            self.root.after(0, lambda: self.key_gen_progress.config(value=0))
            self.root.after(0, lambda: self.status_var.set("Key saving failed."))
            return

    # ==================== Encryption Handlers ====================

    def browse_pub_key(self):
        path = filedialog.askopenfilename(
            title="Select RSA Public Key",
            filetypes=[("PEM files", "*.pem")],
            initialdir=self.save_dir_var.get()
        )
        if path:
            self.pub_key_path.set(path)
            logging.info(f"Public key selected: {path}")

    def browse_files_to_encrypt(self):
        paths = filedialog.askopenfilenames(
            title="Select File(s) to Encrypt",
            initialdir=self.save_dir_var.get()
        )
        if paths:
            self.file_enc_listbox.delete(0, tk.END)
            for path in paths:
                self.file_enc_listbox.insert(tk.END, path)
            logging.info(f"Files selected for encryption: {paths}")

    def encrypt_thread(self):
        """
        Initiates the encryption process in a background thread.
        """
        threading.Thread(target=self.encrypt_gui, daemon=True).start()

    def encrypt_gui(self):
        """
        Handles the encryption process. This runs in a background thread.
        """
        pub_key_path = self.pub_key_path.get()
        if not pub_key_path:
            self.root.after(0, lambda: messagebox.showerror("Error", "Please select an RSA public key."))
            logging.error("No RSA public key selected for encryption.")
            return
        try:
            rsa_public_key = load_rsa_public_key(pub_key_path)
        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("Error", f"Failed to load public key: {e}"))
            logging.exception("Failed to load RSA public key.")
            return

        file_indices = self.file_enc_listbox.curselection()
        if not file_indices:
            self.root.after(0, lambda: messagebox.showerror("Error", "Please select at least one file to encrypt."))
            logging.error("No files selected for encryption.")
            return

        file_paths = [self.file_enc_listbox.get(i) for i in file_indices]
        logging.info(f"Files to encrypt: {file_paths}")

        compression = self.compress_var.get()

        try:
            block_size = self.block_size_var.get()
            if block_size <= 0:
                raise ValueError
        except:
            self.root.after(0, lambda: messagebox.showerror("Error", "Invalid block size. Please enter a positive integer."))
            logging.error("Invalid block size entered.")
            return

        try:
            key_rotation_interval = self.key_rot_var.get()
            if key_rotation_interval < 0:
                raise ValueError
        except:
            self.root.after(0, lambda: messagebox.showerror("Error", "Invalid key rotation interval. Please enter a non-negative integer."))
            logging.error("Invalid key rotation interval entered.")
            return

        # Assuming symmetric_key_size is retrieved from key generation settings
        symmetric_key_size = self.sym_key_size_var.get()

        self.root.after(0, lambda: self.status_var.set("Starting encryption..."))
        self.root.after(0, lambda: self.encrypt_progress.config(value=0))
        self.encrypt_progress.update()
        total_files = len(file_paths)

        for idx, file_path in enumerate(file_paths, start=1):
            try:
                with open(file_path, 'rb') as f:
                    plaintext = f.read()
                logging.info(f"Reading file for encryption: {file_path}")

                encrypted_data = srvk_encrypt(
                    plaintext=plaintext,
                    rsa_public_key=rsa_public_key,
                    block_size=block_size,
                    key_rotation_interval=key_rotation_interval,
                    compress=compression,
                    symmetric_key_size=symmetric_key_size  # Pass symmetric_key_size
                )

                # Save encrypted data
                file_name = os.path.basename(file_path)
                encrypted_file_name = f"{file_name}.enc"
                save_encrypted_path = os.path.join(self.save_dir_var.get(), encrypted_file_name)

                with open(save_encrypted_path, 'w') as f:
                    json.dump(encrypted_data, f)
                logging.info(f"Encrypted data saved to {save_encrypted_path}.")

                # Update Progress
                progress = (idx / total_files) * 100
                self.root.after(0, lambda p=progress: self.encrypt_progress.config(value=p))
                self.root.after(0, lambda p=progress: self.encrypt_progress_label.config(text=f"Progress: {int(p)}%"))
                self.encrypt_progress.update()

            except Exception as e:
                self.root.after(0, lambda: messagebox.showerror("Error", f"Encryption failed for {file_path}: {e}"))
                logging.exception(f"Encryption failed for {file_path}.")
                self.root.after(0, lambda: self.encrypt_progress.config(value=0))
                self.root.after(0, lambda: self.status_var.set("Encryption failed."))
                return

        self.root.after(0, lambda: self.encrypt_progress.config(value=100))
        self.root.after(0, lambda: self.encrypt_progress_label.config(text="Progress: 100%"))
        self.root.after(0, lambda: self.status_var.set("Encryption completed successfully."))
        self.root.after(0, lambda: messagebox.showinfo("Success", "Encryption of all files completed successfully."))

    # ==================== Decryption Handlers ====================

    def browse_priv_key(self):
        path = filedialog.askopenfilename(
            title="Select RSA Private Key",
            filetypes=[("PEM files", "*.pem")],
            initialdir=self.save_dir_var.get()
        )
        if path:
            self.priv_key_path.set(path)
            logging.info(f"Private key selected: {path}")

    def browse_files_to_decrypt(self):
        paths = filedialog.askopenfilenames(
            title="Select Encrypted File(s)",
            filetypes=[("Encrypted files", "*.enc"), ("All files", "*.*")],
            initialdir=self.save_dir_var.get()
        )
        if paths:
            self.file_dec_listbox.delete(0, tk.END)
            for path in paths:
                self.file_dec_listbox.insert(tk.END, path)
            logging.info(f"Encrypted files selected for decryption: {paths}")

    def decrypt_thread(self):
        """
        Initiates the decryption process in a background thread.
        """
        # Collect password input in the main thread
        password = simpledialog.askstring("Private Key Password", "Enter password for private key (leave blank if none):", show='*')
        if password:
            password_bytes = password.encode()
        else:
            password_bytes = None

        threading.Thread(
            target=self.decrypt_gui,
            args=(password_bytes,),
            daemon=True
        ).start()

    def decrypt_gui(self, password_bytes):
        """
        Handles the decryption process. This runs in a background thread.

        Parameters:
        - password_bytes (bytes): Password for decrypting the private key (optional)
        """
        priv_key_path = self.priv_key_path.get()
        if not priv_key_path:
            self.root.after(0, lambda: messagebox.showerror("Error", "Please select an RSA private key."))
            logging.error("No RSA private key selected for decryption.")
            return
        try:
            rsa_private_key = load_rsa_private_key(priv_key_path, password=password_bytes)
        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("Error", f"Failed to load private key: {e}"))
            logging.exception("Failed to load RSA private key.")
            return

        file_indices = self.file_dec_listbox.curselection()
        if not file_indices:
            self.root.after(0, lambda: messagebox.showerror("Error", "Please select at least one encrypted file to decrypt."))
            logging.error("No encrypted files selected for decryption.")
            return

        file_paths = [self.file_dec_listbox.get(i) for i in file_indices]
        logging.info(f"Files to decrypt: {file_paths}")

        self.root.after(0, lambda: self.status_var.set("Starting decryption..."))
        self.root.after(0, lambda: self.decrypt_progress.config(value=0))
        self.decrypt_progress.update()
        total_files = len(file_paths)

        for idx, file_path in enumerate(file_paths, start=1):
            try:
                with open(file_path, 'r') as f:
                    encrypted_data = json.load(f)
                logging.info(f"Reading encrypted file for decryption: {file_path}")

                plaintext = srvk_decrypt(
                    encrypted_data=encrypted_data,
                    rsa_private_key=rsa_private_key
                )

                # Determine the original file name by removing the .enc extension
                original_file_name = os.path.basename(file_path)
                if original_file_name.endswith('.enc'):
                    original_file_name = original_file_name[:-4]
                else:
                    original_file_name = f"decrypted_{original_file_name}"

                save_decrypted_path = os.path.join(self.save_dir_var.get(), original_file_name)

                with open(save_decrypted_path, 'wb') as f:
                    f.write(plaintext)
                logging.info(f"Decrypted data saved to {save_decrypted_path}.")

                # Update Progress
                progress = (idx / total_files) * 100
                self.root.after(0, lambda p=progress: self.decrypt_progress.config(value=p))
                self.root.after(0, lambda p=progress: self.decrypt_progress_label.config(text=f"Progress: {int(p)}%"))
                self.decrypt_progress.update()

            except Exception as e:
                self.root.after(0, lambda: messagebox.showerror("Error", f"Decryption failed for {file_path}: {e}"))
                logging.exception(f"Decryption failed for {file_path}.")
                self.root.after(0, lambda: self.decrypt_progress.config(value=0))
                self.root.after(0, lambda: self.status_var.set("Decryption failed."))
                return

        self.root.after(0, lambda: self.decrypt_progress.config(value=100))
        self.root.after(0, lambda: self.decrypt_progress_label.config(text="Progress: 100%"))
        self.root.after(0, lambda: self.status_var.set("Decryption completed successfully."))
        self.root.after(0, lambda: messagebox.showinfo("Success", "Decryption of all files completed successfully."))

    # ==================== Settings Handlers ====================

    def browse_save_directory(self):
        path = filedialog.askdirectory(
            title="Select Default Save Directory",
            initialdir=self.save_dir_var.get()
        )
        if path:
            self.save_dir_var.set(path)
            logging.info(f"Default save directory set to: {path}")

    def apply_settings(self):
        log_level = self.log_level_var.get()
        level = getattr(logging, log_level, logging.INFO)
        logging.getLogger().setLevel(level)
        self.root.after(0, lambda: self.status_var.set(f"Settings updated. Log level set to {log_level}."))
        logging.info(f"Log level changed to {log_level}.")
        self.root.after(0, lambda: messagebox.showinfo("Success", "Settings applied successfully."))

    # ==================== Logs Handlers ====================

    def load_logs(self):
        try:
            with open('srvk_app.log', 'r') as f:
                log_content = f.read()
            self.log_text.config(state='normal')
            self.log_text.delete(1.0, tk.END)
            self.log_text.insert(tk.END, log_content)
            self.log_text.config(state='disabled')
            logging.debug("Logs loaded into the log tab.")
        except FileNotFoundError:
            self.log_text.config(state='normal')
            self.log_text.insert(tk.END, "Log file not found.")
            self.log_text.config(state='disabled')
            logging.warning("Log file not found when loading logs into GUI.")

    # ==================== Logs Update Handler ====================

    def update_log_tab(self):
        """
        Periodically update the log tab with new log entries.
        """
        try:
            with open('srvk_app.log', 'r') as f:
                log_content = f.read()
            self.log_text.config(state='normal')
            self.log_text.delete(1.0, tk.END)
            self.log_text.insert(tk.END, log_content)
            self.log_text.config(state='disabled')
        except FileNotFoundError:
            pass
        self.root.after(5000, self.update_log_tab)  # Update every 5 seconds

    # ==================== Utility Methods ====================

    def run(self):
        self.update_log_tab()
        self.root.mainloop()

# ==================== Main Function ====================

def main_gui():
    root = tk.Tk()
    app = SRVKApp(root)
    app.run()

if __name__ == "__main__":
    main_gui()