/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2010-2014 - Hans-Kristian Arntzen
 *  Copyright (C) 2011-2021 - Daniel De Matteis
 *
 *  RetroArch is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with RetroArch.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef __RUNLOOP_H
#define __RUNLOOP_H

#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>

#include <boolean.h>
#include <retro_inline.h>
#include <retro_common_api.h>
#include <libretro.h>
#include <dynamic/dylib.h>
#include <queues/message_queue.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_THREADS
#include <rthreads/rthreads.h>
#endif

#include "dynamic.h"
#include "configuration.h"
#include "core_option_manager.h"
#include "performance_counters.h"
#include "state_manager.h"
#include "tasks/tasks_internal.h"

/* Arbitrary twenty subsystems limit */
#define SUBSYSTEM_MAX_SUBSYSTEMS 20

/* Arbitrary 10 roms for each subsystem limit */
#define SUBSYSTEM_MAX_SUBSYSTEM_ROMS 10

#ifdef HAVE_THREADS
#define RUNLOOP_MSG_QUEUE_LOCK(runloop_st) slock_lock((runloop_st)->msg_queue_lock)
#define RUNLOOP_MSG_QUEUE_UNLOCK(runloop_st) slock_unlock((runloop_st)->msg_queue_lock)
#else
#define RUNLOOP_MSG_QUEUE_LOCK(runloop_st) (void)(runloop_st)
#define RUNLOOP_MSG_QUEUE_UNLOCK(runloop_st) (void)(runloop_st)
#endif

enum  runloop_state_enum
{
   RUNLOOP_STATE_ITERATE = 0,
   RUNLOOP_STATE_POLLED_AND_SLEEP,
   RUNLOOP_STATE_MENU_ITERATE,
   RUNLOOP_STATE_END,
   RUNLOOP_STATE_QUIT
};

enum poll_type_override_t
{
   POLL_TYPE_OVERRIDE_DONTCARE = 0,
   POLL_TYPE_OVERRIDE_EARLY,
   POLL_TYPE_OVERRIDE_NORMAL,
   POLL_TYPE_OVERRIDE_LATE
};


typedef struct runloop_ctx_msg_info
{
   const char *msg;
   unsigned prio;
   unsigned duration;
   bool flush;
} runloop_ctx_msg_info_t;

/* Contains the current retro_fastforwarding_override
 * parameters along with any pending updates triggered
 * by RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE */
typedef struct fastmotion_overrides
{
   struct retro_fastforwarding_override current;
   struct retro_fastforwarding_override next;
   bool pending;
} fastmotion_overrides_t;

typedef struct
{
   unsigned priority;
   float duration;
   char str[128];
   bool set;
} runloop_core_status_msg_t;

/* Contains all callbacks associated with
 * core options.
 * > At present there is only a single
 *   callback, 'update_display' - but we
 *   may wish to add more in the future
 *   (e.g. for directly informing a core of
 *   core option value changes, or getting/
 *   setting extended/non-standard option
 *   value data types) */
typedef struct core_options_callbacks
{
   retro_core_options_update_display_callback_t update_display;
} core_options_callbacks_t;

#ifdef HAVE_RUNAHEAD
typedef bool(*runahead_load_state_function)(const void*, size_t);

typedef void *(*constructor_t)(void);
typedef void  (*destructor_t )(void*);

typedef struct my_list_t
{
   void **data;
   constructor_t constructor;
   destructor_t destructor;
   int capacity;
   int size;
} my_list;
#endif

struct runloop
{
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
   rarch_timer_t shader_delay_timer;            /* int64_t alignment */
#endif
   retro_time_t core_runtime_last;
   retro_time_t core_runtime_usec;
   retro_time_t frame_limit_minimum_time;
   retro_time_t frame_limit_last_time;
   retro_usec_t frame_time_last;                /* int64_t alignment */

   struct retro_core_t        current_core;     /* uint64_t alignment */
#if defined(HAVE_RUNAHEAD)
   uint64_t runahead_last_frame_count;          /* uint64_t alignment */
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
   struct retro_core_t secondary_core;          /* uint64_t alignment */
#endif
   retro_ctx_load_content_info_t *load_content_info;
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
   char    *secondary_library_path;
#endif
   my_list *runahead_save_state_list;
   my_list *input_state_list;
#endif

#ifdef HAVE_REWIND
   struct state_manager_rewind_state rewind_st;
#endif
   struct retro_perf_counter *perf_counters_libretro[MAX_COUNTERS];
   bool    *load_no_content_hook;
   struct string_list *subsystem_fullpaths;
   struct retro_subsystem_info subsystem_data[SUBSYSTEM_MAX_SUBSYSTEMS];
   struct retro_callbacks retro_ctx;                     /* ptr alignment */
   msg_queue_t msg_queue;                                /* ptr alignment */
   retro_input_state_t input_state_callback_original;    /* ptr alignment */
#ifdef HAVE_RUNAHEAD
   function_t retro_reset_callback_original;             /* ptr alignment */
   function_t original_retro_deinit;                     /* ptr alignment */
   function_t original_retro_unload;                     /* ptr alignment */
   runahead_load_state_function
      retro_unserialize_callback_original;               /* ptr alignment */
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
   struct retro_callbacks secondary_callbacks;           /* ptr alignment */
#endif
#endif
#ifdef HAVE_THREADS
   slock_t *msg_queue_lock;
#endif

