/* MegaZeux
 *
 * robo_ops.c - Primitive Robotic operations.
 *
 * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>
 * Copyright (c) 2008 Patrick Walton <pcwalton@uchicago.edu>
 *
 * This program 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 Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "audio.h"
#include "board.h"
#include "game.h"
#include "game2.h"
#include "idarray.h"
#include "idput.h"
#include "param.h"
#include "robot.h"
#include "util.h"
#include "world.h"

#include "robo_ops.h"

#include "SDL.h" // for Uint8

/* TODO: this is ugly. Eventually we should eliminate this macro. */
#define PARSEDIR(a, b, c, d) \
 parsedir(ctx->world, a, b, c, d, ctx->blocked[0], ctx->blocked[1], \
         ctx->blocked[2], ctx->blocked[3])
#define PARSEDIR_NO_CONTEXT(a, b, c, d) \
 parsedir(mzx_world, a, b, c, d, _bl[0], _bl[1], _bl[2], _bl[3])

void calculate_blocked(World *mzx_world, int x, int y, int id, int bl[4])
{
  Board *src_board = mzx_world->current_board;
  int board_width = src_board->board_width;
  char *level_id = src_board->level_id;

  if(id)
  {
    int i, offset, new_x, new_y;
    for(i = 0; i < 4; i++)
    {
      new_x = x;
      new_y = y;

      if(!move_dir(src_board, &new_x, &new_y, int_to_dir(i)))
      {
        offset = new_x + (new_y * board_width);
        // Not edge... blocked?
        if((flags[(int)level_id[offset]] & A_UNDER) != A_UNDER)
          bl[i] = 1;
        else
          bl[i] = 0;
      }
      else
      {
        bl[i] = 1;  // Edge is considered blocked
      }
    }
  }
}

void magic_load_mod(World * mzx_world, char *filename)
{
  Board *src_board = mzx_world->current_board;
  int mod_name_size = strlen(filename);
  if((mod_name_size > 1) && (filename[mod_name_size - 1] == '*'))
  {
    filename[mod_name_size - 1] = 0;
    if(strcasecmp(src_board->mod_playing, filename))
      load_module(filename);

    src_board->mod_playing[0] = '*';
  }
  else
  {
    if(filename[0] != '*')
      load_module(filename);

    strcpy(src_board->mod_playing, filename);
  }

  strcpy(mzx_world->real_mod_playing, filename);
}

int place_at_xy(World *mzx_world, mzx_thing id, int color, int param,
 int x, int y)
{
  Board *src_board = mzx_world->current_board;
  int board_width = src_board->board_width;
  int offset = x + (y * board_width);
  mzx_thing new_id = (mzx_thing)src_board->level_id[offset];

  // Okay, I'm really stabbing at behavior here.
  // Wildcard param, use source but only if id matches and isn't
  // greater than or equal to 122..
  if(param == 256)
  {
    // The param must represent the char for this to happen,
    // nothing else will give.
    if((id_chars[new_id] != 255)  || (id_chars[id] != 255))
    {
      if(def_params[id] > 0)
        param = def_params[id];
      else
        param = 0;
    }
    else
    {
      param = src_board->level_param[offset];
    }
  }

  if(new_id == SENSOR)
  {
    int new_param = src_board->level_param[offset];
    clear_sensor_id(src_board, new_param);
  }
  else

  if(is_signscroll(new_id))
  {
    int new_param = src_board->level_param[offset];
    clear_scroll_id(src_board, new_param);
  }
  else

  if(is_robot(new_id))
  {
    int new_param = src_board->level_param[offset];
    clear_robot_id(src_board, new_param);
  }
  else

  if(new_id == PLAYER)
  {
    return 0; // Don't allow the player to be overwritten
  }

  if(param == 256)
  {
    param = src_board->level_param[offset];
  }

  id_place(mzx_world, x, y, id,
   fix_color(color, src_board->level_color[offset]), param);

  return 1;
}

int place_dir_xy(World *mzx_world, mzx_thing id, int color, int param,
 int x, int y, mzx_dir direction, Robot *cur_robot, int *_bl)
{
  Board *src_board = mzx_world->current_board;

  // Check under
  if(direction == BENEATH)
  {
    return place_under_xy(src_board, id, color, param, x, y);
  }
  else
  {
    int new_x = x;
    int new_y = y;
    direction = PARSEDIR_NO_CONTEXT(direction, x, y, cur_robot->walk_dir);

    if(is_cardinal_dir(direction))
    {
      if(!move_dir(src_board, &new_x, &new_y, direction))
      {
        return place_at_xy(mzx_world, id, color, param, new_x, new_y);
      }
    }
    return 0;
  }
}

