#include "main.h"

/* the actual menu rendering is done in proxyIDirect3DDssdgkasdg.......cpp */

/* XXX check NULL alloc, free memory, etc */

#define ID_MENU_MAIN                0
#define ID_MENU_CHEATS              1
#define ID_MENU_CHEATS_SPOOF_WEAPON 2
#define ID_MENU_CHEATS_FAKE_KILL    3
#define ID_MENU_PATCHES             4
#define ID_MENU_WEAPONS             5
#define ID_MENU_VEHICLES            6
#define ID_MENU_VEHICLES_SUB        7
#define ID_MENU_PLAYERS             8
#define ID_MENU_TELEPORTS           9
#define ID_MENU_MISCELLANEOUS       10
#define ID_MENU_DEBUG               11

#define ID_NONE                  -1

#define ID_CHEAT_INVULN          0
#define ID_CHEAT_WEAPONS         1
#define ID_CHEAT_MONEY           2
#define ID_CHEAT_PROT            3
#define ID_CHEAT_WARP_NEAR       4
#define ID_CHEAT_SPOOF_WEAPON    5
#define ID_CHEAT_FAKE_KILL       6
#define ID_CHEAT_JETPACK         8
#define ID_CHEAT_UNLOCK          9
#define ID_CHEAT_WEATHER         10
#define ID_CHEAT_TIME            11
#define ID_CHEAT_GRAVITY_INC     12
#define ID_CHEAT_GRAVITY_DEC     13
#define ID_CHEAT_HAVEN           14

#define ID_WEAPON_ENABLE         0
#define ID_WEAPON_ITEM           1

#define ID_VEHICLES_IWARP        0

#define ID_MISC_COORDS           0
#define ID_MISC_RELOAD           1
#define ID_MISC_HUDTEXT          2

#define ID_DEBUG_ENABLE             0
#define ID_DEBUG_SELF_ACTOR         1
#define ID_DEBUG_SELF_VEHICLE       2
#define ID_DEBUG_CAMERA             3
#define ID_DEBUG_VEHICLE_CONTACT    4
#define ID_DEBUG_BUILDING_CONTACT   5
#define ID_DEBUG_NEAREST_ACTOR      6
#define ID_DEBUG_NEAREST_VEHICLE    7
#define ID_DEBUG_VEHICLE_POOL       8
#define ID_DEBUG_SAMP_PLAYER_LIST   9

struct menu *menu_active = NULL;
static int menu_init;


struct menu *menu_new(struct menu *parent, int id, menu_callback callback)
{
   struct menu *menu = (struct menu *)calloc(1, sizeof(struct menu));

   if(menu == NULL)
      return NULL;

   menu->id = id;
   menu->parent = parent;
   menu->callback = callback;

   return menu;
}


struct menu_item *menu_item_add(struct menu *menu, struct menu *submenu, const char *name, int id, D3DCOLOR color, void *data)
{
   struct menu_item *item;
   int i;

   i = menu->count++;
   menu->item = (struct menu_item *)realloc(menu->item, sizeof(struct menu_item) * menu->count);
   item = &menu->item[i];

   memset(item, 0, sizeof(menu_item));
   item->submenu = submenu;
   item->menu = menu;
   item->name = strdup(name);
   item->id = id;
   item->color = color;
   item->data = data;

   return item;
}


static void menu_cheats_spoof_kill_populate(struct menu *menu)
{
   struct samp_player_list *spl = samp_player_list_get();
   char text[64];
   int i;

   menu_items_free(menu);

   if(spl == NULL)
      return;

   for(i=0; i<SAMP_PLAYER_MAX; i++)
   {
      D3DCOLOR color = MENU_COLOR_DEFAULT;

      if(spl->alloc_map[i] != 1)
         continue;

      if(spl->player_info[i] != NULL)
         color = samp_color_get(spl->player_info[i]->color_id);

      snprintf(text, sizeof(text), "%d. Fake killed by %s", i, spl->name[i]);

      menu_item_add(menu, NULL, text, ID_CHEAT_FAKE_KILL, color, (void *)(UINT_PTR)i);
   }
}


