#include <errno.h>
#include "Map.h"

Cursor cursor;
Map map;
int moves = 0;
Interact interact;

typedef struct _gameCollisionCheck {
  int start_x, start_y, end_x, end_y;
  SDL_Rect rect;
} GameCollisionCheck;

void waitUntilPaintingStops();
void finishDrawMap();
int last_dir = -1;
// some private global variables used to draw the map
int screen_center_x, screen_center_y; // the screen's center in tiles
int screen_w, screen_h;         // the screen's size in tiles

/** 
	Which section of the screen is visible
*/
typedef struct _mapDrawParams {
  int start_x;                  // where to start drawing the map
  int start_y;
  int end_x;                    // where to stop drawing the map
  int end_y;
  int offset_x;                 // how many tiles to offset from the top/left edge of the screen
  int offset_y;
} MapDrawParams;
MapDrawParams global_params;

// artificially moving the background
int move_background_x = 0;
int move_background_y = 0;

/**
   Compute what to draw based on the cursor's location.
 */
void
getMapDrawParams(MapDrawParams * params)
{
  int n;

  map.top_left_x = cursor.pos_x - screen_center_x;
  map.top_left_y = cursor.pos_y - screen_center_y;

  params->start_x = cursor.pos_x - screen_center_x;
  params->start_y = cursor.pos_y - screen_center_y;
  params->end_x = params->start_x + screen_w + 1;
  params->end_y = params->start_y + screen_h + 1;

  params->offset_x = 0;
  params->offset_y = 0;
  if(params->start_x < 0) {
    params->offset_x = -params->start_x;
    params->start_x = 0;
  } else {
    n = (params->start_x >= EXTRA_X ? EXTRA_X : params->start_x);
    params->offset_x = -n;
    params->start_x -= n;
  }
  if(params->start_y < 0) {
    params->offset_y = -params->start_y;
    params->start_y = 0;
  } else {
    n = (params->start_y >= EXTRA_Y ? EXTRA_Y : params->start_y);
    params->offset_y = -n;
    params->start_y -= n;
  }
  if(params->end_x > map.w)
    params->end_x = map.w;
  if(params->end_y > map.h)
    params->end_y = map.h;

  // store the original
  memcpy(&global_params, params, sizeof(global_params));
}

/**
   Draw the part of the map that appears on the screen if the cursor
   is the center of the screen.
 */
void
drawMap()
{
  MapDrawParams params;
  SDL_Rect pos;
  int x, m, y, level, n;

  // increment the number of moves
  moves++;

  // compute what to draw
  getMapDrawParams(&params);

  // draw the map
  for(level = LEVEL_BACK; level < LEVEL_COUNT - 1; level++) {

    // erase the screen
    pos.x = 0;
    pos.y = 0;
    pos.w = map.level[level]->w;
    pos.h = map.level[level]->h;
    SDL_FillRect(map.level[level], &pos,
                 SDL_MapRGBA(screen->format, 0x0, 0x0, 0x0, 0x00));

    for(y = params.start_y; y < params.end_y; y++) {
      for(x = params.start_x; x < params.end_x;) {
        n = map.image_index[level][x + (y * map.w)];
        if(map.monsters) {
          m = isMonsterImage(n);
          if(m > -1) {
            addLiveMonster(m, n, x, y);
            continue;
          }
        }
        if(n != EMPTY_MAP) {
          // Draw the image
          // should be some check here in case images[n] points to la-la-land.
          pos.x = (params.offset_x + (x - params.start_x)) * TILE_W;
          pos.y = (params.offset_y + (y - params.start_y)) * TILE_H;
          pos.w = images[n]->image->w;
          pos.h = images[n]->image->h;

          // compensate for extra area
          pos.x += (EXTRA_X * TILE_W) - cursor.pixel_x;
          pos.y += (EXTRA_Y * TILE_H) - cursor.pixel_y;
          SDL_BlitSurface(images[n]->image, NULL, map.level[level], &pos);

          // skip ahead
          x += images[n]->image->w / TILE_W;
        } else {
          x++;
        }
      }
    }
  }
  finishDrawMap();
}

/**
   Draw only the left first column of the map.
   This is the first column which is displayed on the virtual screens
   given that the cursor is in the middle of the screen. Note that this
   column will be off-screen since the virtual screens extend offscreen to
   the left and top by EXTRA_X and EXTRA_Y tiles.
 */
void
drawMapLeftEdge()
{
  MapDrawParams params;
  SDL_Rect pos;
  int n, m, level, x, y;

  pos.x = 0;
  pos.y = 0;
  pos.w = cursor.speed_x;
  pos.h = map.level[0]->h;
  for(level = LEVEL_BACK; level < LEVEL_COUNT - 1; level++) {
    SDL_FillRect(map.level[level], &pos,
                 SDL_MapRGBA(screen->format, 0x00, 0x00, 0x00, 0x00));
  }

  // compute what to draw
  getMapDrawParams(&params);

  // override the left edge param
  params.start_x = (cursor.pos_x - screen_center_x) - EXTRA_X;


  // redraw the left edge of the screen
  if(params.start_x >= 0) {
    for(level = LEVEL_BACK; level < LEVEL_COUNT - 1; level++) {
      for(y = params.start_y; y < params.end_y; y++) {
        for(x = params.start_x; x <= params.start_x + 1;) {
          n = (map.image_index[level][x + (y * map.w)]);
          if(map.monsters) {
            m = isMonsterImage(n);
            if(m > -1) {
              addLiveMonster(m, n, x, y);
              continue;
            }
          }
          if(n != EMPTY_MAP) {
            // Draw the image
            // should be some check here in case images[n] points to la-la-land.
            //pos.x = (offset_x + (x - start_x)) * TILE_W;
            pos.x = ((x - params.start_x) * TILE_W) - cursor.pixel_x;
            pos.y = (params.offset_y + (y - params.start_y)) * TILE_H;
            pos.w = images[n]->image->w;
            pos.h = images[n]->image->h;

            // compensate for extra area
            //pos.x += (EXTRA_X * TILE_W) - cursor.pixel_x;
            pos.y += (EXTRA_Y * TILE_H) - cursor.pixel_y;
            SDL_BlitSurface(images[n]->image, NULL, map.level[level], &pos);
            // skip ahead
            x += images[n]->image->w / TILE_W;
          } else {
            x++;
          }
        }
      }
    }
  }
}

