#include "main.h"


void cheat_teleport(const float pos[3], int interior_id)
{
   if(cheat_state->state == CHEAT_STATE_ACTOR)
      cheat_actor_teleport(actor_info_get(ACTOR_SELF, 0), pos, interior_id);
   else if(cheat_state->state == CHEAT_STATE_VEHICLE)
      cheat_vehicle_teleport(vehicle_info_get(VEHICLE_SELF, 0), pos, interior_id);
}


void cheat_teleport_nearest_car(void)
{
   int id = vehicle_find_nearest(VEHICLE_ALIVE|VEHICLE_EMPTY);
   struct vehicle_info *info = vehicle_info_get(id, VEHICLE_ALIVE|VEHICLE_EMPTY);

   if(id == -1 || info == NULL)
      return;

   cheat_teleport(&info->base.matrix[4*3], info->base.interior_id);
}


void cheat_handle_misc(void)
{
   if(KEY_PRESSED(set.key_map))
      cheat_state->generic.map ^= 1;           /* toggle minimap */

   /* time */
   if(cheat_state->generic.force_time >= 0 && set.force_hour >= 0)
      gta_time_hour_set(set.force_hour);

   /* weather */
   if(cheat_state->generic.force_weather && set.force_weather >= 0)
      gta_weather_state_set(set.force_weather);
}


void cheat_handle_debug(void)
{
   static const int data_size[4] = { 1, 2, 4, 4 };
   struct debug_info *debug = &cheat_state->debug;
   int move = 0, hist_chng = 0, i;


   if(!cheat_state->debug_enabled)
      return;

   /* go to pointer */
   if(KEY_PRESSED(VK_NUMPAD1))
   {
      debug_ptr_set(*(void **)debug->cursor_data);
      hist_chng = 1;
   }

   /* go back */
   if(KEY_PRESSED(VK_NUMPAD7))
   {
      if(debug->hist_pos > 0)
      {
         debug->hist_pos--;
         hist_chng = 1;
      }
   }

   /* change data type */
   if(KEY_PRESSED(VK_DIVIDE))
      debug->data_type = (debug->data_type + 1) % 4;

   /* inc/dec value */
   if(KEY_PRESSED(VK_ADD) || KEY_PRESSED(VK_SUBTRACT))
   {
      const int value = KEY_PRESSED(VK_ADD) ? 1 : -1;
      uint8_t data[4] = { 0, 0, 0, 0 };

      if(memcpy_safe(data,
         debug->ptr[debug->hist_pos] + debug->offset[debug->hist_pos],
         data_size[debug->data_type]))
      {
         switch(debug->data_type)
         {
         #pragma warning(disable:4244)
         case 0: (*(uint8_t  *)data) += (uint8_t )value; break;
         case 1: (*(uint16_t *)data) += (uint16_t)value; break;
         case 2: (*(uint32_t *)data) += (uint32_t)value; break;
         case 3: (*(float    *)data) += (float)value / 10.0f; break;
         }
         memcpy_safe(
            debug->ptr[debug->hist_pos] + debug->offset[debug->hist_pos],
            data,
            data_size[debug->data_type]);
      }
   }

   /* copy info to clipboard */
   if(KEY_PRESSED(VK_MULTIPLY))
   {
      if(OpenClipboard(NULL))
      {
         HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, sizeof(debug->ptr_hist_str) * 2);

         if(mem != NULL)
         { 
            char *str = (char *)GlobalLock(mem);

            strlcpy(str, debug->ptr_hist_str, sizeof(debug->ptr_hist_str));
            GlobalUnlock(str);

            SetClipboardData(CF_OEMTEXT, mem);
            /*SetClipboardData(CF_TEXT, mem);*/
         }
         CloseClipboard();
      }
   }

   if(KEY_PRESSED(VK_NUMPAD4)) move -= data_size[debug->data_type];
   if(KEY_PRESSED(VK_NUMPAD6)) move += data_size[debug->data_type];
   if(KEY_PRESSED(VK_NUMPAD8)) move += -16;
   if(KEY_PRESSED(VK_NUMPAD2)) move += 16;
   if(KEY_PRESSED(VK_NUMPAD9)) move += -160;
   if(KEY_PRESSED(VK_NUMPAD3)) move += 160;

   debug->offset[debug->hist_pos] += move;

   if(move != 0 || hist_chng)
   {
      memset(debug->modify_time, 0, DEBUG_DATA_SIZE * sizeof(uint32_t));
      debug->data_prev_clear = 1;
   }


   for(i=0; i<9; i++)
      KEY_CONSUME(VK_NUMPAD1 + i);
   KEY_CONSUME(VK_MULTIPLY);
   KEY_CONSUME(VK_DIVIDE);
   KEY_CONSUME(VK_ADD);
   KEY_CONSUME(VK_SUBTRACT);
}


