/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * Copyright © 2014 Nikhar Agrawal
 * Copyright © 2025-2026 Ben Corby
 *
 * This file is part of libgnome-games-support.
 *
 * libgnome-games-support 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 3 of the License, or
 * (at your option) any later version.
 *
 * libgnome-games-support 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 libgnome-games-support.  If not, see <http://www.gnu.org/licenses/>.
 */

namespace Games {
namespace Scores {

private class Dialog : Adw.Dialog
{
    private Context context;
    private unowned Category active_category;
    private ListStore? score_model = null;

    private Adw.ToolbarView toolbar;
    private Gtk.Button? done_button = null;
    private Gtk.ColumnView? score_view;
    private Gtk.ColumnViewColumn? rank_column;
    private Gtk.ColumnViewColumn? score_column;
    private Gtk.ColumnViewColumn? player_column;

    private Style scores_style;
    private Score? new_high_score;
    private string? score_or_time;
    private bool single_category;
    private unowned string[] clear_dialog_strings;

    public Dialog (Context context, string[] clear_dialog_strings, Style style, Score? new_high_score, Category? current_cat, string icon_name)
    {
        this.context = context;
        this.clear_dialog_strings = clear_dialog_strings;
        this.new_high_score = new_high_score;

        Gtk.Builder builder = new Gtk.Builder ();
        toolbar = new Adw.ToolbarView ();
        Adw.HeaderBar headerbar = new Adw.HeaderBar ();
        headerbar.set_show_end_title_buttons (new_high_score == null);
        set_child (toolbar);
        toolbar.add_child (builder, headerbar, "top");
        set_content_width (400);
        set_content_height (500);

        if (!context.has_scores () && new_high_score == null)
        {
            /* Empty State */
            set_title (_("No scores yet"));

            Adw.StatusPage status_page = new Adw.StatusPage ();
            toolbar.add_child (builder, status_page, null);
            status_page.set_icon_name (icon_name + "-symbolic");
            status_page.set_description (_("Play some games and your scores will show up here."));
            status_page.add_css_class ("dim-label");

            return;
        }

        scores_style = style;
        uint active_category_index = 0; /* default to the first category */
        var categories = context.get_categories ();
        /* Use current_cat as our active_category if it is valid. */
        active_category = categories.find (current_cat, out active_category_index) ?
            current_cat : (Category)categories.get_item (active_category_index);
        
        single_category = categories.get_n_items () == 1;
        score_or_time = "";
        string new_score_or_time = "";

        if (scores_style == Style.POINTS_GREATER_IS_BETTER || scores_style == Style.POINTS_LESS_IS_BETTER)
        {
            score_or_time = _("Score");
            new_score_or_time = _("New Score in");
        }
        else
        {
            score_or_time = _("Time");
            new_score_or_time = _("New Time in");
        }

        /* Decide what the title should be */
        if (new_high_score != null)
        {
            var title_widget = new Adw.WindowTitle (_("Congratulations!"), @"$new_score_or_time $(active_category.name)");
            headerbar.set_title_widget (title_widget);

            /* Button in the top right corner, finishes the dialog */
            done_button = new Gtk.Button.with_label (_("Done"));
            done_button.add_css_class ("suggested-action");
            done_button.clicked.connect (() => this.close ());
            headerbar.pack_end (done_button);
        }
        else
        {
            if (single_category)
            {
                /* active category has been set to the only category in the list */
                set_title (active_category.name);
            }
            else
            {
                var drop_down = new Gtk.DropDown (categories, new Gtk.PropertyExpression (typeof(Category), null, "name"));
                /* set_selected must be called before the selected callback is setup because
                   load_scores_for_category uses score_model which hasn't been initilized yet. */
                drop_down.set_selected (active_category_index);
                drop_down.notify["selected"].connect(() => {
                    Category? selected = (Category?)drop_down.get_selected_item();
                    if (null != selected)
                    {
                        active_category = selected;
                        load_scores_for_category (selected);
                    }
                });
                headerbar.set_title_widget (drop_down);
            }
            var trash_button = new Gtk.Button.from_icon_name ("user-trash-symbolic");
            trash_button.tooltip_text = _("Clear Scores");
            trash_button.clicked.connect (clear_scores_dialog);
            headerbar.pack_start (trash_button);
        }

        /* Add the data to the dialog */
        var scroll = new Gtk.ScrolledWindow ();
        score_view = new Gtk.ColumnView (null);
        score_view.set_reorderable (false);
        score_view.set_tab_behavior (ITEM);
        setup_columns (active_category);
        load_scores_for_category (active_category);
        scroll.set_child (score_view);
        toolbar.add_child (builder, scroll, null);
    }