static void menu_vehicles_populate(struct menu *menu, int class_id)
{
   const struct actor_info *actor_self = actor_info_get(ACTOR_SELF, 0);
   char name[64];
   int i;

   menu_items_free(menu);

   if(pool_vehicle != NULL)
   {
      for(i=0; i<pool_vehicle->size; i++)
      {
         static const struct vehicle_entry unknown = { 0, VEHICLE_CLASS_CAR, "[Unknown vehicle]" };
         const struct vehicle_info *info = vehicle_info_get(i, VEHICLE_ALIVE|VEHICLE_EMPTY);
         const struct vehicle_entry *vehicle;
         float dist[3] = { 0.0f, 0.0f, 0.0f };

         if(info == NULL)
            continue;

         vehicle = gta_vehicle_get_by_id(info->base.model_alt_id);
         if(vehicle == NULL)
            vehicle = &unknown;

         if(vehicle->class_id != class_id)
            continue;

         if(actor_self != NULL)
            vect3_vect3_sub(&info->base.matrix[4*3], &actor_self->base.matrix[4*3], dist);

         snprintf(name, sizeof(name), "%s (%.2fm)", vehicle->name, vect3_length(dist));

         /* XXX maybe use color of car ... */
         menu_item_add(menu, NULL, name, i, MENU_COLOR_DEFAULT, NULL);
      }
   }
}


static void menu_players_populate(struct menu *menu)
{
   struct samp_player_list *spl = samp_player_list_get();
   char text[64];
   int i;

   menu_items_free(menu);

   if(spl == NULL)
      return;

   for(i=0; i<SAMP_PLAYER_MAX; i++)
   {
      D3DCOLOR color = MENU_COLOR_DEFAULT;

      if(spl->alloc_map[i] != 1)
         continue;

      if(spl->player_info[i] != NULL)
         color = samp_color_get(spl->player_info[i]->color_id);

      snprintf(text, sizeof(text), "%d. %s", i, spl->name[i]);

      menu_item_add(menu, NULL, text, i, color, NULL);
   }
}


/* called when a menu is going to be displayed
   (eg. when you hit f9, or go into a submenu) */
static void menu_event_activate(struct menu *menu)
{
   if(menu == NULL)
      return;

   switch(menu->id)
   {
   case ID_MENU_VEHICLES_SUB:
      menu_vehicles_populate(menu, (int)(UINT_PTR)menu->parent->item[menu->parent->pos].data);
      break;
   case ID_MENU_PLAYERS:
      menu_players_populate(menu);
      break;
   case ID_MENU_CHEATS_FAKE_KILL:
      menu_cheats_spoof_kill_populate(menu);
      break;
   }
}


