/*
 *   SDL_loop.c - Pluggable main loop and Thinwire based on SDL.
 *   Copyright 2010-2017 Citrix Systems, Inc. All rights reserved.
 *
 *   $Id$
 */

#include "citrix.h"

#include <unistd.h>
#include <fcntl.h>
#include <time.h>

#include <SDL/SDL.h>

#include "mainloop.h"
#include "CTX_SDL_internal.h"
#include "SDL_loop.h"
#include "mlooplog.h"
#include "epoll_simple.h"

/* The main loop plugin connector. */

struct mainloop_plugin Plug;

/* Local constants specific to SDL. */

#define NO_TIMER      0   /* Invalid SDL_TimerID  value. */

/* Local variables specific to SDL. */

static struct sdl {
    SDL_TimerID         timer;
    SDL_Joystick       *joy;
} Sdl;

/* Callback function for SDL_AddTimer().
 * The SDL documentation warns that this may be called in another thread,
 * so it just queues a user event and stops the timer.
 */

static Uint32 do_timer(Uint32 interval, void* param)
{
    static SDL_UserEvent event = {SDL_USEREVENT, RUN_TIMER, NULL, NULL};

    event.data1 = param;	/* Copy callback address. */
    if (!SDL_PushEvent((union SDL_Event *)&event))
        return 10; /* Event queue full?  Retry in 10mS. */
    Sdl.timer = NO_TIMER;
    return 0; /* Kills the timer. */
}

/* Plugin function to start a single one-shot timer.
 */

static void start_timer(UINT32 interval, UINT32 (*timer_done)(void))
{
    Sdl.timer = SDL_AddTimer(interval, do_timer, timer_done);
}

/* The receive thread wakes the event loop by calling this. */

static void wake_main(void)
{
    static SDL_UserEvent event = {SDL_USEREVENT, WAKE, NULL, NULL};

    SDL_PushEvent((union SDL_Event *)&event);
}

/* Plugin function to run the event loop.  It never returns. */

static void run(void)
{
    SDL_Event event;

    /* Process the events */

    for (;;) {

        /* Wait for and process input events. */

        SDL_WaitEvent(NULL);
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
              case SDL_ACTIVEEVENT:
                MLoopTrace("%s mask %x\n",
                       event.active.gain ? "Focus" : "Lost focus",
                       event.active.state);
                break;

              case SDL_KEYDOWN:
              case SDL_KEYUP:
                handle_SDL_keyboard(&event.key);
                break;

              case SDL_MOUSEMOTION:
              case SDL_MOUSEBUTTONDOWN:
              case SDL_MOUSEBUTTONUP:
                handle_SDL_mouse(&event);
                break;

              case SDL_QUIT:
                exit(0);
                break;

              case SDL_USEREVENT:
                switch (event.user.code) {
                  case RUN_TIMER:
                    {
                        int wait; /* Milliseconds. */

                        /* There should be no active SDL timer when
                         * this is called: belt and braces.
                         */

                        if (Sdl.timer != NO_TIMER)
                            SDL_RemoveTimer(Sdl.timer); /* No use now! */

                        /* Process the pending PAL timers and
                         * start a new SDL timer to fire when
                         * the next PAL one is due.
                         * The processing function is passed in the event.
                         */

                        wait = (*(UINT32 (*)(void))event.user.data1)();
                        if (wait)
                            Sdl.timer = SDL_AddTimer(wait, do_timer,
                                                     event.user.data1);
                    }
                    break;
                  case WAKE:
                    /* A wake-up from the receive thread. */

		      if (Do_work()) {
			  // If there's still more to do, then send
			  // another event. The receive thread only
			  // seems to send one when going from empty
			  // to non-empty...
			  wake_main();
		      }
                    break;
                  case FD_READY:
                    /* File descriptor poll thread found something. */

                    Poller_make_call((int) event.user.data1,
                                     (int) event.user.data2);
                    break;
                  default:
                    break;
                }
                break;

              default:
                break;
            }
        }
    }
}

/* Shutdown function. */

static void close_mainloop(void)
{
    SDL_Quit();
}

void Plugin_init(struct mainloop_plugin *plug)
{
    /* Set up our entry points and interface data. */

    plug->major_version = KVME_MAJOR_VERSION;
    plug->minor_version = KVME_MINOR_VERSION;
    plug->plugin_flags = 0;   /* Nominally not X11, but works with it! */
    plug->create_thread = NULL;
    plug->get_screen_size = Get_max_screen_size;
    plug->start_video = SetupMainWindow;
    plug->start_timer = start_timer;
    plug->select_fd = SDL_select_fd;
    plug->run = run;
    plug->wake_main = wake_main;
    plug->close_mainloop = close_mainloop;
    plug->draw = SDL_draw_LVB;
    plug->show_dialogue = stub_dialogue_handler;

    /* Set up SDL mechanisms. */

    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_NOPARACHUTE);
    SDL_EnableUNICODE(1);
}
