import socket
import threading
import tkinter as tk
from tkinter import messagebox, simpledialog, StringVar, ttk, Menu, Text
import re
import os
import time
import datetime
import sys
import json
import winsound

def check_for_xp():
    import platform

    if platform.system() == "Windows":
        if platform.release() == "XP":
            return True

    return False

MESSAGES_FILENAME = "messages.txt"

def save_message(user, message):
    timestamp = datetime.datetime.now().strftime("%H:%M:%S %d/%m/%Y")
    formatted_message = "-\n" + str(user) + " (" + str(timestamp) + ")\n" + str(message) + "\n\n"
    
    with open(MESSAGES_FILENAME, "a", encoding="utf-8") as file:
        file.write(formatted_message)

SETTINGS_FILE = "settings.json"

def load_settings():
    try:
        with open(SETTINGS_FILE, "r") as file:
            return json.load(file)
    except FileNotFoundError:
        return {}

def save_settings(settings):
    with open(SETTINGS_FILE, "w") as file:
        json.dump(settings, file, indent=4)

ROSTER_FILE = "roster.json"

def load_contacts():
    if os.path.exists(ROSTER_FILE):
        with open(ROSTER_FILE, "r", encoding="utf-8") as file:
            contacts = json.load(file)
    else:
        contacts = []

    if "Chat Room" not in contacts:
        contacts.insert(0, "Chat Room")

    return contacts


def save_contacts(contacts):
    with open(ROSTER_FILE, "w", encoding="utf-8") as file:
        json.dump(contacts, file, ensure_ascii=False, indent=4)

windows2000_support = False
is_xp = check_for_xp()
settings__ = load_settings()
enable_debugging = settings__.get('debug_option_0')
save_roster_local = settings__.get('roster_option_0')
stop_event = threading.Event()

def receive_messages(sock, text_area, text_area2, text_area3, current_username, update_roster_callback):
    while not stop_event.is_set():
        try:
            message = sock.recv(1024).decode('utf-8')
            if not message:
                break

            if enable_debugging:
                print("RECEIVE " + str(message))
            if not message.startswith("Private message sent to "):
                insert_formatted_message(text_area, message, current_username)
            
            byte_data = message.encode('utf-8')
            hex_representation = ' '.join('{:02X}'.format(byte) for byte in byte_data)
            if enable_debugging:
                print("RECEIVE HEX " + str(hex_representation))

            text_area2.config(state=tk.NORMAL)
            text_area2.insert(tk.END, hex_representation + "\n", "message")
            text_area2.insert(tk.END, "-----\n", "message")
            text_area2.see(tk.END)
            text_area2.config(state=tk.DISABLED)

            text_area3.config(state=tk.NORMAL)
            text_area3.insert(tk.END, str(message.encode('utf-8')) + "\n", "message")
            if enable_debugging:
                print("RECEIVE BYTES " + str(message.encode('utf-8')))
            text_area3.insert(tk.END, "-----\n", "message")
            text_area3.see(tk.END)
            text_area3.config(state=tk.DISABLED)
            
            if "***" in message:
                if "has joined the server" in message:
                    user = message.split(" ")[1]
                    update_roster_callback(user, "join")
                elif "has left the server" in message:
                    user = message.split(" ")[1]
                    update_roster_callback(user, "leave")
        except Exception as e:
            if not sock._closed:
                messagebox.showerror("Error receiving message",  str(e))
            break

def receive_messages_(sock):
    while True:
        try:
            message = sock.recv(1024).decode('utf-8')
            if not message:
                break
            break

            if enable_debugging:
                print("AUTHORIZE RECEIVE " + str(messages))

        except Exception as e:
            if not sock._closed:
                messagebox.showerror("Error receiving message", str(e))
            break

