import tkinter as tk
from time import strftime
from tkinter import filedialog, messagebox
import os
import pickle
from PIL import Image, ImageTk, UnidentifiedImageError
import requests
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from ftplib import FTP
from pywinauto import Application
from datetime import datetime
from threading import Thread, Event
import threading
import time

SETTINGS_FILE = "ftp_settings.pkl"

# Global paths
file_a_path = None
file_b_path = None

# Load settings or initialize empty settings
def load_ftp_settings():
    try:
        if os.path.exists(SETTINGS_FILE):
            with open(SETTINGS_FILE, "rb") as f:
                settings = pickle.load(f)
                global file_a_path, file_b_path
                file_a_path = settings.get('file_a_path')
                file_b_path = settings.get('file_b_path')
                return settings
    except Exception as e:
        print(f"[Error] Failed to load settings: {e}")
    return {}

ftp_settings = load_ftp_settings()

# Extract call sign from File A and find URL in File B
def extract_url_from_files():
    try:
        if not file_a_path or not os.path.exists(file_a_path):
            print("[Error] File A path is not set or does not exist.")
            return None
        if not file_b_path or not os.path.exists(file_b_path):
            print("[Error] File B path is not set or does not exist.")
            return None

        with open(file_a_path, "r", encoding="utf-8") as f:
            lines = f.readlines()
            if not lines:
                print("[Error] File A is empty.")
                return None

            last_line = lines[-1].strip()
            print(f"[Debug] Last line in File A: {last_line}")

            if "/IMAGE" not in last_line:
                print("[Error] Invalid line format in File A.")
                return None
            call_sign = last_line.split()[-1].split("/")[0]
            print(f"[Debug] Extracted callsign: {call_sign}")

        with open(file_b_path, "r", encoding="utf-8") as f:
            for line in f:
                if line.startswith(call_sign):
                    url = line.split()[1]
                    print(f"[Debug] Found URL for {call_sign}: {url}")
                    return url

        print(f"[Error] Callsign {call_sign} not found in File B.")
        return None
    except Exception as e:
        print(f"[Error] Failed to extract URL: {e}")
        return None

# Download image from URL
def download_image(url, output_path):
    try:
        print(f"[Debug] Downloading image: URL={url}, Output={output_path}")
        response = requests.get(url, stream=True, timeout=10)
        if response.status_code == 200:
            with open(output_path, "wb") as f:
                for chunk in response.iter_content(chunk_size=1024):
                    if chunk:
                        f.write(chunk)
            print(f"[Debug] Download successful: Saved as {output_path}")
            return output_path
        else:
            print(f"[Error] Failed to download: Status code={response.status_code}")
    except requests.RequestException as e:
        print(f"[Error] Failed to download image: {e}")
    return None

# Upload image to FTP server for Tx
def upload_image(file_path, ftp_settings):
    try:
        if not file_path or not os.path.exists(file_path):
            messagebox.showwarning("No Image File", "Please load an image file to upload.")
            return
        if not ftp_settings.get('ftp_server') or not ftp_settings.get('username') or not ftp_settings.get('password') or not ftp_settings.get('directory'):
            messagebox.showwarning("Missing FTP Settings", "Please configure FTP settings in the initial setup.")
            return

        server = ftp_settings['ftp_server']
        username = ftp_settings['username']
        password = ftp_settings['password']
        directory = ftp_settings['directory']
        upload_filename = "image1"

        ftp = FTP(server)
        ftp.login(user=username, passwd=password)
        ftp.cwd(directory)
        with open(file_path, "rb") as f:
            ftp.storbinary(f"STOR {upload_filename}", f)
        ftp.quit()
        messagebox.showinfo("Success", f"Image file uploaded as {upload_filename}.")
    except Exception as e:
        messagebox.showerror("Upload Error", f"An error occurred during upload: {e}")