/**
   Draw only the top first row of the map.
   This is the first row which is displayed on the virtual screens
   given that the cursor is in the middle of the screen. Note that this
   row will be off-screen since the virtual screens extend offscreen to
   the left and top by EXTRA_X and EXTRA_Y tiles.
 */
void
drawMapTopEdge()
{
  MapDrawParams params;
  SDL_Rect pos;
  int n, m, level, x, y;

  pos.x = 0;
  pos.y = 0;
  pos.w = map.level[0]->w;
  pos.h = cursor.speed_y;
  for(level = LEVEL_BACK; level < LEVEL_COUNT - 1; level++) {
    SDL_FillRect(map.level[level], &pos,
                 SDL_MapRGBA(screen->format, 0x00, 0x00, 0x00, 0x00));
  }

  // compute what to draw
  getMapDrawParams(&params);

  // override the top edge param
  params.start_y = (cursor.pos_y - screen_center_y) - EXTRA_Y;

  if(params.start_y >= 0) {
    // redraw the left edge of the screen
    for(level = LEVEL_BACK; level < LEVEL_COUNT - 1; level++) {
      for(y = params.start_y; y <= params.start_y + 1; y++) {
        for(x = params.start_x; x < params.end_x;) {
          n = (map.image_index[level][x + (y * map.w)]);
          if(map.monsters) {
            m = isMonsterImage(n);
            if(m > -1) {
              addLiveMonster(m, n, x, y);
              continue;
            }
          }
          if(n != EMPTY_MAP) {
            // Draw the image
            // should be some check here in case images[n] points to la-la-land.
            pos.x = (params.offset_x + (x - params.start_x)) * TILE_W;
            pos.y = ((y - params.start_y) * TILE_H) - cursor.pixel_y;
            pos.w = images[n]->image->w;
            pos.h = images[n]->image->h;

            // compensate for extra area
            pos.x += (EXTRA_X * TILE_W) - cursor.pixel_x;
            //pos.y += (EXTRA_Y * TILE_H) - cursor.pixel_y;
            SDL_BlitSurface(images[n]->image, NULL, map.level[level], &pos);
            // skip ahead
            x += images[n]->image->w / TILE_W;
          } else {
            x++;
          }
        }
      }
    }
  }
}

/**
   Draw only the right last column of the map.
   This is the last column which is displayed on the virtual screens
   given that the cursor is in the middle of the screen.
 */
void
drawMapRightEdge()
{
  MapDrawParams params;
  SDL_Rect pos;
  int n, m, level, x, y;

  pos.x = map.level[0]->w - cursor.speed_x;
  pos.y = 0;
  pos.w = cursor.speed_x;
  pos.h = map.level[0]->h;
  for(level = LEVEL_BACK; level < LEVEL_COUNT - 1; level++) {
    SDL_FillRect(map.level[level], &pos,
                 SDL_MapRGBA(screen->format, 0x00, 0x00, 0x00, 0x00));
  }

  // compute what to draw
  getMapDrawParams(&params);

  // override the right edge param
  params.end_x = (cursor.pos_x - screen_center_x) + screen_w;

  // redraw the left edge of the screen
  for(level = LEVEL_BACK; level < LEVEL_COUNT - 1; level++) {
    for(y = params.start_y; y < params.end_y; y++) {
      // here we have to draw more than 1 column b/c images
      // extend from right_edge-EXTRA_X on. 
      for(x = params.end_x - EXTRA_X; x <= params.end_x;) { // FIXME: this may be x < params.end_x
        if(params.end_x >= map.w)
          break;
        n = (map.image_index[level][x + (y * map.w)]);
        if(map.monsters) {
          m = isMonsterImage(n);
          if(m > -1) {
            addLiveMonster(m, n, x, y);
            continue;
          }
        }
        if(n != EMPTY_MAP) {
          // Draw the image
          // should be some check here in case images[n] points to la-la-land.
          //pos.x = (offset_x + (x - start_x)) * TILE_W;
          pos.x =
            (map.level[level]->w - ((params.end_x - x) * TILE_W)) -
            cursor.pixel_x;
          pos.y = (params.offset_y + (y - params.start_y)) * TILE_H;
          pos.w = images[n]->image->w;
          pos.h = images[n]->image->h;

          // compensate for extra area
          //pos.x += (EXTRA_X * TILE_W) - cursor.pixel_x;
          pos.y += (EXTRA_Y * TILE_H) - cursor.pixel_y;
          SDL_BlitSurface(images[n]->image, NULL, map.level[level], &pos);
          // skip ahead
          x += images[n]->image->w / TILE_W;
        } else {
          x++;
        }
      }
    }
  }
}

/**
   Draw only the right last column of the map.
   This is the last column which is displayed on the virtual screens
   given that the cursor is in the middle of the screen.
 */
