/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2011-2017 - Higor Euripedes
 *  Copyright (C) 2011-2017 - Daniel De Matteis
 *  Copyright (C) 2014-2017 - Jean-André Santoni
 *
 *  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 <stdint.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>

#include <file/file_path.h>
#include <lists/dir_list.h>
#include <compat/posix_string.h>
#include <gfx/math/matrix_4x4.h>
#include <string/stdstring.h>
#include <formats/image.h>
#include <compat/strl.h>
#include <string/stdstring.h>

#include "menu_generic.h"

#include "../../config.def.h"

#include "../../list_special.h"
#include "../../file_path_special.h"

#include "../menu_driver.h"
#include "../menu_animation.h"
#include "../widgets/menu_entry.h"
#include "../../retroarch.h"

#include "../../gfx/font_driver.h"

#include "../../core_info.h"
#include "../../configuration.h"
#include "../../retroarch.h"
#include "../../verbosity.h"
#include "../../tasks/tasks_internal.h"

#define ZUI_FG_NORMAL         (~0)
#define ZUI_ITEM_SIZE_PX      54

enum zarch_zui_input_state
{
    MENU_ZARCH_MOUSE_X = 0,
    MENU_ZARCH_MOUSE_Y,
    MENU_POINTER_ZARCH_X,
    MENU_POINTER_ZARCH_Y,
    MENU_ZARCH_PRESSED
};

enum zarch_layout_type
{
   LAY_HOME = 0,
   LAY_PICK_CORE,
   LAY_SETTINGS
};

static const float zui_bg_panel[] = {
   0, 0, 0, 0.25,
   0, 0, 0, 0.25,
   0, 0, 0, 0.25,
   0, 0, 0, 0.25,
};

static const float zui_bg_screen[] = {
   0.07, 0.19, 0.26, 0.75,
   0.07, 0.19, 0.26, 0.75,
   0.15, 0.31, 0.47, 0.75,
   0.15, 0.31, 0.47, 0.75,
};

static const float zui_bg_hilite[] = {
   0.22, 0.60, 0.74, 1,
   0.22, 0.60, 0.74, 1,
   0.22, 0.60, 0.74, 1,
   0.22, 0.60, 0.74, 1,
};

static const float zui_bg_pad_hilite[] = {
   0.30, 0.76, 0.93, 1,
   0.30, 0.76, 0.93, 1,
   0.30, 0.76, 0.93, 1,
   0.30, 0.76, 0.93, 1,
};

typedef struct zarch_handle
{
   enum menu_action action;
   bool rendering;
   math_matrix_4x4 mvp;
   unsigned width;
   unsigned height;
   video_font_raster_block_t tmp_block;
   unsigned hash;
   uint64_t frame_count;

   struct {
      unsigned active;
      unsigned hot;
   } item;

   struct
   {
      int    wheel;
   } mouse;

   /* LAY_ROOT's "Load" file browser */
   struct string_list *load_dlist;
   char *load_cwd;
   int  load_dlist_first;

   struct
   {
      menu_texture_item bg;
   } textures;

   /* LAY_ROOT's "Recent" */

   int  recent_dlist_first;

   /* LAY_PICK_CORE */
   int pick_first;
   char pick_content[PATH_MAX_LENGTH];
   const core_info_t *pick_cores;
   size_t pick_supported;

   void *font;
   int font_size;
   int header_height;
   unsigned next_id;
   unsigned prev_id;
   bool     next_selection_set;
} zui_t;

struct zui_tabbed
{
   unsigned prev_id;
   unsigned active_id;
   unsigned next_id;
   int x, y;
   int width;
   int height; /* unused */
   int tabline_size;
   bool update_width;
   bool vertical;
   int tab_width;
   unsigned tab_selection;
   bool inited;
};


static enum zarch_layout_type zarch_layout = LAY_HOME;

static float zarch_zui_strwidth(void *fb_buf, const char *text, float scale)
{
   return font_driver_get_message_width(fb_buf, text, strlen(text), scale);
}