# Display a Tx image on a canvas
def display_image(image_path, canvas):
    try:
        print(f"[Debug] Displaying image: {image_path}")
        with open(image_path, "rb") as f:
            image = Image.open(f)
            image.thumbnail((400, 300))
            photo = ImageTk.PhotoImage(image)
            canvas.create_image(200, 150, image=photo)
            canvas.image = photo
        print("[Debug] Image displayed successfully on canvas.")
    except FileNotFoundError:
        print(f"[Error] Image file not found: {image_path}")
        messagebox.showerror("Error", f"Image file not found: {image_path}")
    except UnidentifiedImageError:
        print(f"[Error] Failed to identify the image file: {image_path}")
        messagebox.showerror("Error", f"Failed to identify the image file: {image_path}")
    except Exception as e:
        print(f"[Error] Error displaying image: {e}")
        messagebox.showerror("Error", f"Error displaying image: {e}")

# Watchdog handler for monitoring File A
class FileAHandler(FileSystemEventHandler):
    def __init__(self, canvas, label):
        self.canvas = canvas
        self.label = label

    def on_modified(self, event):
        if event.src_path == file_a_path:
            print(f"[Debug] File A was modified: {event.src_path}")
            url = extract_url_from_files()
            if url:
                temp_image_path = "image1"
                downloaded_path = download_image(url, temp_image_path)
                if downloaded_path:
                    display_image(downloaded_path, self.canvas)
            
            # Extract call sign and update the label
            try:
                with open(file_a_path, "r", encoding="utf-8") as f:
                    lines = f.readlines()
                    if lines:
                        last_line = lines[-1].strip()
                        call_sign = last_line.split()[-1].split("/")[0]
                        current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                        self.label.config(text=f"RX from : {call_sign}\nTime: {current_time}")
                        print(f"[Debug] Updated label with call sign: {call_sign} and time: {current_time}")
            except Exception as e:
                print(f"[Error] Failed to update label: {e}")

# ********************* Digital Clock *************************************************
class DigitalClock:
    def __init__(self, parent):
        self.label = tk.Label(parent, font=("Arial", 12, "bold"), fg="black")
        self.label.pack(pady=1)
        self.update_clock()

    def update_clock(self):
        current_time = strftime("%H:%M:%S")
        self.label.config(text=current_time)
        self.label.after(1000, self.update_clock)

# ********************* Control WSJTX *************************************************

# Defined as a global variable
pid = None
global_value = None
i = 0
app = None

# ターゲット秒数
TARGET_SECONDS = {14, 29, 44, 59}

# 停止フラグ
stop_event = Event()

def stop_tx():
    # 監視を停止する
    print("Tx Stop at:", datetime.now())
    # Connect using PID
    app = Application().connect(process=pid)
    window = app.top_window()  # Focus on the topmost window
    window.set_focus()
    window.type_keys('%H')  # %H means Alt + H
    stop_event.set()