void
drawMapBottomEdge()
{
  MapDrawParams params;
  SDL_Rect pos;
  int n, m, level, x, y;

  pos.x = 0;
  pos.y = map.level[0]->h - cursor.speed_y;
  pos.w = map.level[0]->w;
  pos.h = cursor.speed_y;
  for(level = LEVEL_BACK; level < LEVEL_COUNT - 1; level++) {
    SDL_FillRect(map.level[level], &pos,
                 SDL_MapRGBA(screen->format, 0x00, 0x00, 0x00, 0x00));
  }

  // compute what to draw
  getMapDrawParams(&params);

  // override the bottom edge param
  params.end_y = (cursor.pos_y - screen_center_y) + screen_h;

  // redraw the left edge of the screen
  for(level = LEVEL_BACK; level < LEVEL_COUNT - 1; level++) {
    // here we have to draw more than 1 column b/c images
    // extend from right_edge-EXTRA_X on. 
    for(y = params.end_y - EXTRA_Y; y <= params.end_y; y++) { // FIXME: this may be y < params.end_y
      if(params.end_y >= map.h)
        break;
      for(x = params.start_x; x < params.end_x;) {
        n = (map.image_index[level][x + (y * map.w)]);
        if(map.monsters) {
          m = isMonsterImage(n);
          if(m > -1) {
            addLiveMonster(m, n, x, y);
            continue;
          }
        }
        if(n != EMPTY_MAP) {
          // Draw the image
          // should be some check here in case images[n] points to la-la-land.
          pos.x = (params.offset_x + (x - params.start_x)) * TILE_W;
          pos.y =
            (map.level[level]->h - ((params.end_y - y) * TILE_H)) -
            cursor.pixel_y;
          pos.w = images[n]->image->w;
          pos.h = images[n]->image->h;

          // compensate for extra area
          pos.x += (EXTRA_X * TILE_W) - cursor.pixel_x;
          //pos.y += (EXTRA_Y * TILE_H) - cursor.pixel_y;
          SDL_BlitSurface(images[n]->image, NULL, map.level[level], &pos);
          // skip ahead
          x += images[n]->image->w / TILE_W;
        } else {
          x++;
        }
      }
    }
  }
}

/**
   Scrolls but doesn't update the screen.
   You must call finishDrawMap() after this method.
   (this is so you can string together multiple calls to this method.)
*/
void
scrollMap(int dir)
{
  int level;
  //long skipped;
  SDL_Rect pos;

  if(dir == DIR_NONE)
    return;

  // increment the number of moves
  moves++;

  // move the screen
  switch (dir) {
  case DIR_LEFT:

    for(level = LEVEL_BACK; level < LEVEL_COUNT - 1; level++) {
      SDL_FillRect(map.transfer, NULL,
                   SDL_MapRGBA(screen->format, 0x00, 0x00, 0x00, 0x00));
      pos.x = 0;
      pos.y = 0;
      pos.w = map.level[level]->w - cursor.speed_x;
      pos.h = map.level[level]->h;
      SDL_BlitSurface(map.level[level], NULL, map.transfer, NULL);
      pos.x = cursor.speed_x;
      pos.y = 0;
      pos.w = map.level[level]->w - cursor.speed_x;
      pos.h = map.level[level]->h;
      SDL_BlitSurface(map.transfer, NULL, map.level[level], &pos);
    }

    // draw only the new left edge
    drawMapLeftEdge();

    break;


  case DIR_RIGHT:

    for(level = LEVEL_BACK; level < LEVEL_COUNT - 1; level++) {
      SDL_FillRect(map.transfer, NULL,
                   SDL_MapRGBA(screen->format, 0x00, 0x00, 0x00, 0x00));
      pos.x = cursor.speed_x;
      pos.y = 0;
      pos.w = map.level[level]->w - cursor.speed_x;
      pos.h = map.level[level]->h;
      SDL_BlitSurface(map.level[level], &pos, map.transfer, NULL);
      pos.x = 0;
      pos.y = 0;
      pos.w = map.level[level]->w - cursor.speed_x;
      pos.h = map.level[level]->h;
      SDL_BlitSurface(map.transfer, NULL, map.level[level], &pos);
    }

    // draw only the new left edge
    drawMapRightEdge();

    break;


  case DIR_UP:
    for(level = LEVEL_BACK; level < LEVEL_COUNT - 1; level++) {
      SDL_FillRect(map.transfer, NULL,
                   SDL_MapRGBA(screen->format, 0x00, 0x00, 0x00, 0x00));
      pos.x = 0;
      pos.y = 0;
      pos.w = map.level[level]->w;
      pos.h = map.level[level]->h - cursor.speed_y;
      SDL_BlitSurface(map.level[level], &pos, map.transfer, NULL);
      pos.x = 0;
      pos.y = cursor.speed_y;
      pos.w = map.level[level]->w;
      pos.h = map.level[level]->h - cursor.speed_y;
      SDL_BlitSurface(map.transfer, NULL, map.level[level], &pos);
    }

    // draw only the new left edge
    drawMapTopEdge();

    break;


  case DIR_DOWN:

    for(level = LEVEL_BACK; level < LEVEL_COUNT - 1; level++) {
      SDL_FillRect(map.transfer, NULL,
                   SDL_MapRGBA(screen->format, 0x00, 0x00, 0x00, 0x00));
      pos.x = 0;
      pos.y = cursor.speed_y;
      pos.w = map.level[level]->w;
      pos.h = map.level[level]->h - cursor.speed_y;
      SDL_BlitSurface(map.level[level], &pos, map.transfer, NULL);
      pos.x = 0;
      pos.y = 0;
      pos.w = map.level[level]->w;
      pos.h = map.level[level]->h - cursor.speed_y;
      SDL_BlitSurface(map.transfer, NULL, map.level[level], &pos);
    }

    // draw only the new left edge
    drawMapBottomEdge();

    break;
  }
}

int
moveLeft(int checkCollision)
{
  int move, old_pixel, old_pos, old_speed;

  old_speed = cursor.speed_x;
  while(cursor.speed_x > 0) {
    move = 1;
    old_pixel = cursor.pixel_x;
    old_pos = cursor.pos_x;

    cursor.pixel_x -= cursor.speed_x;
    if(cursor.pixel_x < 0) {
      cursor.pixel_x += TILE_W;
      cursor.pos_x--;
      if(cursor.pos_x < 0) {
        move = 0;
      }
    }
    if(move && (!checkCollision || map.detectCollision(DIR_LEFT))) {
      scrollMap(DIR_LEFT);
      if(map.accelerate) {
        if(cursor.speed_x < SPEED_MAX_X + map.max_speed_boost) {
          cursor.speed_x +=
            SPEED_INC_X + (map.max_speed_boost ? SPEED_INC_X : 0);
          if(cursor.speed_x >= SPEED_MAX_X + map.max_speed_boost)
            cursor.speed_x = SPEED_MAX_X + map.max_speed_boost;
        }
      }
      return 1;
    }
    cursor.pixel_x = old_pixel;
    cursor.pos_x = old_pos;
    if(!cursor.pixel_x)
      break;
    //  cursor.speed_x = cursor.pixel_x;
    cursor.speed_x -= SPEED_INC_X + (map.max_speed_boost ? SPEED_INC_X : 0);
  }
  cursor.speed_x = old_speed;
  return 0;
}