def send_message(sock, entry_field, text_area):
    message = entry_field.get()
    if message:
        try:
            sock.send(message.encode('utf-8'))
            if enable_debugging:
                print("SENT x SOCK")
                print("SENT SOURCE: " + str(message))
                print("SENT UTF-8: " + str(message.encode('utf-8')))
            entry_field.delete(0, tk.END)
        except Exception as e:
            if enable_debugging:
                print("SENT ERR " + str(e))
            messagebox.showerror("Error", "Failed to send message: " + str(e))

def send_private_message(sock, entry_field, text_area, recipient):
    message = entry_field.get()
    if message and recipient:
        try:
            private_message = "/pm " + str(recipient) + " " + str(message)
            sock.send(private_message.encode('utf-8'))
            entry_field.delete(0, tk.END)
            msg_changed = "(Private to " + str(recipient) + ") " + str(message)
            insert_formatted_message(text_area, msg_changed)
        except Exception as e:
            messagebox.showerror("Error", "Failed to send message: " + str(e))

def insert_formatted_message(text_area, message, current_username=None):
    text_area.config(state=tk.NORMAL)

    settings_ = load_settings()
    parts = message.split(":", 1)
    if len(parts) == 2 and not message.startswith("***") and not message.startswith("(Private)"):

        username, user_message = parts
        username = username.strip()
        user_message = user_message.strip()

        if " " not in username and username != "Usage" and username != "Servers":
            save_message(username, user_message)

        if username == current_username and settings_.get("main_option_2"):
            text_area.insert(tk.END, username, "username2")
        elif " " not in username and username != "Usage" and username != "Servers" and settings_.get("main_option_3"):
            text_area.insert(tk.END, username, "username")
        else:
            text_area.insert(tk.END, username, "message")

        text_area.insert(tk.END, ": ")
        text_area.insert(tk.END, user_message, "message")
    elif message.startswith("(Private)"):
        if settings_.get("notifications_option_0"):
            winsound.PlaySound("sounds/msgIncoming.wav", winsound.SND_FILENAME | winsound.SND_ASYNC)

        if settings_.get("main_option_4"):
            text_area.insert(tk.END, message.strip(), "pm_message")
        else:
            text_area.insert(tk.END, message.strip(), "message")
    elif message.startswith("(Private to"):
        if settings_.get("notifications_option_1"):
            winsound.PlaySound("sounds/msgSent.wav", winsound.SND_FILENAME | winsound.SND_ASYNC)

        if settings_.get("main_option_4"):
            text_area.insert(tk.END, message.strip(), "pm_message")
        else:
            text_area.insert(tk.END, message.strip(), "message")
    else:
        text_area.insert(tk.END, message.strip(), "message")

    text_area.insert(tk.END, "\n")
    text_area.see(tk.END)
    text_area.config(state=tk.DISABLED)

def extract_users(input_str):
    match = re.search(r"Members in '[^']+': (.+)", input_str)
    if match:
        users = [user.strip() for user in match.group(1).split(',')]
        return users
    else:
        return []