/* run every frame */
void menu_run(void)
{
   struct menu_item *item;
   static uint32_t key_time;


   if(menu_active == NULL)
      return;

   if(KEY_PRESSED(set.key_menu))
   {
      cheat_state->generic.menu ^= 1;
      if(cheat_state->generic.menu)
         menu_event_activate(menu_active);
   }

   if(cheat_state->generic.menu)
   {
      if(KEY_PRESSED(set.key_menu_up) || KEY_PRESSED(set.key_menu_down))
      {
         menu_active->pos += KEY_PRESSED(set.key_menu_up) ? -1 : 1;
         key_time = time_get() + MSEC_TO_TIME(MENU_SCROLL_DELAY);
      }

      if(KEY_DOWN(set.key_menu_up) || KEY_DOWN(set.key_menu_down))
      {
         if(time_get() > key_time)
         {
            int diff = time_get() - key_time;
            int overruns = diff / MENU_SCROLL_TIME;
            int dir = KEY_DOWN(set.key_menu_up) ? -1 : 1;

            menu_active->pos += dir * (overruns + 1);

            key_time = time_get() + MENU_SCROLL_TIME - (diff % MENU_SCROLL_TIME);
         }
      }

      if(menu_active->pos < 0)
         menu_active->pos = menu_active->count - 1;
      if(menu_active->pos >= menu_active->count)
         menu_active->pos = 0;

      if(menu_active->count <= 0)
         item = NULL;
      else
         item = &menu_active->item[ menu_active->pos ];

      if(KEY_PRESSED(set.key_menu_left))
      {
         /* pressing left in the main menu, exits the menu */
         if(menu_active->parent == NULL)
         {
            cheat_state->generic.menu ^= 1;
            return;
         }
         /* XXX menu_event_activate(menu_active->parent); (should we do this?) */
         menu_active = menu_active->parent;
      }

      if(KEY_PRESSED(set.key_menu_right) || KEY_PRESSED(set.key_menu_select))
      {
         if(item != NULL && item->submenu != NULL)
         {
            menu_event_activate(item->submenu);
            menu_active = item->submenu;
         }
         else if(KEY_PRESSED(set.key_menu_select) && menu_active->callback != NULL)
         {
            if(item != NULL)
               menu_active->callback(MENU_OP_SELECT, item);
         }
      }

      KEY_CONSUME(set.key_menu_up);
      KEY_CONSUME(set.key_menu_right);
      KEY_CONSUME(set.key_menu_down);
      KEY_CONSUME(set.key_menu_left);
      KEY_CONSUME(set.key_menu_select);
   }
}


static int menu_callback_main(int op, struct menu_item *)
{
   return 0;
}


static int menu_callback_cheats(int op, struct menu_item *item)
{
   if(op == MENU_OP_ENABLED)
   {
      switch(item->id)
      {
      case ID_CHEAT_INVULN:        return cheat_state->generic.hp_cheat;
      case ID_CHEAT_WEAPONS:       return 0;
      case ID_CHEAT_MONEY:         return cheat_state->generic.money;
      case ID_CHEAT_PROT:          return cheat_state->vehicle.protection;
      case ID_CHEAT_WARP_NEAR:     return 0;
      case ID_CHEAT_SPOOF_WEAPON:  return (cheat_state->generic.spoof_weapon == (int)(UINT_PTR)item->data);
      case ID_CHEAT_FAKE_KILL:     return 0;
      case ID_CHEAT_JETPACK:       return 0;
      case ID_CHEAT_UNLOCK:        return cheat_state->generic.vehicles_unlock;
      case ID_CHEAT_WEATHER:       return cheat_state->generic.force_weather;
      case ID_CHEAT_TIME:          return cheat_state->generic.force_time;
      /*case ID_CHEAT_GRAVITY_INC:  return 0;
      case ID_CHEAT_GRAVITY_DEC:  return 0;*/
      case ID_CHEAT_HAVEN:         return cheat_state->generic.hp_safe_haven;
      }
   }
   else if(op == MENU_OP_SELECT)
   {
      switch(item->id)
      {
      case ID_CHEAT_INVULN:    cheat_state->generic.hp_cheat ^= 1;   break;
      case ID_CHEAT_WEAPONS:   break;
      case ID_CHEAT_MONEY:     cheat_state->generic.money ^= 1;      break;
      case ID_CHEAT_PROT:      cheat_state->vehicle.protection ^= 1; break;
      case ID_CHEAT_WARP_NEAR: cheat_teleport_nearest_car();         break;
      case ID_CHEAT_SPOOF_WEAPON:
         if(cheat_state->generic.spoof_weapon == (int)(UINT_PTR)item->data)
            cheat_state->generic.spoof_weapon = -1;
         else
            cheat_state->generic.spoof_weapon = (int)(UINT_PTR)item->data;
         break;
      case ID_CHEAT_FAKE_KILL:
         {
            struct actor_info *info = samp_player_actor_info_get((int)(UINT_PTR)item->data);
            struct actor_info *self = actor_info_get(ACTOR_SELF, ACTOR_ALIVE);
            const char *errmsg = NULL;

            if(info == NULL)
               errmsg = "Player does not exists.";
            else if(ACTOR_IS_DEAD(info))
               errmsg = "The player is dead.";
            if(self == NULL)
               errmsg = "You are dead.";

            if(errmsg == NULL)
            {
               self->hitpoints = 0.0f;
               self->weapon_hit_by = info;
               if(cheat_state->generic.spoof_weapon == -1)
               {
                  if(info->weapon_slot >= 0)
                     self->weapon_hit_type = info->weapon[info->weapon_slot].id;
                  else
                     self->weapon_hit_type = 0;
               }
            }
            else
            {
               cheat_state_text(errmsg);
            }
         }
         break;
      case ID_CHEAT_JETPACK:       gta_jetpack_give();                         break;
      case ID_CHEAT_UNLOCK:        cheat_state->generic.vehicles_unlock ^= 1;  break;
      case ID_CHEAT_WEATHER:       cheat_state->generic.force_weather ^= 1;    break;
      case ID_CHEAT_TIME:          cheat_state->generic.force_time ^= 1;       break;
      /*case ID_CHEAT_GRAVITY_INC: gta_gravity_set(gta_gravity_get() + 0.1f);  break;
      case ID_CHEAT_GRAVITY_DEC: gta_gravity_set(gta_gravity_get() - 0.1f);  break;*/
      case ID_CHEAT_HAVEN:         cheat_state->generic.hp_safe_haven ^= 1;    break;
      }
   }
   return 0;
}