    async void clear_scores_dialog ()
    {
        var dialog = new Adw.AlertDialog
        (
            clear_dialog_strings[0],
            single_category ? clear_dialog_strings[1] : clear_dialog_strings[2]
        ) {default_response = "cancel"};
        dialog.add_response ("cancel", clear_dialog_strings[3]);
        dialog.add_response ("clear", clear_dialog_strings[4]);
        dialog.set_response_appearance ("clear", Adw.ResponseAppearance.DESTRUCTIVE);
        if (!single_category)
        {
            dialog.add_response ("clearall", clear_dialog_strings[5]);
            dialog.set_response_appearance ("clearall", Adw.ResponseAppearance.DESTRUCTIVE);
        }
        switch (yield dialog.choose (this, null))
        {
        case "clear":
            context.clear_score_file (active_category);
            close ();
            break;
        case "clearall":
            context.clear_score_files ();
            close ();
            break;
        default:
            break;
        }
    }

    /*
     * Most of the code below is from gnome-mahjongg
     * Copyright 2010-2013 Robert Ancell
     * Copyright 2010-2025 Mahjongg Contributors
     */

    private void load_scores_for_category (Category category)
        requires (null != score_model)
    {
        score_model.remove_all ();
        var best_n_scores = context.get_high_scores (category, 10);
        foreach (var score in best_n_scores) {
            score_model.append (score);
        }
        score_view.scroll_to (0, null, Gtk.ListScrollFlags.NONE, null);
    }

    private void setup_columns (Category category)
    {
        set_up_rank_column ();
        set_up_score_column ();
        set_up_player_column (category);
        score_view.append_column (rank_column);
        score_view.append_column (score_column);
        score_view.append_column (player_column);
        score_column.set_expand (true);
        score_column.set_fixed_width (0);
        player_column.set_expand (true);
        player_column.set_fixed_width (0);

        score_model = new ListStore (typeof (Score));
        var sort_model = new Gtk.SortListModel (score_model, score_view.sorter);
        score_view.model = new Gtk.NoSelection (sort_model);

        if (scores_style == Style.POINTS_LESS_IS_BETTER || scores_style == Style.TIME_LESS_IS_BETTER)
            score_view.sort_by_column (rank_column, Gtk.SortType.ASCENDING);
        else
            score_view.sort_by_column (rank_column, Gtk.SortType.DESCENDING);

        score_view.sorter.changed.connect (() => {
            /* Scroll to top when resorting */
            score_view.scroll_to (0, null, Gtk.ListScrollFlags.FOCUS, null);
        });
    }

    private static int rank_sorter_cb (Score entry1, Score entry2) {
        return (int) (entry1.score > entry2.score) - (int) (entry1.score < entry2.score);
    }

    private void set_up_rank_column () {
        var factory = new Gtk.SignalListItemFactory ();
        var sorter = new Gtk.MultiSorter ();

        factory.setup.connect ((factory, object) => {
            unowned var list_item = object as Gtk.ListItem;
            var label = new Gtk.Label (null) {
                width_chars = 3,
                xalign = 0
            };
            label.add_css_class ("caption");
            label.add_css_class ("numeric");
            list_item.child = label;
        });
        factory.bind.connect ((factory, object) => {
            unowned var list_item = object as Gtk.ListItem;
            unowned var label = list_item.child as Gtk.Label;
            unowned var score = list_item.item as Score;
            uint position;
            score_model.find (score, out position);

            if (score == new_high_score)
                    label.add_css_class ("heading");

            label.label = (position + 1).to_string ();
        });
        sorter.append (new Gtk.CustomSorter ((CompareDataFunc<Score>) rank_sorter_cb));

        rank_column = new Gtk.ColumnViewColumn ("Rank", factory);
        rank_column.sorter = sorter;
    }