static int16_t zarch_zui_input_state(zui_t *zui, enum zarch_zui_input_state state)
{
   switch (state)
   {
      case MENU_ZARCH_MOUSE_X:
         return menu_input_mouse_state(MENU_MOUSE_X_AXIS);
      case MENU_ZARCH_MOUSE_Y:
         return menu_input_mouse_state(MENU_MOUSE_Y_AXIS);
      case MENU_POINTER_ZARCH_X:
         return menu_input_pointer_state(MENU_POINTER_X_AXIS);
      case MENU_POINTER_ZARCH_Y:
         return menu_input_pointer_state(MENU_POINTER_Y_AXIS);
      case MENU_ZARCH_PRESSED:
         if (     menu_input_mouse_state(MENU_MOUSE_LEFT_BUTTON)
               || menu_input_pointer_state(MENU_POINTER_PRESSED))
            return 1;
         if (zui->action == MENU_ACTION_OK)
            return 1;
         break;
   }

   return 0;
}

static bool zarch_zui_check_button_down(zui_t *zui,
      unsigned id, int x1, int y1, int x2, int y2)
{
   menu_input_ctx_hitbox_t hitbox;

   hitbox.x1   = x1;
   hitbox.x2   = x2;
   hitbox.y1   = y1;
   hitbox.y2   = y2;

   if (menu_input_mouse_check_vector_inside_hitbox(&hitbox))
      zui->item.hot = id;

   if (     zui->item.hot == id
         && zarch_zui_input_state(zui, MENU_ZARCH_PRESSED))
   {
      zui->item.active = id;
      return true;
   }

   return false;
}

static bool zarch_zui_check_button_up(zui_t *zui,
      unsigned id, int x1, int y1, int x2, int y2)
{
   menu_input_ctx_hitbox_t hitbox;
   bool result = false;

   hitbox.x1   = x1;
   hitbox.x2   = x2;
   hitbox.y1   = y1;
   hitbox.y2   = y2;

   if (menu_input_mouse_check_vector_inside_hitbox(&hitbox))
      zui->item.hot = id;

   if (     zui->item.active == id
         && !zarch_zui_input_state(zui, MENU_ZARCH_PRESSED))
   {
      if (zui->item.hot == id)
         result = true;

      zui->item.active = 0;
   }

   zarch_zui_check_button_down(zui, id, x1, y1, x2, y2);

   return result;
}

static unsigned zarch_zui_hash(zui_t *zui, const char *s)
{
   unsigned hval = zui->hash;

   while(*s != 0)
   {
      hval+=*s++;
      hval+=(hval<<10);
      hval^=(hval>>6);
      hval+=(hval<<3);
      hval^=(hval>>11);
      hval+=(hval<<15);
   }
   return zui->hash = hval;
}

static void zarch_zui_draw_text(zui_t *zui,
      uint32_t color, int x, int y, const char *text)
{
   struct font_params params;

   if (!zui || !zui->font || string_is_empty(text))
      return;

   /* need to use height-y because the font renderer
    * uses a different model-view-projection (MVP). */
   params.x           = x / (float)zui->width;
   params.y           = (zui->height - y) / (float)zui->height;
   params.scale       = 1.0f;
   params.drop_mod    = 0.0f;
   params.drop_x      = 0.0f;
   params.drop_y      = 0.0f;
   params.color       = color;
   params.full_screen = true;
   params.text_align  = TEXT_ALIGN_LEFT;

   video_driver_set_osd_msg(text, &params, zui->font);
}

static bool zarch_zui_button_full(zui_t *zui,
      int x1, int y1, int x2, int y2, const char *label)
{
   unsigned       id = zarch_zui_hash(zui, label);
   bool       active = zarch_zui_check_button_up(zui, id, x1, y1, x2, y2);
   const float *bg   = zui_bg_panel;

   if (zui->item.active == id || zui->item.hot == id)
      bg = zui_bg_hilite;

   menu_display_push_quad(zui->width, zui->height,  bg, x1, y1, x2, y2);
   zarch_zui_draw_text(zui, ZUI_FG_NORMAL, x1+12, y1 + 41, label);

   return active;
}

static bool zarch_zui_button(zui_t *zui, int x1, int y1, const char *label)
{
   if (!zui || !zui->font)
      return false;
   return zarch_zui_button_full(zui, x1, y1, x1
         + zarch_zui_strwidth(zui->font, label, 1.0) + 24, y1 + 64, label);
}

