namespace QuickSetup {
    // Bundled symbolic icon. Registered with the icon theme by main.vala so
    // GTK can recolor it like any other symbolic icon.
    private const string DRIVER_ICON_NAME = "firmware-update-symbolic";

    public abstract class DriverPhasePage : QsPage {
        private const uint MINIMUM_PAGE_TIME_MS = 1000;
        private const uint AUTO_ADVANCE_DELAY_MS = 700;

        private DriverManager driver_manager;
        private Adw.StatusPage status_page;
        private Gtk.Spinner spinner;
        private Gtk.Label detail_label;
        private Gtk.Box action_box;
        private Gtk.Button retry_button;
        private Gtk.Button skip_button;
        private int64 started_at_usec = 0;
        private bool started = false;

        protected abstract DriverPhase phase { get; }
        protected abstract string loading_title { get; }
        protected abstract string loading_description { get; }

        protected DriverPhasePage(PageContext context, string title) {
            base(context, title);

            this.driver_manager = new DriverManager();
            this.status_page = new Adw.StatusPage();
            this.status_page.vexpand = true;
            this.status_page.hexpand = true;
            this.status_page.valign = Gtk.Align.FILL;
            this.status_page.halign = Gtk.Align.FILL;
            this.status_page.icon_name = DRIVER_ICON_NAME;
            this.add_content(this.status_page);

            var clamp = new Adw.Clamp();
            clamp.maximum_size = 560;
            clamp.tightening_threshold = 400;

            var content_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 12);
            content_box.valign = Gtk.Align.CENTER;
            content_box.halign = Gtk.Align.FILL;

            this.spinner = new Gtk.Spinner();
            this.spinner.halign = Gtk.Align.CENTER;
            this.spinner.valign = Gtk.Align.CENTER;
            this.spinner.visible = false;
            content_box.append(this.spinner);

            this.detail_label = new Gtk.Label("");
            this.detail_label.wrap = true;
            this.detail_label.wrap_mode = Pango.WrapMode.WORD_CHAR;
            this.detail_label.justify = Gtk.Justification.CENTER;
            this.detail_label.halign = Gtk.Align.CENTER;
            this.detail_label.selectable = true;
            this.detail_label.visible = false;
            this.detail_label.add_css_class("dim-label");
            this.detail_label.add_css_class("caption");
            content_box.append(this.detail_label);

            this.action_box = this.create_button_bar();
            this.action_box.margin_top = 12;
            this.action_box.visible = false;

            this.retry_button = this.create_page_button("Retry", true);
            this.retry_button.clicked.connect(() => {
                this.start_phase_run();
            });
            this.action_box.append(this.retry_button);

            this.skip_button = this.create_page_button("Skip");
            this.skip_button.clicked.connect(() => {
                this.context.window.push_next_page();
            });
            this.action_box.append(this.skip_button);
            content_box.append(this.action_box);

            clamp.set_child(content_box);
            this.status_page.set_child(clamp);

            this.driver_manager.progress.connect((driver_name, stage) => {
                this.detail_label.label = progress_text(driver_name, stage);
                this.detail_label.visible = true;
            });

            GLib.Idle.add(() => {
                if (!this.started) {
                    this.start_phase_run();
                }
                return false;
            });
        }

        private void start_phase_run() {
            this.started = true;
            this.started_at_usec = GLib.get_monotonic_time();
            this.action_box.visible = false;
            this.retry_button.label = tr("Retry");
            this.skip_button.visible = true;
            this.spinner.visible = true;
            this.spinner.spinning = true;
            this.status_page.title = this.loading_title;
            this.status_page.description = this.loading_description;
            this.detail_label.label = tr("Starting driver check…");
            this.detail_label.visible = true;
            this.run_phase_async.begin();
        }

        private async void run_phase_async() {
            var result = yield this.driver_manager.run_phase(this.phase);

            if (this.phase == DriverPhase.PRE_WIFI
                && this.context.window.combine_driver_phases
                && !result.has_failures()) {
                this.status_page.title = tr("Installing Additional Drivers");
                this.status_page.description = tr(
                    "Checking for drivers that require internet access."
                );

                var post_result = yield this.driver_manager.run_phase(DriverPhase.POST_WIFI);
                foreach (var driver_result in post_result.results) {
                    result.results.append(driver_result);
                }
            }

            uint elapsed_ms = (uint) ((GLib.get_monotonic_time() - this.started_at_usec) / 1000);
            if (elapsed_ms < MINIMUM_PAGE_TIME_MS) {
                yield async_sleep(MINIMUM_PAGE_TIME_MS - elapsed_ms);
            }

            if (this.phase == DriverPhase.PRE_WIFI
                && result.requires_reboot()
                && result.has_successes()
                && !this.context.window.driver_reboot_prompt_shown) {
                show_pre_wifi_reboot_prompt();
                return;
            }

            if (result.requires_reboot()) {
                this.context.window.driver_reboot_required = true;
            }

            foreach (var driver_result in result.results) {
                if (driver_result.driver_id == "nvidia-gpu" && driver_result.is_success()) {
                    this.context.window.nvidia_driver_queued = true;
                }
            }

            if (result.has_failures()) {
                show_failure_state(result);
                return;
            }

            this.spinner.spinning = false;
            this.spinner.visible = false;
            this.detail_label.label = "";
            this.detail_label.visible = false;

            if (result.has_successes()) {
                this.status_page.title = tr("Drivers Installed");
                this.status_page.description = result.summarize_successes() + tr("\n\nContinuing setup...");
            } else {
                this.status_page.title = tr("Hardware Compatibility Checked");
                this.status_page.description = tr("No additional drivers were needed.\n\nContinuing setup...");
            }

            yield async_sleep(AUTO_ADVANCE_DELAY_MS);
            this.context.window.push_next_page();
        }