# Main application class
class ImageUploaderApp:
    def __init__(self, root):
        self.root = root
        self.local_image_file = None
        self.create_ui()

    def exit_software(self):
        """Properly stop the observer and close the application."""
        try:
            if hasattr(self, 'observer') and self.observer.is_alive():
                self.observer.stop()
                self.observer.join()
        except Exception as e:
            print(f"[Error] Failed to stop observer: {e}")
        self.root.quit()
        self.root.destroy()

    def create_ui(self):
        canvas_frame = tk.Frame(self.root)
        canvas_frame.grid(row=1, column=0, padx=10, pady=10)

        # Canvas 1 and its label
        self.canvas1 = tk.Canvas(canvas_frame, width=400, height=300, bg="lightblue")
        self.canvas1.grid(row=1, column=0, padx=10)

        # current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        self.tx_image_label = tk.Label(canvas_frame, font=("Arial", 10), bg="lightblue")
        self.tx_image_label.grid(row=2, column=0, padx=10)

        # Canvas 2 and its label
        self.canvas2 = tk.Canvas(canvas_frame, width=400, height=300, bg="lightgreen")
        self.canvas2.grid(row=1, column=1, padx=10)
        self.canvas2.bind("<Button-1>", lambda e: self.open_image_window("image1"))

        # Add a label below canvas2 for call sign and time
        self.call_sign_label = tk.Label(canvas_frame, text="", font=("Arial", 10), bg="lightgreen")
        self.call_sign_label.grid(row=2, column=1, padx=10)

        button_frame = tk.Frame(self.root)
        button_frame.grid(row=2, column=0, padx=10, pady=10)

        self.setup_button = tk.Button(button_frame, text="Initial Setup", font=("Arial", 10, "bold"), fg="blue",  command=self.open_settings_window)
        self.setup_button.grid(row=0, column=0, padx=5, pady=5)

        self.load_button = tk.Button(button_frame, text="Load Local Image", font=("Arial", 10, "bold"), fg="blue", command=self.load_local_image)
        self.load_button.grid(row=0, column=1, padx=5, pady=5)

        self.upload_button = tk.Button(button_frame, text="Upload Image", font=("Arial", 10, "bold"), fg="blue", command=self.upload_local_image, state=tk.DISABLED)
        self.upload_button.grid(row=0, column=2, padx=5, pady=5)

        self.runwsjtx_button = tk.Button(button_frame, text="Run WSJTX", font=("Arial", 10, "bold"), fg="blue", command=self.launch_wsjtx)
        self.runwsjtx_button.grid(row=0, column=3, padx=5, pady=5)

        self.txwsjtx_button = tk.Button(button_frame, text="TX Permission", font=("Arial", 10, "bold"), fg="blue", command=self.start_monitoring, state=tk.DISABLED)
        self.txwsjtx_button.grid(row=0, column=4, padx=5, pady=5)

        self.exit_button = tk.Button(button_frame, text="Exit Software", font=("Arial", 10, "bold"), fg="maroon", command=self.exit_software)
        self.exit_button.grid(row=0, column=7, padx=5, pady=5)

        self.exit_button = tk.Button(button_frame, text="Digi_Sites", font=("Arial", 10, "bold"), fg="maroon", command=self.upload_digisites_image)
        self.exit_button.grid(row=0, column=6, padx=5, pady=5)

        self.file_a_handler = FileAHandler(self.canvas2, self.call_sign_label)
        self.observer = Observer()

        if file_a_path and os.path.exists(file_a_path):
            self.observer.schedule(self.file_a_handler, path=os.path.dirname(file_a_path), recursive=False)
        else:
            print("[Error] File A path is not set. Watchdog not started.")
        self.observer.start()

        # Add digital clock to the UI
        clock_frame = tk.Frame(self.root)
        clock_frame.grid(row=0, column=0, padx=10, pady=10)
        self.clock = DigitalClock(clock_frame)

    def start_monitoring(self):
        # ボタンを押したときに監視を開始する
        self.txwsjtx_button.config(fg="red")  # ボタンの文字色を変更
        # 13秒後に元の色に戻す処理をスケジュール
        self.txwsjtx_button.after(13000, lambda: self.txwsjtx_button.config(fg="black"))  # 13000ms =13秒

        stop_event.clear()
        monitoring_thread = Thread(target=self.monitor_seconds, daemon=True)
        monitoring_thread.start()

    def monitor_seconds(self):
        print("Monitoring seconds: 14, 29, 44, 59")
        while not stop_event.is_set():
            current_time = datetime.now()
            current_second = current_time.second

            # 秒がターゲット秒数に一致した場合に関数を駆動
            if current_second in TARGET_SECONDS:
                self.send_keys()

                # 13秒後に監視を停止
                print("Stopping monitoring in 13 seconds...")
                time.sleep(13)
                stop_tx()
                break

            time.sleep(0.1)  # 秒の変化を効率的に監視


    def send_keys(self):
        """WSJTXに対してAlt+Nキーを送信する"""
        print("Tx Permission at:", datetime.now())
        global pid
        if pid:
            try:
                app = Application().connect(process=pid)
                window = app.top_window()
                window.set_focus()
                window.type_keys('%N')
                print("Alt + N sent for TX permission")
            except Exception as e:
                print(f"[Error] Failed to send keys: {e}")
        else:
            print("Application is not running")

    def launch_wsjtx(self):
        global pid, app
        try:
            app = Application().start(r"C:\WSJT\wsjtx\bin\wsjtx.exe")
            pid = app.process  # Obtain the PID of the launched process and store it in a variable
            print("The application has been launched. PID:", pid)
            self.txwsjtx_button.config(state=tk.NORMAL)  # Enable the button
        except Exception as e:
            messagebox.showerror("Error", f"An error occurred while launching WSJTX: {e}")

    def open_image_window(self, image_path):
        """Create a new window that displays an enlarged version of the image."""
        try:
            # Open the image
            with open(image_path, "rb") as f:
                image = Image.open(f)
                # Resize the image for enlargement
                expanded_size = (1300, 900)  # Specify the enlargement size (width, height)
                img_resized = image.resize(expanded_size, Image.LANCZOS)
                img_tk = ImageTk.PhotoImage(img_resized)

            # Create a new window
            window = tk.Toplevel(self.root)
            window.title("Expanded Image")

            # Add the enlarged image to the window
            label = tk.Label(window, image=img_tk)
            label.image = img_tk  # Keep a reference to avoid garbage collection
            label.pack()

            # Bind a click event to close the window
            label.bind("<Button-1>", lambda e: window.destroy())
        except Exception as e:
            messagebox.showerror("Error", f"Error opening image window: {e}")

    def open_settings_window(self):
        settings_window = tk.Toplevel(self.root)
        settings_window.title("Settings")
        labels = ["FTP Server", "Username", "Password", "Directory",
                  "File A Path", "File B Path", "Local file Path", 
                  "DigiSites FTP Server", "DigiSites Username", "DigiSites Password", "DigiSites Directory"]
        entries = {}

        for i, label in enumerate(labels):
            tk.Label(settings_window, text=label).grid(row=i, column=0, padx=5, pady=5)
            entry = tk.Entry(settings_window, width=50, show="*" if "Password" in label else None)
            entry.grid(row=i, column=1, padx=5, pady=5)
            entries[label] = entry

        if ftp_settings:
            for label in labels:
                entries[label].insert(0, ftp_settings.get(label.lower().replace(" ", "_"), ''))

        def save_settings():
            try:
                updated_settings = {label.lower().replace(" ", "_"): entry.get().strip() for label, entry in entries.items()}
                with open(SETTINGS_FILE, "wb") as f:
                    pickle.dump(updated_settings, f)
                global file_a_path, file_b_path
                file_a_path = updated_settings['file_a_path']
                file_b_path = updated_settings['file_b_path']
                messagebox.showinfo("Success", "Settings saved successfully.")
                settings_window.destroy()
            except Exception as e:
                messagebox.showerror("Error", f"Error saving settings: {e}")

        tk.Button(settings_window, text="Save", command=save_settings).grid(row=len(labels), column=0, columnspan=2, pady=10)

    def load_local_image(self):
        try:
            file_path = filedialog.askopenfilename(
                title="Select Image File",
                filetypes=[("Image files", "*.jpg;*.png;*.jpeg;*.gif")]
            )
            if not file_path:
                messagebox.showwarning("No Image Selected", "Please select an image file.")
                return
            self.local_image_file = file_path
            display_image(file_path, self.canvas1)
            self.upload_button.config(state=tk.NORMAL)
        except Exception as e:
            messagebox.showerror("Error", f"Error loading image: {e}")

    def upload_local_image(self):
        upload_image(self.local_image_file, ftp_settings)
        current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        self.tx_image_label.config(text=f"Loaded Tx Image\nTime: {current_time}")

