/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2011-2022 - Daniel De Matteis
 *  Copyright (C) 2019-2022 - James Leaver
 *
 *  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/>.
 */

#include <stddef.h>
#include <compat/strcasestr.h>
#include <compat/strl.h>
#include <array/rhmap.h>
#include <file/file_path.h>
#include <string/stdstring.h>

#include "menu_driver.h"
#include "menu_displaylist.h"
#include "../file_path_special.h"
#include "../retroarch.h"
#include "../core_info.h"
#include "../configuration.h"

#define CONTENTLESS_CORE_ICON_DEFAULT "default.png"

typedef struct
{
   uintptr_t **system;
   uintptr_t fallback;
} contentless_core_icons_t;

typedef struct
{
   contentless_core_info_entry_t **info_entries;
   contentless_core_icons_t *icons;
   bool icons_enabled;
} contentless_cores_state_t;

static contentless_cores_state_t *contentless_cores_state = NULL;

static void contentless_cores_free_runtime_info(
      contentless_core_runtime_info_t *runtime_info)
{
   if (!runtime_info)
      return;

   if (runtime_info->runtime_str)
   {
      free(runtime_info->runtime_str);
      runtime_info->runtime_str = NULL;
   }

   if (runtime_info->last_played_str)
   {
      free(runtime_info->last_played_str);
      runtime_info->last_played_str = NULL;
   }

   runtime_info->status = CONTENTLESS_CORE_RUNTIME_UNKNOWN;
}

static void contentless_cores_free_info_entries(
      contentless_cores_state_t *state)
{
   size_t i, cap;

   if (!state || !state->info_entries)
      return;

   for (i = 0, cap = RHMAP_CAP(state->info_entries); i != cap; i++)
   {
      if (RHMAP_KEY(state->info_entries, i))
      {
         contentless_core_info_entry_t *entry = state->info_entries[i];

         if (!entry)
            continue;

         if (entry->licenses_str)
            free(entry->licenses_str);
         entry->licenses_str = NULL;

         contentless_cores_free_runtime_info(&entry->runtime);

         free(entry);
      }
   }

   RHMAP_FREE(state->info_entries);
}

static void contentless_cores_init_info_entries(
      contentless_cores_state_t *state)
{
   core_info_list_t *core_info_list = NULL;
   size_t i;

   if (!state)
      return;

   /* Free any existing entries */
   contentless_cores_free_info_entries(state);

   /* Create an entry for each contentless core */
   core_info_get_list(&core_info_list);

   if (!core_info_list)
      return;

   for (i = 0; i < core_info_list->count; i++)
   {
      core_info_t *core_info = core_info_get(core_info_list, i);

      if (   core_info
          && core_info->supports_no_game)
      {
         char licenses_str[MENU_SUBLABEL_MAX_LENGTH];
         contentless_core_info_entry_t *entry =
               (contentless_core_info_entry_t*)malloc(sizeof(*entry));
         size_t _len          = strlcpy(licenses_str,
               msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_LICENSES),
               sizeof(licenses_str));
         licenses_str[  _len] = ':';
         licenses_str[++_len] = ' ';
         licenses_str[++_len] = '\0';

         /* Populate licences string */
         if (core_info->licenses_list)
         {
            char tmp_str[MENU_SUBLABEL_MAX_LENGTH - 2];
            tmp_str[0] = '\0';
            string_list_join_concat(tmp_str, sizeof(tmp_str),
                  core_info->licenses_list, ", ");
            strlcpy(licenses_str       + _len, tmp_str,
                  sizeof(licenses_str) - _len);
         }
         /* No license found - set to N/A */
         else
            strlcpy(licenses_str       + _len,
                  msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE),
                  sizeof(licenses_str) - _len);

         entry->licenses_str            = strdup(licenses_str);

         /* Initialise runtime info */
         entry->runtime.runtime_str     = NULL;
         entry->runtime.last_played_str = NULL;
         entry->runtime.status          = CONTENTLESS_CORE_RUNTIME_UNKNOWN;

         /* Add entry to hash map */
         RHMAP_SET_STR(state->info_entries, core_info->core_file_id.str, entry);
      }
   }
}

