/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.android.accessibility.talkback.eventprocessor;

import android.os.Message;
import android.view.accessibility.AccessibilityEvent;
import com.google.android.accessibility.compositor.Compositor;
import com.google.android.accessibility.compositor.EventFilter;
import com.google.android.accessibility.utils.AccessibilityEventListener;
import com.google.android.accessibility.utils.Performance;
import com.google.android.accessibility.utils.Performance.EventId;
import com.google.android.accessibility.utils.WeakReferenceHandler;
import com.google.android.libraries.accessibility.utils.log.LogUtils;

/**
 * Manages the event feedback queue. Queued events are run through the {@link Compositor} to
 * generate spoken, haptic, and audible feedback.
 */
public class ProcessorEventQueue implements AccessibilityEventListener {

  private static final String TAG = "ProcessorEventQueue";

  /** Manages pending speech events. */
  private final ProcessorEventHandler handler = new ProcessorEventHandler(this);

  /** Event types that are handled by ProcessorEventQueue. */
  private static final int MASK_EVENTS_HANDLED_BY_PROCESSOR_EVENT_QUEUE =
      AccessibilityEvent.TYPES_ALL_MASK;

  /**
   * We keep the accessibility events to be processed. If a received event is the same type as the
   * previous one it replaces the latter, otherwise it is added to the queue. All events in this
   * queue are processed while we speak and this occurs after a certain timeout since the last
   * received event.
   */
  private final EventQueue eventQueue = new EventQueue();

  private EventFilter eventFilter;

  public ProcessorEventQueue(EventFilter eventFilter) {
    this.eventFilter = eventFilter;
  }

  @Override
  public int getEventTypes() {
    return MASK_EVENTS_HANDLED_BY_PROCESSOR_EVENT_QUEUE;
  }

  @Override
  public void onAccessibilityEvent(AccessibilityEvent event, EventId eventId) {

    synchronized (eventQueue) {
      eventQueue.enqueue(event);
      handler.postSpeak();
    }
  }

  public void clearQueue() {
    handler.removeMessages(WHAT_SPEAK);
  }

  /**
   * Processes an <code>event</code> by asking the {@link Compositor} to match it against its rules
   * and in case an utterance is generated it is spoken. This method is responsible for recycling of
   * the processed event.
   *
   * @param event The event to process.
   */
  private void processAndRecycleEvent(AccessibilityEvent event, EventId eventId) {
    if (event == null) {
      return;
    }
    LogUtils.d(TAG, "Processing event: %s", event);

    eventFilter.sendEvent(event, eventId);

    event.recycle();
  }

  private static final int WHAT_SPEAK = 1;

  private static class ProcessorEventHandler extends WeakReferenceHandler<ProcessorEventQueue> {
    /** Speak action. */

    public ProcessorEventHandler(ProcessorEventQueue parent) {
      super(parent);
    }

    @Override
    public void handleMessage(Message message, ProcessorEventQueue parent) {
      switch (message.what) {
        case WHAT_SPEAK:
          processAllEvents(parent);
          break;
        default: // fall out
      }
    }

    /** Attempts to process all events in the queue. */
    private void processAllEvents(ProcessorEventQueue parent) {
      while (true) {
        final AccessibilityEvent event;

        synchronized (parent.eventQueue) {
          if (parent.eventQueue.isEmpty()) {
            return;
          }

          event = parent.eventQueue.dequeue();
        }

        // Re-generate event id -- slower than passing event id, but avoids modifying
        // the EventQueue to hold event ids.
        EventId eventId = Performance.getInstance().toEventId(event);

        parent.processAndRecycleEvent(event, eventId);
      }
    }

    /**
     * Sends {@link #WHAT_SPEAK} to the speech handler. This method cancels the old message (if such
     * exists) since it is no longer relevant.
     */
    public void postSpeak() {
      if (!hasMessages(WHAT_SPEAK)) {
        sendEmptyMessage(WHAT_SPEAK);
      }
    }
  }
}