# ********************************************************
    def upload_digisites_image(self):
        try:
            # Extract DigiSites FTP settings
            ftp_server = ftp_settings.get('digisites_ftp_server')
            username = ftp_settings.get('digisites_username')
            password = ftp_settings.get('digisites_password')
            server_folder = ftp_settings.get('digisites_directory')
            local_file_path = ftp_settings.get('local_file_path')

            print(f"[Debug] FTP Server: {ftp_server}")
            print(f"[Debug] Username: {username}")
            print(f"[Debug] Password: {password}")
            print(f"[Debug] Directory: {server_folder}")
            print(f"[Debug] Directory: {local_file_path}")

            with FTP(ftp_server) as ftp:
                ftp.login(user=username, passwd=password)
                ftp.cwd(server_folder)
                with open(local_file_path, "rb") as file:
                    ftp.storbinary(f"STOR {os.path.basename(local_file_path)}", file)
            print("画像をdigisites_FTPにアップロードしました。")
        #
        # with open(ALL_TXT_PATH, 'w'):
            # pass
        except Exception as e:
            print(f"FTP upload error: {e}")

# ************************************************************

    def exit_software(self):
        """Properly stop the observer and close the application."""
        try:
            if hasattr(self, 'observer') and self.observer.is_alive():
                self.observer.stop()
                self.observer.join()
        except Exception as e:
            print(f"[Error] Failed to stop observer: {e}")
        self.root.quit()
        self.root.destroy()

    def __del__(self):
        self.observer.stop()
        self.observer.join()