static bool zarch_zui_list_item(video_frame_info_t *video_info,
      zui_t *zui, struct zui_tabbed *tab, int x1, int y1,
      const char *label, unsigned item_id, const char *entry, bool selected)
{
   menu_animation_ctx_ticker_t ticker;
   unsigned ticker_size;
   char title_buf[PATH_MAX_LENGTH];
   unsigned           id = zarch_zui_hash(zui, label);
   int                x2 = x1 + zui->width - 290 - 40;
   int                y2 = y1 + 50;
   bool           active = zarch_zui_check_button_up(zui, id, x1, y1, x2, y2);
   const float       *bg = zui_bg_panel;
   uint64_t frame_count  = zui->frame_count;

   title_buf[0] = '\0';

   if (tab->active_id != tab->prev_id)
      tab->prev_id         = tab->active_id;

   if (selected)
   {
      zui->next_id            = item_id;
      zui->next_selection_set = true;
   }

   /* Set background color */
   if (zui->item.active == id || zui->item.hot == id)
      bg = zui_bg_hilite;
   else if (selected)
      bg = zui_bg_pad_hilite;

   ticker_size = x2 / 14;

   ticker.s        = title_buf;
   ticker.len      = ticker_size;
   ticker.idx      = frame_count / 50;
   ticker.str      = label;
   ticker.selected = (bg == zui_bg_hilite || bg == zui_bg_pad_hilite);

   menu_animation_ticker(&ticker);

   menu_display_push_quad(zui->width, zui->height, bg, x1, y1, x2, y2);
   zarch_zui_draw_text(zui, ZUI_FG_NORMAL, 12, y1 + 35, title_buf);

   if (entry)
      zarch_zui_draw_text(zui, ZUI_FG_NORMAL, x2 - 200, y1 + 35, entry);

   return active;
}

static void zarch_zui_tabbed_begin(zui_t *zui, struct zui_tabbed *tab, int x, int y)
{
   tab->x            = x;
   tab->y            = y;
   tab->tabline_size = 60 + 4;
}

static bool zarch_zui_tab(zui_t *zui, struct zui_tabbed *tab,
      const char *label, unsigned tab_id)
{
   bool active;
   int x1, y1, x2, y2;
   unsigned       id = zarch_zui_hash(zui, label);
   int         width = tab->tab_width;
   const float   *bg = zui_bg_panel;
   bool selected     = tab->tab_selection == tab_id; /* TODO/FIXME */

   if (!width)
   {
      if (!zui->font)
         return false;
      width          = zarch_zui_strwidth(zui->font, label, 1.0) + 24;
   }

   x1                = tab->x;
   y1                = tab->y;
   x2                = x1 + width;
   y2                = y1 + 60;
   active            = zarch_zui_check_button_up(zui, id, x1, y1, x2, y2);

   tab->prev_id      = tab->active_id;

   if (zui->item.active == id || tab->active_id == ~0U || !tab->inited)
      tab->active_id    = id;
   else if (id > tab->active_id)
      tab->next_id            = id;

   if (!tab->inited)
      tab->inited = true;

   if (tab->active_id == id || zui->item.active == id || zui->item.hot == id)
      bg             = zui_bg_hilite;
   else if (selected)
      bg             = zui_bg_pad_hilite;

   menu_display_push_quad(zui->width, zui->height,  bg, x1+0, y1+0, x2, y2);
   zarch_zui_draw_text(zui, ZUI_FG_NORMAL, x1+12, y1 + 41, label);

   if (tab->vertical)
      tab->y        += y2 - y1;
   else
      tab->x         = x2;

   return active || (tab->active_id == id);
}


static void zarch_zui_render_lay_settings(zui_t *zui)
{
   int width, x1, y1;
   static struct zui_tabbed tabbed = {~0U};

   tabbed.vertical            = true;
   tabbed.tab_width           = 100;

   zarch_zui_tabbed_begin(zui, &tabbed, zui->width - 100, 20);

   width                      = 290;
   x1                         = zui->width - width - 20;
   y1                         = 20;
   y1                        += 64;

   if (zarch_zui_button_full(zui, x1, y1, x1 + width, y1 + 64, "Back"))
      zarch_layout = LAY_HOME;
}