        private void show_pre_wifi_reboot_prompt() {
            this.spinner.spinning = false;
            this.spinner.visible = false;
            this.detail_label.label = "";
            this.detail_label.visible = false;
            this.action_box.visible = false;
            this.context.window.driver_reboot_required = true;
            this.context.window.driver_reboot_prompt_shown = true;
            this.context.navigation_view.push(new DriverRebootPromptPage(this.context));
        }

        private void show_failure_state(DriverPhaseResult result) {
            this.spinner.spinning = false;
            this.spinner.visible = false;
            this.retry_button.label = tr("Retry");
            this.skip_button.visible = true;
            this.status_page.title = tr("Driver Setup Needs Attention");
            this.status_page.description = tr("Quick Setup could not finish installing every required driver.");
            this.detail_label.label = result.summarize_failures();
            this.detail_label.visible = true;
            this.action_box.visible = true;
        }

        private string progress_text(string driver_name, string stage) {
            switch (stage) {
                case "installing":
                    return tr("Installing %s…").printf(driver_name);
                case "verifying":
                    return tr("Verifying %s…").printf(driver_name);
                case "complete":
                    return tr("Installed %s").printf(driver_name);
                case "not_needed":
                    return tr("%s is not needed").printf(driver_name);
                case "failed":
                    return tr("%s needs attention").printf(driver_name);
                default:
                    return tr("Checking %s…").printf(driver_name);
            }
        }

        private async void async_sleep(uint ms) {
            GLib.Timeout.add(ms, () => {
                async_sleep.callback();
                return false;
            });
            yield;
        }
    }

    public class DriversPreWifiPage : DriverPhasePage {
        public override string page_id { get { return "drivers-pre-wifi"; } }
        public override bool can_go_back_to { get { return false; } }
        protected override DriverPhase phase { get { return DriverPhase.PRE_WIFI; } }
        protected override string loading_title { get { return "Checking Drivers"; } }
        protected override string loading_description {
            get {
                return "Checking hardware compatibility and enabling any drivers needed before WiFi setup.";
            }
        }

        public DriversPreWifiPage(PageContext context) {
            base(context, "Checking Drivers");
        }
    }

    public class DriversPostWifiPage : DriverPhasePage {
        public override string page_id { get { return "drivers-post-wifi"; } }
        public override bool can_go_back_to { get { return false; } }
        public override bool needs_network { get { return true; } }
        protected override DriverPhase phase { get { return DriverPhase.POST_WIFI; } }
        protected override string loading_title { get { return "Installing Additional Drivers"; } }
        protected override string loading_description {
            get {
                return "Checking for any drivers that can be installed after network access is available.";
            }
        }

        public DriversPostWifiPage(PageContext context) {
            base(context, "Additional Drivers");
        }
    }

    public class DriverRebootPromptPage : QsPage {
        public override string page_id { get { return "drivers-reboot"; } }
        public override bool can_go_back_to { get { return false; } }

        private DriverManager driver_manager;
        private Adw.StatusPage status_page;
        private Gtk.Button reboot_button;
        private Gtk.Label detail_label;

        public DriverRebootPromptPage(PageContext context) {
            base(context, "Restart Required");
            this.set_back_button(false);

            this.driver_manager = new DriverManager();

            this.status_page = new Adw.StatusPage();
            this.status_page.title = tr("Restart Required");
            this.status_page.description = tr("Drivers were installed that need a restart before WiFi setup can continue. Quick Setup will resume automatically after the reboot.");
            this.status_page.vexpand = true;
            this.status_page.hexpand = true;
            this.status_page.valign = Gtk.Align.FILL;
            this.status_page.halign = Gtk.Align.FILL;
            this.status_page.icon_name = DRIVER_ICON_NAME;
            this.add_content(this.status_page);

            var clamp = new Adw.Clamp();
            clamp.maximum_size = 560;
            clamp.tightening_threshold = 400;

            var content_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 12);
            content_box.valign = Gtk.Align.CENTER;
            content_box.halign = Gtk.Align.FILL;

            this.detail_label = new Gtk.Label("");
            this.detail_label.wrap = true;
            this.detail_label.wrap_mode = Pango.WrapMode.WORD_CHAR;
            this.detail_label.justify = Gtk.Justification.CENTER;
            this.detail_label.halign = Gtk.Align.CENTER;
            this.detail_label.selectable = true;
            this.detail_label.visible = false;
            this.detail_label.add_css_class("dim-label");
            this.detail_label.add_css_class("caption");
            content_box.append(this.detail_label);

            var button_bar = this.create_button_bar();
            button_bar.margin_top = 12;
            this.reboot_button = this.create_page_button("Restart Now", true);
            this.reboot_button.clicked.connect(() => { this.request_reboot(); });
            button_bar.append(this.reboot_button);
            content_box.append(button_bar);

            clamp.set_child(content_box);
            this.status_page.set_child(clamp);
        }

        private void request_reboot() {
            this.reboot_button.sensitive = false;
            this.reboot_button.label = tr("Restarting...");
            this.detail_label.label = "";
            this.detail_label.visible = false;
            this.context.window.set_resume_page("network");
            this.perform_reboot_async.begin();
        }

        private async void perform_reboot_async() {
            string details;
            bool started = yield this.driver_manager.request_reboot(out details);
            if (!started) {
                this.reboot_button.sensitive = true;
                this.reboot_button.label = tr("Retry Restart");
                this.detail_label.label = details.strip();
                this.detail_label.visible = true;
                return;
            }

            this.context.window.allow_close = true;
            this.context.window.close();
        }
    }
}