if __name__ == "__main__":
    root = tk.Tk()
    root.title("FT8 Image Transceiver")
    # root.attributes('-topmost', True)  #画面を最前面にする
    app = ImageUploaderApp(root)
    root.mainloop()

# **********　コードはここまで　******************************************************************

以下、コード内の各関数・クラスの役割を記す。

1. load_ftp_settings
役割:
ftp_settings.pkl ファイルからFTP設定を読み込む関数である。
ファイルが存在しない場合、空の設定を返す。
ファイルを読み込む際にエラーが発生した場合、エラーを表示して空の設定を返す。

2. extract_url_from_files
役割:
File AとFile Bを解析し、File Aの最後の行に記載されたコールサインを基にFile BからURLを取得する関数。
File Aの最後の行が正しい形式 (/IMAGE を含む) であるかを確認する。
コールサインを抽出し、File Bで対応するURLを探する。

3. download_image
役割:
指定されたURLから画像ファイルをダウンロードし、指定されたパスに保存する関数である。
HTTPリクエストで画像データを取得し、ファイルに書き込みます。
ダウンロード成功/失敗をログに記録する。

4. upload_image
役割:
画像ファイルをFTPサーバーにアップロードする関数である。
FTPサーバーへの接続や認証、指定されたディレクトリへのファイルアップロードを行う。
必要なFTP設定が不足している場合やエラーが発生した場合に警告メッセージを表示する。

5. display_image
役割:
指定された画像をTkinterのキャンバス上に表示する関数である。
画像をサムネイルサイズにリサイズし、キャンバスに描画する。
ファイルが見つからない、または画像形式が不正な場合にエラーメッセージを表示する。

6. FileAHandler クラス
役割:
WatchdogイベントハンドラとしてFile Aの変更を監視し、変更があった場合に対応する処理を実行する。
File Aの内容を基にURLを抽出し、画像をダウンロードして表示する。
コールサインを抽出し、ラベルに現在時刻とともに表示する。

7. DigitalClock クラス
役割:
現在時刻をTkinterウィジェットに1秒ごとに更新して表示するクラスである。

8. stop_tx
役割:
WSJT-Xソフトウェアで送信(Tx)を停止する関数である。
指定したプロセスID (PID) を持つアプリケーションを操作し、Alt+Hのキーストロークを送信する。

9. ImageUploaderApp クラス
役割:
メインのGUIアプリケーションクラスである。以下の機能を提供する。


サブ関数：
create_ui

GUIレイアウトを構築する。
キャンバス、ボタン、デジタル時計などを配置する。
open_settings_window
設定ウィンドウを開き、FTPサーバー情報やファイルパスを入力・保存できるようにする。

load_local_image
ファイルダイアログを開いてローカル画像を選択し、Canvas1に表示する。

upload_local_image
選択した画像をFTPサーバーにアップロードする。

start_monitoring
指定した秒数（14, 29, 44, 59）の監視を開始し、秒が一致した際にTX許可を行う。

monitor_seconds
秒を監視し、ターゲット秒数に達した場合にsend_keysを呼び出する。

send_keys
WSJT-XソフトウェアにAlt+Nを送信し、TXを許可する。

launch_wsjtx
WSJT-Xソフトウェアを起動する。

open_image_window
別ウィンドウを開き、画像を拡大表示する。

upload_digisites_image
DigiSites用のFTPサーバーに画像をアップロードする。

exit_software
アプリケーションを終了し、必要に応じて監視を停止する。

10. メイン関数
役割:
Tkinterのメインループを開始し、アプリケーションを実行する。
全体として、このプログラムはFile A/Bを用いたURL抽出、画像のダウンロード・アップロード、FTPサーバー設定、WSJT-Xの制御、Tkinterを使用したGUI構築を行う。