static bool zarch_zui_gamepad_input(zui_t *zui,
      int *gamepad_index, int *list_first,
      unsigned skip)
{
   unsigned size          = menu_entries_get_size();
   unsigned cutoff_point  = size - 5;

   switch (zui->action)
   {
      case MENU_ACTION_LEFT:
         if (*gamepad_index == 0)
            break;

         *gamepad_index = *gamepad_index - 5;

         if (*gamepad_index < 0)
            *gamepad_index = 0;
         return true;
      case MENU_ACTION_RIGHT:
         if (*gamepad_index == (signed)(size-1))
            break;

         *gamepad_index = *gamepad_index + 5;

         if (*gamepad_index > (signed)(size-1))
            *gamepad_index   = (size -1);

         return true;
      case MENU_ACTION_UP:
         *gamepad_index = *gamepad_index - 1;

         if (*gamepad_index < 0) /* and wraparound enabled */
            *gamepad_index = size -1;
         else if (*gamepad_index >= (signed)cutoff_point) /* if greater than cutoff point,
                                                don't scroll */
            return false;

         return true;
      case MENU_ACTION_DOWN:
         *gamepad_index = *gamepad_index + 1;

         if (*gamepad_index > (signed)(size - 1)) /* and wraparound enabled */
            *gamepad_index = 0;
         else if (*gamepad_index >= (signed)cutoff_point) /* if greater than cutoff point,
                                                don't scroll */
            return false;
         return true;
      default:
         {
            *list_first += zui->mouse.wheel;
            if (*list_first < 0)
               *list_first = 0;
            if (*list_first > (int)cutoff_point)
               *list_first = cutoff_point;

            *list_first = MIN(MAX(*list_first, 0), cutoff_point - skip);
         }
         return false;
   }

   return false;
}

static int zarch_zui_render_lay_root_recent(
      video_frame_info_t *video_info,
      zui_t *zui, struct zui_tabbed *tabbed)
{
   if (zarch_zui_tab(zui, tabbed, "Recent", 0))
   {
      static int gamepad_index = 0;
      unsigned size = menu_entries_get_size();
      unsigned i, j = 0;

      if (zarch_zui_gamepad_input(zui, &gamepad_index,
               &zui->recent_dlist_first, 0))
         zui->recent_dlist_first = gamepad_index;

      for (i = zui->recent_dlist_first; i < size; ++i)
      {
         char entry_value[PATH_MAX_LENGTH];
         menu_entry_t entry;
         char *rich_label  = NULL;

         entry_value[0]    = '\0';

         menu_entry_init(&entry);
         menu_entry_get(&entry, 0, i, NULL, true);
         rich_label = menu_entry_get_rich_label(&entry);
         menu_entry_get_value(&entry, entry_value,sizeof(entry_value));

         if (zarch_zui_list_item(
                  video_info,
                  zui, tabbed, 0,
                  tabbed->tabline_size + j * ZUI_ITEM_SIZE_PX,
                  rich_label, i, entry_value, gamepad_index == (signed)i))
         {
            if (menu_entry_action(&entry, i, MENU_ACTION_OK))
            {
               menu_entry_free(&entry);
               if (!string_is_empty(rich_label))
                  free(rich_label);
               return 1;
            }
         }

         j++;
         menu_entry_free(&entry);
         if (!string_is_empty(rich_label))
            free(rich_label);
      }

   }

   return 0;
}

static void zarch_zui_render_lay_root_load_free(zui_t *zui)
{
   if (!zui)
      return;

   free(zui->load_cwd);
   dir_list_free(zui->load_dlist);
   zui->load_cwd   = NULL;
   zui->load_dlist = NULL;
}

static void zarch_zui_render_lay_root_load_set_new_path(zui_t *zui,
      const char *newpath)
{
   if (!zui)
      return;

   free(zui->load_cwd);
   zui->load_cwd = strdup(newpath);
   dir_list_free(zui->load_dlist);
   zui->load_dlist = NULL;
}

static int zarch_zui_render_lay_root_load(
      video_frame_info_t *video_info,
      zui_t *zui,
      struct zui_tabbed *tabbed)
{
   char parent_dir[PATH_MAX_LENGTH];
   settings_t           *settings   = config_get_ptr();
   core_info_list_t           *list = NULL;

   parent_dir[0] = '\0';