int place_under_xy(Board *src_board, mzx_thing id, int color, int param,
 int x, int y)
{
  int board_width = src_board->board_width;
  char *level_under_id = src_board->level_under_id;
  char *level_under_color = src_board->level_under_color;
  char *level_under_param = src_board->level_under_param;

  int offset = x + (y * board_width);
  if(param == 256)
    param = level_under_param[offset];

  color = fix_color(color, level_under_color[offset]);
  if(level_under_id[offset] == SENSOR)
    clear_sensor_id(src_board, level_under_param[offset]);

  level_under_id[offset] = (char)id;
  level_under_color[offset] = color;
  level_under_param[offset] = param;

  return 1;
}

void robo_op_recalculate_blocked(RoboOpContext * ctx)
{
  calculate_blocked(ctx->world, *ctx->x, *ctx->y, ctx->id, ctx->blocked);
}

void send_at_xy(World *mzx_world, int id, int x, int y, char *label)
{
  Board *src_board = mzx_world->current_board;
  int offset = x + (y * src_board->board_width);
  mzx_thing d_id = (mzx_thing)src_board->level_id[offset];

  if(is_robot(d_id))
  {
    char label_buffer[ROBOT_MAX_TR];
    int d_param = src_board->level_param[offset];

    tr_msg(mzx_world, label, id, label_buffer);

    if(d_param == id)
    {
      send_robot_self(mzx_world,
       mzx_world->current_board->robot_list[id], label_buffer);
    }
    else
    {
      send_robot_id(mzx_world, src_board->level_param[offset],
       label_buffer, 0);
    }
  }
}

void split_colors(int color, int *fg, int *bg)
{
  if(color & 256)
  {
    color ^= 256;
    if(color == 32)
    {
      *fg = 16;
      *bg = 16;
    }
    else
     if(color < 16)
    {
      *bg = 16;
      *fg = color;
    }
    else
    {
      *bg = color - 16;
      *fg = 16;
    }
  }
  else
  {
    *bg = color >> 4;
    *fg = color & 0x0F;
  }
}

void robo_op_avalanche(World * mzx_world, Board * src_board)
{
  char *level_id = src_board->level_id;
  int board_width = src_board->board_width;
  int board_height = src_board->board_height;

  int x, y, offset;
  // Set the placement rate
  int placement_rate = 18;
  int d_flag;

  for(y = 0, offset = 0; y < board_height; y++)
  {
    for(x = 0; x < board_width; x++, offset++)
    {
      d_flag = flags[(int)level_id[offset]];

      if((d_flag & A_UNDER) && !(d_flag & A_ENTRANCE) &&
       (Random(placement_rate)) == 0)
      {
        id_place(mzx_world, x, y, BOULDER, 7, 0);
      }
    }
  }
}

int robo_op_become(RoboOpContext *ctx, int color, mzx_thing new_id,
 int param)
{
  World *mzx_world;
  Board *src_board;
  Uint8 *level_color, *level_param;
  mzx_thing *level_id;
  int id, board_width, offset;

  if (!ctx->id)
    return 0;

  mzx_world = ctx->world;
  src_board = ctx->board;
  id = ctx->id;
  board_width = ctx->board->board_width;
  offset = *ctx->x + (*ctx->y * board_width);
  level_color = (Uint8 *) ctx->board->level_color;
  level_id = (mzx_thing *) ctx->board->level_id;
  level_param = (Uint8 *) ctx->board->level_param;

  color = fix_color(color, level_color[offset]);

  level_color[offset] = color;
  level_id[offset] = new_id;

  // Param- Only change if not becoming a robot
  if(!is_robot(new_id))
  {
    level_param[offset] = param;
    clear_robot_id(src_board, id);

    // If became a scroll, sensor, or sign...
    if((new_id >= SENSOR) && (new_id <= PLAYER))
      id_remove_top(mzx_world, *ctx->x, *ctx->y);

    // Delete "under"? (if became another type of "under")
    if(flags[new_id] & A_UNDER)
    {
      // Became an under, so delete under.
      src_board->level_under_param[offset] = SPACE;
      src_board->level_under_id[offset] = 0;
      src_board->level_under_color[offset] = 7;
    }

    return 1;
  }

  return 0;
}