static int menu_callback_weapons(int op, struct menu_item *item)
{
   struct weapon_entry *weapon = (struct weapon_entry *)item->data;

   if(op == MENU_OP_ENABLED)
   {
      switch(item->id)
      {
      case ID_NONE:          return 0;
      case ID_WEAPON_ENABLE: return cheat_state->generic.weapon;
      }

      if(weapon == NULL)
         return 0;

      if(set.weapon[weapon->slot] == weapon)
         return 1;
   }
   else if(op == MENU_OP_SELECT)
   {
      switch(item->id)
      {
      case ID_NONE:          return 0;
      case ID_WEAPON_ENABLE: cheat_state->generic.weapon ^= 1; return 0;
      }

      if(weapon == NULL)
         return 0;

      if(set.weapon[weapon->slot] == weapon)
         set.weapon[weapon->slot] = NULL;
      else
         set.weapon[weapon->slot] = weapon;
   }
   return 0;
}


static int menu_callback_patches(int op, struct menu_item *item)
{
   struct patch_set *patch = &set.patch[item->id];

   if(op == MENU_OP_ENABLED)
   {
      return patch->installed;
   }
   else if(op == MENU_OP_SELECT)
   {
      if(patch->installed || patch->failed)
         patcher_remove(patch);
      else
         patcher_install(patch);
   }

   return 0;
}


static int menu_callback_vehicles(int op, struct menu_item *item)
{
   if(item->id == ID_VEHICLES_IWARP)
   {
      if(op == MENU_OP_ENABLED)
         return cheat_state->generic.vehicles_warp_invert;
      else if(op == MENU_OP_SELECT)
         cheat_state->generic.vehicles_warp_invert ^= 1;
   }

   return 0;
}