   if (zarch_zui_tab(zui, tabbed, "Load", 1))
   {
      unsigned cwd_offset;

      if (!zui->load_cwd)
         zui->load_cwd = strdup(settings->paths.directory_menu_content);

      if (!zui->load_dlist)
      {
         core_info_t *core_info = NULL;
         core_info_get_current_core(&core_info);

         zui->load_dlist = dir_list_new(zui->load_cwd,
               core_info->supported_extensions, true, true, false, true);
         dir_list_sort(zui->load_dlist, true);
         zui->load_dlist_first  = 0;
      }

      cwd_offset = MIN(strlen(zui->load_cwd), 60);

      zarch_zui_draw_text(zui, ZUI_FG_NORMAL, 15,
            tabbed->tabline_size + 5 + 41,
            &zui->load_cwd[strlen(zui->load_cwd) - cwd_offset]);

      if (zarch_zui_button(zui, zui->width - 290 - 129,
               tabbed->tabline_size + 5, "Home"))
         zarch_zui_render_lay_root_load_free(zui);

      if (zui->load_dlist)
      {
         fill_pathname_parent_dir(parent_dir,
               zui->load_cwd, sizeof(parent_dir));
         if (!string_is_empty(parent_dir) &&
               zarch_zui_list_item(
                  video_info,
                  zui, tabbed, 0,
                  tabbed->tabline_size + 73, " ..", 0, NULL, false /* TODO/FIXME */))
         {
            zarch_zui_render_lay_root_load_set_new_path(zui, parent_dir);
         }
         else
         {
            static int gamepad_index = 0;
            unsigned size = zui->load_dlist->size;
            unsigned i, j = 1;
            unsigned skip = 0;

            for (i = 0; i < size; ++i)
            {
               const char *basename =
                  path_basename(zui->load_dlist->elems[i].data);
               if (basename[0] != '.')
                  break;
               skip++;
            }

            if (zarch_zui_gamepad_input(zui, &gamepad_index,
                     &zui->load_dlist_first, skip))
               zui->load_dlist_first = gamepad_index;

            for (i = skip + zui->load_dlist_first; i < size; ++i)
            {
               char label[PATH_MAX_LENGTH];
               const char        *path     = NULL;
               const char        *basename = NULL;

               if (j > 10)
                  break;

               label[0] = '\0';

               path     = zui->load_dlist->elems[i].data;
               basename = path_basename(path);

               *label = 0;
               strncat(label, "  ", sizeof(label)-1);
               strncat(label, basename, sizeof(label)-1);

               if (path_is_directory(path))
                  strncat(label, "/", sizeof(label)-1);

               if (zarch_zui_list_item(
                        video_info,
                        zui, tabbed, 0,
                        tabbed->tabline_size + 73 + j * ZUI_ITEM_SIZE_PX,
                        label, i, NULL, gamepad_index == (signed)(i-skip)))
               {
                  if (path_is_directory(path))
                  {
                     zarch_zui_render_lay_root_load_set_new_path(zui, path);
                     break;
                  }

                  zui->pick_cores     = NULL;
                  zui->pick_supported = 0;
                  strlcpy(zui->pick_content,
                        path, sizeof(zui->pick_content));

                  core_info_get_list(&list);

                  core_info_list_get_supported_cores(list, path,
                        &zui->pick_cores, &zui->pick_supported);
                  zarch_layout = LAY_PICK_CORE;
                  break;
               }
               j++;
            }
         }
      }
   }
   else if (zui->load_dlist)
   {
      dir_list_free(zui->load_dlist);
      zui->load_dlist = NULL;
   }

   return 0;
}

static int zarch_zui_render_lay_root_collections(
      zui_t *zui, struct zui_tabbed *tabbed)
{
   if (zarch_zui_tab(zui, tabbed, "Collections", 2))
   {
      /* STUB/FIXME */
   }

   return 0;
}

static int zarch_zui_render_lay_root_downloads(
      zui_t *zui, struct zui_tabbed *tabbed)
{
   if (zarch_zui_tab(zui, tabbed, "Download", 3))
   {
      /* STUB/FIXME */
   }

   return 0;
}

static int zarch_zui_render_lay_root(video_frame_info_t *video_info,
      zui_t *zui)
{
#ifdef ZARCH_DEBUG
   char item[PATH_MAX_LENGTH];
#endif
   static struct zui_tabbed tabbed = {~0U};

   zarch_zui_tabbed_begin(zui, &tabbed, 0, 0);

   tabbed.width            = zui->width - 290 - 40;
   zui->next_selection_set = false;

   if (zarch_zui_render_lay_root_recent(video_info, zui, &tabbed))
      return 0;
   if (zarch_zui_render_lay_root_load(video_info, zui, &tabbed))
      return 0;
   if (zarch_zui_render_lay_root_collections(zui, &tabbed))
      return 0;
   if (zarch_zui_render_lay_root_downloads(zui, &tabbed))
      return 0;

#ifdef ZARCH_DEBUG
   item[0] = '\0';

   snprintf(item, sizeof(item), "item id: %d\n", zui->active_id);
   zarch_zui_draw_text(zui, ZUI_FG_NORMAL, 1600 +12, 300 + 41, item);
   snprintf(item, sizeof(item), "tab  idx: %d\n", tabbed.active_id);
   zarch_zui_draw_text(zui, ZUI_FG_NORMAL, 1600 +12, 300 + 81, item);
   snprintf(item, sizeof(item), "item hot idx: %d\n", zui->item.hot);
   zarch_zui_draw_text(zui, ZUI_FG_NORMAL, 1600 +12, 300 + 111, item);
#endif

   menu_display_push_quad(zui->width, zui->height,
         zui_bg_hilite, 0, 60, zui->width - 290 - 40, 60+4);

   return 0;
}

