/* -*- 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 static int dpi;
  
  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, bool timeout=true)
  {
    if (status_timeout_id != 0)
      Source.remove (status_timeout_id);

    clear_feedback();
    header_title.title = feedback;

    if (timeout)
      status_timeout_id = Timeout.add_seconds(5, clear_feedback);
  }

  public GtkMult(Gtk.Application app)
  {
    Object(application: app);
    menu_button.menu_model = app.get_menu_by_id("app-menu");
  }
  
  construct
  {
    vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
    vbox.halign = Gtk.Align.FILL;
    vbox.valign = Gtk.Align.FILL;

    score = new Gtk.Label ("");
    canvas = null;
    status_timeout_id = 0;
    timer = new Timer();
    timer_timeout_id = 0;
    
    // Convert pixel space to desired point size
    var settings = Gtk.Settings.get_for_display(this.display);
    dpi = settings.gtk_xft_dpi / Pango.SCALE;
    
    var height = 4*dpi;
    set_default_size((int)(height*ASPECT), height);
    
    content = vbox;
    
    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("", "");
    header_title.add_css_class("numeric"); // for time counter

    header = new Adw.HeaderBar();
    header.title_widget = header_title;
    header.pack_end(menu_button);
    vbox.append(header);

    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;
    vbox.append(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.Box vbox;
  private Timer timer;
  private Gtk.Label score;
  Adw.HeaderBar header;
  Adw.WindowTitle header_title;
  Gtk.MenuButton menu_button;
  Gtk.AspectFrame aspect;
  private uint status_timeout_id;
  private uint timer_timeout_id;
  
  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;
    aspect.child = canvas;
    
    timer.start();
    if (timer_timeout_id != 0)
      Source.remove(timer_timeout_id);
    timer_timeout_id = Timeout.add_seconds (1, update_subtitle);
    
    cheated = false;
    
    on_puzzle_change(puzzle);

    clear_feedback();
    update_subtitle();

    if (status_timeout_id != 0)
      Source.remove (status_timeout_id);
    status_timeout_id = 0;
    
    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)
  {
    update_subtitle();

    if (puzzle.is_done)
    {
      set_feedback(_("Congratulations!"), false);
    }
    else
    {
      string message_pattern = null;
      
      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)
      timer.stop();
  }

  bool clear_feedback()
  {
    status_timeout_id = 0;
    header_title.title = _("Multiplication Puzzle");
    return false;
  }

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

    var secs = (uint)timer.elapsed(null);
    var time = secs > 60 * 60 ?
               "%02u∶%02u∶%02u".printf(secs/60/60, secs/60%60, secs%60) :
               "%02u∶%02u".printf(secs/60%60, secs%60);

    header_title.subtitle = "%s   %s".printf(guesses, time);

    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();
  }
}