static int menu_callback_vehicles_sub(int op, struct menu_item *item)
{
   if(op == MENU_OP_SELECT && item->id != ID_NONE)
   {
      struct vehicle_info *info = vehicle_info_get(item->id, VEHICLE_EMPTY|VEHICLE_ALIVE);
      struct actor_info *self = actor_info_get(ACTOR_SELF, ACTOR_ALIVE);
      float pos[3];

      if(info != NULL && self != NULL)
      {
         if(cheat_state->generic.vehicles_warp_invert)
         {
            vect3_copy(&self->base.matrix[4*3], pos);
            pos[0] += sinf(-self->z_angle) * 5.0f;
            pos[1] += cosf(-self->z_angle) * 5.0f;
            pos[2] += 1.0f;
            cheat_vehicle_teleport(info, pos, gta_interior_id_get());

            matrix_identity(info->base.matrix);
            vect3_copy(pos, &info->base.matrix[4*3]);
            vect3_mult(info->speed, 0.0f, info->speed);
            info->speed[2] = 0.05f;
            vect3_mult(info->spin, 0.0f, info->spin);
         }
         else
         {
            vect3_copy(&info->base.matrix[4*3], pos);
            pos[2] += 1.5f;
            cheat_teleport(pos, info->base.interior_id);
         }
      }
   }

   return 0;
}


static int menu_callback_players(int op, struct menu_item *item)
{
   if(op == MENU_OP_SELECT)
   {
      struct samp_player_list *spl = samp_player_list_get();
      struct actor_info *actor = NULL;
      struct vehicle_info *vehicle = NULL;
      int id = item->id;
      float pos[3];

      if(spl->alloc_map[id] != 1)
      {
         cheat_state_text("Player does not exist.");
         return 0;
      }

      if(spl->player_info[id] == NULL)
         goto spl_bad;

      if(spl->player_info[id]->actor != NULL)
         actor = *spl->player_info[id]->actor;
      if(spl->player_info[id]->vehicle != NULL)
         vehicle = *spl->player_info[id]->vehicle;

      if(actor == NULL && vehicle == NULL)
         goto spl_bad;

      if(actor == NULL && vehicle == NULL)
         goto spl_bad;

      if(ACTOR_IS_DEAD(actor))
      {
         cheat_state_text("Player is dead.");
         return 0;
      }


      /* the vehicle position is not updated if the player is a passenger... */
      if(vehicle != NULL && actor->vehicle == vehicle)
         vect3_copy(&actor->vehicle->base.matrix[4*3], pos);
      else if(actor != NULL)
         vect3_copy(spl->player_info[id]->actor_pos, pos);
      else
         goto spl_bad;

      /* detect zombies */
      if(vect3_near_zero(pos))
      {
         if(actor == NULL || actor->base.matrix == NULL)
            goto spl_bad;
         vect3_copy(&actor->base.matrix[4*3], pos);
         if(vect3_near_zero(pos))
            goto spl_bad;
      }

      pos[2] += 1.0f;
      cheat_teleport(pos, 0);  /* XXX interior ID */
   }

   return 0;
spl_bad:;
   cheat_state_text("Bad player info.");
   return 0;
}


static int menu_callback_teleports(int op, struct menu_item *item)
{
   if(op == MENU_OP_SELECT)
   {
      cheat_state_text("Teleported to: %s.",
         set.static_teleport_name[item->id]);
      cheat_teleport(
         set.static_teleport[item->id].pos,
         set.static_teleport[item->id].interior_id);
   }

   return 0;
}


static int menu_callback_misc(int op, struct menu_item *item)
{
   if(op == MENU_OP_ENABLED)
   {
      if(item->id == ID_MISC_HUDTEXT)
         return set.d3dtext_hud;
   }
   else if(op == MENU_OP_SELECT)
   {
      switch(item->id)
      {
      case ID_MISC_COORDS:
         {
            float *pos = (cheat_state->state == CHEAT_STATE_VEHICLE) ?
                  cheat_state->vehicle.coords
                  : cheat_state->actor.coords;
            log_debug("static_teleport_name[] = \"\"");
            log_debug("static_teleport_pos[] = %.2f %.2f %.2f   %d",
               pos[0], pos[1], pos[2], gta_interior_id_get());
            cheat_state_text("Current coordinates written to log file.");
         }
         break;
      case ID_MISC_RELOAD:
         ini_reload();
         break;
      case ID_MISC_HUDTEXT:
         set.d3dtext_hud ^= 1;
         break;
      }
   }

   return 0;
}