int
moveRight(int checkCollision)
{
  int move, old_pixel, old_pos, old_speed;

  old_speed = cursor.speed_x;
  while(cursor.speed_x > 0) {
    move = 1;
    old_pixel = cursor.pixel_x;
    old_pos = cursor.pos_x;

    cursor.pixel_x += cursor.speed_x;
    if(cursor.pixel_x >= TILE_W) {
      cursor.pixel_x -= TILE_W;
      cursor.pos_x++;
      if(cursor.pos_x >= map.w) {
        move = 0;
      }
    }
    if(move && (!checkCollision || map.detectCollision(DIR_RIGHT))) {
      scrollMap(DIR_RIGHT);
      if(map.accelerate) {
        if(cursor.speed_x < SPEED_MAX_X + map.max_speed_boost) {
          cursor.speed_x +=
            SPEED_INC_X + (map.max_speed_boost ? SPEED_INC_X : 0);
          if(cursor.speed_x >= SPEED_MAX_X + map.max_speed_boost)
            cursor.speed_x = SPEED_MAX_X + map.max_speed_boost;
        }
      }
      return 1;
    }
    cursor.pixel_x = old_pixel;
    cursor.pos_x = old_pos;
    if(!cursor.pixel_x)
      break;
    //cursor.speed_x = TILE_W - cursor.pixel_x;
    cursor.speed_x -= SPEED_INC_X + (map.max_speed_boost ? SPEED_INC_X : 0);
  }
  cursor.speed_x = old_speed;
  return 0;
}

int
moveUp(int checkCollision, int platform)
{
  int move, old_pixel, old_pos, old_speed;

  old_speed = cursor.speed_y;
  while(cursor.speed_y > 0) {
    move = 1;
    old_pixel = cursor.pixel_y;
    old_pos = cursor.pos_y;

    cursor.pixel_y -= cursor.speed_y;
    if(cursor.pixel_y < 0) {
      cursor.pixel_y += TILE_H;
      cursor.pos_y--;
      if(cursor.pos_y < 0) {
        move = 0;
      }
    }
    if(move && (!checkCollision ||
                ((platform || cursor.jump || interact.on_ladder == 2
                  || cursor.stepup) && map.detectCollision(DIR_UP)))) {
      scrollMap(DIR_UP);
      if(map.accelerate) {
        if(cursor.speed_y < SPEED_MAX_Y + map.max_speed_boost) {
          cursor.speed_y +=
            SPEED_INC_Y + (map.max_speed_boost ? SPEED_INC_Y : 0);
          if(cursor.speed_y >= SPEED_MAX_Y + map.max_speed_boost)
            cursor.speed_y = SPEED_MAX_Y + map.max_speed_boost;
        }
      }
      return 1;
    }
    cursor.pixel_y = old_pixel;
    cursor.pos_y = old_pos;
    cursor.speed_y -= SPEED_INC_Y + (map.max_speed_boost ? SPEED_INC_Y : 0);
  }
  cursor.speed_y = old_speed;
  return 0;
}

int
moveDown(int checkCollision, int platform, int slide)
{
  int move, old_pixel, old_pos, old_speed;

  old_speed = cursor.speed_y;
  while(cursor.speed_y > 0) {
    move = 1;
    old_pixel = cursor.pixel_y;
    old_pos = cursor.pos_y;

    cursor.pixel_y += cursor.speed_y;
    if(cursor.pixel_y >= TILE_H) {
      cursor.pixel_y -= TILE_H;
      cursor.pos_y++;
      if(cursor.pos_y >= map.h) {
        move = 0;
      }
    }
    if(move && (!checkCollision ||
                ((platform || cursor.gravity || interact.on_ladder
                  || interact.ladder_change || slide)
                 && map.detectCollision(DIR_DOWN)))) {
      scrollMap(DIR_DOWN);
      if(map.accelerate) {
        if(cursor.speed_y < SPEED_MAX_Y + map.max_speed_boost) {
          cursor.speed_y +=
            SPEED_INC_Y + (map.max_speed_boost ? SPEED_INC_Y : 0);
          if(cursor.speed_y >= SPEED_MAX_Y + map.max_speed_boost)
            cursor.speed_y = SPEED_MAX_Y + map.max_speed_boost;
        }
      }
      return 1;
    }
    cursor.pixel_y = old_pixel;
    cursor.pos_y = old_pos;
    cursor.speed_y -= SPEED_INC_Y + (map.max_speed_boost ? SPEED_INC_Y : 0);
  }
  cursor.speed_y = old_speed;
  return 0;
}

/**
   When moving left or right and hitting an obsticle, call this method
   to check if we can step onto the object.
   Returns 1 on success, 0 on failure.
 */
int
canStepUp(int dir)
{
  int ret;
  int old_speed_x, old_speed_y;

  // Take an unchecked step into the wall and don't scroll the screen.
  // This is so after stepping up, gravity won't pull us back down.
  // TILE_W b/c we always stop on pixel_x==0      

  old_speed_x = cursor.speed_x;
  old_speed_y = cursor.speed_y;
  //  cursor.speed_x = TILE_W;
  if(dir == DIR_LEFT) {
    moveLeft(0);
  } else {
    moveRight(0);
  }

  // take a step up
  cursor.speed_y = (cursor.pixel_y == 0 ? TILE_H : cursor.pixel_y);
  cursor.stepup = 1;
  if(!moveUp(1, 0)) {
    cursor.stepup = 0;
    // if can't step up
    //  cursor.speed_x = TILE_W;
    if(dir == DIR_LEFT) {
      moveRight(0);
    } else {
      moveLeft(0);
    }
    cursor.speed_x = 0;
    ret = 0;
  } else {
    cursor.stepup = 0;
    ret = 1;
  }

  cursor.speed_x = old_speed_x;
  cursor.speed_y = old_speed_y;
  return ret;
}

