/* -*- Mode: Vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*-
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 * SPDX-FileCopyrightText: Michael Terry
 */

using GLib;

public class GtkMult : Adw.ApplicationWindow
{
  public MultPuzzle puzzle { get; private set; }
  public bool cheated { get; private set; }
  
  public const double ASPECT = 9.0/5.0;
  
  public void set_feedback(string feedback)
  {
    toast.title = feedback;
    overlay.add_toast(toast);
  }

  public GtkMult(Gtk.Application app)
  {
    Object(application: app);
    menu_button.menu_model = app.get_menu_by_id("app-menu");
  }
  
  construct
  {
    var settings = new Settings(Config.APPID);
    settings.bind("window-width", this, "default-width", SettingsBindFlags.DEFAULT);
    settings.bind("window-height", this, "default-height", SettingsBindFlags.DEFAULT);
    settings.bind("window-maximized", this, "maximized", SettingsBindFlags.DEFAULT);
    settings.bind("window-fullscreened", this, "fullscreened", SettingsBindFlags.DEFAULT);

    var view = new Adw.ToolbarView();
    this.content = view;

    var vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
    view.content = vbox;

    score = new Gtk.Label ("");
    canvas = null;
    
    var key_controller = new Gtk.EventControllerKey();
    key_controller.key_pressed.connect(handle_key_press);
    ((Gtk.Widget)this).add_controller(key_controller);
    
    set_title (_("Multiplication Puzzle"));

    menu_button = new Gtk.MenuButton();
    menu_button.icon_name = "open-menu-symbolic";
    menu_button.tooltip_text = _("Main Menu");
    menu_button.primary = true;

    header_title = new Adw.WindowTitle(_("Multiplication Puzzle"), "");

    header = new Adw.HeaderBar();
    header.title_widget = header_title;
    header.pack_end(menu_button);
    view.add_top_bar(header);

    banner = new Adw.Banner(_("Congratulations!"));
    banner.button_label = _("_New Game");
    banner.action_name = "app.new-game";
    vbox.append(banner);

    overlay = new Adw.ToastOverlay();
    vbox.append(overlay);

    toast = new Adw.Toast("");
    toast.ref_sink(); // own this toast for our lifetime

    aspect = new Gtk.AspectFrame(0.5f, 0.5f, (float)ASPECT, false);
    aspect.margin_top = 12;
    aspect.margin_bottom = aspect.margin_top;
    aspect.margin_start = aspect.margin_top;
    aspect.margin_end = aspect.margin_top;
    overlay.child = aspect;

    new_puzzle ();
  }
  
  static unichar a_char = _("A").get_char().toupper();
  static unichar b_char = _("B").get_char().toupper();
  static unichar c_char = _("C").get_char().toupper();
  static unichar d_char = _("D").get_char().toupper();
  static unichar e_char = _("E").get_char().toupper();
  static unichar f_char = _("F").get_char().toupper();
  static unichar g_char = _("G").get_char().toupper();
  static unichar h_char = _("H").get_char().toupper();
  static unichar i_char = _("I").get_char().toupper();
  static unichar j_char = _("J").get_char().toupper();
  static unichar 0_char = _("0").get_char().toupper();
  static unichar 1_char = _("1").get_char().toupper();
  static unichar 2_char = _("2").get_char().toupper();
  static unichar 3_char = _("3").get_char().toupper();
  static unichar 4_char = _("4").get_char().toupper();
  static unichar 5_char = _("5").get_char().toupper();
  static unichar 6_char = _("6").get_char().toupper();
  static unichar 7_char = _("7").get_char().toupper();
  static unichar 8_char = _("8").get_char().toupper();
  static unichar 9_char = _("9").get_char().toupper();
  private bool handle_key_press (Gtk.EventControllerKey controller,
                                 uint keyval, uint keycode,
                                 Gdk.ModifierType state)
  {
    // Don't recognize any keys if ctrl or alt are pressed.
    state = state & Gtk.accelerator_get_default_mod_mask();
    state = state & (~Gdk.ModifierType.SHIFT_MASK); // ignore shift
    if (state != 0)
      return false;
    
    char ch;
    unichar ev_char = Gdk.keyval_to_unicode(Gdk.keyval_to_upper(keyval));

    if      (ev_char == a_char) ch = 'A';
    else if (ev_char == b_char) ch = 'B';
    else if (ev_char == c_char) ch = 'C';
    else if (ev_char == d_char) ch = 'D';
    else if (ev_char == e_char) ch = 'E';
    else if (ev_char == f_char) ch = 'F';
    else if (ev_char == g_char) ch = 'G';
    else if (ev_char == h_char) ch = 'H';
    else if (ev_char == i_char) ch = 'I';
    else if (ev_char == j_char) ch = 'J';
    else if (ev_char == 0_char) ch = '0';
    else if (ev_char == 1_char) ch = '1';
    else if (ev_char == 2_char) ch = '2';
    else if (ev_char == 3_char) ch = '3';
    else if (ev_char == 4_char) ch = '4';
    else if (ev_char == 5_char) ch = '5';
    else if (ev_char == 6_char) ch = '6';
    else if (ev_char == 7_char) ch = '7';
    else if (ev_char == 8_char) ch = '8';
    else if (ev_char == 9_char) ch = '9';
    else if (keyval == Gdk.Key.Escape) {
      this.canvas.clear_mode();
      return true;
    }
    else
      return false;
    
    this.canvas.start_choice(ch);
    return true;
  }
    