static int menu_callback_debug(int op, struct menu_item *item)
{
   if(op == MENU_OP_ENABLED)
   {
      if(item->id == ID_DEBUG_ENABLE)
         return cheat_state->debug_enabled;
      return 0;
   }

   if(op == MENU_OP_SELECT)
   {
      switch(item->id)
      {
      case ID_DEBUG_ENABLE:
         cheat_state->debug_enabled ^= 1;
         break;
      case ID_DEBUG_SELF_ACTOR:       debug_ptr_set((void *)actor_info_get(ACTOR_SELF, 0)); break;
      case ID_DEBUG_SELF_VEHICLE:     debug_ptr_set((void *)vehicle_info_get(VEHICLE_SELF, 0)); break;
      case ID_DEBUG_CAMERA:           debug_ptr_set((void *)cam_matrix); break;
      case ID_DEBUG_VEHICLE_CONTACT:  debug_ptr_set((void *)actor_info_get(ACTOR_SELF, 0)->vehicle_contact); break;
      case ID_DEBUG_BUILDING_CONTACT: debug_ptr_set((void *)actor_info_get(ACTOR_SELF, 0)->building_contact); break;
      case ID_DEBUG_NEAREST_ACTOR:    debug_ptr_set((void *)actor_info_get(actor_find_nearest(0), 0)); break;
      case ID_DEBUG_NEAREST_VEHICLE:  debug_ptr_set((void *)vehicle_info_get(vehicle_find_nearest(0), 0)); break;
      case ID_DEBUG_VEHICLE_POOL:     debug_ptr_set((void *)pool_vehicle); break;
      case ID_DEBUG_SAMP_PLAYER_LIST: debug_ptr_set((void *)samp_player_list_get()); break;
      }
   }

   return 0;
}


