# coding: utf-8
# Copyright 2019 DragonRuby LLC
# MIT License
# keyboard_keys.rb has been released under MIT (*only this file*).
module GTK
  class KeyboardKeys
    attr :last_directional_vector

    def self.sdl_shift_key? raw_key
      sdl_lshift_key?(raw_key) || sdl_rshift_key?(raw_key)
    end

    def self.sdl_lshift_key? raw_key
      raw_key == 1073742049
    end

    def self.sdl_rshift_key? raw_key
      raw_key == 1073742053
    end

    def self.sdl_modifier_key? raw_key
      (raw_key >= 1073742048 && raw_key <= 1073742055) # Modifier Keys
    end

    def self.sdl_modifier_key_methods modifier
      names = []
      if (modifier & 1) != 0
        names << :shift_left
        names << :shift
      end

      if (modifier & 2) != 0
        names << :shift_right
        names << :shift
      end

      if (modifier & 256) != 0
        names << :alt_left
        names << :alt
      end

      if (modifier & 512) != 0
        names << :alt_right
        names << :alt
      end

      if (modifier & 1024) != 0
        names << :meta_left
        names << :meta
      end

      if (modifier & 2048) != 0
        names << :meta_right
        names << :meta
      end

      if (modifier & 64) != 0
        names << :control_left
        names << :control
      end

      if (modifier & 128) != 0
        names << :control_right
        names << :control
      end

      names.uniq!
      names
    end

    def self.char_to_shift_char_hash
      @char_to_shift_char ||= shift_char_to_char_hash.invert
    end

    def self.shift_char_to_char_hash
      @shift_char_to_char ||= {
        tilde: :backtick,
        underscore: :hyphen,
        double_quotation_mark: :single_quotation_mark,
        exclamation_point: :one,
        at: :two,
        hash: :three,
        dollar: :four,
        percent: :five,
        caret: :six,
        ampersand: :seven,
        asterisk: :eight,
        open_round_brace: :nine,
        close_round_brace: :zero,
        open_curly_brace: :open_square_brace,
        close_curly_brace: :close_square_brace,
        colon: :semicolon,
        plus: :equal,
        pipe: :back_slash,
        question_mark: :forward_slash,
        less_than: :comma,
        greater_than: :period
      }
    end

    def self.scancode_to_method_hash
      @scancode_to_method ||= {
        26 => :w_scancode,
        4  => :a_scancode,
        22 => :s_scancode,
        7  => :d_scancode
      }
    end

    def self.sdl_to_key raw_key, modifier
      return nil unless (raw_key >= 0 && raw_key <= 255) ||
                        KeyboardKeys.char_to_method_hash[raw_key]

      char = KeyboardKeys.char_with_shift raw_key, modifier
      names = KeyboardKeys.char_to_method char, raw_key
      names << :alt if KeyboardKeys.modifier_alt? modifier
      names << :meta if KeyboardKeys.modifier_meta? modifier
      names << :control if KeyboardKeys.modifier_ctrl? modifier
      names << :shift if KeyboardKeys.modifier_shift? modifier
      names
    end

    def self.modifier_shift? modifier
      (modifier & (1|2)) != 0
    end

    def self.modifier_ctrl? modifier
      (modifier & (64|128)) != 0
    end

    def self.modifier_alt? modifier
      (modifier & (256|512)) != 0
    end

    def self.modifier_meta? modifier
      (modifier & (1024|2048)) != 0
    end

    def self.utf_8_char raw_key
      return "²" if raw_key == 178
      return "§" if raw_key == 167
      return "º" if raw_key == 186
      return raw_key.chr
    end

    def self.char_with_shift raw_key, modifier
      return nil unless raw_key >= 0 && raw_key <= 255
      if !KeyboardKeys.modifier_shift?(modifier)
        return utf_8_char raw_key
      else
        @shift_keys ||= {
          '`' => '~', '-' => '_', "'" => '"', "1" => '!',
          "2" => '@', "3" => '#', "4" => '$', "5" => '%',
          "6" => '^', "7" => '&', "8" => '*', "9" => '(',
          "0" => ')', ";" => ":", "=" => "+", "[" => "{",
          "]" => "}", '\\'=> "|", '/' => "?", '.' => ">",
          ',' => "<", 'a' => 'A', 'b' => 'B', 'c' => 'C',
          'd' => 'D', 'e' => 'E', 'f' => 'F', 'g' => 'G',
          'h' => 'H', 'i' => 'I', 'j' => 'J', 'k' => 'K',
          'l' => 'L', 'm' => 'M', 'n' => 'N', 'o' => 'O',
          'p' => 'P', 'q' => 'Q', 'r' => 'R', 's' => 'S',
          't' => 'T', 'u' => 'U', 'v' => 'V', 'w' => 'W',
          'x' => 'X', 'y' => 'Y', 'z' => 'Z'
        }

        @shift_keys[raw_key.chr.to_s] || raw_key.chr.to_s
      end
    end

    def self.char_to_method_hash
      @char_to_method ||= {
        'A'  => [:a],
        'B'  => [:b],
        'C'  => [:c],
        'D'  => [:d],
        'E'  => [:e],
        'F'  => [:f],
        'G'  => [:g],
        'H'  => [:h],
        'I'  => [:i],
        'J'  => [:j],
        'K'  => [:k],
        'L'  => [:l],
        'M'  => [:m],
        'N'  => [:n],
        'O'  => [:o],
        'P'  => [:p],
        'Q'  => [:q],
        'R'  => [:r],
        'S'  => [:s],
        'T'  => [:t],
        'U'  => [:u],
        'V'  => [:v],
        'W'  => [:w],
        'X'  => [:x],
        'Y'  => [:y],
        'Z'  => [:z],
        "!"  => [:exclamation_point],
        "0"  => [:zero],
        "1"  => [:one],
        "2"  => [:two],
        "3"  => [:three],
        "4"  => [:four],
        "5"  => [:five],
        "6"  => [:six],
        "7"  => [:seven],
        "8"  => [:eight],
        "9"  => [:nine],
        "\b" => [:backspace],
        "\e" => [:escape],
        "\r" => [:enter],
        "\t" => [:tab],
        "("  => [:open_round_brace],
        ")"  => [:close_round_brace],
        "{"  => [:open_curly_brace],
        "}"  => [:close_curly_brace],
        "["  => [:open_square_brace],
        "]"  => [:close_square_brace],
        ":"  => [:colon],
        ";"  => [:semicolon],
        "="  => [:equal],
        "-"  => [:hyphen],
        " "  => [:space],
        "$"  => [:dollar],
        "\"" => [:double_quotation_mark],
        "'"  => [:single_quotation_mark],
        "`"  => [:backtick],
        "~"  => [:tilde],
        "."  => [:period],
        ","  => [:comma],
        "|"  => [:pipe],
        "_"  => [:underscore],
        "#"  => [:hash],
        "+"  => [:plus],
        "@"  => [:at],
        "/"  => [:forward_slash],
        "\\" => [:back_slash],
        "*"  => [:asterisk],
        "<"  => [:less_than],
        ">"  => [:greater_than],
        "^"  => [:caret],
        "&"  => [:ampersand],
        "²"  => [:superscript_two],
        "§"  => [:section],
        "?"  => [:question_mark],
        '%'  => [:percent],
        "º"  => [:ordinal_indicator],
        1073741881 => [:caps_lock],
        1073741882 => [:f1],
        1073741883 => [:f2],
        1073741884 => [:f3],
        1073741885 => [:f4],
        1073741886 => [:f5],
        1073741887 => [:f6],
        1073741888 => [:f7],
        1073741889 => [:f8],
        1073741890 => [:f9],
        1073741891 => [:f10],
        1073741892 => [:f11],
        1073741893 => [:f12],
        1073741894 => [:print_screen],
        1073741895 => [:scroll_lock],
        1073741896 => [:pause],
        1073741897 => [:insert],
        1073741898 => [:home],
        1073741899 => [:page_up],
        127        => [:delete],
        1073741901 => [:end],
        1073741902 => [:page_down],
        1073741903 => [:right_arrow],
        1073741904 => [:left_arrow],
        1073741905 => [:down_arrow],
        1073741906 => [:up_arrow],
        1073741907 => [:num_lock],
        1073741908 => [:kp_divide],
        1073741909 => [:kp_multiply],
        1073741910 => [:kp_minus],
        1073741911 => [:kp_plus],
        1073741912 => [:kp_enter],
        1073741913 => [:kp_one],
        1073741914 => [:kp_two],
        1073741915 => [:kp_three],
        1073741916 => [:kp_four],
        1073741917 => [:kp_five],
        1073741918 => [:kp_six],
        1073741919 => [:kp_seven],
        1073741920 => [:kp_eight],
        1073741921 => [:kp_nine],
        1073741922 => [:kp_zero],
        1073741923 => [:kp_period],
        1073741927 => [:kp_equals],
        1073742048 => [:control_left, :control],
        1073742049 => [:shift_left, :shift],
        1073742050 => [:alt_left, :alt],
        1073742051 => [:meta_left, :meta],
        1073742052 => [:control_right, :control],
        1073742053 => [:shift_right, :shift],
        1073742054 => [:alt_right, :alt],
        1073742055 => [:meta_right, :meta],
        1073742092 => [:ac_search],
        1073742093 => [:ac_home],
        1073742094 => [:ac_back],
        1073742095 => [:ac_forward],
        1073742096 => [:ac_stop],
        1073742097 => [:ac_refresh],
        1073742098 => [:ac_bookmarks]
      }
    end

    def self.method_to_key_hash
      return @method_to_key_hash if @method_to_key_hash
      @method_to_key_hash = {}
      string_representation_overrides ||= {
        backspace: '\b'
      }
      char_to_method_hash.each do |k, v|
        v.each do |vi|
          t = { char_or_raw_key: k }

          if k.is_a? Numeric
            t[:raw_key] = k
            t[:string_representation] = "raw_key == #{k}"
          else
            t[:char] = k
            t[:string_representation] = "\"#{k.strip}\""
          end

          @method_to_key_hash[vi] = t
        end
      end
      @method_to_key_hash
    end

    def self.char_to_method char, int = nil
      methods = char_to_method_hash[char] || char_to_method_hash[int]
      methods ? methods.dup : [char.to_sym || int]
    end

    def clear
      set truthy_keys, nil
      @keycodes.clear
      @scrubbed_ivars = nil
    end

    def left_right
      directional_vector&.x&.sign || 0
    end

    def last_left_right
      self&.last_directional_vector&.x&.sign || 0
    end

    def up_down
      directional_vector&.y&.sign || 0
    end

    def last_up_down
      self&.last_directional_vector&.y&.sign || 0
    end

    def truthy_keys
      get(all).find_all { |_, v| v }
              .map { |k, _| k.to_sym }
    end

    def all? keys
      values = get(keys.map { |k| k.without_ending_bang })
      all_true = values.all? do |k, v|
        v
      end

      if all_true
        keys.each do |k|
          clear_key k if k.end_with_bang?
        end
      end

      all_true
    end

    def any? keys
      values = get(keys.map { |k| k.without_ending_bang })
      any_true = values.any? do |k, v|
        v
      end

      if any_true
        keys.each do |k|
          clear_key k if k.end_with_bang?
        end
      end

      any_true
    end

    def clear_key key
      @scrubbed_ivars = nil
      self.instance_variable_set("@#{key.without_ending_bang}", false)
    end

    def all
      @scrubbed_ivars ||= self.instance_variables
                              .reject { |i| i == :@all || i == :@scrubbed_ivars || i == :@keycodes || i == :@last_directional_vector }
                              .map { |i| i.to_s.gsub("@", "") }

      get(@scrubbed_ivars).map { |k, _| k }
    end

    def get collection
      return [] if collection.length == 0
      collection.map do |m|
        resolved_m = KeyboardKeys.aliases[m] || m
        if resolved_m.end_with_bang?
          clear_after_return = true
        end

        value = self.instance_variable_get("@#{resolved_m.without_ending_bang}".to_sym)
        clear_key resolved_m if clear_after_return
        [m.without_ending_bang, value]
      end
    end

    def set collection, value
      return if collection.length == 0
      @scrubbed_ivars = nil

      collection.each do |m|
        resolved_m = KeyboardKeys.aliases[m] || m
        m_to_s = resolved_m.to_s
        self.instance_variable_set("@#{m_to_s}".to_sym, value) if m_to_s.strip.length > 0
      rescue Exception => e
        raise e, <<-S