void robo_op_change(RoboOpChangeDescriptor * desc)
{
  World *mzx_world = desc->mzx_world;
  Board *src_board = desc->src_board;
  int check_color = desc->check_color;
  mzx_thing check_id = desc->check_id;
  int check_param = desc->check_param;
  int put_color = desc->put_color;
  mzx_thing put_id = desc->put_id;
  int put_param = desc->put_param;

  char *level_id = src_board->level_id;
  int board_width = src_board->board_width;
  int board_height = src_board->board_height;
  char *level_param = src_board->level_param;
  char *level_color = src_board->level_color;

  int check_fg, check_bg, put_fg, put_bg;
  mzx_thing d_id;
  int offset, d_param, d_color;

  split_colors(check_color, &check_fg, &check_bg);
  split_colors(put_color, &put_fg, &put_bg);

  // Make sure the change isn't incompatable
  // Only robots (pushable or otherwise) can be changed to
  // robots. The same goes for scrolls/signs and sensors.
  // Players cannot be changed at all

  if(((put_id != SENSOR) || (check_id == SENSOR)) &&
    (((put_id != ROBOT_PUSHABLE) || ((check_id == ROBOT)
          || (check_id == ROBOT_PUSHABLE))) &&
      ((put_id != ROBOT) || ((check_id == ROBOT_PUSHABLE) ||
          (check_id == ROBOT))) &&
      ((put_id != SIGN) || (check_id == SCROLL))) &&
    ((put_id != SCROLL) || (check_id == SIGN)) &&
    ((put_id != PLAYER) && (check_id != PLAYER)))
  {
    // Clear out params for param-less objects,
    // lava, fire, ice, energizer, rotates, or life
    if((put_id == ICE) || (put_id == LAVA) ||
      (put_id == ENERGIZER) || (put_id == CW_ROTATE) ||
      (put_id == CCW_ROTATE) || (put_id == FIRE) || (put_id == LIFE))
    {
      put_param = 0;
    }

    // Ignore upper stages for explosions
    if(put_id == EXPLOSION)
      put_param &= 0xF3;

    // Open door becomes door
    if(check_id == OPEN_DOOR)
      check_id = DOOR;

    // Whirlpool becomes base whirlpool
    if(is_whirlpool(check_id))
      check_id = WHIRLPOOL_1;

    // Cannot change param on these
    if(put_id > SENSOR)
      put_param = 256;

    for(offset = 0; offset < (board_width * board_height); offset++)
    {
      d_id = (mzx_thing) level_id[offset];
      d_param = level_param[offset];
      d_color = level_color[offset];

      // open door becomes door
      if(d_id == OPEN_DOOR)
        d_id = DOOR;

      // Whirpool becomes base one
      if(is_whirlpool(d_id))
        d_id = WHIRLPOOL_1;

      if((d_id == check_id) && (((d_color & 0x0F) == check_fg) ||
          (check_fg == 16)) && (((d_color >> 4) == check_bg) ||
          (check_bg == 16)) && ((d_param == check_param) ||
          (check_param == 256)))
      {
        // Change the color and the ID
        level_color[offset] = fix_color(put_color, d_color);
        level_id[offset] = put_id;

        if(((d_id == ROBOT_PUSHABLE) || (d_id == ROBOT)) &&
          (put_id != ROBOT) && (put_id != ROBOT_PUSHABLE))
        {
          // delete robot if not changing to a robot
          clear_robot_id(src_board, d_param);
        }
        else
         if(((d_id == SIGN) || (d_id == SCROLL)) &&
          (put_id != SIGN) && (put_id != SCROLL))
        {
          // delete scroll if not changing to a scroll/sign
          clear_scroll_id(src_board, d_param);
        }
        else
         if((d_id == SENSOR) && (put_id != SENSOR))
        {
          // delete sensor if not changing to a sensor
          clear_sensor_id(src_board, d_param);
        }

        if(put_id == 0)
        {
          offs_remove_id(mzx_world, offset);
          // If this LEAVES a space, use given color
          if(level_id[offset] == 0)
            level_color[offset] = fix_color(put_color, d_color);
        }
        else
        {
          if(put_param != 256)
            level_param[offset] = put_param;
        }
      }
    }
  }
}