void menu_maybe_init(void)
{
   struct menu *menu_main,
                  *menu_cheats,
                     *menu_cheats_spoof_weapon,
                     *menu_cheats_fake_kill,
                  *menu_patches,
                  *menu_weapons,
                  *menu_vehicles,
                  *menu_players,
                  *menu_teleports,
                  *menu_misc,
                     *menu_debug;
   int i, slot;

   if(menu_init)
      return;

   menu_init = 1;

   menu_main                = menu_new(NULL,        ID_MENU_MAIN,                menu_callback_main);
   menu_cheats              = menu_new(menu_main,   ID_MENU_CHEATS,              menu_callback_cheats);
   menu_cheats_spoof_weapon = menu_new(menu_cheats, ID_MENU_CHEATS_SPOOF_WEAPON, menu_callback_cheats);
   menu_cheats_fake_kill    = menu_new(menu_cheats, ID_MENU_CHEATS_FAKE_KILL,    menu_callback_cheats);
   menu_patches             = menu_new(menu_main,   ID_MENU_PATCHES,             menu_callback_patches);
   menu_weapons             = menu_new(menu_main,   ID_MENU_WEAPONS,             menu_callback_weapons);
   menu_vehicles            = menu_new(menu_main,   ID_MENU_VEHICLES,            menu_callback_vehicles);
   menu_players             = menu_new(menu_main,   ID_MENU_PLAYERS,             menu_callback_players);
   menu_teleports           = menu_new(menu_main,   ID_MENU_TELEPORTS,           menu_callback_teleports);
   menu_misc                = menu_new(menu_main,   ID_MENU_MISCELLANEOUS,       menu_callback_misc);
   menu_debug               = menu_new(menu_misc,   ID_MENU_DEBUG,               menu_callback_debug);

   /* main menu */
   menu_item_add(menu_main,   menu_cheats,         "Cheats",             ID_NONE, MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_main,   menu_patches,        "Patches",            ID_NONE, MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_main,   menu_weapons,        "Weapons",            ID_NONE, MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_main,   menu_vehicles,       "Vehicles",           ID_NONE, MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_main,   menu_players,        "Players",            ID_NONE, MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_main,   menu_teleports,      "Teleports",          ID_NONE, MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_main,   menu_misc,           "Miscellaneous",      ID_NONE, MENU_COLOR_DEFAULT, NULL);

   /* main menu -> cheats */
   menu_item_add(menu_cheats, NULL, "Invulnerable",              ID_CHEAT_INVULN,        MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_cheats, NULL, "Money",                     ID_CHEAT_MONEY,         MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_cheats, NULL, "Misc. protections",         ID_CHEAT_PROT,          MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_cheats, NULL, "Warp to nearest empty car", ID_CHEAT_WARP_NEAR,     MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_cheats, NULL, "Give Jetpack",              ID_CHEAT_JETPACK,       MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_cheats, menu_cheats_spoof_weapon, "Spoof weapon", ID_NONE,         MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_cheats, menu_cheats_fake_kill,   "Fake kill",   ID_NONE,           MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_cheats, NULL, "Unlock vehicles",           ID_CHEAT_UNLOCK,        MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_cheats, NULL, "Freeze weather",            ID_CHEAT_WEATHER,       MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_cheats, NULL, "Freeze time",               ID_CHEAT_TIME,          MENU_COLOR_DEFAULT, NULL);
   /*menu_item_add(menu_cheats, NULL, "Increase gravity",          ID_CHEAT_GRAVITY_INC,  MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_cheats, NULL, "Reduce gravity",            ID_CHEAT_GRAVITY_DEC,   MENU_COLOR_DEFAULT, NULL);*/
   menu_item_add(menu_cheats, NULL, "Safe haven (crap)",         ID_CHEAT_HAVEN,         MENU_COLOR_DEFAULT, NULL);

   /* main menu -> cheats -> fake weapon */
   for(i=0; weapon_list[i].name!=NULL; i++)
   {
      const struct weapon_entry *weapon = &weapon_list[i];
      char str[64];

      if(strcmp(weapon->name, "Camera")     == 0 ||
         strcmp(weapon->name, "NV Goggles") == 0 ||
         strcmp(weapon->name, "IR Goggles") == 0 ||
         strcmp(weapon->name, "Parachute")  == 0 ||
         strcmp(weapon->name, "Detonator")  == 0)
         continue;

      snprintf(str, sizeof(str), "Fake killed by %s", weapon->name);
      menu_item_add(menu_cheats_spoof_weapon, NULL, str, ID_CHEAT_SPOOF_WEAPON, MENU_COLOR_DEFAULT, (void *)(UINT_PTR)weapon->id);
   }

   /* main menu -> weapons */
   menu_item_add(menu_weapons, NULL, "Enable weapon cheat",             ID_WEAPON_ENABLE, MENU_COLOR_DEFAULT, NULL);
   for(slot=0; slot<13; slot++)
   {
      const struct weapon_entry *weapon = weapon_list;
      char str[16];

      snprintf(str, sizeof(str), "\tSlot %d", slot);
      menu_item_add(menu_weapons, NULL, str, ID_NONE, MENU_COLOR_SEPARATOR, NULL);

      while(weapon->name != NULL)
      {
         if(weapon->slot == slot)
            menu_item_add(menu_weapons, NULL, weapon->name, ID_WEAPON_ITEM, MENU_COLOR_DEFAULT, (void *)weapon);
         weapon++;
      }
   }

   /* main menu -> vehicles */
   for(i=0; i<VEHICLE_CLASS_COUNT; i++)
   {
      struct menu *menu = menu_new(menu_vehicles, ID_MENU_VEHICLES_SUB, menu_callback_vehicles_sub);
      char str[64];

      snprintf(str, sizeof(str), "%s", gta_vehicle_class_name(i));
      menu_item_add(menu_vehicles, menu, str, ID_NONE, MENU_COLOR_DEFAULT, (void *)(UINT_PTR)i);
   }
   menu_item_add(menu_vehicles, NULL, "Warp vehicles to you", ID_VEHICLES_IWARP, MENU_COLOR_DEFAULT, NULL);

   /* main menu -> patches */
   for(i=0; i<INI_PATCHES_MAX; i++)
   {
      if(set.patch[i].name == NULL)
         continue;

      menu_item_add(menu_patches, NULL, set.patch[i].name, i, MENU_COLOR_DEFAULT, NULL);
   }

   /* teleports */
   for(i=0; i<STATIC_TELEPORT_MAX; i++)
   {
      if(strlen(set.static_teleport_name[i]) == 0)
         continue;

      if(vect3_near_zero(set.static_teleport[i].pos))
         continue;

      menu_item_add(menu_teleports, NULL, set.static_teleport_name[i], i, MENU_COLOR_DEFAULT, NULL);
   }

   /* misc */
   menu_item_add(menu_misc, NULL, "Write coordinates to log file", ID_MISC_COORDS,  MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_misc, NULL, "Reload settings",               ID_MISC_RELOAD,  MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_misc, NULL, "Enable HUD text",               ID_MISC_HUDTEXT, MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_misc, menu_debug, "Debug",                   ID_NONE,         MENU_COLOR_DEFAULT, NULL);

   /* misc -> debug */
   menu_item_add(menu_debug, NULL, "Enable",             ID_DEBUG_ENABLE,           MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_debug, NULL, "Self actor",         ID_DEBUG_SELF_ACTOR,       MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_debug, NULL, "Self vehicle",       ID_DEBUG_SELF_VEHICLE,     MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_debug, NULL, "Camera",             ID_DEBUG_CAMERA,           MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_debug, NULL, "Vehicle contact",    ID_DEBUG_VEHICLE_CONTACT,  MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_debug, NULL, "Building contact",   ID_DEBUG_BUILDING_CONTACT, MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_debug, NULL, "Nearest actor",      ID_DEBUG_NEAREST_ACTOR,    MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_debug, NULL, "Nearest vehicle",    ID_DEBUG_NEAREST_VEHICLE,  MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_debug, NULL, "Vehicle pool",       ID_DEBUG_VEHICLE_POOL,     MENU_COLOR_DEFAULT, NULL);
   menu_item_add(menu_debug, NULL, "SA:MP Player List",  ID_DEBUG_SAMP_PLAYER_LIST, MENU_COLOR_DEFAULT, NULL);

   menu_active = menu_main;
}


