# frozen_string_literal: true

require_relative "buffer"

module Fusuma
  module Plugin
    module Buffers
      # manage events and generate command
      class GestureBuffer < Buffer
        CacheEntry = Struct.new(:checked, :value)
        DEFAULT_SOURCE = "libinput_gesture_parser"
        DEFAULT_SECONDS_TO_KEEP = 100

        #: (*nil | Array[untyped]) -> void
        def initialize(*args)
          super
          @cache = {}
          @cache_select_by = {}
          @cache_sum10 = {}
        end

        #: () -> Hash[untyped, untyped]
        def clear
          super.clear
          @cache = {}
          @cache_select_by = {}
          @cache_sum10 = {}
        end

        #: () -> Hash[Symbol, Array[Class] | Class]
        def config_param_types
          {
            source: [String],
            seconds_to_keep: [Float, Integer]
          }
        end

        # @param event [Event]
        # @return [Buffer, FalseClass]
        #: (Fusuma::Plugin::Events::Event) -> Fusuma::Plugin::Buffers::GestureBuffer?
        def buffer(event)
          # TODO: buffering events into buffer plugins
          # - gesture event buffer
          # - window event buffer
          # - other event buffer
          return if event&.tag != source

          @events.push(event)
          self
        end

        #: (?current_time: Time) -> void
        def clear_expired(current_time: Time.now)
          clear if ended?

          @seconds_to_keep ||= config_params(:seconds_to_keep) || DEFAULT_SECONDS_TO_KEEP
          @events.each do |e|
            break if current_time - e.time < @seconds_to_keep

            MultiLogger.debug("#{self.class.name}##{__method__}")

            @events.delete(e)
            @cache = {}
            @cache_select_by = {}
            @cache_sum10 = {}
          end
        end

        #: () -> bool
        def ended?
          return false if empty?

          case @events.last.record.status
          when "end", "cancelled"
            true
          else
            false
          end
        end

        # @param attr [Symbol]
        # @return [Float]
        #: (Symbol) -> Float
        def sum_attrs(attr)
          updating_events.map do |gesture_event|
            gesture_event.record.delta[attr].to_f
          end.reduce(:+)
        end

        # @param attr [Symbol]
        # @return [Float]
        #: (Symbol) -> Float
        def sum_last10_attrs(attr) # sums last 10 values of attr (or all if length < 10)
          cache_entry = (@cache_sum10[attr] ||= CacheEntry.new(0, 0))
          upd_ev = updating_events
          if upd_ev.length > cache_entry.checked + 1
            cache_entry.value = upd_ev.last(10).map do |gesture_event|
              gesture_event.record.delta[attr].to_f
            end.reduce(:+)
          elsif upd_ev.length > cache_entry.checked
            cache_entry.value = cache_entry.value + upd_ev[-1].record.delta[attr].to_f -
              ((upd_ev.length > 10) ? upd_ev[-11].record.delta[attr].to_f : 0)
          else
            return cache_entry.value
          end
          cache_entry.checked = upd_ev.length
          cache_entry.value
        end

        #: () -> Array[untyped]
        def updating_events
          cache_entry = (@cache[:updating_events] ||= CacheEntry.new(0, []))
          cache_entry.checked.upto(@events.length - 1).each do |i|
            (cache_entry.value << @events[i]) if @events[i].record.status == "update"
          end
          cache_entry.checked = @events.length
          cache_entry.value
        end

        # @param attr [Symbol]
        # @return [Float]
        #: (Symbol) -> Float
        def avg_attrs(attr)
          sum_attrs(attr).to_f / updating_events.length
        end

        # return [Integer]
        #: () -> Integer
        def finger
          @events.last.record.finger.to_i
        end

        # @example
        #  event_buffer.gesture
        #  => 'swipe'
        # @return [String]
        def gesture
          @events.last.record.gesture
        end

        #: (String) -> Fusuma::Plugin::Buffers::GestureBuffer
        def select_by_type(type)
          cache_entry = (@cache_select_by[type] ||= CacheEntry.new(0, self.class.new([])))
          cache_entry.checked.upto(@events.length - 1).each do |i|
            (cache_entry.value.events << @events[i]) if @events[i].record.gesture == type
          end
          cache_entry.checked = @events.length
          cache_entry.value
        end

        #: () -> Fusuma::Plugin::Buffers::GestureBuffer
        def select_from_last_begin
          return self if empty?
          cache_entry = (@cache[:last_begin] ||= CacheEntry.new(0, nil))

          cache_entry.value = (@events.length - 1).downto(cache_entry.checked).find do |i|
            @events[i].record.status == "begin"
          end || cache_entry.value
          cache_entry.checked = @events.length

          return self if cache_entry.value == 0
          return GestureBuffer.new([]) if cache_entry.value.nil?

          GestureBuffer.new(@events[cache_entry.value..-1])
        end
      end
    end
  end
end