    private void set_up_score_column () {
        var factory = new Gtk.SignalListItemFactory ();

        factory.setup.connect ((factory, object) => {
            unowned var list_item = object as Gtk.ListItem;
            var label = new Gtk.Inscription (null);

            label.add_css_class ("numeric");
            list_item.child = label;
        });
        if (scores_style == Style.POINTS_GREATER_IS_BETTER || scores_style == Style.POINTS_LESS_IS_BETTER)
        {
            factory.bind.connect ((factory, object) => {
                unowned var list_item = object as Gtk.ListItem;
                unowned var label = list_item.child as Gtk.Inscription;
                unowned var score = list_item.item as Score;

                if (score == new_high_score)
                    label.add_css_class ("heading");

                label.text = score.score.to_string ();
            });
        }
        else
        {
            factory.bind.connect ((factory, object) => {
                unowned var list_item = object as Gtk.ListItem;
                unowned var label = list_item.child as Gtk.Inscription;
                unowned var score = list_item.item as Score;
                string time_label = "%lds".printf (score.score);
                if (score.score >= 60)
                    time_label = "%ldm %lds".printf (score.score / 60, score.score % 60);

                if (score == new_high_score)
                    label.add_css_class ("heading");

                label.text = time_label;
            });
        }

        score_column = new Gtk.ColumnViewColumn (score_or_time, factory);
        score_column.sorter = rank_column.sorter;
    }

    private void set_up_player_column (Category active_category) {
        var factory = new Gtk.SignalListItemFactory ();

        factory.bind.connect ((factory, object) => {
            unowned var list_item = object as Gtk.ListItem;
            unowned var score = list_item.item as Score;

            if (score == new_high_score)
            {
                var entry = new Gtk.Entry ();
                entry.text = score.user;
                entry.set_has_frame (false);
                entry.add_css_class ("heading");
                entry.notify["text"].connect (() => {
                    context.update_score_name (score, active_category, entry.get_text ());
                    score.user = entry.get_text ();
                });
                entry.activate.connect (() => this.close ());
                list_item.child = entry;
                this.set_focus (score_view);
                entry.grab_focus ();
                score_view.scroll_to (list_item.get_position (), null, Gtk.ListScrollFlags.NONE, null);
            }
            else
            {
                list_item.child = new Gtk.Inscription (score.user);
            }
        });

        player_column = new Gtk.ColumnViewColumn (_("Player"), factory);
    }

    internal void add_bottom_buttons (Context.NewGameFunc new_game_func, Context.QuitAppFunc quit_app_func)
    {
        var builder = new Gtk.Builder ();
        Gtk.CenterBox bottom_bar = new Gtk.CenterBox ();
        Gtk.Button new_game_button = new Gtk.Button.with_label (_("_New Game"));
        new_game_button.set_use_underline (true);
        new_game_button.set_can_shrink (true);
        new_game_button.clicked.connect (() => {
            this.close ();
            new_game_func ();
        });
        Gtk.Button quit_button = new Gtk.Button ();
        quit_button.set_can_shrink (true);
        Adw.ButtonContent content = new Adw.ButtonContent ();
        content.set_icon_name ("application-exit-symbolic");
        content.set_label (_("_Quit"));
        content.set_use_underline (true);
        content.set_can_shrink (true);
        quit_button.set_child (content);
        quit_button.clicked.connect (() => {
            this.close ();
            quit_app_func ();
        });
        new_game_button.add_css_class ("pill");
        quit_button.add_css_class ("toolbar");
        bottom_bar.add_css_class ("toolbar");
        bottom_bar.add_child (builder, new_game_button, "center");
        bottom_bar.add_child (builder, quit_button, "end");
        toolbar.add_child (builder, bottom_bar, "bottom");
    }
}

} /* namespace Scores */
} /* namespace Games */