int
moveJump()
{
  int ret = 0;
  int old_speed;

  if(cursor.jump > 0) {
    cursor.jump--;

    old_speed = cursor.speed_y;
    cursor.speed_y = JUMP_SPEED + map.max_speed_boost;
    moveUp(1, 0);
    cursor.speed_y = old_speed;
    ret = 1;
  }
  return ret;
}

int
moveSlide()
{
  int n;
  int old_speed_x, old_speed_y;

  cursor.slide = 0;
  if(!map.slides)
    return 0;

  n = map.detectSlide();
  if(!n)
    return 0;

  cursor.slide = 1;

  old_speed_x = cursor.speed_x;
  old_speed_y = cursor.speed_y;
  cursor.speed_x = TILE_W;
  cursor.speed_y = TILE_H;
  if(n == img_slide_left[0] || n == img_slide_left[1]
     || n == img_slide_left[2]) {
    moveLeft(0);
  } else if(n == img_slide_right[0] || n == img_slide_right[1]
            || n == img_slide_right[2]) {
    moveRight(0);
  }
  moveDown(1, 0, 1);

  cursor.speed_x = old_speed_x;
  cursor.speed_y = old_speed_y;

  return 1;
}

/**
   Drop down.
 */
void
moveGravity()
{
  int old_speed;

  if(!map.gravity || interact.on_ladder || cursor.platform) {
    return;
  }

  old_speed = cursor.speed_y;
  cursor.speed_y = GRAVITY_SPEED + map.max_speed_boost;
  cursor.gravity = 1;
  cursor.gravity = moveDown(1, 0, 0);
  cursor.gravity = 0;
  cursor.speed_y = old_speed;
}

int
moveWithPlatform()
{
  int py;
  int old_speed;
  int diff, dir;

  if(!cursor.platform)
    return 0;

  // make sure we're on top of the platform
  py = cursor.platform->pos_y * TILE_H + cursor.platform->pixel_y;
  if(cursor.platform->dir == DIR_UP) {
    py -= cursor.platform->speed_y + map.max_speed_boost;
  } else if(cursor.platform->dir == DIR_DOWN) {
    py += cursor.platform->speed_y + map.max_speed_boost;
  }
  diff = py - (cursor.pos_y * TILE_H + tom[0]->h + cursor.pixel_y);
  dir = (diff > 0 ? DIR_DOWN : DIR_UP);
  if(diff) {
    old_speed = cursor.speed_y;
    cursor.speed_y = (abs(diff) < TILE_H ? abs(diff) : TILE_H);
    if(dir == DIR_UP)
      moveUp(1, 1);
    else
      moveDown(1, 1, 0);
    cursor.speed_y = old_speed;
  }
  // move sideways with the platform
  if(cursor.platform->monster->type == MONSTER_PLATFORM) {
    old_speed = cursor.speed_x;
    cursor.speed_x = cursor.platform->speed_x + map.max_speed_boost;
    if(cursor.platform->dir == DIR_LEFT)
      moveLeft(1);
    else
      moveRight(1);
    cursor.speed_x = old_speed;
  }

  return 1;
}

void
initInteract()
{
  int on_ladder;

  interact.ladder_change = 0;
  on_ladder = map.detectLadder();
  if(interact.on_ladder != on_ladder) {
    interact.ladder_change = 1;
    interact.on_ladder = on_ladder;
  }
}

/**
   The main thread loop
 */
void
moveMap()
{
  SDL_Event event;
  //int delay;
  Uint32 curr_time, next_time = 0;
  Uint32 TICK_AMOUNT = 1000 / FPS_THROTTLE;

  while(1) {

    curr_time = SDL_GetTicks();

    // is it time to draw the map?
    if(map.fps_override || !next_time || curr_time > next_time) {


      // where is the map?
      map.top_left_x = cursor.pos_x - screen_center_x;
      map.top_left_y = cursor.pos_y - screen_center_y;

      // fill up interact
      initInteract();

      // handle events.
      while(SDL_PollEvent(&event)) {
        map.handleMapEvent(&event);
      }
      // jumping & platform
      if(!moveJump() && !moveWithPlatform() && !moveSlide()) {
        // activate gravity
        moveGravity();
      }
      // set unaccelerated speed
      if(!map.accelerate) {
        cursor.speed_x = TILE_W;
        cursor.speed_y = TILE_H;
      }
      // special flow logic
      if(map.quit) {
        return;
      } else if(cursor.dir & DIR_UPDATE) {
        cursor.dir = DIR_NONE;
      }

      if(cursor.dir & DIR_LEFT) {
        if(!moveLeft(1)) {
          // try to step up onto the obsticle
          canStepUp(DIR_LEFT);
        }
      }
      if(cursor.dir & DIR_RIGHT) {
        if(!moveRight(1)) {
          // try to step up onto the obsticle
          canStepUp(DIR_RIGHT);
        }
      }
      if(cursor.dir & DIR_UP) {
        moveUp(1, 0);
      }
      if(cursor.dir & DIR_DOWN) {
        // jumping off a platform
        if(cursor.platform) {
          cursor.speed_y = TILE_H;
          moveDown(1, 1, 0);
          cursor.speed_y = GRAVITY_SPEED + map.max_speed_boost;
          moveDown(1, 1, 0);
        } else {
          moveDown(1, 0, 0);
        }
      }
      // check for monsters, etc.
      if(map.checkPosition)
        map.checkPosition();

      // update the map?
      if(map.redraw) {
        map.redraw = 0;
        drawMap();              // this calls finishDrawMap
      } else {
        // update the screen
        finishDrawMap(!map.redraw);
      }

      // uncomment to test slow machine game speed
      //SDL_Delay(30);

      // if the video is too slow to display FPS_THROTTLE then remove the constraint.
      if(!moves)
        moves = 1;
      map.delta = (SDL_GetTicks() - curr_time) / moves;
      moves = 0;
      map.fps_override = (map.delta > TICK_AMOUNT);

      // when to update the screen next?
      next_time += TICK_AMOUNT;
      // skip next frame if time passed already
      if(curr_time > next_time)
        next_time = curr_time + TICK_AMOUNT;

      // for slow machines: max speed adjustment
      if(map.fps_override) {
        map.max_speed_boost = (map.delta - TICK_AMOUNT) / 5;
        if(map.max_speed_boost > TILE_W - SPEED_MAX_X)
          map.max_speed_boost = TILE_W - SPEED_MAX_X;
      } else {
        map.max_speed_boost = 0;
      }
    }
    //  SDL_Delay(20);
  }
}