void cheat_handle_spoof_weapon(void)
{
   struct actor_info *info = actor_info_get(ACTOR_SELF, ACTOR_ALIVE);
   /*struct samp_player_list *spl = samp_player_list_get();*/

   if(info != NULL)
   {
      if(cheat_state->generic.spoof_weapon != -1)
         info->weapon_hit_type = (uint8_t)cheat_state->generic.spoof_weapon;
   }
}


void cheat_handle_unlock(void)
{
   static struct patch_set patch_unlock = {
         "unlock", 0, 0, {
         { 2, (void *)((uint8_t *)dll_baseptr_get("samp.dll") + 0x00011FE1),
              (uint8_t *)"\x75\x14", (uint8_t *)"\xEB\x14", NULL }
      } };
   struct vehicle_info *info;
   int i;

   if(cheat_state->generic.vehicles_unlock)
   {
      for(i=0; i<pool_vehicle->size; i++)
      {
         info = vehicle_info_get(i, 0);
         if(info != NULL)
            info->door_status = 1;  /* unlocked */
      }
      patcher_install(&patch_unlock);
   }
   else
   {
      patcher_remove(&patch_unlock);
   }
}


void cheat_handle_hp(struct vehicle_info *vehicle_info, struct actor_info *actor_info, float time_diff)
{
   /* HP reduction code */
   static struct patch_set hp_patch = {
         "extra invincibility", 0, 0, {
         { 10, (void *)0x00637590, (uint8_t *)"\xC7\x87\x40\x05\x00\x00\x00\x00\x00\x00", NULL, NULL },
         { 10, (void *)0x0063070C, (uint8_t *)"\xC7\x86\x40\x05\x00\x00\x00\x00\x00\x00", NULL, NULL },
         { 6,  (void *)0x004B331F, (uint8_t *)"\x89\x96\x40\x05\x00\x00", NULL, NULL },
         { 6,  (void *)0x004B3395, (uint8_t *)"\x89\x9e\x40\x05\x00\x00", NULL, NULL }
      } };
   static uint32_t time_safe_haven;
   static float prev_pos[3];
   static float safe_pos[3];


   if(KEY_PRESSED(set.key_hp_cheat))
      cheat_state->generic.hp_cheat ^= 1;      /* toggle hp cheat */

   if(KEY_PRESSED(set.key_safe_haven))
      cheat_state->generic.hp_safe_haven ^= 1; /* toggle "safe haven" hp cheat */

   if(cheat_state->generic.hp_cheat)
      patcher_install(&hp_patch);
   else
      patcher_remove(&hp_patch);

   if(vehicle_info != NULL)
   {
      static float hitpoints_last = 1000.0f;
      struct vehicle_info *info = vehicle_info;

      if(cheat_state->generic.hp_cheat)
      {
         /* damage reduction */
         if(!near_zero(set.hp_damage_reduce))
         {
            if(info->hitpoints < hitpoints_last)
            {
               float diff = hitpoints_last - info->hitpoints;

               info->hitpoints =
                     hitpoints_last - diff * (1.0f - set.hp_damage_reduce / 100.0f);
            }
         }

         /* minimum hp */
         if(info->hitpoints < set.hp_minimum)
            info->hitpoints = set.hp_minimum;

         /* regen */
         if(info->hitpoints < 1000.0f)
            info->hitpoints += time_diff * set.hp_regen;

         if(set.hp_indestructible)
         {
            /* http://gtamodding.com/index.php?title=Memory_Addresses_%28SA%29 */
            info->flags |= VEHICLE_FLAGS_INDESTRUCTIBLE;    /* invulnerable to most things */
            info->flags &= ~2;                              /* make sure we're not frozen :p */
         }
         else
         {
            info->flags &= ~VEHICLE_FLAGS_INDESTRUCTIBLE;
         }
      }
      else
      {
         /* HP cheat disabled - keep HP value sane */
         if(info->hitpoints > 1000.0f)
            info->hitpoints = 1000.0f;
         info->flags &= ~VEHICLE_FLAGS_INDESTRUCTIBLE;
      }

      hitpoints_last = info->hitpoints;
   }

   if(actor_info != NULL)
   {
      struct actor_info *info = actor_info;

      if(cheat_state->generic.hp_cheat && set.hp_indestructible)
         info->flags |= ACTOR_FLAGS_INVULNERABLE;
      else
         info->flags &= ~ACTOR_FLAGS_INVULNERABLE;

      if(cheat_state->generic.hp_safe_haven              &&
         info->hitpoints > 1.0f                          &&
         info->hitpoints < set.hp_safe_haven_warp_below  &&
         time_safe_haven == 0)
      {
         const float *pos = safe_haven_nearest_pos_get(&actor_info->base.matrix[4*3]);

         if(pos != NULL)
         {
            time_safe_haven = time_get();

            vect3_copy(&actor_info->base.matrix[4*3], prev_pos);
            vect3_copy(pos, safe_pos);
         }
      }

      if(time_safe_haven != 0)
      {
         if(time_get() - time_safe_haven > MSEC_TO_TIME(100))
         {
            actor_info->hitpoints = 100.0f;
            vect3_copy(safe_pos, &actor_info->base.matrix[4*3]);

            if(time_get() - time_safe_haven > MSEC_TO_TIME(200))
            {
               time_safe_haven = 0;
               vect3_copy(prev_pos, &actor_info->base.matrix[4*3]);
            }
         }
         else
         {
            vect3_copy(safe_pos, &actor_info->base.matrix[4*3]);
         }
      }
   }
}