* ERROR:
Attempted to set the a key on the DragonRuby GTK's Keyboard data
structure, but the property isn't available for raw_key #{raw_key} #{m}.

You should contact DragonRuby and tell them to associate the raw_key #{raw_key}
with a friendly property name (we are open to suggestions if you have any).
[GTK::KeyboardKeys#set, GTK::KeyboardKeys#char_to_method]

S
      end
    end

    def method_missing m, *args
      without_bang = m.without_ending_bang.to_sym
      without_bang_equal = "#{without_bang}=".to_sym
      if respond_to? without_bang
        begin
          define_singleton_method(m) do
            r = send(without_bang)
            send(without_bang_equal, nil)
            return r
          end

          return self.send m
        rescue Exception => e
          log_important "#{e}"
        end
      end

      did_you_mean = KeyboardKeys.method_to_key_hash.find_all do |k, v|
        k.to_s[0..1] == m.to_s[0..1]
      end.map {|k, v| ":#{k} (#{v[:string_representation]})" }
      did_you_mean_string = ""
      did_you_mean_string = ". Did you mean #{did_you_mean.join ", "}?"

      raise <<-S
* ERROR:
#{KeyboardKeys.method_to_key_hash.map { |k, v| "** :#{k} #{v.string_representation}" }.join("\n")}

There is no key on the keyboard called :#{m}#{did_you_mean_string}.
Full list of available keys =:points_up:=.
S
    end

    def serialize
      hash = super
      hash.delete(:scrubbed_ivars)
      hash[:truthy_keys] = self.truthy_keys
      hash
    end

    def ctrl
      @control
    end

    def ctrl= value
      @control = value
    end

    def directional_vector
      l = left_arrow  || a_scancode || false
      r = right_arrow || d_scancode || false
      u = up_arrow    || w_scancode || false
      d = down_arrow  || d_scancode || false

      # if both left right keys are held, then return last left right key
      lr = if l && r && last_left_right != 0
             last_left_right
           elsif l
             -1
           elsif r
             1
           else
             0
           end

      # if both up down keys are held, then return last up down key
      ud = if u && d && last_up_down != 0
             last_up_down
           elsif u
             -1
           elsif d
             1
           else
             0
           end

      if lr == 0 && ud == 0
        return nil
      elsif lr.abs == ud.abs
        return { x: 45.vector_x * lr.sign, y: 45.vector_y * ud.sign }
      else
        return { x: lr, y: ud }
      end
    end

    def left_with_wasd
      @left || @a_scancode || nil
    end
  end
end