void menu_contentless_cores_set_runtime(const char *core_id,
      const contentless_core_runtime_info_t *runtime_info)
{
   contentless_core_info_entry_t *info_entry = NULL;

   if (   !contentless_cores_state
       || !contentless_cores_state->info_entries
       || !runtime_info 
       || string_is_empty(core_id))
      return;

   info_entry = RHMAP_GET_STR(contentless_cores_state->info_entries, core_id);

   if (!info_entry)
      return;

   if (!string_is_empty(runtime_info->runtime_str))
   {
      if (info_entry->runtime.runtime_str)
         free(info_entry->runtime.runtime_str);

      info_entry->runtime.runtime_str = strdup(runtime_info->runtime_str);
   }

   if (!string_is_empty(runtime_info->last_played_str))
   {
      if (info_entry->runtime.last_played_str)
         free(info_entry->runtime.last_played_str);

      info_entry->runtime.last_played_str = strdup(runtime_info->last_played_str);
   }

   info_entry->runtime.status = runtime_info->status;
}

void menu_contentless_cores_get_info(const char *core_id,
      const contentless_core_info_entry_t **info)
{
   if (!info)
      return;

   if (   !contentless_cores_state
       || !contentless_cores_state->info_entries
       || string_is_empty(core_id))
      *info = NULL;

   *info = RHMAP_GET_STR(contentless_cores_state->info_entries, core_id);
}

void menu_contentless_cores_flush_runtime(void)
{
   contentless_cores_state_t *state = contentless_cores_state;
   size_t i, cap;

   if (!state || !state->info_entries)
      return;

   for (i = 0, cap = RHMAP_CAP(state->info_entries); i != cap; i++)
   {
      if (RHMAP_KEY(state->info_entries, i))
      {
         contentless_core_info_entry_t *entry = state->info_entries[i];

         if (!entry)
            continue;

         contentless_cores_free_runtime_info(&entry->runtime);
      }
   }
}

static void contentless_cores_unload_icons(contentless_cores_state_t *state)
{
   size_t i, cap;

   if (!state || !state->icons)
      return;

   if (state->icons->fallback)
      video_driver_texture_unload(&state->icons->fallback);

   for (i = 0, cap = RHMAP_CAP(state->icons->system); i != cap; i++)
   {
      if (RHMAP_KEY(state->icons->system, i))
      {
         uintptr_t *icon = state->icons->system[i];

         if (!icon)
            continue;

         video_driver_texture_unload(icon);
         free(icon);
      }
   }

   RHMAP_FREE(state->icons->system);
   free(state->icons);
   state->icons = NULL;
}

static void contentless_cores_load_icons(contentless_cores_state_t *state)
{
   size_t i;
   char icon_path[PATH_MAX_LENGTH];
   char icon_directory[PATH_MAX_LENGTH];
   bool rgba_supported              = video_driver_supports_rgba();
   core_info_list_t *core_info_list = NULL;

   if (!state)
      return;

   /* Unload any existing icons */
   contentless_cores_unload_icons(state);

   if (!state->icons_enabled)
      return;

   /* Create new icon container */
   state->icons = (contentless_core_icons_t*)calloc(
         1, sizeof(*state->icons));

   /* Get icon directory */
   fill_pathname_application_special(icon_directory,
         sizeof(icon_directory),
         APPLICATION_SPECIAL_DIRECTORY_ASSETS_SYSICONS);

   if (string_is_empty(icon_directory))
      return;

   /* Load fallback icon */
   fill_pathname_join_special(icon_path, icon_directory,
         CONTENTLESS_CORE_ICON_DEFAULT, sizeof(icon_path));

   if (path_is_valid(icon_path))
   {
      struct texture_image ti;
      ti.pixels               = NULL;
      ti.width                = 0;
      ti.height               = 0;
      ti.supports_rgba        = rgba_supported;

      if (image_texture_load(&ti, icon_path))
      {
         if (ti.pixels)
            video_driver_texture_load(&ti,
                  TEXTURE_FILTER_MIPMAP_LINEAR,
                  &state->icons->fallback);

         image_texture_free(&ti);
      }
   }

   /* Get icons for all contentless cores */
   core_info_get_list(&core_info_list);

   if (!core_info_list)
      return;

   for (i = 0; i < core_info_list->count; i++)
   {
      core_info_t *core_info = core_info_get(core_info_list, i);

      /* Icon name is the first entry in the core
       * info database list */
      if (    core_info
          &&  core_info->supports_no_game
          &&  core_info->databases_list
          && (core_info->databases_list->size > 0))
      {
         struct texture_image ti;
         const char *icon_name   =
               core_info->databases_list->elems[0].data;
         size_t len              = fill_pathname_join_special(
               icon_path, icon_directory,
               icon_name, sizeof(icon_path));
         icon_path[  len]        = '.';
         icon_path[++len]        = 'p';
         icon_path[++len]        = 'n';
         icon_path[++len]        = 'g';
         icon_path[++len]        = '\0';

         ti.pixels               = NULL;
         ti.width                = 0;
         ti.height               = 0;
         ti.supports_rgba        = rgba_supported;

         if (!path_is_valid(icon_path))
            continue;

         if (image_texture_load(&ti, icon_path))
         {
            if (ti.pixels)
            {
               uintptr_t *icon = (uintptr_t*)calloc(1, sizeof(*icon));

               video_driver_texture_load(&ti,
                     TEXTURE_FILTER_MIPMAP_LINEAR,
                     icon);

               /* Add icon to hash map */
               RHMAP_SET_STR(state->icons->system, core_info->core_file_id.str, icon);
            }

            image_texture_free(&ti);
         }
      }
   }
}