static int zarch_zui_render_sidebar(zui_t *zui)
{
   int width, x1, y1;
   static struct zui_tabbed tabbed = {~0U};
   tabbed.vertical = true;
   tabbed.tab_width = 100;

   zarch_zui_tabbed_begin(zui, &tabbed, zui->width - 100, 20);

   width = 290;
   x1    = zui->width - width - 20;
   y1    = 20;

   if (zarch_zui_button_full(zui, x1, y1, x1 + width, y1 + 64, "Settings"))
      zarch_layout = LAY_SETTINGS;

   y1 += 64;

   if (zarch_zui_button_full(zui, x1, y1, x1 + width, y1 + 64, "Exit"))
   {
      menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_SHUTDOWN, NULL);
      return 1;
   }

   return 0;
}

static int zarch_zui_load_content(zui_t *zui, unsigned i)
{
   content_ctx_info_t content_info = {0};

   task_push_load_content_with_new_core_from_menu(
         zui->pick_cores[i].path,
         zui->pick_content,
         &content_info,
         CORE_TYPE_PLAIN,
         NULL, NULL);

   zarch_layout = LAY_HOME;

   return 0;
}

static void zarch_zui_draw_cursor(float x, float y)
{
}

static int zarch_zui_render_pick_core(video_frame_info_t *video_info,
      zui_t *zui)
{
   static struct zui_tabbed tabbed = {~0U};
   unsigned i, j = 0;
   if (zui->pick_supported == 1)
   {
      int ret = zarch_zui_load_content(zui, 0);

      (void)ret;

      zarch_layout = LAY_HOME;
      menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUIT, NULL);
      return 1;
   }

   zarch_zui_draw_text(zui, ~0, 8, 18, "Select a core: ");

   if (zarch_zui_button(zui, 0, 18 + zui->font_size, "<- Back"))
      zarch_layout = LAY_HOME;

   if (!zui->pick_supported)
   {
      zarch_zui_list_item(
            video_info,
            zui, &tabbed, 0, ZUI_ITEM_SIZE_PX,
            "Content unsupported", 0, NULL, false /* TODO/FIXME */);
      return 1;
   }

   zui->pick_first += zui->mouse.wheel;

   zui->pick_first = MIN(MAX(zui->pick_first, 0), zui->pick_supported - 5);

   for (i = zui->pick_first; i < zui->pick_supported; ++i)
   {
      if (j > 10)
         break;

      if (zarch_zui_list_item(
               video_info,
               zui, &tabbed, 0, ZUI_ITEM_SIZE_PX + j * ZUI_ITEM_SIZE_PX,
               zui->pick_cores[i].display_name, i, NULL, false))
      {
         int ret = zarch_zui_load_content(zui, i);

         (void)ret;

         zarch_layout = LAY_HOME;

         menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUIT, NULL);
         break;
      }
      j++;
   }

   return 0;
}

