/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * Cockpit is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * Cockpit 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
 */

import $ from "jquery";
import cockpit from "cockpit";

import { show_modal_dialog } from "cockpit-components-dialog.jsx";
import * as service from "service";
import React from "react";

import { TunedDialogBody } from "./change-profile.jsx";
import link_html from "raw-loader!./link.html";

const _ = cockpit.gettext;
var permission = cockpit.permission({ admin: true });

function setup() {
    var tuned_service = service.proxy('tuned.service');

    var element = $(link_html);

    var button = element.find(".action-trigger");
    var tooltip = element.find("#tuned-status-tooltip");
    tooltip.tooltip({ placement: "top" });

    /* Tuned doesn't implement the DBus.Properties interface, so
     * we occasionally poll for what we need.
     *
     * Tuned doesn't auto-activate on the bus, so we have to start
     * it explicitly when opening the dialog.
     */

    function poll(tuned) {
        var dfd = cockpit.defer();

        Promise.all([
            tuned.call('/Tuned', 'com.redhat.tuned.control', 'is_running', []),
            tuned.call('/Tuned', 'com.redhat.tuned.control', 'active_profile', []),
            tuned.call('/Tuned', 'com.redhat.tuned.control', 'recommend_profile', [])
        ])
                .then(function([ is_running_result, active_result, recommended_result ]) {
                    var is_running = is_running_result[0];
                    var active = is_running ? active_result[0] : "none";
                    var recommended = recommended_result[0];

                    dfd.resolve("running", active, recommended);
                })
                .catch(function(ex) {
                    tuned_service.wait(function () {
                        if (!tuned_service.exists)
                            dfd.resolve("not-installed");
                        else if (tuned_service.state != "running")
                            dfd.resolve("not-running");
                        else
                            dfd.reject(ex);
                    });
                });

        return dfd.promise();
    }

    function update_button() {
        /* Reading the current profile works as user */
        var tuned = cockpit.dbus('com.redhat.tuned');

        function set_status(text) {
            tooltip.attr("data-original-title", text);
        }

        poll(tuned)
                .done(function (state, active, recommended) {
                    var status;

                    if (state == "not-installed")
                        status = _("Tuned is not available");
                    else if (state == "not-running")
                        status = _("Tuned is not running");
                    else if (active == "none")
                        status = _("Tuned is off");
                    else if (!permission.allowed)
                        status = cockpit.format(
                            _("The user <b>$0</b> is not permitted to change profiles"),
                            permission.user ? permission.user.name : '');
                    else if (active == recommended)
                        status = _("This system is using the recommended profile");
                    else
                        status = _("This system is using a custom profile");

                    button.text(state == "running" ? active : _("none"));
                    button.prop('disabled', state == "not-installed" || !permission.allowed);
                    button.toggleClass('disabled', state == "not-installed" || !permission.allowed);
                    set_status(status);
                })
                .fail(function (ex) {
                    console.warn("failed to poll tuned", ex);
                    button.text("error");
                    button.prop('disabled', true);
                    button.toggleClass('disabled', true);
                    set_status(_("Communication with tuned has failed"));
                });
    }

    function open_dialog() {
        var tuned;
        var dialog_selected;

        function set_profile() {
            // no need to check input here, all states are valid
            var profile = dialog_selected;
            var promise;

            if (profile == "none") {
                promise = tuned.call("/Tuned", 'com.redhat.tuned.control', 'disable', [])
                        .then(function(results) {
                        /* Yup this is how tuned returns failures */
                            if (!results[0]) {
                                console.warn("Failed to disable tuned profile:", results);
                                return cockpit.reject(_("Failed to disable tuned profile"));
                            }

                            update_button();
                        });
            } else {
                promise = tuned.call('/Tuned', 'com.redhat.tuned.control', 'switch_profile', [ profile ])
                        .then(function(results) {
                        /* Yup this is how tuned returns failures */
                            if (!results[0][0]) {
                                console.warn("Failed to switch profile:", results);
                                return cockpit.reject(results[0][1] || _("Failed to switch profile"));
                            }

                            update_button();
                        });
            }

            return promise.then(set_service);
        }

        function set_service() {
            /* When the profile is none we disable tuned */
            var enable = (dialog_selected != "none");
            var action = enable ? "start" : "stop";
            return tuned.call('/Tuned', 'com.redhat.tuned.control', action, [])
                    .then(function(results) {
                    /* Yup this is how tuned returns failures */
                        if (!results[0]) {
                            console.warn("Failed to " + action + " tuned:", results);
                            if (results[1])
                                return cockpit.reject(results[1]);
                            else if (enable)
                                return cockpit.reject(cockpit.format(_("Failed to enable tuned")));
                            else
                                return cockpit.reject(cockpit.format(_("Failed to disable tuned")));
                        }

                        /* Now tell systemd about this change */
                        if (enable && !tuned_service.enabled)
                            return tuned_service.enable();
                        else if (!enable && tuned_service.enabled)
                            return tuned_service.disable();
                        else
                            return null;
                    });
        }

        function update_selected_item(selected) {
            dialog_selected = selected;
        }

        function create_dialog(profiles, active_profile, primary_disabled, static_error) {
            dialog_selected = active_profile;
            var dialog_props = {
                'title': _("Change Performance Profile"),
                'body': React.createElement(TunedDialogBody, {
                    'active_profile': active_profile,
                    'change_selected': update_selected_item,
                    'profiles': profiles,
                }),
            };
            var footer_props = {
                'actions': [ { 'clicked': set_profile,
                               'caption': _("Change Profile"),
                               'style': 'primary',
                }
                ],
                'static_error': static_error,
            };
            show_modal_dialog(dialog_props, footer_props);
        }

        function with_info(active, recommended, profiles) {
            var model = [];
            profiles.forEach(function(p) {
                var name, desc;
                if (typeof p === "string") {
                    name = p;
                    desc = "";
                } else {
                    name = p[0];
                    desc = p[1];
                }
                if (name != "none") {
                    model.push({
                        name: name,
                        title: name,
                        description: desc,
                        active: name == active,
                        recommended: name == recommended,
                    });
                }
            });

            model.unshift({
                name: "none",
                title: _("None"),
                description: _("Disable tuned"),
                active: active == "none",
                recommended: recommended == "none",
            });

            create_dialog(model, active);
        }

        function show_error(error) {
            create_dialog([ ], "none", true, error);
        }

        function tuned_profiles() {
            return tuned.call('/Tuned', 'com.redhat.tuned.control', 'profiles2', [])
                    .then(function(result) {
                        return result[0];
                    }, function() {
                        return tuned.call('/Tuned', 'com.redhat.tuned.control', 'profiles', [])
                                .then(function(result) {
                                    return result[0];
                                });
                    });
        }

        function with_tuned() {
            poll(tuned)
                    .done(function (state, active, recommended) {
                        if (state != "running") {
                            show_error(_("Tuned has failed to start"));
                            return;
                        }
                        tuned_profiles().then(function(profiles) {
                            with_info(active, recommended, profiles);
                        }, show_error);
                    })
                    .fail(show_error);
        }

        tuned_service.start()
                .done(function () {
                    update_button();
                    /* There are a few cases where tuned can be started by any user,
                 * but as the dialog needs superuser anyway, there's little
                 * reason to put 'try'.
                 */
                    tuned = cockpit.dbus('com.redhat.tuned', { superuser: true });
                    with_tuned();
                })
                .fail(show_error);
    }

    button.on('click', open_dialog);

    tuned_service.addEventListener("changed", update_button);
    update_button();

    return element[0];
}

$(permission).on('changed', function () {
    var element = $('#system-info-performance');
    element.append(setup());
    element.removeAttr('hidden');
});