uintptr_t menu_contentless_cores_get_entry_icon(const char *core_id)
{
   contentless_cores_state_t *state = contentless_cores_state;
   uintptr_t *icon                  = NULL;
   if (   !state
       || !state->icons_enabled
       || !state->icons
       || string_is_empty(core_id))
      return 0;
   if ((icon = RHMAP_GET_STR(state->icons->system, core_id)))
      return *icon;
   return state->icons->fallback;
}

void menu_contentless_cores_context_init(void)
{
   if (contentless_cores_state)
      contentless_cores_load_icons(contentless_cores_state);
}

void menu_contentless_cores_context_deinit(void)
{
   if (contentless_cores_state)
      contentless_cores_unload_icons(contentless_cores_state);
}

void menu_contentless_cores_free(void)
{
   if (!contentless_cores_state)
      return;

   contentless_cores_free_info_entries(contentless_cores_state);
   contentless_cores_unload_icons(contentless_cores_state);
   free(contentless_cores_state);
   contentless_cores_state = NULL;
}

unsigned menu_displaylist_contentless_cores(file_list_t *list, settings_t *settings)
{
   unsigned count                   = 0;
   enum menu_contentless_cores_display_type
         core_display_type          = (enum menu_contentless_cores_display_type)
               settings->uints.menu_content_show_contentless_cores;
   core_info_list_t *core_info_list = NULL;

   /* Get core list */
   core_info_get_list(&core_info_list);

   if (core_info_list)
   {
      size_t i;
      size_t menu_index = 0;

      /* Sort cores alphabetically */
      core_info_qsort(core_info_list, CORE_INFO_LIST_SORT_DISPLAY_NAME);

      /* Loop through cores */
      for (i = 0; i < core_info_list->count; i++)
      {
         core_info_t *core_info = core_info_get(core_info_list, i);

         if (core_info)
         {
            switch (core_display_type)
            {
               case MENU_CONTENTLESS_CORES_DISPLAY_ALL:
                  if (!(      core_info->supports_no_game))
                     continue;
                  break;
               case MENU_CONTENTLESS_CORES_DISPLAY_SINGLE_PURPOSE:
                  if (!(      core_info->supports_no_game
                           && core_info->single_purpose))
                     continue;
                  break;
               case MENU_CONTENTLESS_CORES_DISPLAY_CUSTOM:
                  if (!(       core_info->supports_no_game
                           && !core_info->is_standalone_exempt))
                     continue;
                  break;
               default:
                  break;
            }

            /* Valid core if we have reached here */
            if (menu_entries_append(list,
                     core_info->path,
                     core_info->core_file_id.str,
                     MENU_ENUM_LABEL_CONTENTLESS_CORE,
                     MENU_SETTING_ACTION_CONTENTLESS_CORE_RUN,
                     0, 0, NULL))
            {
               file_list_set_alt_at_offset(
                     list, menu_index, core_info->display_name);

               menu_index++;
               count++;
            }
         }
      }
   }

   /* Initialise global state, if required */
   if (!contentless_cores_state && (count > 0))
   {
      contentless_cores_state = (contentless_cores_state_t*)calloc(1,
            sizeof(*contentless_cores_state));

      /* Disable icons when using menu drivers without
       * icon support */
      contentless_cores_state->icons_enabled =
            !string_is_equal(menu_driver_ident(), "rgui");

      contentless_cores_init_info_entries(contentless_cores_state);
      contentless_cores_load_icons(contentless_cores_state);
   }

   if (  (count == 0)
       && menu_entries_append(list,
            msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_CORES_AVAILABLE),
            msg_hash_to_str(MENU_ENUM_LABEL_NO_CORES_AVAILABLE),
            MENU_ENUM_LABEL_NO_CORES_AVAILABLE,
            0, 0, 0, NULL))
      count++;

   return count;
}
