#!/usr/bin/env python3

# This file is part of I38.

# I38 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.

# I38 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.

# You should have received a copy of the GNU General Public License along with I38. If not, see <https://www.gnu.org/licenses/>.


import gi
import os
import subprocess
import threading

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, GLib, Pango

DEFAULT_DOWNLOAD_DIR = os.path.expanduser("~/Downloads")

class WormholeGUI(Gtk.Window):
    def __init__(self):
        super().__init__(title="Magic Wormhole GUI")
        self.set_border_width(10)
        self.set_default_size(500, 400)

        self.download_dir = DEFAULT_DOWNLOAD_DIR
        self.notebook = Gtk.Notebook()
        self.add(self.notebook)

        self.init_main_tab()
        self.init_settings_tab()

        # Escape key closes app
        self.connect("key-press-event", self.on_key_press)

    def init_main_tab(self):
        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        self.notebook.append_page(main_box, Gtk.Label(label="Main"))

        button_box = Gtk.Box(spacing=10)
        self.send_button = Gtk.Button(label="Send")
        self.send_button.connect("clicked", self.on_send_clicked)
        button_box.pack_start(self.send_button, True, True, 0)

        self.receive_button = Gtk.Button(label="Receive")
        self.receive_button.connect("clicked", self.on_receive_clicked)
        button_box.pack_start(self.receive_button, True, True, 0)

        main_box.pack_start(button_box, False, False, 0)

        # Add a frame for the code display
        code_frame = Gtk.Frame(label="Wormhole Code")
        main_box.pack_start(code_frame, False, False, 5)
        
        code_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        code_box.set_border_width(5)
        code_frame.add(code_box)
        
        self.code_display = Gtk.Entry()
        self.code_display.set_editable(False)
        code_box.pack_start(self.code_display, False, False, 0)

        # Add a frame for progress output
        progress_frame = Gtk.Frame(label="Transfer Progress")
        main_box.pack_start(progress_frame, True, True, 5)
        
        # Add a scrolled window for the progress text
        scrolled_window = Gtk.ScrolledWindow()
        scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        progress_frame.add(scrolled_window)
        
        # Add a text view for progress output
        self.progress_text = Gtk.TextView()
        self.progress_text.set_editable(False)
        self.progress_text.set_cursor_visible(False)
        self.progress_text.set_wrap_mode(Gtk.WrapMode.WORD)
        self.progress_text.override_font(Pango.FontDescription("Monospace 10"))
        self.progress_buffer = self.progress_text.get_buffer()
        scrolled_window.add(self.progress_text)

        # Add action buttons
        action_box = Gtk.Box(spacing=10)
        self.copy_button = Gtk.Button(label="Copy Code")
        self.copy_button.connect("clicked", self.copy_code)
        action_box.pack_start(self.copy_button, True, True, 0)

        self.cancel_button = Gtk.Button(label="Cancel")
        self.cancel_button.connect("clicked", self.cancel_transfer)
        action_box.pack_start(self.cancel_button, True, True, 0)

        main_box.pack_start(action_box, False, False, 0)

    def init_settings_tab(self):
        settings_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        settings_box.set_border_width(10)
        self.notebook.append_page(settings_box, Gtk.Label(label="Settings"))

        # Create a frame for download settings
        download_frame = Gtk.Frame(label="Download Location")
        settings_box.pack_start(download_frame, False, False, 0)
        
        # Add a container for the frame content
        download_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        download_box.set_border_width(10)
        download_frame.add(download_box)
        
        # Add a description label
        description = Gtk.Label(label="Files will be saved to this directory:")
        description.set_xalign(0) # Align to left
        download_box.pack_start(description, False, False, 0)

        # Add a directory selector
        dir_box = Gtk.Box(spacing=5)
        download_box.pack_start(dir_box, False, False, 5)
        
        # Add an entry to show the current path
        self.dir_entry = Gtk.Entry()
        self.dir_entry.set_text(self.download_dir)
        dir_box.pack_start(self.dir_entry, True, True, 0)
        
        # Add a browse button
        browse_button = Gtk.Button(label="Browse...")
        browse_button.connect("clicked", self.on_browse_clicked)
        dir_box.pack_start(browse_button, False, False, 0)
        
        # Add a save button
        save_button = Gtk.Button(label="Save Settings")
        save_button.connect("clicked", self.on_save_settings)
        download_box.pack_start(save_button, False, False, 5)

    def on_key_press(self, widget, event):
        if event.keyval == Gdk.KEY_Escape:
            # Check if a transfer is currently in progress
            if hasattr(self, 'current_process') and self.current_process and self.current_process.poll() is None:
                # Show a dialog indicating transfer is in progress
                dialog = Gtk.MessageDialog(
                    parent=self,
                    flags=0,
                    message_type=Gtk.MessageType.WARNING,
                    buttons=Gtk.ButtonsType.OK,
                    text="Transfer in Progress"
                )
                dialog.format_secondary_text("Please wait for the transfer to complete or cancel it before closing.")
                dialog.run()
                dialog.destroy()
                return True
            else:
                # No transfer in progress, confirm quit
                dialog = Gtk.MessageDialog(
                    parent=self,
                    flags=0,
                    message_type=Gtk.MessageType.QUESTION,
                    buttons=Gtk.ButtonsType.YES_NO,
                    text="Quit Application"
                )
                dialog.format_secondary_text("Are you sure you want to quit?")
                response = dialog.run()
                dialog.destroy()
                
                if response == Gtk.ResponseType.YES:
                    Gtk.main_quit()
                
                return True
        return False

    def on_browse_clicked(self, button):
        """Browse for a directory"""
        dialog = Gtk.FileChooserDialog(
            title="Select Download Directory",
            parent=self,
            action=Gtk.FileChooserAction.SELECT_FOLDER,
            buttons=(
                Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                Gtk.STOCK_OPEN, Gtk.ResponseType.OK
            )
        )
        
        # Set the current folder to the current download directory
        if os.path.exists(self.download_dir):
            dialog.set_current_folder(self.download_dir)
            
        if dialog.run() == Gtk.ResponseType.OK:
            self.dir_entry.set_text(dialog.get_filename())
            
        dialog.destroy()
    
    def on_save_settings(self, button):
        """Save the settings"""
        new_dir = self.dir_entry.get_text().strip()
        
        # Handle ~ in path
        if new_dir.startswith("~"):
            new_dir = os.path.expanduser(new_dir)
            
        # Validate the directory
        if not os.path.isdir(new_dir):
            try:
                os.makedirs(new_dir, exist_ok=True)
            except Exception as e:
                self.show_error(f"Could not create directory: {e}")
                return
                
        # Update the directory
        self.download_dir = new_dir
        
        # Show confirmation
        self.show_info("Settings saved successfully.")

    def on_download_dir_changed(self, widget):
        self.download_dir = widget.get_filename()

    def on_send_clicked(self, widget):
        chooser = Gtk.FileChooserDialog(
            title="Select File or Folder", parent=self,
            action=Gtk.FileChooserAction.OPEN,
            buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                     Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
        )
        chooser.set_select_multiple(False)
        chooser.set_local_only(True)
        chooser.set_modal(True)
        chooser.set_property("show-hidden", False)
        chooser.connect("key-press-event", self.on_key_press)

        if chooser.run() == Gtk.ResponseType.OK:
            path = chooser.get_filename()
            chooser.destroy()
            self.send_file(path)
        else:
            chooser.destroy()

    def send_file(self, path):
        self.code_display.set_text("Sending...")
        self.clear_progress()
        self.update_progress(f"Starting to send: {os.path.basename(path)}\n")
        
        # Initialize current_process attribute
        self.current_process = None

        def send():
            self.current_process = subprocess.Popen(
                ["wormhole", "send", path],
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                stdin=subprocess.DEVNULL,
                text=True
            )
            for line in self.current_process.stdout:
                print("SEND OUTPUT:", line.strip())  # Debug info
                
                # Update the progress display
                GLib.idle_add(self.update_progress, line)
                
                if "Wormhole code is:" in line:
                    code = line.strip().split(":", 1)[-1].strip()
                    GLib.idle_add(self.code_display.set_text, code)
            self.current_process.stdout.close()
            self.current_process.wait()
            
            # Clear the current_process when done
            GLib.idle_add(self.clear_current_process)

        threading.Thread(target=send, daemon=True).start()

    def on_receive_clicked(self, widget):
        dialog = Gtk.Dialog(
            title="Enter Wormhole Code",
            parent=self,
            flags=0
        )
        dialog.add_buttons(
            Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
            Gtk.STOCK_OK, Gtk.ResponseType.OK
        )
        dialog.connect("key-press-event", self.on_key_press)

        # Make OK button the default
        ok_button = dialog.get_widget_for_response(Gtk.ResponseType.OK)
        ok_button.set_can_default(True)
        ok_button.grab_default()

        entry = Gtk.Entry()
        entry.set_activates_default(True)
        entry.grab_focus()

        box = dialog.get_content_area()
        box.set_border_width(10)
        box.set_spacing(10)
        box.add(Gtk.Label(label="Enter the wormhole code:"))
        box.add(entry)
        box.show_all()

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            code = entry.get_text()
            dialog.destroy()
            self.receive_file(code)
        else:
            dialog.destroy()

    def receive_file(self, code):
        self.code_display.set_text("Receiving...")
        self.clear_progress()
        self.update_progress(f"Starting to receive with code: {code}\n")
        
        # Initialize current_process attribute
        self.current_process = None

        def receive():
            # Save current directory
            original_dir = os.getcwd()
            
            try:
                # Create download directory if it doesn't exist
                os.makedirs(self.download_dir, exist_ok=True)
                
                # Change to download directory before starting the process
                os.chdir(self.download_dir)
                
                self.update_progress(f"Downloading to: {self.download_dir}\n")
                
                # Start the wormhole receive process
                self.current_process = subprocess.Popen(
                    ["wormhole", "receive", "--accept-file"],
                    stdin=subprocess.PIPE,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.STDOUT,
                    text=True
                )
                
                # Send the code to the process
                self.current_process.stdin.write(code + "\n")
                self.current_process.stdin.flush()
                self.current_process.stdin.close()

                for line in self.current_process.stdout:
                    print("RECEIVE OUTPUT:", line.strip())  # Debug info
                    
                    # Update the progress display
                    GLib.idle_add(self.update_progress, line)
                    
                    # Handle questions about accepting the file
                    if "ok? (y/N):" in line:
                        # Auto-accept the file
                        self.current_process.stdin = open("/dev/stdin", "w")
                        self.current_process.stdin.write("y\n")
                        self.current_process.stdin.flush()
                        self.current_process.stdin.close()
                    
                    if "Received file" in line or "File received" in line:
                        GLib.idle_add(self.code_display.set_text, "File received.")
                
                self.current_process.stdout.close()
                self.current_process.wait()
                
                # Add final status message
                GLib.idle_add(self.update_progress, f"\nFile saved to: {self.download_dir}\n")
            
            except Exception as e:
                GLib.idle_add(self.update_progress, f"Error: {e}\n")
            
            finally:
                # Change back to original directory
                os.chdir(original_dir)
                
                # Clear the current_process when done
                GLib.idle_add(self.clear_current_process)

        threading.Thread(target=receive, daemon=True).start()

    def copy_code(self, widget):
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        clipboard.set_text(self.code_display.get_text(), -1)
        self.update_progress("Code copied to clipboard.\n")

    def cancel_transfer(self, widget):
        if hasattr(self, 'current_process') and self.current_process and self.current_process.poll() is None:
            try:
                self.current_process.terminate()
                self.update_progress("Transfer process terminated.\n")
            except Exception as e:
                self.update_progress(f"Error canceling transfer: {e}\n")
        
        self.code_display.set_text("Transfer canceled.")
        self.update_progress("Transfer canceled by user.\n")
        self.clear_current_process()

    def update_progress(self, text):
        """Update the progress text view"""
        end = self.progress_buffer.get_end_iter()
        self.progress_buffer.insert(end, text)
        
        # Scroll to the end
        self.progress_text.scroll_to_iter(end, 0.0, False, 0.0, 0.0)
        
        return False  # Required for GLib.idle_add
        
    def clear_progress(self):
        """Clear the progress text view"""
        self.progress_buffer.set_text("")
        
    def show_error(self, message):
        """Show an error dialog"""
        dialog = Gtk.MessageDialog(
            parent=self,
            flags=0,
            message_type=Gtk.MessageType.ERROR,
            buttons=Gtk.ButtonsType.OK,
            text="Error"
        )
        dialog.format_secondary_text(message)
        dialog.run()
        dialog.destroy()
        
    def show_info(self, message):
        """Show an info dialog"""
        dialog = Gtk.MessageDialog(
            parent=self,
            flags=0,
            message_type=Gtk.MessageType.INFO,
            buttons=Gtk.ButtonsType.OK,
            text="Information"
        )
        dialog.format_secondary_text(message)
        dialog.run()
        dialog.destroy()
        
    def clear_current_process(self):
        """Clear the current process reference"""
        self.current_process = None
        return False  # Required for GLib.idle_add

def main():
    app = WormholeGUI()
    app.connect("destroy", Gtk.main_quit)
    app.show_all()
    Gtk.main()

if __name__ == "__main__":
    main()