// artificially move the background in a direction
void
scrollBackground()
{
  move_background_x--;
  if((int) move_background_x < -((int) map.background_image->w))
    move_background_x = 0;
  move_background_y--;
  if((int) move_background_y < -((int) map.background_image->h))
    move_background_y = 0;
}

void
drawLevelFore()
{
  SDL_Rect pos;
  int x, y, n, i;

  // draw the map
  for(y = global_params.start_y; y < global_params.end_y; y++) {
    for(x = global_params.start_x; x < global_params.end_x;) {
      i = x + (y * map.w);
      if(i < 0) {
        fprintf(stderr, "ERROR: x=%d y=%d start_x=%d start_y=%d i=%d\n", x, y,
                global_params.start_x, global_params.start_y, i);
        exit(1);
      }
      n = map.image_index[LEVEL_FORE][i];
      if(n != EMPTY_MAP) {
        // Draw the image
        // should be some check here in case images[n] points to la-la-land.
        pos.x =
          (global_params.offset_x + (x - global_params.start_x)) * TILE_W;
        pos.y =
          (global_params.offset_y + (y - global_params.start_y)) * TILE_H;
        pos.w = images[n]->image->w;
        pos.h = images[n]->image->h;

        // compensate for extra area
        pos.x += -cursor.pixel_x;
        pos.y += -cursor.pixel_y;

        SDL_BlitSurface(images[n]->image, NULL, screen, &pos);

        // skip ahead
        x += images[n]->image->w / TILE_W;
      } else {
        x++;
      }
    }
  }
}

void
finishDrawMap()
{
  SDL_Rect pos;
  int level;

  if(mainstruct.drawBackground) {
    // draw the background
    if(map.moveBackground) {
      scrollBackground();
      pos.x = move_background_x;
      pos.y = move_background_y;
    } else {
      pos.x =
        -((cursor.pos_x * TILE_W +
           cursor.pixel_x) % (map.background_image->w * 2)) / 2;
      pos.y =
        -((cursor.pos_y * TILE_H +
           cursor.pixel_y) % (map.background_image->h * 2)) / 2;
    }
    pos.w = map.background->w;
    pos.h = map.background->h;
    SDL_BlitSurface(map.background, NULL, screen, &pos);
  }
  // draw on screen
  for(level = LEVEL_BACK; level < LEVEL_COUNT - 1; level++) {
    pos.x = -(EXTRA_X * TILE_W);
    pos.y = -(EXTRA_Y * TILE_H);
    pos.w = map.level[level]->w;
    pos.h = map.level[level]->h;
    SDL_BlitSurface(map.level[level], NULL, screen, &pos);
  }
  // make a callback if it exists
  // draw creatures
  if(map.monsters)
    drawLiveMonsters(screen,
                     (global_params.start_x -
                      global_params.offset_x) * TILE_W + cursor.pixel_x,
                     (global_params.start_y -
                      global_params.offset_y) * TILE_H + cursor.pixel_y);
  // draw Tom
  if(map.afterMainLevelDrawn)
    map.afterMainLevelDrawn();

  // draw the overlay level
  drawLevelFore();

  // if the callback function is set, call it now.
  if(map.beforeDrawToScreen) {
    map.beforeDrawToScreen();
  }
  // show map status
  if(map.status_time > 0) {
    map.status_time--;
    drawString(screen, screen->w / 2, 5, map.status);
  }
  SDL_Flip(screen);

  if(map.afterScreenFlipped)
    map.afterScreenFlipped();
}

void
setImage(int level, int index)
{
  Position pos;
  pos.pos_x = cursor.pos_x;
  pos.pos_y = cursor.pos_y;
  setImagePosition(level, index, &pos);
  cursor.pos_x = pos.pos_x;
  cursor.pos_y = pos.pos_y;
  drawMap();
}

void
setImagePosition(int level, int index, Position * pos)
{
  int x, y, n;
  SDL_Rect rect, img_rect;
  int start_x, start_y, end_x, end_y;

  // clear the area 
  if(index != EMPTY_MAP) {
    img_rect.x = pos->pos_x;
    img_rect.y = pos->pos_y;
    img_rect.w = images[index]->image->w / TILE_W;
    img_rect.h = images[index]->image->h / TILE_H;
  }
  start_x = pos->pos_x - EXTRA_X;
  if(start_x < 0)
    start_x = 0;
  start_y = pos->pos_y - EXTRA_Y;
  if(start_y < 0)
    start_y = 0;
  end_x =
    pos->pos_x + (index != EMPTY_MAP ? images[index]->image->w / TILE_W : 1);
  if(end_x >= map.w)
    end_x = map.w;
  end_y =
    pos->pos_y + (index != EMPTY_MAP ? images[index]->image->h / TILE_H : 1);
  if(end_y >= map.h)
    end_y = map.h;
  for(y = start_y; y < end_y; y++) {
    for(x = start_x; x < end_x; x++) {
      n = map.image_index[level][x + (y * map.w)];
      if(n != EMPTY_MAP) {
        rect.x = x;
        rect.y = y;
        rect.w = images[n]->image->w / TILE_W;
        rect.h = images[n]->image->h / TILE_H;
        if(contains(&rect, pos->pos_x, pos->pos_y) ||
           (index != EMPTY_MAP && intersects(&rect, &img_rect))) {
          map.image_index[level][x + (y * map.w)] = EMPTY_MAP;
        }
      }
    }
  }
  // add the image
  map.image_index[level][pos->pos_x + (pos->pos_y * map.w)] = index;
  // move the cursor
  if(index != EMPTY_MAP) {
    if(pos->pos_x + (images[index]->image->w / TILE_W) < map.w)
      pos->pos_x += (images[index]->image->w / TILE_W);
  }
  drawMap();
}

