/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2017 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 cockpit from "cockpit";
import React from "react";
import {
    Alert, Card, CardTitle, CardHeader, CardActions, CardBody,
    Text, TextVariants,
    DescriptionList,
    DescriptionListTerm,
    DescriptionListGroup,
    DescriptionListDescription
} from "@patternfly/react-core";
import { MinusIcon, PlusIcon } from "@patternfly/react-icons";
import * as utils from "./utils.js";
import { StdDetailsLayout } from "./details.jsx";
import { SidePanel, SidePanelBlockRow } from "./side-panel.jsx";
import { Block } from "./content-views.jsx";
import { StorageButton, StorageOnOff } from "./storage-controls.jsx";
import { dialog_open, SelectSpaces, BlockingMessage, TeardownMessage, teardown_and_apply_title } from "./dialog.jsx";

const _ = cockpit.gettext;

class MDRaidSidebar extends React.Component {
    render() {
        const self = this;
        const client = self.props.client;
        const mdraid = self.props.mdraid;

        function filter_inside_mdraid(spc) {
            let block = spc.block;
            if (client.blocks_part[block.path])
                block = client.blocks[client.blocks_part[block.path].Table];
            return block && block.MDRaid != mdraid.path;
        }

        function rescan(path) {
            // mdraid often forgets to trigger udev, let's do it explicitly
            return client.wait_for(() => client.blocks[path]).then(block => block.Rescan({ }));
        }

        function add_disk() {
            dialog_open({
                Title: _("Add disks"),
                Fields: [
                    SelectSpaces("disks", _("Disks"),
                                 {
                                     empty_warning: _("No disks are available."),
                                     validate: function (disks) {
                                         if (disks.length === 0)
                                             return _("At least one disk is needed.");
                                     },
                                     spaces: utils.get_available_spaces(client).filter(filter_inside_mdraid)
                                 })
                ],
                Action: {
                    Title: _("Add"),
                    action: function(vals) {
                        return utils.prepare_available_spaces(client, vals.disks).then(paths =>
                            Promise.all(paths.map(p => mdraid.AddDevice(p, {}).then(() => rescan(p)))));
                    }
                }
            });
        }

        const members = client.mdraids_members[mdraid.path] || [];
        const dynamic_members = (mdraid.Level != "raid0");

        let n_spares = 0;
        let n_recovering = 0;
        mdraid.ActiveDevices.forEach(function(as) {
            if (as[2].indexOf("spare") >= 0) {
                if (as[1] < 0)
                    n_spares += 1;
                else
                    n_recovering += 1;
            }
        });

        /* Older versions of Udisks/storaged don't have a Running property */
        let running = mdraid.Running;
        if (running === undefined)
            running = mdraid.ActiveDevices && mdraid.ActiveDevices.length > 0;

        function render_member(block) {
            const active_state = utils.array_find(mdraid.ActiveDevices, function(as) {
                return as[0] == block.path;
            });

            function state_text(state) {
                return {
                    faulty:       _("Failed"),
                    in_sync:      _("In sync"),
                    spare:        active_state[1] < 0 ? _("Spare") : _("Recovering"),
                    write_mostly: _("Write-mostly"),
                    blocked:      _("Blocked")
                }[state] || cockpit.format(_("Unknown ($0)"), state);
            }

            const slot = active_state && active_state[1] >= 0 && active_state[1].toString();
            let states = active_state && active_state[2].map(state_text).join(", ");

            if (slot)
                states = cockpit.format(_("Slot $0"), slot) + ", " + states;

            const is_in_sync = (active_state && active_state[2].indexOf("in_sync") >= 0);
            const is_recovering = (active_state && active_state[2].indexOf("spare") >= 0 && active_state[1] >= 0);

            let remove_excuse = false;
            if (!running)
                remove_excuse = _("The RAID device must be running in order to remove disks.");
            else if ((is_in_sync && n_recovering > 0) || is_recovering)
                remove_excuse = _("This disk cannot be removed while the device is recovering.");
            else if (is_in_sync && n_spares < 1)
                remove_excuse = _("A spare disk needs to be added first before this disk can be removed.");
            else if (members.length <= 1)
                remove_excuse = _("The last disk of a RAID device cannot be removed.");

            function remove() {
                return mdraid.RemoveDevice(block.path, { wipe: { t: 'b', v: true } });
            }

            let action = null;
            if (dynamic_members)
                action = (
                    <StorageButton ariaLabel={_("Remove")} onClick={remove} excuse={remove_excuse}>
                        <MinusIcon />
                    </StorageButton>);

            return (
                <SidePanelBlockRow client={client}
                                   block={block}
                                   detail={states}
                                   actions={action}
                                   key={block.path} />);
        }

        let add_excuse = false;
        if (!running)
            add_excuse = _("The RAID device must be running in order to add spare disks.");

        let action = null;
        if (dynamic_members)
            action = (
                <StorageButton ariaLabel={_("Add")} onClick={add_disk} excuse={add_excuse}>
                    <PlusIcon />
                </StorageButton>);

        return (
            <SidePanel title={_("Disks")} actions={action}>
                { members.map(render_member) }
            </SidePanel>);
    }
}