int robo_op_char_id(int id_char, int id_value)
{
  int rv;

  if((id_char >= 0) && (id_char <= 454))
  {
    if(id_char == 323)
      rv = missile_color = id_value;
    else if((id_char >= 324) && (id_char <= 326))
      rv = bullet_color[id_char - 324] = id_value;
    else if((id_char >= 327))
      rv = id_dmg[id_char - 327] = id_value;
    else
      rv = id_chars[id_char] = id_value;
  } else {
    if(id_char == 323)
      rv = missile_color;
    else if(id_char >= 324 && id_char < 326)
      rv = bullet_color[id_char - 324];
    else if(id_char >= 327)
      rv = id_dmg[id_char - 327];
    else
      rv = id_chars[id_char];
  }

  return rv;
}

int robo_op_copy_block(RoboOpContext *ctx, Layer src_layer, Point src_pt,
 Size size, Layer dest_layer, Point dest_pt)
{
  copy_clipped_block(ctx->world, ctx->board, src_layer, src_pt, size,
      dest_layer, dest_pt);
  return 1;
}

int robo_op_fetch(RoboOpContext *ctx, Layer layer, Point pt, Ctp *ctp)
{
  *ctp = yank(ctx->world, ctx->board, layer, pt);
  return 1;
}

int robo_op_gotoxy(RoboOpContext *ctx, Point dest_pt)
{
  Point src_pt;
  src_pt.x = *ctx->x;
  src_pt.y = *ctx->y;
  src_pt.z = 0;
  dest_pt.z = 0;
  move_from(ctx->world, ctx->board, LAYER_BOARD, src_pt, dest_pt);
  *ctx->x = dest_pt.x;
  *ctx->y = dest_pt.y;
  return 0;
}

void robo_op_lock_scroll(RoboOpContext *ctx)
{
  World *mzx_world;
  Board *src_board;
  int scroll_x, scroll_y, n_scroll_x, n_scroll_y;

  mzx_world = ctx->world;
  src_board = ctx->board;

  // The scrolling is locked at the current position.
  // Further scrollview cmds can change it.
  scroll_x = src_board->scroll_x;
  scroll_y = src_board->scroll_y;
  src_board->scroll_x = 0;
  src_board->scroll_y = 0;
  calculate_xytop(mzx_world, &n_scroll_x, &n_scroll_y);
  src_board->locked_x = n_scroll_x;
  src_board->locked_y = n_scroll_y;
  src_board->scroll_x = scroll_x;
  src_board->scroll_y = scroll_y;
}

void robo_op_shootseeker(RoboOpContext *ctx, mzx_dir direction)
{
  if(ctx->id)
  {
    direction = PARSEDIR(direction, *ctx->x, *ctx->y, ctx->robot->walk_dir);
    if(is_cardinal_dir(direction))
    {
      shoot_seeker(ctx->world, *ctx->x, *ctx->y, dir_to_int(direction));
      robo_op_recalculate_blocked(ctx);
    }
  }
}

void robo_op_spitfire(RoboOpContext * ctx, mzx_dir direction)
{
  if(ctx->id)
  {
    direction = PARSEDIR(direction, *ctx->x, *ctx->y, ctx->robot->walk_dir);
    if(is_cardinal_dir(direction))
    {
      shoot_fire(ctx->world, *ctx->x, *ctx->y, dir_to_int(direction));
      robo_op_recalculate_blocked(ctx);
    }
  }
}

int robo_op_teleport_player(RoboOpContext *ctx, const char *board_dest_buffer,
    int teleport_x, int teleport_y)
{
  World *mzx_world;
  int board_id, current_board_id;

  mzx_world = ctx->world;
  current_board_id = mzx_world->current_board_id;

  board_id = find_board(mzx_world, (char *)board_dest_buffer);

  if(board_id != NO_BOARD)
  {
    mzx_world->current_board = mzx_world->board_list[board_id];
    prefix_mid_xy(mzx_world, &teleport_x, &teleport_y,
      *ctx->x, *ctx->y);
    // And switch back
    mzx_world->current_board =
     mzx_world->board_list[current_board_id];
    mzx_world->target_board = board_id;
    mzx_world->target_x = teleport_x;
    mzx_world->target_y = teleport_y;
    mzx_world->target_where = TARGET_TELEPORT;
    return current_board_id == board_id ? 2 : 1;
  }

  return 0;
}