  private Canvas canvas;
  private Gtk.Label score;
  Adw.HeaderBar header;
  Adw.WindowTitle header_title;
  Gtk.MenuButton menu_button;
  Adw.Banner banner;
  Adw.ToastOverlay overlay;
  Adw.Toast toast;
  Gtk.AspectFrame aspect;
  
  public void new_puzzle ()
  {
    puzzle = new MultPuzzle (3, 2);
    
    canvas = new Canvas (this);
    canvas.margin_start = 12;
    canvas.margin_end = 12;
    canvas.margin_top = 12;
    canvas.margin_bottom = 12;
    canvas.halign = Gtk.Align.FILL;
    canvas.valign = Gtk.Align.FILL;
    canvas.set_size_request(400, 222); // 400 width, with 5/9s height
    aspect.child = canvas;
    
    cheated = false;
    
    on_puzzle_change(puzzle);

    clear_feedback();
    update_subtitle();
    
    puzzle.guessed.connect(on_puzzle_guess);
    puzzle.changed.connect(on_puzzle_change);
  }
  
  private void on_puzzle_guess (MultPuzzle p, int digit, MultPuzzleChar letter, MultPuzzleGuessStatus response)
  {
    string message_pattern = null;

    update_subtitle();

    switch (response)
    {
    case MultPuzzleGuessStatus.WRONG:
      // Translators: First argument is letter, second is digit
      message_pattern = _("Incorrect — %1$s is not %2$s");
      break;
    case MultPuzzleGuessStatus.CORRECT:
      // Translators: First argument is letter, second is digit
      message_pattern = _("Correct — %1$s is %2$s");
      break;
    default:
    case MultPuzzleGuessStatus.KNOWN:
    case MultPuzzleGuessStatus.INVALID:
      // shouldn't happen
      break;
    }

    if (message_pattern != null) {
      var letter_str = "%c".printf ((char)letter);
      var digit_str = "%i".printf (digit);
      set_feedback(message_pattern.printf(_(letter_str), _(digit_str)));
    }
  }
  
  private void on_puzzle_change (MultPuzzle p)
  {
    if (application != null) {
      var hint_action = application.lookup_action("hint") as SimpleAction;
      hint_action.set_enabled(!p.is_done);
    }

    if (p.is_done) {
      banner.revealed = true;
    }
  }

  bool clear_feedback()
  {
    banner.revealed = false;
    toast.dismiss();
    return false;
  }

  bool update_subtitle()
  {
    var wrong = puzzle.wrong_guesses;
    var guesses = "%s: %i%s".printf(_("Incorrect Guesses"), wrong, cheated ? "*" : "");
    header_title.subtitle = guesses;
    return true;
  }

  public void show_hint()
  {
    cheated = true;
    
    // Pick a random unknown digit.  Keep trying until we actually solve one
    // (because the digit may not have been in the puzzle -- we want to be
    // more useful than that.
    var character = MultPuzzleChar.INVALID;
    var digit = 0;
    var needed = this.puzzle.get_needed_digits();
    var num_needed = 0;
    foreach (bool n in needed)
      num_needed += n ? 1 : 0;
    if (num_needed == 0) // shouldn't happen
      return;
    
    var choice = Random.int_range(0, num_needed);
    for (int i = 0; i < 10; ++i) {
      if (needed[i] && choice == 0) {
        character = this.puzzle.solve_digit(i);
        digit = i;
        break;
      }
      else if (needed[i])
        --choice;
    }

    // Translators: First argument is letter, second is digit
    var message_pattern = _("%1$s is %2$s");
    var letter_str = "%c".printf ((char)character);
    var digit_str = "%i".printf (digit);
    set_feedback(message_pattern.printf(_(letter_str), _(digit_str)));

    update_subtitle();
  }
}