export class MDRaidDetails extends React.Component {
    render() {
        const client = this.props.client;
        const mdraid = this.props.mdraid;
        const block = mdraid && client.mdraids_block[mdraid.path];

        function format_level(str) {
            return {
                raid0: _("RAID 0"),
                raid1: _("RAID 1"),
                raid4: _("RAID 4"),
                raid5: _("RAID 5"),
                raid6: _("RAID 6"),
                raid10: _("RAID 10")
            }[str] || cockpit.format(_("RAID ($0)"), str);
        }

        let level = format_level(mdraid.Level);
        if (mdraid.NumDevices > 0)
            level += ", " + cockpit.format(_("$0 disks"), mdraid.NumDevices);
        if (mdraid.ChunkSize > 0)
            level += ", " + cockpit.format(_("$0 chunk size"), utils.fmt_size(mdraid.ChunkSize));

        function toggle_bitmap(val) {
            return mdraid.SetBitmapLocation(utils.encode_filename(val ? 'internal' : 'none'), {});
        }

        let bitmap = null;
        if (mdraid.BitmapLocation) {
            const value = utils.decode_filename(mdraid.BitmapLocation) != "none";
            bitmap = (
                <DescriptionListGroup>
                    <DescriptionListTerm>{_("storage", "Bitmap")}</DescriptionListTerm>
                    <DescriptionListDescription>
                        <StorageOnOff state={value} aria-label={_("Toggle bitmap")} onChange={toggle_bitmap} />
                    </DescriptionListDescription>
                </DescriptionListGroup>
            );
        }

        let degraded_message = null;
        if (mdraid.Degraded > 0) {
            const text = cockpit.format(
                cockpit.ngettext("$0 disk is missing", "$0 disks are missing", mdraid.Degraded),
                mdraid.Degraded
            );
            degraded_message = (
                <Alert isInline variant="danger" title={_("The RAID array is in a degraded state")}> {text} </Alert>
            );
        }

        /* Older versions of Udisks/storaged don't have a Running property */
        let running = mdraid.Running;
        if (running === undefined)
            running = mdraid.ActiveDevices && mdraid.ActiveDevices.length > 0;

        function start() {
            return mdraid.Start({ "start-degraded": { t: 'b', v: true } });
        }

        function stop() {
            const usage = utils.get_active_usage(client, block ? block.path : "");

            if (usage.Blocking) {
                dialog_open({
                    Title: cockpit.format(_("$0 is in active use"), utils.mdraid_name(mdraid)),
                    Body: BlockingMessage(usage),
                });
                return;
            }

            if (usage.Teardown) {
                dialog_open({
                    Title: cockpit.format(_("Please confirm stopping of $0"),
                                          utils.mdraid_name(mdraid)),
                    Teardown: TeardownMessage(usage),
                    Action: {
                        Title: teardown_and_apply_title(usage,
                                                        _("Stop device"),
                                                        _("Unmount and stop device"),
                                                        _("Remove and stop device")),
                        action: function () {
                            return utils.teardown_active_usage(client, usage)
                                    .then(function () {
                                        return mdraid.Stop({});
                                    });
                        }
                    }
                });
                return;
            }

            return mdraid.Stop({});
        }

        function delete_dialog() {
            const location = cockpit.location;

            function delete_() {
                if (mdraid.Delete)
                    return mdraid.Delete({ 'tear-down': { t: 'b', v: true } });

                // If we don't have a Delete method, we simulate
                // it by stopping the array and wiping all
                // members.

                function wipe_members() {
                    return Promise.all(client.mdraids_members[mdraid.path].map(member => member.Format('empty', { })));
                }

                if (mdraid.ActiveDevices && mdraid.ActiveDevices.length > 0)
                    return mdraid.Stop({}).then(wipe_members);
                else
                    return wipe_members();
            }

            const usage = utils.get_active_usage(client, block ? block.path : "");

            if (usage.Blocking) {
                dialog_open({
                    Title: cockpit.format(_("$0 is in active use"), utils.mdraid_name(mdraid)),
                    Body: BlockingMessage(usage)
                });
                return;
            }

            dialog_open({
                Title: cockpit.format(_("Permanently delete $0?"), utils.mdraid_name(mdraid)),
                Teardown: TeardownMessage(usage),
                Action: {
                    Title: teardown_and_apply_title(usage,
                                                    _("Delete"),
                                                    _("Unmount and delete"),
                                                    _("Remove and delete")),
                    Danger: _("Deleting erases all data on a RAID device."),
                    action: function () {
                        return utils.teardown_active_usage(client, usage)
                                .then(delete_)
                                .then(function () {
                                    location.go('/');
                                });
                    }
                }
            });
        }

        const header = (
            <Card>
                <CardHeader>
                    <CardTitle><Text component={TextVariants.h2}>{ cockpit.format(_("RAID device $0"), utils.mdraid_name(mdraid)) }</Text></CardTitle>
                    <CardActions>
                        { running
                            ? <StorageButton onClick={stop}>{_("Stop")}</StorageButton>
                            : <StorageButton onClick={start}>{_("Start")}</StorageButton>
                        }
                        { "\n" }
                        <StorageButton kind="danger" onClick={delete_dialog}>{_("Delete")}</StorageButton>
                    </CardActions>
                </CardHeader>
                <CardBody>
                    <DescriptionList className="pf-m-horizontal-on-sm">
                        <DescriptionListGroup>
                            <DescriptionListTerm>{_("storage", "Device")}</DescriptionListTerm>
                            <DescriptionListDescription>{ block ? utils.decode_filename(block.PreferredDevice) : "-" }</DescriptionListDescription>
                        </DescriptionListGroup>

                        <DescriptionListGroup>
                            <DescriptionListTerm>{_("storage", "UUID")}</DescriptionListTerm>
                            <DescriptionListDescription>{ mdraid.UUID }</DescriptionListDescription>
                        </DescriptionListGroup>

                        <DescriptionListGroup>
                            <DescriptionListTerm>{_("storage", "Capacity")}</DescriptionListTerm>
                            <DescriptionListDescription>{ utils.fmt_size_long(mdraid.Size) }</DescriptionListDescription>
                        </DescriptionListGroup>

                        <DescriptionListGroup>
                            <DescriptionListTerm>{_("storage", "RAID level")}</DescriptionListTerm>
                            <DescriptionListDescription>{ level }</DescriptionListDescription>
                        </DescriptionListGroup>

                        { bitmap }

                        <DescriptionListGroup>
                            <DescriptionListTerm>{_("storage", "State")}</DescriptionListTerm>
                            <DescriptionListDescription>{ running ? _("Running") : _("Not running") }</DescriptionListDescription>
                        </DescriptionListGroup>
                    </DescriptionList>
                </CardBody>
            </Card>
        );

        const sidebar = <MDRaidSidebar client={this.props.client} mdraid={mdraid} />;

        const content = <Block client={this.props.client} block={block} />;

        return <StdDetailsLayout client={this.props.client} alert={degraded_message}
                                 header={ header }
                                 sidebar={ sidebar }
                                 content={ content } />;
    }
}