static void zarch_frame(void *data, video_frame_info_t *video_info)
{
   unsigned i;
   float coord_color[16];
   float coord_color2[16];
   menu_display_ctx_draw_t draw;
   menu_display_ctx_coord_draw_t coord_draw;
   settings_t *settings    = config_get_ptr();
   zui_t *zui              = (zui_t*)data;
   video_coord_array_t *ca = menu_display_get_coords_array();

   if (!zui)
      return;

   zui->frame_count++;

   video_driver_get_size(&zui->width, &zui->height);

   menu_display_set_viewport(video_info->width, video_info->height);

   for (i = 0; i < 16; i++)
   {
      coord_color[i]  = 0;
      coord_color2[i] = 2.0f;

      if (i == 3 || i == 7 || i == 11 || i == 15)
      {
         coord_color[i]  = 0.10f;
         coord_color2[i] = 0.10f;
      }
   }

   zui->rendering = true;
   zui->hash      = 0;
   zui->item.hot  = 0;

   /* why do i need this? */
   zui->mouse.wheel = menu_input_mouse_state(MENU_MOUSE_WHEEL_DOWN) -
      menu_input_mouse_state(MENU_MOUSE_WHEEL_UP);

   menu_display_coords_array_reset();

   zui->tmp_block.carr.coords.vertices = 0;

   font_driver_bind_block(zui->font, &zui->tmp_block);

   menu_display_push_quad(zui->width, zui->height, zui_bg_screen,
         0, 0, zui->width, zui->height);
   menu_display_snow(zui->width, zui->height);

   switch (zarch_layout)
   {
      case LAY_HOME:
         if (zarch_zui_render_sidebar(zui))
            return;
         if (zarch_zui_render_lay_root(video_info, zui))
            return;
         break;
      case LAY_SETTINGS:
         zarch_zui_render_lay_settings(zui);
         break;
      case LAY_PICK_CORE:
         if (zarch_zui_render_sidebar(zui))
            return;
         if (zarch_zui_render_pick_core(video_info, zui))
            return;
         break;
   }

   if (settings->bools.menu_mouse_enable)
      zarch_zui_draw_cursor(
            zarch_zui_input_state(zui, MENU_ZARCH_MOUSE_X),
            zarch_zui_input_state(zui, MENU_ZARCH_MOUSE_Y));


   if (!zarch_zui_input_state(zui, MENU_ZARCH_PRESSED))
      zui->item.active = 0;
   else if (zui->item.active == 0)
      zui->item.active = -1;

   menu_display_blend_begin(video_info);

   draw.x           = 0;
   draw.y           = 0;
   draw.width       = zui->width;
   draw.height      = zui->height;
   draw.coords      = (struct video_coords*)ca;
   draw.matrix_data = &zui->mvp;
   draw.texture     = menu_display_white_texture;
   draw.prim_type   = MENU_DISPLAY_PRIM_TRIANGLES;
   draw.pipeline.id = 0;

   menu_display_draw(&draw, video_info);
   menu_display_blend_end(video_info);

   memset(&draw, 0, sizeof(menu_display_ctx_draw_t));

   coord_draw.ptr       = NULL;

   menu_display_get_tex_coords(&coord_draw);

   draw.width              = zui->width;
   draw.height             = zui->height;
   draw.texture            = zui->textures.bg;
   draw.color              = &coord_color[0];
   draw.vertex             = NULL;
   draw.tex_coord          = coord_draw.ptr;
   draw.vertex_count       = 4;
   draw.prim_type          = MENU_DISPLAY_PRIM_TRIANGLESTRIP;

   if (!video_info->libretro_running && draw.texture)
      draw.color           = &coord_color2[0];

   menu_display_blend_begin(video_info);
   draw.x              = 0;
   draw.y              = 0;

   menu_display_draw_bg(&draw, video_info, false,
         video_info->menu_wallpaper_opacity);
   menu_display_draw(&draw, video_info);
   menu_display_blend_end(video_info);

   zui->rendering = false;

   font_driver_flush(video_info->width, video_info->height, zui->font,
         video_info);
   font_driver_bind_block(zui->font, NULL);

   menu_display_unset_viewport(video_info->width, video_info->height);
}

static void *zarch_init(void **userdata, bool video_is_threaded)
{
   zui_t *zui                              = NULL;
   menu_handle_t *menu                     = (menu_handle_t*)
      calloc(1, sizeof(*menu));

   if (!menu)
      goto error;

   if (!menu_display_init_first_driver(video_is_threaded))
      goto error;

   zui       = (zui_t*)calloc(1, sizeof(zui_t));

   if (!zui)
      goto error;

   *userdata            = zui;
   zui->header_height   = 1000; /* dpi / 3; */
   zui->font_size       = 28;

   matrix_4x4_ortho(zui->mvp, 0, 1, 1, 0, 0, 1);

   return menu;
error:
   if (menu)
      free(menu);
   return NULL;
}

static void zarch_free(void *data)
{
   zui_t        *zui                       = (zui_t*)data;

   if (zui)
      video_coord_array_free(&zui->tmp_block.carr);

   font_driver_bind_block(NULL, NULL);
}