void cheat_handle_stick(struct vehicle_info *vehicle_info, struct actor_info *actor_info, float time_diff)
{
   struct object_base *base_stick, *base_self;
   struct actor_info *actor_stick;
   struct vehicle_info *vehicle_stick;
   float *speed_stick, *speed_self;
   float *spin_stick, *spin_self;
   static int id = -1;
   int i;


   if(KEY_PRESSED(set.key_stick))
   {
      if(vehicle_info != NULL)
         cheat_state->vehicle.stick ^= 1;
      else
         cheat_state->actor.stick ^= 1;
      id = actor_find(id - 1, 1, ACTOR_ALIVE|ACTOR_NOT_SAME_VEHICLE);
   }

   if(KEY_PRESSED(set.key_stick_nearest))
   {
      if(vehicle_info != NULL)
         cheat_state->vehicle.stick ^= 1;
      else
         cheat_state->actor.stick ^= 1;
      id = actor_find_nearest(ACTOR_ALIVE|ACTOR_NOT_SAME_VEHICLE);
   }

   if((vehicle_info != NULL && cheat_state->vehicle.stick) ||
      (actor_info   != NULL && cheat_state->actor.stick) )
   {
      /* check if actor has disappeared.. and if it has, switch to teh nearest */
      if(id != -1 && actor_info_get(id, ACTOR_ALIVE) == NULL)
         id = actor_find_nearest(ACTOR_ALIVE|ACTOR_NOT_SAME_VEHICLE);

      if(KEY_PRESSED(set.key_stick_prev))
         id = actor_find(id, -1, ACTOR_ALIVE|ACTOR_NOT_SAME_VEHICLE);

      if(KEY_PRESSED(set.key_stick_next))
         id = actor_find(id, 1, ACTOR_ALIVE|ACTOR_NOT_SAME_VEHICLE);

      /* no actors to stick to */
      if(id == -1)
      {
         cheat_state_text("No players found; stick disabled.");
         cheat_state->vehicle.stick = 0;
         cheat_state->actor.stick = 0;
         return;
      }

      /* get actor struct for the actor we're sticking to */
      actor_stick = actor_info_get(id, ACTOR_ALIVE|ACTOR_NOT_SAME_VEHICLE);
      if(actor_stick == NULL)
         return;

      /* is this actor in a vehicle? */
      vehicle_stick = actor_vehicle_get(actor_stick);

      base_stick  = vehicle_stick ? &vehicle_stick->base : &actor_stick->base;
      base_self   = vehicle_info  ? &vehicle_info->base  : &actor_info->base;

      speed_stick = vehicle_stick ? vehicle_stick->speed : actor_stick->speed;
      speed_self  = vehicle_info  ? vehicle_info->speed  : actor_info->speed;

      spin_stick  = vehicle_stick ? vehicle_stick->spin  : actor_stick->spin;
      spin_self   = vehicle_info  ? vehicle_info->spin   : actor_info->spin;

      /* allow warping to work + always warp towards whatever we're sticking to...
         but only when we're in a vehicle */
      if(KEY_PRESSED(set.key_warp_mod) && vehicle_info != NULL)
      {
         float out[4];

         /* multiply the matrix of whatever we're sticking to with the user supplied vector */
         matrix_vect4_mult(base_stick->matrix, set.stick_vect, out);
         /* multiply the result with the negative warp-speed value, and put it in the speed vector
            (negative because we want to warp towards teh target, not away from it */
         vect3_mult(out, -set.warp_speed, speed_self);
      }

      if(!KEY_DOWN(set.key_warp_mod))
      {
         float d[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
         float accel_mult = 1.0f;
         float out[4];


         /* handle stick movement keys */
         if(KEY_DOWN(set.key_stick_forward))  d[1] += 1.0f;
         if(KEY_DOWN(set.key_stick_backward)) d[1] -= 1.0f;
         if(KEY_DOWN(set.key_stick_left))     d[0] -= 1.0f;
         if(KEY_DOWN(set.key_stick_right))    d[0] += 1.0f;
         if(KEY_DOWN(set.key_stick_up))       d[2] += 1.0f;
         if(KEY_DOWN(set.key_stick_down))     d[2] -= 1.0f;
         if(KEY_DOWN(set.key_stick_in))       d[3] -= 1.0f;
         if(KEY_DOWN(set.key_stick_out))      d[3] += 1.0f;

         if(!near_zero(set.stick_accel_time))
         {
            static uint32_t time_start;

            if(!vect4_near_zero(d))
               time_start = (time_start == 0) ? time_get() : time_start;
            else
               time_start = 0;   /* no keys pressed */

            /* acceleration */
            if(time_start != 0)
            {
               float t = TIME_TO_FLOAT(time_get() - time_start);
               if(t < set.stick_accel_time)
                  accel_mult *= t / set.stick_accel_time;
            }
         }

         /* calculate new vector + dist */
         if(!vect3_near_zero(d) && !vect3_near_zero(set.stick_vect))
         {
            for(i=0; i<3; i++)
            {
               d[i] = set.stick_vect[i] * set.stick_vect_dist
                     + d[i] * time_diff * 8.0f * accel_mult;
            }
            set.stick_vect_dist = vect3_length(d);
            vect3_normalize(d, set.stick_vect);
         }

         /* move towards/away from the center */
         if(!near_zero(d[3]))
            set.stick_vect_dist += d[3] * time_diff * 40.0f * accel_mult;


         /* Teleport vehicle detachables */
         if(vehicle_info != NULL)
            vehicle_detachables_teleport(vehicle_info, &base_self->matrix[4*3], &base_stick->matrix[4*3]);

         matrix_copy(base_stick->matrix, base_self->matrix);
         vect3_copy(speed_stick, speed_self);
         vect3_copy(spin_stick, spin_self);

         /*base_self->interior_id = base_stick->interior_id;
         gta_interior_id_set(base_stick->interior_id);*/

         /* multiply the matrix of the target with the user supplied vector */
         matrix_vect4_mult(base_stick->matrix, set.stick_vect, out);
         /* multiply the result with the user supplied vector distance */
         vect3_mult(out, set.stick_vect_dist, out);
         /* and add it to our position */
         vect3_vect3_add(&base_self->matrix[4*3], out, &base_self->matrix[4*3]);

         if(vehicle_info != NULL)
         {
            /* Teleport detachables again :p */
            vehicle_detachables_teleport(vehicle_info, &base_stick->matrix[4*3], &base_self->matrix[4*3]);
            vehicle_prevent_below_height(vehicle_info, set.stick_min_height);
         }

         /*if(actor_info != NULL && actor_stick != NULL)
         {
            actor_info->z_angle = actor_stick->z_angle;
            actor_info->z_angle2 = actor_stick->z_angle2;
         }*/
      }
   }
}


static int __money_interval_rand_time(void)
{
   const int min = set.money_interval_rand_min;
   const int max = set.money_interval_rand_max;

   return MSEC_TO_TIME( (min + (rand() % (max - min))) * 1000 );
}


void cheat_handle_money(void)
{
   static int init_money = -1;
   static uint32_t next_time;


   if(init_money != set.money_amount_max)
   {
      /* add up to 50% more $$$ */
      if(set.money_amount_max > 2)
         set.money_amount_max += rand() % (set.money_amount_max / 2);
      /* cheap way to detect .ini reload */
      init_money = set.money_amount_max;
   }

   if(!cheat_state->generic.money)
      next_time = time_get();

   if(KEY_PRESSED(set.key_money))
      cheat_state->generic.money ^= 1;

   if(cheat_state->generic.money && time_get() >= next_time)
   {
      const int min = set.money_amount_rand_min;
      const int max = set.money_amount_rand_max;
      uint32_t money = gta_money_get();
      int add;

      if(money < (uint32_t)set.money_amount_max)
      {
         add = ((max - min) > 0) ? (rand() % (max - min)) : 0;
         add = ((add + 50) / 100) * 100;
         money += min + add;
         gta_money_set(money);
      }

      next_time = time_get() + __money_interval_rand_time();
   }
}


void cheat_handle_weapon(void)
{
   static int models_loaded;
   struct actor_info *actor_info = actor_info_get(ACTOR_SELF, ACTOR_ALIVE);
   int i;


   if(!models_loaded)
   {
      const struct weapon_entry *weapon;

      log_debug("Loading all weapon models...");
      for(weapon=weapon_list; weapon->name!=NULL; weapon++)
      {
         if(weapon->model_id == -1)
            continue;
         /*log_debug("Requesting weapon model %d...", weapon->model_id);*/
         gta_model_request(weapon->model_id);
      }
      log_debug("Done.");
      models_loaded = 1;
   }

   if(KEY_PRESSED(set.key_weapon))
      cheat_state->generic.weapon ^= 1;        /* toggle weapon cheat */

   if(cheat_state->generic.weapon)
   {
      if(actor_info != NULL)
      {
         for(i=0; i<13; i++)
         {
            if(set.weapon[i] == NULL)
               continue;

            gta_weapon_set(actor_info, set.weapon[i]->slot, set.weapon[i]->id, 198, 99);
         }
      }
   }
}


void cheat_handle_teleport(struct vehicle_info *vehicle_info, struct actor_info *actor_info, float time_diff)
{
   struct object_base *base = (vehicle_info != NULL) ? &vehicle_info->base : &actor_info->base;
   int i;


   /* Set teleport coordinates */
   for(i=0; i<TELEPORT_MAX; i++)
   {
      if(set.key_teleport_set[i] == 0)
         continue;
      if(KEY_PRESSED(set.key_teleport_set[i]))
      {
         cheat_state->teleport[i].set = 1;
         matrix_copy(base->matrix, cheat_state->teleport[i].matrix);
         cheat_state->teleport[i].interior_id = gta_interior_id_get();
         cheat_state_text("Teleport coordinates set.");
      }
   }

   /* Teleport to stored coordinates */
   for(i=0; i<TELEPORT_MAX; i++)
   {
      if(set.key_teleport[i] == 0)
         continue;
      if(KEY_PRESSED(set.key_teleport[i]))
      {
         if(cheat_state->teleport[i].set)
         {
            cheat_teleport(&cheat_state->teleport[i].matrix[4*3], cheat_state->teleport[i].interior_id);
            /* when teleports are stored in-game, we have a copy of the matrix to preserve rotation, etc.. */
            matrix_copy(cheat_state->teleport[i].matrix, base->matrix);
         }
         else if(!vect3_near_zero(set.teleport[i].pos))
         {
            cheat_teleport(set.teleport[i].pos, set.teleport[i].interior_id);
         }
         else
         {
            cheat_state_text("Teleport coordinates not set.");
         }
         return;
      }
   }
}


void cheat_handle_checkpoint(void)
{
   if(KEY_PRESSED(set.key_checkpoint_1) || KEY_PRESSED(set.key_checkpoint_2))
   {
      int n = KEY_PRESSED(set.key_checkpoint_1) ? 0 : 1;
      struct checkpoint *cp = gta_checkpoint_info_get(n);
      float pos[3];

      if(cp != NULL)
      {
         struct vehicle_info *vehicle_info = vehicle_info_get(VEHICLE_SELF, 0);

         vect3_copy(cp->position, pos);
         pos[2] += 1.0f;
         cheat_teleport(pos, 0); /* XXX interior id? */

         if(vehicle_info != NULL)
            vehicle_prevent_below_height(vehicle_info, set.checkpoint_min_height);
      }
      else
      {
         cheat_state_text("Checkpoint does not exist.");
      }
   }
}

