#!/usr/bin/env python3
#
#################################
### A-Team Android Rom Builder ##
#################################
#
# Copyright (C) 2019-Present A-Team Digital Solutions
#

#
# IMPORTS & REQUIREMENTS
#
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Vte", "2.91")
import os
import subprocess
import shutil
import threading
import sys
from gi.repository import (
    Gtk,
    GLib,
    Vte
)
from builder import (
    get_devices,
    load_project,
    run_script,
    run_script_async,
    TerminalManager,
    build_project_summary
)
from pathlib import Path
from builder.actions import (
    build_command,
    sync_command
)
from builder.resource_monitor import ResourceMonitor
from builder.button_actions import (
    ButtonActions
)
from builder.hosts_manager import (
    load_hosts,
    get_host_config
)
from builder.version import DISPLAY_VERSION
from builder.updater import check_for_update
from builder.updater_overlay import UpdaterOverlay
BASE = str(
    Path(__file__).resolve().parent
)

#
# UI SIZING
#
ANDROID_WIDTH = 65
LABEL_WIDTH = 100
ROM_PATH_WIDTH = 300
DEVICE_WIDTH = 185
INSTALLER_WIDTH=190
GAPPS_WIDTH=100
CLEAN_WIDTH=75

#
# A-TEAM ANDROID ROM BUILDER APP
#
class ROMBuilder(Gtk.Window):

    #
    # INITIATE APP
    #
    def __init__(self):
        super().__init__(
        title=f"A-Team Android ROM Builder  |  Version--> {DISPLAY_VERSION}"
        )
        self.set_default_size(
            1100,
            750
        )
        self.project = None
        self.ssh_connected = False
        self.connected_host = None
        self.notebook = Gtk.Notebook()
        self.overlay = Gtk.Overlay()
        self.overlay.add(self.notebook)
        self.update_overlay = UpdaterOverlay()
        self.overlay.add_overlay(self.update_overlay)
        self.add(self.overlay)
        self.build_setup_tab()    #0
        self.sync_tab()           #1
        self.build_tab()          #2
        self.tools_tab()          #3
        self.terminal_tab()       #4
        self.resource_tab()       #5
        self.actions = ButtonActions(
            self.terminal,
            self.notebook
        )
        self.monitor = ResourceMonitor()
        self.local_rom_path = ""
        GLib.idle_add(self.check_for_updates)
        css = Gtk.CssProvider()
        css.load_from_data(b"""
        #update-overlay {
            background-color: rgba(0,0,0,0.55);
        }
        """)

        Gtk.StyleContext.add_provider_for_screen(
            self.get_screen(),
            css,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )
        # Update overlay Debugging
        #GLib.timeout_add_seconds(
        #    2,
        #    self.test_update_overlay
        #)
    #def test_update_overlay(self):
    #    self.update_overlay.set_status(
    #        "Testing Update Overlay ..."
    #    )
    #
    #    self.update_overlay.show_overlay()
    #    self.update_overlay.start()
    #    return False
    #
    # UPDATE CHECKER
    #
    def check_for_updates(self):
        update = check_for_update()
        if update:
            self.show_update_dialog(update)
        return False
    def show_update_dialog(self, update):

        dialog = Gtk.MessageDialog(
            transient_for=self,
            flags=0,
            message_type=Gtk.MessageType.INFO,
            buttons=Gtk.ButtonsType.NONE,
            text="**--> Update Available <--**"
        )

        dialog.format_secondary_text(
            f"""
        Installed Version : {DISPLAY_VERSION}

        Available Version : {update['display']}

        Would You Like To Install It Now ??
        """
        )
        dialog.add_button(
            "Later",
            Gtk.ResponseType.CANCEL
        )
        dialog.add_button(
            "Proceed",
            Gtk.ResponseType.OK
        )
        response = dialog.run()
        dialog.destroy()
        if response == Gtk.ResponseType.OK:
            self.run_updater(update)
    # Update Run
    def run_updater(self, update):
        updater = Path(BASE) / "scripts" / "updater.sh"
        download_base = (
            "https://ateam.gotadell.com/git/PizzaG/"
            "A-Team-Android_ROM_Builder/raw/branch/releases/"
        )
        archive_url = download_base + update["file"]
        self.update_overlay.set_status("Preparing Update ...")
        self.update_overlay.show_overlay()
        self.update_overlay.start()
        threading.Thread(
            target=self._updater_thread,
            args=(updater, archive_url),
            daemon=True,
        ).start()
    def _updater_thread(self, updater, archive_url):
        process = subprocess.Popen(
            [
                str(updater),
                archive_url,
                str(BASE),
            ],
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            bufsize=1,
        )
        for line in process.stdout:
            line = line.strip()
            print(line)
            if line.startswith("::STATUS::"):
                status = line.replace("::STATUS::", "", 1).strip()
                GLib.idle_add(
                    self.update_overlay.set_status,
                    status,
                )
        process.wait()
        if process.returncode == 0:
            GLib.idle_add(self.finish_update)
        else:
            GLib.idle_add(
                self.update_failed,
                process.returncode,
            )
    def finish_update(self):
        self.update_overlay.set_status(
            "**--> Update Complete <--**"
        )
        #self.update_overlay.stop()
        GLib.timeout_add(
            10000,    # 10 seconds
        )
        self.update_overlay.set_status(
            "**--> Restarting A-Team Android ROM Builder ... <--**"
        )
        #self.update_overlay.stop()
        GLib.timeout_add(
            10000,    # 10 seconds
            self._restart_after_update
        )
        return False
    def _restart_after_update(self):
        subprocess.Popen([
            sys.executable,
            str(Path(BASE) / "gui.py"),
        ])
        self.close()
        return False
    def update_failed(self, code):
        self.update_overlay.stop()
        self.update_overlay.hide_overlay()
        dialog = Gtk.MessageDialog(
            transient_for=self,
            flags=0,
            message_type=Gtk.MessageType.ERROR,
            buttons=Gtk.ButtonsType.OK,
            text="Update Failed",
        )
        dialog.format_secondary_text(
            f"The Updater Exited With Code: {code}."
        )
        dialog.run()
        dialog.destroy()
        return False
    #
    # TAB--> 1- BUILD SETUP TAB
    #
    def build_setup_tab(self):
        page = Gtk.Box(
            orientation=Gtk.Orientation.HORIZONTAL,
            spacing=10
        )
        page.set_border_width(10)
        grid = Gtk.Grid(
            row_spacing=10,
            column_spacing=10
        )
        grid.set_column_spacing(10)
        grid.set_row_spacing(10)
        grid.set_column_homogeneous(False)
        #
        # BUILD HOST
        #
        label = Gtk.Label(label="Build Host:")
        grid.attach(
          label,
          0, 0, 1, 1
        )
        host_box = Gtk.Box(
            orientation=Gtk.Orientation.HORIZONTAL,
            spacing=6
        )
        self.build_host = Gtk.ComboBoxText()
        self.connection_status = Gtk.Label()
        self.connection_status.set_markup(
            '<span foreground="red"><b>● Disconnected</b></span>'
        )
        self.connection_status.set_halign(Gtk.Align.START)
        hosts = load_hosts()
        for host in hosts:
            self.build_host.append_text(host)
        if hosts:
            self.build_host.set_active(0)
        self.build_host.connect(
            "changed",
            self.build_host_changed
        )
        host_box.pack_start(
            self.build_host,
            False,
            False,
            0
        )
        host_box.pack_start(
            self.connection_status,
            False,
            False,
            10
        )
        has_remote_host = False
        for host_name in hosts:
            cfg = get_host_config(host_name)
            if not cfg:
                continue
            if cfg.get(
                "server_address",
                ""
            ).strip():
                has_remote_host = True
                break
        # Create buttons if remote host exists
        if has_remote_host:
            self.connect_btn = Gtk.Button(label="Connect")
            self.disconnect_btn = Gtk.Button(label="Disconnect")
            host_box.pack_start(
                self.connect_btn,
                False,
                False,
                0
            )
            host_box.pack_start(
                self.disconnect_btn,
                False,
                False,
                0
            )
        self.connect_btn.connect(
            "clicked",
            self.connect_host_clicked
        )
        self.disconnect_btn.connect(
            "clicked",
            self.disconnect_host_clicked
        )
        grid.attach(
            host_box,
            1, 0, 1, 1
        )
        self.update_build_host_ui()
        #
        # DEVICE DROPDOWN
        #
        grid.attach(
            Gtk.Label(label="Device"),
            0, 1, 1, 1
        )
        self.device_combo = Gtk.ComboBoxText()
        self.device_combo.set_size_request(
            DEVICE_WIDTH,
            -1
        )
        self.device_combo.set_halign(
            Gtk.Align.START
        )
        self.devices = get_devices()
        for device in self.devices:
            self.device_combo.append_text(
                device["display"]
            )
        self.device_combo.set_active(0)
        grid.attach(
            self.device_combo,
            1, 1, 1, 1
        )
        #
        # ROM PATH
        #
        grid.attach(
            Gtk.Label(label="ROM Source"),
            0, 2, 1, 1
        )
        rom_box = Gtk.Box(
            orientation=Gtk.Orientation.HORIZONTAL,
            spacing=10
        )
        self.rom_entry = Gtk.Entry()
        self.rom_entry.set_size_request(
            ROM_PATH_WIDTH,
            -1
        )
        self.rom_entry.set_hexpand(True)
        self.rom_entry.set_halign(
            Gtk.Align.START
        )
        rom_box.pack_start(
            self.rom_entry,
            True,
            True,
            0
        )
        self.browse_btn = Gtk.Button(
            label="Browse"
        )
        self.browse_btn.connect(
            "clicked",
            self.choose_rom
        )
        rom_box.pack_start(
            self.browse_btn,
            False,
            False,
            0
        )
        grid.attach(
            rom_box,
            1, 2, 1, 1
        )
        #
        # ANDROID VERSION
        #
        grid.attach(
            Gtk.Label(label="Android"),
            0, 3, 1, 1
        )
        self.android = Gtk.ComboBoxText()
        self.android.set_size_request(
            ANDROID_WIDTH,
            -1
        )
        self.android.set_halign(
            Gtk.Align.START
        )
        for version in [
            "16"
        ]:
            self.android.append_text(
                version
            )
        self.android.set_active(0)
        grid.attach(
            self.android,
            1, 3, 1, 1
        )
        #
        # BUILD VARIANT
        #
        grid.attach(
            Gtk.Label(label="Build Variant"),
            0, 4, 1, 1
        )
        self.build_variant = Gtk.ComboBoxText()
        self.build_variant.set_size_request(
            120,
            -1
        )
        self.build_variant.set_halign(
            Gtk.Align.START
        )
        for variant in [
            "Userdebug",
            "Eng",
            "User"
        ]:
            self.build_variant.append_text(
                variant
            )

        self.build_variant.set_active(0)
        grid.attach(
            self.build_variant,
            1, 4, 1, 1
        )
        #
        # GAPPS VARIANT
        #
        grid.attach(
            Gtk.Label(label="Gapps Variant"),
            0, 5, 1, 1
        )
        self.gapps_variant = Gtk.ComboBoxText()
        self.gapps_variant.set_size_request(
            GAPPS_WIDTH,
            -1
        )
        self.gapps_variant.set_halign(
            Gtk.Align.START
        )
        for variant in [
            "Vanilla",
            "Gapps",
            "Micro-G",
            "Foss"
        ]:
            self.gapps_variant.append_text(
                variant
            )
        self.gapps_variant.set_active(0)
        grid.attach(
            self.gapps_variant,
            1, 5, 1, 1
        )
        #
        # CLEAN VARIANT
        #
        grid.attach(
            Gtk.Label(label="Clean Variant"),
            0, 6, 1, 1
        )
        self.clean_variant = Gtk.ComboBoxText()
        self.clean_variant.set_size_request(
            CLEAN_WIDTH,
            -1
        )
        self.clean_variant.set_halign(
            Gtk.Align.START
        )
        for variant in [
            "Full",
            "Lite"
        ]:
            self.clean_variant.append_text(
                variant
            )
        self.clean_variant.set_active(0)
        grid.attach(
            self.clean_variant,
            1, 6, 1, 1
        )
        #
        # INSTALLER VARIANT
        #
        grid.attach(
            Gtk.Label(label="Installer Variant"),
            0, 7, 1, 1
        )
        self.installer_variant = Gtk.ComboBoxText()
        self.installer_variant.set_size_request(
            INSTALLER_WIDTH,
            -1
        )

        #self.installer_variant.set_size_request(
        #    180,
        #    -1
        #)

        self.installer_variant.set_halign(
            Gtk.Align.START
        )
        for variant in [
            "Custom A-Team Installer",
            "Default Rom Installer"
        ]:
            self.installer_variant.append_text(
                variant
            )
        grid.attach(
            self.installer_variant,
            1, 7, 1, 1
        )

        self.installer_variant.set_active(0)


        #
        # BUILD JOBS
        #
        grid.attach(
            Gtk.Label(label="Build Jobs"),
            0, 8, 1, 1
        )
        self.jobs = Gtk.SpinButton()
        self.jobs.set_halign(
            Gtk.Align.START
        )
        self.jobs.set_hexpand(False)
        self.jobs.set_range(
            1,
            128
        )
        self.jobs.set_value(
            6
        )
        self.jobs.set_increments(
            1,
            4
        )
        self.jobs.set_numeric(True)
        grid.attach(
            self.jobs,
            1, 8, 1, 1
        )
        #
        # CCACHE
        #
        self.ccache = Gtk.CheckButton(
            label="Use Ccache"
        )
        self.ccache.set_active(
            True
        )
        grid.attach(
            self.ccache,
            0, 9, 2, 1
        )
        #
        # LOAD PROJECT
        #
        load_btn = Gtk.Button(
            label="Load Project"
        )
        load_btn.connect(
            "clicked",
            self.load_project_clicked
        )
        grid.attach(
            load_btn,
            0, 10, 2, 1
        )
        #   
        # ADD SETTINGS GRID
        #
        page.pack_start(
            grid,
            False,
            False,
            0
        )
        #
        # Project Summary
        #
        frame = Gtk.Frame(
            label="Project Summary"
        )
        self.info = Gtk.TextView()
        self.info.set_editable(
            False
        )
        frame.add(
            self.info
        )
        page.pack_start(
            frame,
            True,
            True,
            0
        )
        self.notebook.append_page(
            page,
            Gtk.Label(label="1- Build Setup")
        )
    def build_host_changed(self, combo):
        self.update_build_host_ui()
    # Connect Button Pressed
    def connect_host_clicked(self, button):
        host_name = self.build_host.get_active_text()
        cfg = get_host_config(host_name)
        if not cfg:
            return
        server = cfg["server_address"]
        user = cfg["user"]
        port = cfg.get("port", "22")
        remote_path = cfg.get(
            "remote_rom_source_path",
            ""
        ).strip()
        if not remote_path:
            print("** ERROR **: No remote_rom_source_path configured in hosts.conf")
            return
        command = f"ssh -p {port} {user}@{server}"
        self.local_rom_path = self.rom_entry.get_text()
        self.connected_host = cfg
        self.ssh_connected = True
        self.terminal.ssh_connected = True
        self.update_build_host_ui()
        self.terminal.remote_path = remote_path
        self.rom_entry.set_text(remote_path)
        self.rom_entry.set_editable(False)
        self.browse_btn.hide()
        #self.connection_status.set_markup(
        #    '<span foreground="green"><b>● Connected</b></span>'
        #)
        print(f"Connecting {host_name} @ {command} ...")
        self.terminal.run(command)
        self.terminal.run(
            f"cd {remote_path}"
        )
    # Disconnect Button Pressed
    def disconnect_host_clicked(self, button):
        print("Disconnecting ...")
        self.terminal.run("exit")
        self.connected_host = None
        self.ssh_connected = False
        self.terminal.ssh_connected = False
        self.update_build_host_ui()
        self.terminal.remote_path = None
        self.rom_entry.set_text(self.local_rom_path)
        self.rom_entry.set_editable(True)
        self.browse_btn.show()
        self.connection_status.set_markup(
            '<span foreground="red"><b>● Disconnected</b></span>'
        )
    def update_build_host_ui(self):
        host = self.build_host.get_active_text()
        if host == "Local":
            self.connection_status.hide()
            self.connect_btn.hide()
            self.disconnect_btn.hide()
            return
        self.connection_status.show()
        if self.ssh_connected:
             self.connection_status.set_markup(
                '<span foreground="green"><b>● Connected</b></span>'
             )
             self.connect_btn.hide()
             self.disconnect_btn.show()
        else:
             self.connection_status.set_markup(
                '<span foreground="red"><b>● Disconnected</b></span>'
             )
             self.connect_btn.show()
             self.disconnect_btn.hide()
    #
    # ROM SELECTION BROWSE BUTTON
    #
    def choose_rom(self, widget):
        dialog = Gtk.FileChooserDialog(
            title="Select ROM Folder",
            parent=self,
            action=Gtk.FileChooserAction.SELECT_FOLDER
        )
        dialog.add_buttons(
            Gtk.STOCK_CANCEL,
            Gtk.ResponseType.CANCEL,
            Gtk.STOCK_OPEN,
            Gtk.ResponseType.OK
        )
        if dialog.run() == Gtk.ResponseType.OK:
           self.rom_entry.set_text(
                dialog.get_filename()
            )
        dialog.destroy()
    #
    # Load Project
    #
    def load_project_clicked(
        self,
        widget
    ):
        index = self.device_combo.get_active()
        if index < 0:
            return
        device = self.devices[index]["name"]
        rom_path = (
            self.rom_entry
            .get_text()
            .strip()
        )
        build_variant = (
            self.build_variant
            .get_active_text()
            .lower()
        )
        self.project = (
            self.actions.load_project(
                rom_path,
                device
            )
        )
        gapps_variant = (
            self.gapps_variant
            .get_active_text()
            .lower()
        )       
        clean_variant = (
            self.clean_variant
            .get_active_text()
            .lower()
        )  
        installer_variant = (
            self.installer_variant
            .get_active_text()
        )
        if installer_variant == "Custom A-Team Installer":
            installer = "custom"
        else:
            installer = "default"
        self.project.build_variant = build_variant
        self.project.gapps_variant = gapps_variant
        self.project.clean_variant = clean_variant
        self.project.installer_variant = installer
        self.project.build_jobs = (
            self.jobs.get_value_as_int()
        )
        self.project.use_ccache = (
            self.ccache.get_active()
        )
        self.update_project_summary()

    #
    # Project Summary Info
    #
    def update_project_summary(self):
        buffer = self.info.get_buffer()
        buffer.set_text(
            build_project_summary(
                self.project
            )
        )
    #
    # TAB--> 2- SYNC & SETUP DEVICE
    #
    def sync_tab(self):
        page = Gtk.Box(
            orientation=Gtk.Orientation.VERTICAL,
            spacing=10
        )
        page.set_border_width(10)
        sync_btn = Gtk.Button(
            label="Sync & Setup Device"
        )
        sync_btn.connect(
            "clicked",
            self.sync_clicked
        )
        page.pack_start(
            sync_btn,
            False,
            False,
            0
        )
        self.notebook.append_page(
            page,
            Gtk.Label(label="2- Sync & Setup Device Source")
        )
    #
    # SYNC & SETUP BUTTON PRESSED
    #
    def sync_clicked(self, button):

        self.actions.sync(
            self.project
        )
    #
    # TAB--> 3- BUILD
    #
    def build_tab(self):
        page = Gtk.Box(
            orientation=Gtk.Orientation.VERTICAL,
            spacing=10
        )  
        build_btn = Gtk.Button(
            label="Build ROM"
        )
        build_btn.connect(
            "clicked",
            self.build_clicked
        )
        page.pack_start(
            build_btn,
            False,
            False,
            0
        )
        self.notebook.append_page(
            page,
            Gtk.Label(label="3- Build")
        )
    #
    # BUILD ROM BUTTON PRESSED
    #
    def build_clicked(self, button):
        self.actions.build(
            self.project,
            self.android.get_active_text(),
            self.jobs.get_value_as_int(),
            self.ccache.get_active()
        )
    #
    # TAB--> TERMINAL
    #
    def terminal_tab(self):
        self.term = Vte.Terminal()
        self.terminal = TerminalManager(
            self.term
        )
        self.term.spawn_async(
            Vte.PtyFlags.DEFAULT,
            None,
            ["/bin/bash"],
            [],
            GLib.SpawnFlags.DEFAULT,
            None,
            None,
            -1,
            None,
            None,
            None
        )
        self.notebook.append_page(
            self.term,
            Gtk.Label(label="Terminal")
        )
    #
    # TAB--> RESOURCE MONITOR
    #
    def resource_tab(self):
        self.resource_term = Vte.Terminal()
        self.resource_term.set_scroll_on_output(True)
        self.notebook.append_page(
            self.resource_term,
            Gtk.Label(label="Resource Monitor")
        )
        GLib.idle_add(
            self.start_resource_monitor
        )
    #
    # RESOURCE MONITOR START
    #
    def start_resource_monitor(self):
        self.monitor.start(
            self.resource_term
        )
        return False
    #
    # TAB--> TOOLS
    #
    def tools_tab(self):
        box = Gtk.Box(
            orientation=Gtk.Orientation.VERTICAL,
            spacing=6
        )
        self.clean_button = Gtk.Button(
            label="Clean Rom"
        )
        self.clean_button.connect(
            "clicked",
            self.clean_clicked
        )
        box.pack_start(
            self.clean_button,
            False,
            False,
            0
        )
        self.notebook.append_page(
            box,
            Gtk.Label(label="Tools")
        )
    #
    # CLEAN BUTTON PRESSED
    #
    def clean_clicked(self, button):

        self.actions.clean(
            self.project
        )

win = ROMBuilder()
win.connect(
"destroy",
Gtk.main_quit
)
win.show_all()
win.update_build_host_ui()
Gtk.main()