void
setImageNoCheck(int level, int x, int y, int image_index)
{
  map.image_index[level][x + (y * map.w)] = image_index;
}

int
defaultDetectCollision(int dir)
{
  return 1;
}

int
defaultDetectLadder()
{
  return 1;
}

int
defaultDetectSlide()
{
  return 0;
}

void
resetMap()
{
  int i;

  map.gravity = 0;
  map.accelerate = 0;
  map.monsters = 0;
  map.slides = 0;
  map.beforeDrawToScreen = NULL;
  map.afterMainLevelDrawn = NULL;
  map.detectCollision = defaultDetectCollision;
  map.detectLadder = defaultDetectLadder;
  map.detectSlide = defaultDetectSlide;
  map.handleMapEvent = NULL;
  map.delay = 25;
  map.redraw = 0;
  map.delta = 0;
  map.fps_override = 0;
  map.moveBackground = 0;
  map.status_time = 0;
  // clean map
  for(i = 0; i < (map.w * map.h); i++) {
    map.image_index[LEVEL_BACK][i] = EMPTY_MAP;
    map.image_index[LEVEL_MAIN][i] = EMPTY_MAP;
    map.image_index[LEVEL_FORE][i] = EMPTY_MAP;
  }
  map.quit = 0;
}

int
initMap(char *name, int w, int h)
{
  int i;
  int hw_surface;

  int x, y;
  SDL_Rect pos;

  // start a new Map
  map.gravity = 0;
  map.accelerate = 0;
  map.monsters = 0;
  map.slides = 0;
  map.name = strdup(name);
  map.w = w;
  map.h = h;
  map.beforeDrawToScreen = NULL;
  map.afterScreenFlipped = NULL;
  map.afterScreenFlipped = NULL;
  map.afterMainLevelDrawn = NULL;
  map.detectCollision = defaultDetectCollision;
  map.detectLadder = defaultDetectLadder;
  map.detectSlide = defaultDetectSlide;
  map.handleMapEvent = NULL;
  map.checkPosition = NULL;
  map.delay = 25;
  map.redraw = 0;
  map.delta = 0;
  map.fps_override = 0;
  map.moveBackground = 0;
  map.quit = 0;
  map.status_time = 0;
  for(i = LEVEL_BACK; i < LEVEL_COUNT; i++) {
    if(!(map.image_index[i] = (Uint16 *) malloc(sizeof(Uint16) * w * h))) {
      fprintf(stderr, "Out of memory.\n");
      fflush(stderr);
      exit(0);
    }
    if(!(map.level[i] = SDL_CreateRGBSurface(SDL_HWSURFACE,
                                             screen->w + EXTRA_X * TILE_W,
                                             screen->h + EXTRA_Y * TILE_H,
                                             screen->format->BitsPerPixel,
                                             0, 0, 0, 0))) {
      fprintf(stderr, "Error creating surface: %s\n", SDL_GetError());
      fflush(stderr);
      return 0;
    }
    hw_surface = (map.level[i]->flags & SDL_HWSURFACE ? 1 : 0);
    fprintf(stderr, "level[%d] is HW surface? %d\n", i, hw_surface);
    if(screen->flags & SDL_HWSURFACE & !hw_surface) {
      fprintf(stderr,
              "*** Can't create surface in video memory. Since the screen is there, this surface must too.\n");
      fprintf(stderr, "*** This may make the game very slow!");
      fflush(stderr);
      //    return 0;
    }
  }

  // set the pixel blending for some levels
  if(mainstruct.drawBackground) {
    SDL_SetColorKey(map.level[LEVEL_BACK], SDL_SRCCOLORKEY,
                    SDL_MapRGBA(map.level[LEVEL_BACK]->format, 0x00, 0x00,
                                0x00, 0x00));
  }
  SDL_SetColorKey(map.level[LEVEL_MAIN], SDL_SRCCOLORKEY,
                  SDL_MapRGBA(map.level[LEVEL_MAIN]->format, 0x00, 0x00, 0x00,
                              0x00));
  //  if(!mainstruct.alphaBlend) {
  //  SDL_SetColorKey(map.level[LEVEL_FORE], SDL_SRCCOLORKEY, SDL_MapRGBA(map.level[LEVEL_BACK]->format, 0x00, 0x00, 0x00, 0x00));
  //  }

  // init the transfer area
  if(!(map.transfer = SDL_CreateRGBSurface(SDL_HWSURFACE,
                                           screen->w + EXTRA_X * TILE_W,
                                           screen->h + EXTRA_Y * TILE_H,
                                           screen->format->BitsPerPixel,
                                           0, 0, 0, 0))) {
    fprintf(stderr, "Error creating surface: %s\n", SDL_GetError());
    fflush(stderr);
    return 0;
  }
  fprintf(stderr, "transfer area is HW surface? %d\n", hw_surface);
  if(screen->flags & SDL_HWSURFACE & !hw_surface) {
    fprintf(stderr,
            "Can't create surface in video memory. Since the screen is there, this surface must too.\n");
    fflush(stderr);
    return 0;
  }
  // init the background
  map.background_image = images[img_back]->image;
  if(!(map.background = SDL_CreateRGBSurface(SDL_HWSURFACE,
                                             screen->w +
                                             map.background_image->w,
                                             screen->h +
                                             map.background_image->h,
                                             screen->format->BitsPerPixel, 0,
                                             0, 0, 0))) {
    fprintf(stderr, "Error creating background surface: %s\n",
            SDL_GetError());
    fflush(stderr);
    return 0;
  }
  hw_surface = (map.background->flags & SDL_HWSURFACE ? 1 : 0);
  fprintf(stderr, "background is HW surface? %d\n", hw_surface);
  if(screen->flags & SDL_HWSURFACE & !hw_surface) {
    fprintf(stderr,
            "*** Can't create background in video memory. Since the screen is there, this surface must too.\n");
    fprintf(stderr, "*** This may make the game very slow!");
    fflush(stderr);
    //    return 0;
  }

  for(y = 0; y < map.background->h; y += map.background_image->h) {
    for(x = 0; x < map.background->w; x += map.background_image->w) {
      pos.x = x;
      pos.y = y;
      pos.w = map.background_image->w;
      pos.h = map.background_image->h;
      SDL_BlitSurface(map.background_image, NULL, map.background, &pos);
    }
  }

  // clean map
  for(i = 0; i < (w * h); i++) {
    map.image_index[LEVEL_BACK][i] = EMPTY_MAP;
    map.image_index[LEVEL_MAIN][i] = EMPTY_MAP;
    map.image_index[LEVEL_FORE][i] = EMPTY_MAP;
  }

  // init some variables
  screen_center_x = (screen->w / TILE_W) / 2;
  screen_center_y = (screen->h / TILE_H) / 2;
  screen_w = screen->w / TILE_W;
  //  screen_h = (screen->h - edit_panel.image->h) / TILE_H;
  screen_h = screen->h / TILE_H;
  return 1;
}