class ChatClient:
    def __init__(self, master):
        self.master = master
        self.master.geometry("0x0")
        self.master.iconify()

        self.settings = load_settings()
        self.load_members_from_server = self.settings.get("roster_option_1")
        self.show_hex = self.settings.get("main_option_1")
        self.show_bytes = self.settings.get("main_option_2")
        self.sock = None
        self.server = None
        self.username = None
        self.receiving_thread = None

        self.login_frame = tk.Toplevel(self.master)
        self.login_frame.title("Login")
        if not windows2000_support:
            self.login_frame.iconbitmap("icon.ico")
        self.login_frame.resizable(False, False)
        self.login_frame.attributes("-topmost", True)

        window_width = 150
        window_height = 255

        screen_width = self.login_frame.winfo_screenwidth()
        screen_height = self.login_frame.winfo_screenheight()

        position_top = int(screen_height / 2 - window_height / 2)
        position_left = int(screen_width / 2 - window_width / 2)

        self.login_frame.geometry(str(window_width) + "x" + str(window_height) + "+" + str(position_left) + "+" + str(position_top))

        self.login_var = StringVar()
        self.password_var = StringVar()

        ttk.Label(self.login_frame, text="Username:").pack(padx=10, pady=5)
        self.username_entry = ttk.Entry(self.login_frame)
        self.username_entry.pack(padx=10, pady=5)

        ttk.Label(self.login_frame, text="Password:").pack(padx=10, pady=5)
        self.password_entry = ttk.Entry(self.login_frame, show='*')
        self.password_entry.pack(padx=10, pady=5)

        login_button = ttk.Button(self.login_frame, text="Login", command=self.login)
        login_button.pack(padx=10, pady=5)

        register_button = ttk.Button(self.login_frame, text="Register", command=self.register)
        register_button.pack(padx=10, pady=5)

        exit_button = ttk.Button(self.login_frame, text="Exit", command=sys.exit)
        exit_button.pack(padx=10, pady=5)

        self.login_frame.protocol("WM_DELETE_WINDOW", sys.exit)

        self.login_frame2 = tk.Toplevel(self.master)
        self.login_frame2.geometry("0x0")
        self.login_frame2.iconify()
        if not windows2000_support:
            self.login_frame2.iconbitmap("icon.ico")
        self.login_frame2.resizable(False, False)
        self.login_frame2.attributes("-topmost", True)

        tk.Label(self.login_frame2, text="My Contact List", font=("Arial", 9, "bold")).pack()

        self.contacts = []

        self.listbox = tk.Listbox(self.login_frame2, height=15, width=50, selectmode=tk.SINGLE)
        self.listbox.pack(padx=10, pady=10)

        for contact in self.contacts:
            self.listbox.insert(tk.END, contact)

        self.listbox.bind("<Double-1>", self.on_double_click)
        self.listbox.bind("<Button-3>", self.show_context_menu)
 
        self.action_button = tk.Menubutton(
            self.login_frame2,
            text="QDMC",
            relief=tk.RAISED,
            font=("Arial", 10),
            width=30,
            height=2
        )

        self.action_menu = tk.Menu(self.action_button, tearoff=0)

        self.action_menu.add_command(label="About QDMC", command=self.open_about_window)
        self.action_menu.add_command(label="Settings", command=self.show_settings_window)
        self.action_menu.add_separator()
        self.action_menu.add_command(label="Add contact", command=self.add_contact)
        self.action_menu.add_command(label="Remove contact", command=self.remove_contact)
        self.action_menu.add_separator()
        self.action_menu.add_command(label="Exit", command=sys.exit)
        
        self.action_button.config(menu=self.action_menu)
        self.action_button.pack(pady=10, padx=10)

        self.context_menu = tk.Menu(self.listbox, tearoff=0)
        self.context_menu.add_command(label="Remove", command=self.remove_contact)
        self.context_menu.add_command(label="Send Private Message", command=self.send_private_message_context)

        self.login_frame2.protocol("WM_DELETE_WINDOW", sys.exit)

        self.tab_control = ttk.Notebook(self.master)

        self.tab_main = ttk.Frame(self.tab_control)
        self.tab_main2 = ttk.Frame(self.tab_control)
        self.tab_main3 = ttk.Frame(self.tab_control)

        self.tab_control.add(self.tab_main, text="Messages")

        if self.show_hex:
            self.tab_control.add(self.tab_main2, text="HEX")

        if self.show_bytes:
            self.tab_control.add(self.tab_main3, text="Bytes")

        self.tab_control.pack(expand=1, fill="both")

        self.text_area_main = tk.Text(self.tab_main, state=tk.DISABLED, wrap=tk.WORD, height=10, width=50)
        self.text_area_main.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        self.text_area_main.tag_configure("username", foreground="red", font=("Arial", 10, "bold"))
        self.text_area_main.tag_configure("username2", foreground="blue", font=("Arial", 10, "bold"))
        self.text_area_main.tag_configure("message", foreground="black", font=("Arial", 10))
        self.text_area_main.tag_configure("pm_message", foreground="green", font=("Arial", 10, "bold"))

        self.context_menu2 = tk.Menu(self.text_area_main, tearoff=0)
        self.context_menu2.add_command(label="Copy", command=self.copy_selected_text_)
        self.context_menu2.add_command(label="Quote", command=self.quote_selected_text_)

        self.text_area_main.bind("<Button-3>", self.show_context_menu_)

        entry_frame_main = tk.Frame(self.tab_main)
        entry_frame_main.pack(side=tk.BOTTOM, fill=tk.X, padx=5, pady=5)

        self.entry_field = tk.Entry(entry_frame_main)
        self.entry_field.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
        self.entry_field.bind("<Return>", self.on_enter_pressed)
        self.entry_field.bind("<Control-v>", self.paste_text)

        self.recipient_entry = ttk.Entry(entry_frame_main)
        self.recipient_entry.pack(side=tk.LEFT, padx=5)

        self.send_button = tk.Button(entry_frame_main, text="Send", command=self.send_message)
        self.send_button.pack(side=tk.RIGHT, padx=5)

        self.text_area_main2 = tk.Text(self.tab_main2, state=tk.NORMAL, wrap=tk.WORD, height=20, width=50)
        self.text_area_main2.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        self.text_area_main2.tag_configure("message", foreground="black", font=("Arial", 10))

        self.text_area_main3 = tk.Text(self.tab_main3, state=tk.NORMAL, wrap=tk.WORD, height=20, width=50)
        self.text_area_main3.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        self.text_area_main3.tag_configure("message", foreground="black", font=("Arial", 10))

    def show_context_menu_(self, event):
        try:
            self.context_menu2.tk_popup(event.x_root, event.y_root)
        finally:
            self.context_menu2.grab_release()

    def copy_selected_text_(self):
        try:
            selected_text = self.text_area_main.selection_get()
            self.master.clipboard_clear()
            self.master.clipboard_append(selected_text)
            self.master.update()
        except tk.TclError:
            pass

    def paste_text(self, event=None):
        try:
            clipboard_text = self.master.clipboard_get()
            self.entry_field.insert(tk.INSERT, clipboard_text)
        except tk.TclError:
            pass

    def quote_selected_text_(self):
        try:
            selected_text = self.text_area_main.selection_get()
            current_text = self.entry_field.get()
            quoted_text = "> " + str(selected_text)
            if current_text:
                quoted_text = str(current_text) + "\n" + str(quoted_text)
            self.entry_field.delete(0, tk.END)
            self.entry_field.insert(0, quoted_text)
        except tk.TclError:
            pass

    def send_message(self):
        recipient = self.recipient_entry.get()
        if recipient == "":
            send_message(self.sock, self.entry_field, self.text_area_main)
        else:
            send_private_message(self.sock, self.entry_field, self.text_area_main, recipient)

    def on_double_click(self, event):
        selected_contact = self.listbox.get(self.listbox.curselection())

        if selected_contact == "Chat Room":
            selected_contact = ""

        self.recipient_entry.delete(0, tk.END)
        self.recipient_entry.insert(0, selected_contact)

    def send_private_message_context(self):
        try:
            selected_contact = self.listbox.get(self.listbox.curselection())

            if selected_contact == "Chat Room":
                selected_contact = ""

            self.recipient_entry.delete(0, tk.END)
            self.recipient_entry.insert(0, selected_contact)
        except Exception as e:
            messagebox.showerror("Error", "Could not send message: " + str(e))

    def show_context_menu(self, event):
        try:
            self.listbox.selection_clear(0, tk.END)
            index = self.listbox.nearest(event.y)
            self.listbox.selection_set(index)
            self.context_menu.post(event.x_root, event.y_root)
        except Exception as e:
            messagebox.showerror("Error showing menu", str(e))

    def on_enter_pressed(self, event=None):
        self.send_message()

    def connect(self):
        try:
            server_address = '147.185.221.19'
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.connect((server_address, 42439))

            if enable_debugging:
                print("SOCK x TCP " + str(server_address) + ":" + str(42439))

            receive_messages_(self.sock)

            return True
        except Exception as e:
            messagebox.showerror("Connection Error", "Could not connect to server: " + str(e))
            return False


    def login(self):
        if enable_debugging:
            print("LOGIN START")

        if self.connect():
            if enable_debugging:
                print("LOGIN x SOCK")
            username = self.username_entry.get()
            password = self.password_entry.get()
            if enable_debugging:
                print("LOGIN INFO " + str(username) + " x " + str(password))

            string_log = "/login " + str(username) + " " + str(password)
            if enable_debugging:
                print("LOGIN RES " + str(string_log))
            self.sock.send(string_log.encode('utf-8'))
            response = self.sock.recv(1024).decode('utf-8')
            if enable_debugging:
                print("LOGIN GOT RESPONSE: " + str(response))

            if "Login successful" in response:
                self.username = username

                response = self.sock.recv(1024).decode('utf-8')
                if enable_debugging:
                    print("EXTR SERVERS")
                available_servers = self.extract_servers(response)

                self.login_frame.destroy()

                if available_servers:
                    self.show_server_selection(available_servers)
                else:
                    messagebox.showwarning("No Servers", "No available servers found.")

                self.receiving_thread = threading.Thread(target=receive_messages, args=(self.sock, self.text_area_main, self.text_area_main2, self.text_area_main3, self.username, self.update_roster))
                self.receiving_thread.daemon = True
                self.receiving_thread.start()

                if enable_debugging:
                    print("RECEIV x SOCK")

            elif "Usage: /login <username> <password>" in response:
                if enable_debugging:
                    print("RESPONSE U x P: " + str(response))
                messagebox.showerror("Login Failed", "You should enter username and password!")
            else:
                if enable_debugging:
                    print("RESPONSE: " + str(response))
                messagebox.showerror("Login Failed", response)

    def register(self):
        if enable_debugging:
            print("REG START")

        if self.connect():
            if enable_debugging:
                print("REG x SOCK")

            username = self.username_entry.get()
            password = self.password_entry.get()
            if enable_debugging:
                print("REG INFO " + str(username) + " x " + str(password))

            string_reg = "/register " + str(username) + " " + str(password)
            if enable_debugging:
                print("REG RES " + str(string_reg))
            self.sock.send(string_reg.encode('utf-8'))
            response = self.sock.recv(1024).decode('utf-8')
            if enable_debugging:
                print("REG GOT RESPONSE: " + str(response))

            if "Registration successful. Please log in." in response:
                if enable_debugging:
                    print("REG SUCESS")
                messagebox.showinfo("Successful!", response)
            elif "Usage: /register <username> <password>" in response:
                if enable_debugging:
                    print("REG U x P: " + str(response))
                messagebox.showerror("Registration Failed", "You should enter username and password!")
            else:
                if enable_debugging:
                    print("RESPONSE: " + str(response))
                messagebox.showerror("Registration Failed", response)

    def show_server_selection(self, servers):
        self.server_selection_window = tk.Toplevel(self.master)
        self.server_selection_window.title("Select Server")

        window_width = 330
        window_height = 270

        screen_width = self.server_selection_window.winfo_screenwidth()
        screen_height = self.server_selection_window.winfo_screenheight()

        position_top = int(screen_height / 2 - window_height / 2)
        position_left = int(screen_width / 2 - window_width / 2)

        self.server_selection_window.geometry(str(window_width) + "x" + str(window_height) + "+" + str(position_left) + "+" + str(position_top))

        if not windows2000_support:
            self.server_selection_window.iconbitmap("icon.ico")

        label = tk.Label(self.server_selection_window, text="Available Servers:")
        label.pack(pady=10)

        server_listbox = tk.Listbox(self.server_selection_window, height=10, width=50, selectmode=tk.SINGLE)
        server_listbox.pack(padx=10, pady=10)

        for server in servers:
            server_listbox.insert(tk.END, server)

        def on_server_select(event):
            selected_server = server_listbox.get(server_listbox.curselection())
            server_join_string = "/join_server " + str(selected_server)
            self.sock.send(server_join_string.encode('utf-8'))
            response = self.sock.recv(1024).decode('utf-8')
            
            time.sleep(1)

            if self.load_members_from_server:
                self.sock.send("/members".encode('utf-8'))

                response = self.sock.recv(1024).decode('utf-8')
                
                members = self.extract_members(response)
                if members:
                    self.update_roster_by_contacts(members)
                else:
                    messagebox.showwarning("No Members", "No members found in this server.")
            else:
                if save_roster_local:
                    self.update_roster_by_contacts(load_contacts())

            self.server_selection_window.destroy()


            self.master.geometry("400x320")
            self.master.title("QDMC")
            if not windows2000_support:
                self.master.iconbitmap("icon.ico")
            self.master.resizable(False, False)
            self.master.deiconify()

            if is_xp:
                self.login_frame2.geometry("150x300")
            else:
                self.login_frame2.geometry("150x330")

            self.login_frame2.title("QDMC")
            if not windows2000_support:
                self.login_frame2.iconbitmap("icon.ico")
            self.login_frame2.resizable(False, False)
            self.login_frame2.deiconify()

        server_listbox.bind("<Double-1>", on_server_select)

        def go_back():
            self.sock.close()
            stop_event.set() 
            self.receiving_thread.join()
            self.server_selection_window.destroy()
            self.__init__(tk.Tk())

        button_frame = tk.Frame(self.server_selection_window)
        button_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=10)

        back_button = tk.Button(button_frame, text="Back", command=go_back, width=10, height=1)
        back_button.pack(side=tk.LEFT, padx=20)

        exit_button = tk.Button(button_frame, text="Exit", command=sys.exit, width=10, height=1)
        exit_button.pack(side=tk.RIGHT, padx=20)


        self.server_selection_window.protocol("WM_DELETE_WINDOW", sys.exit)

    def show_settings_window(self):
        self.settings = load_settings()

        self.main_frame_ = tk.Toplevel(self.master)
        self.main_frame_.title("Settings")
        if not windows2000_support:
            self.main_frame_.iconbitmap("icon.ico")
        self.main_frame_.resizable(False, False)
        self.main_frame_.geometry("440x250")

        self.menu_frame = tk.Frame(self.main_frame_, width=150)
        self.menu_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)

        self.content_frame = tk.Frame(self.main_frame_)
        self.content_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)

        def show_tab(tab_name):
            for widget in self.content_frame.winfo_children():
                widget.destroy()

            for tab, button in self.buttons.items():
                if tab == tab_name:
                    button.config(bg="#d9d9d9")
                else:
                    button.config(bg=self.menu_frame.cget("bg"))

            self.current_tab = tab_name

            if tab_name == "Main":
                render_general_tab()
            elif tab_name == "Notifications":
                render_notifications_tab()
            elif tab_name == "Debug":
                render_debug_tab()
            elif tab_name == "Roster":
                render_roster_tab()
            else:
                tk.Label(self.content_frame, text="содержимое " + str(tab_name), font=("Arial", 14)).pack(pady=10)

        def render_general_tab():
            options_frame = tk.Frame(self.content_frame)
            options_frame.pack(anchor="w", pady=5)

            self.options_vars = []
            options = [
                "Show HEX values of messages",
                "Show byte values of messages",
                "Enable highlighting of your own messages",
                "Enable highlighting of other users messages",
                "Enable highlighting of private messages"
            ]

            for i, opt in enumerate(options):
                var = tk.BooleanVar(value=self.settings.get("main_option_" + str(i), False))
                self.options_vars.append(var)
                tk.Checkbutton(options_frame, text=opt, variable=var).pack(anchor="w")

            button_frame = tk.Frame(self.content_frame)
            button_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5, padx=5)

            tk.Button(button_frame, text="Apply", command=save_general_settings).pack(side=tk.RIGHT, padx=10, pady=5)

        def render_notifications_tab():
            options_frame = tk.Frame(self.content_frame)
            options_frame.pack(anchor="w", pady=5)

            self.options_vars = []
            options = [
                "Play sound when receiving private message",
                "Play sound when private message is sent"
            ]

            for i, opt in enumerate(options):
                var = tk.BooleanVar(value=self.settings.get("notifications_option_" + str(i), False))
                self.options_vars.append(var)
                tk.Checkbutton(options_frame, text=opt, variable=var).pack(anchor="w")

            button_frame = tk.Frame(self.content_frame)
            button_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5, padx=5)

            tk.Button(button_frame, text="Apply", command=save_notification_settings).pack(side=tk.RIGHT, padx=10, pady=5)

        def render_debug_tab():
            options_frame = tk.Frame(self.content_frame)
            options_frame.pack(anchor="w", pady=5)

            self.options_vars = []
            options = [
                "Enable debugging in console"
            ]

            for i, opt in enumerate(options):
                var = tk.BooleanVar(value=self.settings.get("debug_option_" + str(i), False))
                self.options_vars.append(var)
                tk.Checkbutton(options_frame, text=opt, variable=var).pack(anchor="w")

            button_frame = tk.Frame(self.content_frame)
            button_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5, padx=5)

            tk.Button(button_frame, text="Apply", command=save_debug_settings).pack(side=tk.RIGHT, padx=10, pady=5)

        def render_roster_tab():
            options_frame = tk.Frame(self.content_frame)
            options_frame.pack(anchor="w", pady=5)

            self.options_vars = []
            options = [
                "Save roster locally",
                "Load roster from server member list"
            ]

            for i, opt in enumerate(options):
                var = tk.BooleanVar(value=self.settings.get("roster_option_" + str(i), False))
                self.options_vars.append(var)
                tk.Checkbutton(options_frame, text=opt, variable=var).pack(anchor="w")

            button_frame = tk.Frame(self.content_frame)
            button_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5, padx=5)

            tk.Button(button_frame, text="Apply", command=save_roster_settings).pack(side=tk.RIGHT, padx=10, pady=5)

        def save_general_settings():
            for i, var in enumerate(self.options_vars):
                self.settings["main_option_" + str(i)] = var.get()
            save_settings(self.settings)

        def save_notification_settings():
            for i, var in enumerate(self.options_vars):
                self.settings["notifications_option_" + str(i)] = var.get()
            save_settings(self.settings)

        def save_debug_settings():
            for i, var in enumerate(self.options_vars):
                self.settings["debug_option_" + str(i)] = var.get()
            save_settings(self.settings)

        def save_roster_settings():
            for i, var in enumerate(self.options_vars):
                self.settings["roster_option_" + str(i)] = var.get()
            save_settings(self.settings)

        self.tabs = [
            "Main",
            "Notifications",
            "Roster",
            "Debug"
        ]

        self.buttons = {}
        for tab in self.tabs:
            button = tk.Button(self.menu_frame, text=tab, anchor="w", width=20, command=lambda t=tab: show_tab(t))
            button.pack(fill=tk.X, pady=1)
            self.buttons[tab] = button

        self.current_tab = None
        show_tab("Main")

    def extract_members(self, response):
        match = re.search(r"Members in '[^']+':\s*(.+)", response)
        if match:
            members = [member.strip() for member in match.group(1).split(',')]
            members.insert(0, "Chat Room")
            members.remove(self.username)
            return members
        return []

    def update_roster_by_contacts(self, contacts_):
        self.contacts = contacts_
        if enable_debugging:
            print("ROSTER BY CONTACTS " + str(self.contacts))
        if save_roster_local:
            save_contacts(self.contacts)
        for contact in contacts_:
            if enable_debugging:
                print("ROSTER INS " + str(contact))
            self.listbox.insert(tk.END, contact)

    def update_roster(self, user, action):
        if enable_debugging:
            print("ROSTER UPD " + str(user) + " x " + str(action))

        roster_temp = []

        if action == "join":
            if user not in self.contacts:
                self.contacts.append(user)
                if enable_debugging:
                    print("ROSTER NEW " + str(user))
        elif action == "leave":
            if user in self.contacts:
                self.contacts.remove(user)
                if enable_debugging:
                    print("ROSTER REMOVE " + str(user))
        self.listbox.delete(0, tk.END)
        for contact in self.contacts:
            if not contact == self.username:
                if enable_debugging:
                    print("ROSTER INS | UPD " + str(contact))

                roster_temp.append(contact)
                self.listbox.insert(tk.END, contact)

        if save_roster_local:
            save_contacts(roster_temp)

    def remove_contact(self):
        selected = self.listbox.curselection()
        if selected:
            contact = self.listbox.get(selected[0])
            self.contacts.remove(contact)
            self.listbox.delete(selected[0])
            print("ROSTER LB DEL " + str(contact))

            if save_roster_local:
                save_contacts(self.contacts)
        else:
            messagebox.showerror("Remove Contact", "No contact selected.")

    def add_contact(self):
        contact_name = self.get_contact_name()
        if contact_name != "":
            if enable_debugging:
                print("ROSTER SENT " + str(contact_name) + " x join")
            self.update_roster(contact_name, "join")
        else:
            messagebox.showerror("Incorrect format", "Invalid contact name!")

    def get_contact_name(self):
        def submit():
            nonlocal contact_data
            contact_data = name_entry.get()
            window.destroy()

        def cancel():
            nonlocal contact_data
            contact_data = None
            window.destroy()

        window = tk.Toplevel(self.master)
        window.title("Add new contact")
        window.geometry("300x80")
        window.resizable(False, False)

        contact_data = None

        tk.Label(window, text="Contact name:").grid(row=0, column=0, padx=10, pady=5, sticky="e")
        name_entry = tk.Entry(window, width=25)
        name_entry.grid(row=0, column=1, padx=10, pady=5)

        name_entry.bind("<Return>", lambda event: submit())

        tk.Button(window, text="Add", command=submit).grid(row=3, column=0, padx=10, pady=10, sticky="e")
        tk.Button(window, text="Cancel", command=cancel).grid(row=3, column=1, padx=10, pady=10, sticky="w")

        window.transient(self.master)
        window.grab_set()
        self.master.wait_window(window)

        return contact_data

    def open_about_window(self):
        about_window = tk.Toplevel(self.master)
        about_window.title("QDMC")
        about_window.geometry("300x180")
        about_window.resizable(False, False)

        tk.Label(about_window, text="QDMC", font=("Arial", 14, "bold")).pack(pady=10)

        tk.Label(
            about_window,
            text="Version: 1.0\nBuild: #4\nAuthor: Octavian\nUses DMconnect protocol by BitByByte and ftod.",
            font=("Arial", 10),
            justify="center",
        ).pack(pady=10)

        tk.Button(about_window, text="ОК", command=about_window.destroy).pack(pady=5)

        about_window.transient(self.master)
        about_window.grab_set()
        self.master.wait_window(about_window)


    def extract_servers(self, response):
        if enable_debugging:
            print("SERVER EXTR BY: " + str(response))
        match = re.search(r"Available servers:\s*([^\n]+)", response)
        if match:
            if enable_debugging:
                print("SERVER EXTR MATCH x " + str(match))
            return [server.strip() for server in match.group(1).split(',')]

        if enable_debugging:
            print("SERVER EXTR EMPTY")
        return []

    def on_close(self):
        if self.sock:
            self.sock.close()
        self.master.destroy()

if __name__ == "__main__":
    root = tk.Tk()
    client = ChatClient(root)
    root.protocol("WM_DELETE_WINDOW", client.on_close)
    root.mainloop()
