# Copyright (c) 2023 M.J.N. Corino, The Netherlands
#
# This software is released under the MIT license.
# 
# Some parts are
# Copyright 2004-2007, wxRuby development team
# released under the MIT-like wxRuby2 license

# Class allowing periodic or timed events to be fired

class Wx::Timer
  # Convenience method to trigger a one-off action after +interval+
  # milliseconds have passed. The action is specified by the passed
  # block. The Timer is owned by the global App object, and is returned
  # by the method.
  def self.after(interval, &block)
    timer = new(Wx::THE_APP, Wx::ID_ANY)
    Wx::THE_APP.evt_timer(timer.get_id, block)
    timer.start(interval, true)
    timer
  end

  # Convenience method to trigger a repeating action every +interval+
  # milliseconds. The action is specified by the passed block. The Timer
  # is owned by the global App object, and is returned by the method.
  def self.every(interval, &block)
    timer = new(Wx::THE_APP, Wx::ID_ANY)
    Wx::THE_APP.evt_timer(timer.get_id, block)
    timer.start(interval)
    timer
  end

  # In common with other classes, make the id method refer to the
  # wxWidgets id, not ruby's deprecated name for object_id
  alias :id :get_id
  # In case a more explicit option is preferred.
  alias :wx_id :get_id

  # This class can be linked to an owner - an instance of a class
  # derived from EvtHandler which will receive Timer events. However,
  # event if a Wx::Timer is attached to a Wx::Window, it is (unlike most
  # classes) NOT automatically deleted when the window is destroyed. If
  # the Timer continues ticking, it will send events to the
  # now-destroyed window, causing segfaults. So the little acrobatics
  # below set up a hook when a Timer's owner is set, and then ensure the
  # timer is stopped when the window is destroyed.

  # Redefine initialize
  wx_init = self.instance_method(:initialize)
  wx_redefine_method(:initialize) do | *args |
    setup_owner_destruction_hook(args[0])
    wx_init.bind(self).call(*args)
  end

  # Redefine set_owner
  wx_set_owner = self.instance_method(:set_owner)
  wx_redefine_method(:set_owner) do | *args |
    setup_owner_destruction_hook(args[0])
    wx_set_owner.bind(self).call(*args)
  end

  private 
  # This method notes in Ruby the ownership of the timer, from both
  # sides, and sets up an event hook if needed for the window's
  # destruction.
  def setup_owner_destruction_hook(new_owner)
    this_timer = self

    # Class-wide list of global (unowned) timers
    @@__unowned_timers__ ||= []

    # remove from list of previous owner
    if defined?(@__owner__) and @__owner__
      @__owner__.instance_eval { @__owned_timers__.delete(this_timer) }
    end

    # If becoming global unowned timer, add to list of those timers
    if not new_owner
      @__owner__ = nil
      @@__unowned_timers__ << self      
      return
    end
    
    # Otherwise, if previously unowned, remove from global owned
    @@__unowned_timers__.delete(self)
    @__owner__ = new_owner

    # Then add to list of new owner, setting destructor hook if required    
    new_owner.instance_eval do
      if not defined?(@__owned_timers__)
        @__owned_timers__ = []
        unless self.kind_of?(Wx::App) # Don't set up hook on App
          evt_window_destroy do | evt |
            # If it's the owning window being destroyed...
            if evt.get_event_object == self
              @__owned_timers__.each { | timer | timer.stop }
            end
          end
        end
      end
      @__owned_timers__ << this_timer
    end
  end
end

class Wx::EvtHandler
  # missing from XML docs so we add this here manually
  self.register_event_type EventType[
    'evt_timer', 1,
    Wx::EVT_TIMER,
    Wx::TimerEvent
  ] if Wx.const_defined?(:EVT_TIMER)
end