static void zarch_context_bg_destroy(void *data)
{
   zui_t        *zui     = (zui_t*)data;
   if (!zui)
      return;
   video_driver_texture_unload(&zui->textures.bg);
   video_driver_texture_unload(&menu_display_white_texture);
}

static void zarch_context_destroy(void *data)
{
   zui_t        *zui = (zui_t*)data;

   /* why on earth is this called twice on exit? */
   if (!zui)
      return;

   menu_display_font_free((font_data_t*)zui->font);
   zarch_context_bg_destroy(data);

   zui->font = NULL;
}

static bool zarch_load_image(void *userdata,
      void *data, enum menu_image_type type)
{
   zui_t        *zui = (zui_t*)userdata;

   if (!zui || !data)
      return false;

   switch (type)
   {
      case MENU_IMAGE_NONE:
         break;
      case MENU_IMAGE_WALLPAPER:
         zarch_context_bg_destroy(zui);
         video_driver_texture_load(data,
               TEXTURE_FILTER_MIPMAP_LINEAR,
               &zui->textures.bg);
         break;
      case MENU_IMAGE_THUMBNAIL:
      case MENU_IMAGE_LEFT_THUMBNAIL:
      case MENU_IMAGE_SAVESTATE_THUMBNAIL:
         /* TODO/FIXME -implement */
         break;
   }

   return true;
}

static void zarch_context_reset(void *data, bool is_threaded)
{
   settings_t *settings  = config_get_ptr();
   zui_t          *zui   = (zui_t*)data;

   if (!zui || !settings)
      return;

   zarch_context_bg_destroy(zui);

   task_push_image_load(settings->paths.path_menu_wallpaper,
         menu_display_handle_wallpaper_upload, NULL);

   menu_display_allocate_white_texture();

   menu_display_set_header_height(zui->header_height);
   zui->font = menu_display_font(APPLICATION_SPECIAL_DIRECTORY_ASSETS_ZARCH_FONT,
         zui->font_size,
         is_threaded);
}

static int zarch_iterate(void *data, void *userdata, enum menu_action action)
{
   int ret;
   menu_entry_t entry;
   zui_t *zui           = (zui_t*)userdata;
   size_t selection     = menu_navigation_get_selection();

   if (!zui)
      return -1;

   menu_entry_init(&entry);
   menu_entry_get(&entry, 0, selection, NULL, false);

   zui->action       = action;

   ret = menu_entry_action(&entry, selection, action);
   menu_entry_free(&entry);
   if (ret)
      return -1;
   return 0;
}

static bool zarch_menu_init_list(void *data)
{
   menu_displaylist_info_t info;
   file_list_t *menu_stack      = menu_entries_get_menu_stack_ptr(0);
   file_list_t *selection_buf   = menu_entries_get_selection_buf_ptr(0);

   menu_displaylist_info_init(&info);

   info.label    = strdup(
         msg_hash_to_str(MENU_ENUM_LABEL_HISTORY_TAB));
   info.enum_idx = MENU_ENUM_LABEL_HISTORY_TAB;

   menu_entries_append_enum(menu_stack,
         info.path, info.label,
         MENU_ENUM_LABEL_HISTORY_TAB, info.type, info.flags, 0);

   command_event(CMD_EVENT_HISTORY_INIT, NULL);

   info.list  = selection_buf;

   if (menu_displaylist_ctl(DISPLAYLIST_HISTORY, &info))
   {
      bool ret = false;
      info.need_push = true;
      ret = menu_displaylist_process(&info);
      menu_displaylist_info_free(&info);
      return ret;
   }

   menu_displaylist_info_free(&info);

   return false;
}

menu_ctx_driver_t menu_ctx_zarch = {
   NULL,
   NULL,
   zarch_iterate,
   NULL,
   zarch_frame,
   zarch_init,
   zarch_free,
   zarch_context_reset,
   zarch_context_destroy,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   zarch_menu_init_list,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   zarch_load_image,
   "zarch",
   NULL,  /* environ */
   NULL,  /* pointer_tap */
   NULL,  /* update_thumbnail_path */
   NULL,  /* update_thumbnail_image */
   NULL,  /* set_thumbnail_system */
   NULL,  /* set_thumbnail_content */
   NULL,  /* osk_ptr_at_pos */
   NULL,  /* update_savestate_thumbnail_path */
   NULL,  /* update_savestate_thumbnail_image */
};