package com.example.fitbot.pepper;

import com.aldebaran.qi.sdk.QiContext;
import com.aldebaran.qi.sdk.RobotLifecycleCallbacks;
import com.aldebaran.qi.sdk.object.locale.Language;
import com.aldebaran.qi.sdk.object.locale.Locale;
import com.aldebaran.qi.sdk.object.locale.Region;

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Class representing the Pepper robot.
 * All functionality related to Pepper is implemented in this class.
 * <p>
 * Before any of the functions in this class can be used, the Pepper class needs a QiContext to be
 * set. This is retrieved in the `onRobotFocusGained` method of the RobotLifecycleCallbacks
 * interface, and can then provided using the `provideContext` method.
 * </p>
 */
public class Pepper {

    // Queue containing all Pepper actions that need to be executed
    // This is to prevent the app from crashing when attempting to perform multiple actions at once
    private static final ConcurrentLinkedQueue<AbstractPepperActionEvent> pepperActionEventQueue =
            new ConcurrentLinkedQueue<>();

    private static final AtomicBoolean isAnimating = new AtomicBoolean(false);
    private static final AtomicBoolean isSpeaking = new AtomicBoolean(false);

    private static final Locale DEFAULT_LOCALE = new Locale(Language.DUTCH, Region.NETHERLANDS);

    /**
     * The latest QiContext used by Pepper.
     * This can be publicly accessed to get the latest QiContext used by Pepper.
     */
    public static QiContext latestContext = null;

    /**
     * Make the Pepper robot speak a phrase.
     * This function adds a SpeechEvent to the event queue,
     * and will speak the phrase once Pepper is ready to process it.
     *
     * @param phrase The phrase to speak
     */
    public static void say(String phrase) {
        addToEventQueue(new PepperSpeechEvent(phrase, DEFAULT_LOCALE));
    }

    /**
     * Make the Pepper robot perform an animation.
     * This function adds an AnimationEvent to the event queue,
     * and will perform the animation once Pepper is ready to process it.
     *
     * @param animationName The name of the animation to play
     */
    public static void animate(String animationName) {
        addToEventQueue(new PepperAnimationEvent(animationName));
    }

    /**
     * Add an event to the event queue.
     * The event will be executed once Pepper is ready to process it.
     *
     * @param event The event to add to the queue
     */
    public static void addToEventQueue(AbstractPepperActionEvent event) {
        pepperActionEventQueue.add(event);
        processEventQueue();
    }

    /**
     * Process the event queue.
     * This method will process the next event in the queue if Pepper is not currently
     * speaking or animating.
     * This prevents multiple actions from being executed at the same time, which can
     * cause the application to crash.
     */
    private static synchronized void processEventQueue() {
        if (!pepperActionEventQueue.isEmpty()) {
            AbstractPepperActionEvent event = pepperActionEventQueue.poll();

            // Prevent null pointer exceptions
            if (event == null || latestContext == null)
                return;

            switch (event.getAction()) {
                /* Event for speaking a phrase **/
                case ACTION_SPEAK:
                    if (!(event instanceof PepperSpeechEvent) || isSpeaking.get())
                        break;

                    PepperSpeechEvent speechEvent = (PepperSpeechEvent) event;
                    isSpeaking.set(true);
                    speechEvent
                            .getSay(latestContext)
                            .async()
                            .run()
                            .andThenConsume(future -> {
                                isSpeaking.set(false);
                                processEventQueue();
                            });
                    break;

                /* Event for animating the robot **/
                case ACTION_ANIMATE:
                    if (!(event instanceof PepperAnimationEvent) || isAnimating.get())
                        break;

                    PepperAnimationEvent animationEvent = (PepperAnimationEvent) event;
                    animationEvent
                            .getAnimation(latestContext)
                            .async()
                            .run()
                            .andThenConsume(future -> {
                                isAnimating.set(false);
                                processEventQueue();
                            });
                    break;
                default:
                    // Do nothing
                    break;
            }
        }
    }

    /**
     * Function for providing the latest QiContext to Pepper.
     *
     * @param context The QiContext to use.
     *                This can be null if the context is not available.
     * @param origin  The origin of the context.
     *                This parameter is to prevent other classes from
     *                unnecessarily providing a context, or setting
     *                the context to null.
     */
    public static void provideContext(QiContext context, Class<? extends RobotLifecycleCallbacks> origin) {
        latestContext = context;
        if (context != null) {
            processEventQueue();
        }
    }

}