   content_state_t            content_st;                /* ptr alignment */
   struct retro_subsystem_rom_info
      subsystem_data_roms[SUBSYSTEM_MAX_SUBSYSTEMS]
      [SUBSYSTEM_MAX_SUBSYSTEM_ROMS];             /* ptr alignment */
   core_option_manager_t *core_options;
   core_options_callbacks_t core_options_callback;/* ptr alignment */

   retro_keyboard_event_t key_event;             /* ptr alignment */
   retro_keyboard_event_t frontend_key_event;    /* ptr alignment */

   rarch_system_info_t system;                   /* ptr alignment */
   struct retro_frame_time_callback frame_time;  /* ptr alignment */
   struct retro_audio_buffer_status_callback audio_buffer_status; /* ptr alignment */
#ifdef HAVE_DYNAMIC
   dylib_t lib_handle;                                   /* ptr alignment */
#endif
#if defined(HAVE_RUNAHEAD)
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
   dylib_t secondary_lib_handle;                         /* ptr alignment */
#endif
   size_t runahead_save_state_size;
#endif
   size_t msg_queue_size;

#if defined(HAVE_RUNAHEAD)
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
   int port_map[MAX_USERS];
#endif
#endif

   runloop_core_status_msg_t core_status_msg;

   unsigned pending_windowed_scale;
   unsigned max_frames;
   unsigned audio_latency;
   unsigned fastforward_after_frames;
   unsigned perf_ptr_libretro;
   unsigned subsystem_current_count;
   unsigned entry_state_slot;
   unsigned video_swap_interval_auto;

   fastmotion_overrides_t fastmotion_override; /* float alignment */

   retro_bits_t has_set_libretro_device;        /* uint32_t alignment */

   enum rarch_core_type current_core_type;
   enum rarch_core_type explicit_current_core_type;
   enum poll_type_override_t core_poll_type_override;
#if defined(HAVE_RUNAHEAD)
   enum rarch_core_type last_core_type;
#endif

   char runtime_content_path_basename[8192];
   char current_library_name[256];
   char current_library_version[256];
   char current_valid_extensions[256];
   char subsystem_path[256];
#ifdef HAVE_SCREENSHOTS
   char max_frames_screenshot_path[PATH_MAX_LENGTH];
#endif
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
   char runtime_shader_preset_path[PATH_MAX_LENGTH];
#endif
   char runtime_content_path[PATH_MAX_LENGTH];
   char runtime_core_path[PATH_MAX_LENGTH];
   char savefile_dir[PATH_MAX_LENGTH];
   char savestate_dir[PATH_MAX_LENGTH];

   struct
   {
      char *remapfile;
      char savefile[8192];
      char savestate[8192];
      char cheatfile[8192];
      char ups[8192];
      char bps[8192];
      char ips[8192];
      char label[8192];
   } name;

   bool is_inited;
   bool missing_bios;
   bool force_nonblock;
   bool paused;
   bool idle;
   bool focused;
   bool slowmotion;
   bool fastmotion;
   bool shutdown_initiated;
   bool core_shutdown_initiated;
   bool core_running;
   bool perfcnt_enable;
   bool game_options_active;
   bool folder_options_active;
   bool autosave;
#ifdef HAVE_CONFIGFILE
   bool overrides_active;
#endif
   bool remaps_core_active;
   bool remaps_game_active;
   bool remaps_content_dir_active;
#ifdef HAVE_SCREENSHOTS
   bool max_frames_screenshot;
#endif
#ifdef HAVE_RUNAHEAD
   bool has_variable_update;
   bool input_is_dirty;
   bool runahead_save_state_size_known;
   bool request_fast_savestate;
   bool runahead_available;
   bool runahead_secondary_core_available;
   bool runahead_force_input_dirty;
#endif
#ifdef HAVE_PATCH
   bool patch_blocked;
#endif
   bool is_sram_load_disabled;
   bool is_sram_save_disabled;
   bool use_sram;
   bool ignore_environment_cb;
   bool core_set_shared_context;
   bool has_set_core;
};