void
destroyMap()
{
  int i;

  if(map.monsters) {
    removeAllLiveMonsters();
  }
  free(map.name);
  for(i = LEVEL_BACK; i < LEVEL_COUNT; i++) {
    SDL_FreeSurface(map.level[i]);
    free(map.image_index[i]);
  }
  SDL_FreeSurface(map.transfer);
  SDL_FreeSurface(map.background);
}

void
resetCursor()
{
  repositionCursor(0, 0);
}

void
repositionCursor(int tile_x, int tile_y)
{
  cursor.pos_x = tile_x;
  cursor.pos_y = tile_y;
  cursor.pixel_x = 0;
  cursor.pixel_y = 0;
  cursor.speed_x = TILE_W;
  cursor.speed_y = TILE_H;
  cursor.dir = DIR_NONE;
  cursor.wait = 0;
  cursor.jump = 0;
  cursor.gravity = 0;
  cursor.stepup = 0;
  cursor.slide = 0;
  cursor.platform = NULL;
}

int
startJump()
{
  return startJumpN(JUMP_LENGTH);
}

int
startJumpN(int n)
{
  Position pos;
  pos.pos_x = cursor.pos_x;
  pos.pos_y = cursor.pos_y + tom[0]->h / TILE_H;
  pos.pixel_x = cursor.pixel_x;
  pos.pixel_y = cursor.pixel_y;
  pos.w = tom[0]->w / TILE_W;
  pos.h = 1;
  if(!cursor.jump
     && (containsType(&pos, TYPE_WALL | TYPE_LADDER) || cursor.platform)) {
    cursor.jump = n;
    return 1;
  }
  return 0;
}

void
getGameCollisionCheck(GameCollisionCheck * check, Position * p)
{
  check->start_x = p->pos_x - EXTRA_X;
  if(p->pos_x < EXTRA_X)
    check->start_x = 0;
  check->start_y = p->pos_y - EXTRA_Y;
  if(p->pos_y < EXTRA_Y)
    check->start_y = 0;

  check->end_x = p->pos_x + p->w + (p->pixel_x > 0 ? 1 : 0);
  if(check->end_x >= map.w)
    check->end_x = map.w;
  check->end_y = p->pos_y + p->h + (p->pixel_y > 0 ? 1 : 0);
  if(check->end_y >= map.h)
    check->end_y = map.h;

  // tom's rect
  check->rect.x = p->pos_x;
  check->rect.y = p->pos_y;
  check->rect.w = p->w + (p->pixel_x > 0 ? 1 : 0);
  check->rect.h = p->h + (p->pixel_y > 0 ? 1 : 0);
}

/**
   Does the position contain a tile of this type?
   Return 1 if it does, 0 otherwise.
   Hack: no difference between returning index=0 vs. 0 (not found).
 */
int
containsType(Position * p, int type)
{
  return containsTypeInLevel(p, NULL, type, LEVEL_MAIN);
}

int
containsTypeWhere(Position * p, Position * ret, int type)
{
  return containsTypeInLevel(p, ret, type, LEVEL_MAIN);
}

int
containsTypeInLevel(Position * p, Position * ret, int type, int level)
{
  GameCollisionCheck check;
  SDL_Rect rect;
  int x, y, n;

  getGameCollisionCheck(&check, p);

  for(y = check.start_y; y < check.end_y; y++) {
    for(x = check.start_x; x < check.end_x;) {
      n = map.image_index[level][x + (y * map.w)];
      if(n != EMPTY_MAP) {
        if(type & images[n]->type) {
          rect.x = x;
          rect.y = y;
          rect.w = images[n]->image->w / TILE_W;
          rect.h = images[n]->image->h / TILE_H;
          if(intersects(&rect, &check.rect)) {
            if(ret) {
              ret->pos_x = x;
              ret->pos_y = y;
              ret->pixel_x = 0;
              ret->pixel_y = 0;
              ret->w = rect.w;
              ret->h = rect.h;
            }
            return n;
          }
        }
        x += images[n]->image->w / TILE_W;
      } else {
        x++;
      }
    }
  }
  return 0;
}

/**
   If the ground beneath pos is not COMPLETELY solid, this returns 0.
   1 otherwise.
 */
int
onSolidGround(Position * p)
{
  GameCollisionCheck check;
  SDL_Rect rect;
  int x, y, n, r, found;

  getGameCollisionCheck(&check, p);

  //check.rect.h++;
  //if(check.rect.y + check.rect.h >= map.h) return 0;

  y = check.end_y;
  for(x = check.rect.x; x < check.end_x; x++) {
    found = 0;
    for(r = check.start_x; r <= x;) {
      n = map.image_index[LEVEL_MAIN][r + (y * map.w)];
      if(n != EMPTY_MAP) {
        rect.x = r;
        rect.y = y;
        rect.w = images[n]->image->w / TILE_W;
        rect.h = images[n]->image->h / TILE_H;
        if(contains(&rect, x, y)) {
          //    if(intersects(&rect, &check.rect)) {
          found = 1;
          break;
        }
        r += images[n]->image->w / TILE_W;
      } else {
        r++;
      }
    }
    if(!found)
      return 0;
  }
  return 1;
}

void
showMapStatus(char *s)
{
  strcpy(map.status, s);
  map.status_time = STATUS_TIME;
}