static void menu_free(struct menu *menu)
{
   int i;

   for(i=0; i<menu->count; i++)
   {
      if(menu->item[i].submenu != NULL)
      {
         menu_free(menu->item[i].submenu);
         if(menu->item[i].name != NULL)
            free((void *)menu->item[i].name);
      }
   }
   /*if(menu->parent != NULL)
   {
      struct menu *parent = menu->parent;
      for(i=0; i<parent->count; i++)
      {
         if(menu->item[i].submenu == menu)
            menu->item[i].submenu = NULL;
      }
   }*/
   free(menu->item);
   free(menu);
}


void menu_items_free(struct menu *menu)
{
   int i;

   if(menu == NULL)
      return;

   for(i=0; i<menu->count; i++)
   {
      if(menu->item[i].submenu != NULL)
      {
         menu_free(menu->item[i].submenu);
         if(menu->item[i].name != NULL)
            free((void *)menu->item[i].name);
      }
   }
   free(menu->item);
   menu->item = NULL;
   menu->count = 0;
   menu->pos = 0;
   menu->top_pos = 0;
}


void menu_free_all(void)
{
   struct menu *menu;

   if(menu_active == NULL)
      return;

   /* find root menu */
   for(menu=menu_active; menu->parent!=NULL; menu=menu->parent);

   menu_free(menu);
   menu_active = NULL;
   menu_init = 0;
}