typedef struct runloop runloop_state_t;

#ifdef HAVE_BSV_MOVIE
#define BSV_MOVIE_IS_EOF() || (input_st->bsv_movie_state.movie_end && \
input_st->bsv_movie_state.eof_exit)
#else
#define BSV_MOVIE_IS_EOF()
#endif

/* Time to exit out of the main loop?
 * Reasons for exiting:
 * a) Shutdown environment callback was invoked.
 * b) Quit key was pressed.
 * c) Frame count exceeds or equals maximum amount of frames to run.
 * d) Video driver no longer alive.
 * e) End of BSV movie and BSV EOF exit is true. (TODO/FIXME - explain better)
 */
#define RUNLOOP_TIME_TO_EXIT(quit_key_pressed) (runloop_state.shutdown_initiated || quit_key_pressed || !is_alive BSV_MOVIE_IS_EOF() || ((runloop_state.max_frames != 0) && (frame_count >= runloop_state.max_frames)) || runloop_exec)

RETRO_BEGIN_DECLS

void runloop_path_fill_names(void);

/**
 * runloop_environment_cb:
 * @cmd                          : Identifier of command.
 * @data                         : Pointer to data.
 *
 * Environment callback function implementation.
 *
 * Returns: true (1) if environment callback command could
 * be performed, otherwise false (0).
 **/
bool runloop_environment_cb(unsigned cmd, void *data);

void runloop_msg_queue_push(const char *msg,
      unsigned prio, unsigned duration,
      bool flush,
      char *title,
      enum message_queue_icon icon,
      enum message_queue_category category);

void runloop_set_current_core_type(
      enum rarch_core_type type, bool explicitly_set);

/**
 * runloop_iterate:
 *
 * Run Libretro core in RetroArch for one frame.
 *
 * Returns: 0 on successful run,
 * Returns 1 if we have to wait until button input in order
 * to wake up the loop.
 * Returns -1 if we forcibly quit out of the
 * RetroArch iteration loop.
 **/
int runloop_iterate(void);

void runloop_perf_log(void);

void runloop_system_info_free(void);

bool runloop_path_init_subsystem(void);

/**
 * libretro_get_system_info:
 * @path                         : Path to libretro library.
 * @info                         : Pointer to system info information.
 * @load_no_content              : If true, core should be able to auto-start
 *                                 without any content loaded.
 *
 * Gets system info from an arbitrary lib.
 * The struct returned must be freed as strings are allocated dynamically.
 *
 * Returns: true (1) if successful, otherwise false (0).
 **/
bool libretro_get_system_info(
      const char *path,
      struct retro_system_info *info,
      bool *load_no_content);

void runloop_performance_counter_register(
		struct retro_perf_counter *perf);

void runloop_runtime_log_deinit(
      runloop_state_t *runloop_st,
      bool content_runtime_log,
      bool content_runtime_log_aggregate,
      const char *dir_runtime_log,
      const char *dir_playlist);

void runloop_event_deinit_core(void);

#ifdef HAVE_RUNAHEAD
void runloop_runahead_clear_variables(runloop_state_t *runloop_st);
#endif

bool runloop_event_init_core(
      settings_t *settings,
      void *input_data,
      enum rarch_core_type type);

void runloop_pause_checks(void);

float runloop_set_frame_limit(
      const struct retro_system_av_info *av_info,
      float fastforward_ratio);

float runloop_get_fastforward_ratio(
      settings_t *settings,
      struct retro_fastforwarding_override *fastmotion_override);

void runloop_set_video_swap_interval(
      bool vrr_runloop_enable,
      bool crt_switching_active,
      unsigned swap_interval_config,
      float audio_max_timing_skew,
      float video_refresh_rate,
      double input_fps);
unsigned runloop_get_video_swap_interval(
      unsigned swap_interval_config);

void runloop_task_msg_queue_push(
      retro_task_t *task, const char *msg,
      unsigned prio, unsigned duration,
      bool flush);

void runloop_frame_time_free(void);

void runloop_fastmotion_override_free(void);

void runloop_audio_buffer_status_free(void);

bool secondary_core_ensure_exists(settings_t *settings);

void runloop_core_options_cb_free(void);

void runloop_log_counters(
      struct retro_perf_counter **counters, unsigned num);

void runloop_secondary_core_destroy(void);

void runloop_msg_queue_deinit(void);

void runloop_msg_queue_init(void);

void runloop_path_init_savefile(void);

void runloop_path_set_basename(const char *path);

void runloop_path_init_savefile(void);

void runloop_path_set_names(void);

runloop_state_t *runloop_state_get_ptr(void);

RETRO_END_DECLS

#endif