/* Tower Toppler - Nebulus
 * Copyright (C) 2000-2006  Andreas Röver
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include "toppler.h"

#include "robots.h"
#include "elevators.h"
#include "snowball.h"
#include "level.h"
#include "sound.h"

/* the position of the animal on the tower */
int anglepos;
long verticalpos;

/* the state of the toppler */
int state, substate;

/* have we entered the target door */
bool targetdoor;

/* some help variables for the falling toppler */
int falling_howmuch;
long falling_direction;
int falling_minimum;

/* some variables defining how to jump */
int jumping_direction, jumping_how, jumping_howlong;

/* used to time the turning of the tower when a door was entered */
int door_turner;

long elevator_direction;

/* how much must the toppler topple down */
int topple_min;

/* used when on an elevator to delay the toppling until we
 reached the next brick layer */
bool topple_delay;

/* technique points; is decreased each time the toppler gets
 thrown down */
int technic;

/* true if the toppler is visible */
static bool tvisible;
/* should the output routine put an elevator
 platform below the toppler ? */
static bool on_elevator;
/* the actual shape of the toppler */
static int topplershape;
/* the direction the toppler is looking at */
static bool look_left;


/* values for status */
#define STATE_STANDING 0
#define STATE_JUMPING 1
#define STATE_FALLING 2
#define STATE_TURNING 3
#define STATE_DOOR 4
#define STATE_SHOOTING 5
#define STATE_ELEVATOR 6
#define STATE_TOPPLING 7
#define STATE_DROWN 8
#define STATE_DROWNED 9
#define STATE_FINISHED 10


void top_init(void) {
  anglepos = 4;
  verticalpos = 8;
  state = 0;
  substate = 0;
  tvisible = true;
  on_elevator = false;
  look_left = true;
  targetdoor = false;
  topple_delay = false;
  door_turner = 0;
  technic = 0x100;
}

/* tests the underground of the animal at the given position returning
 0 if everything is all right
 1 if there is no underground below us (fall vertical)
 2 if there is no underground behind us (fall backwards)
 3 if there is no underground in front of us (fall forwards) */
static int testunderground(int verticalpos, int anglepos, bool look_left) {
  static unsigned char unter[32] = {
    0x11, 0x20, 0x02, 0x00,
    0x11, 0x00, 0x32, 0x00,
    0x11, 0x00, 0x32, 0x00,
    0x11, 0x00, 0x11, 0x00,
    0x11, 0x00, 0x11, 0x00,
    0x11, 0x00, 0x11, 0x00,
    0x11, 0x23, 0x00, 0x00,
    0x11, 0x23, 0x00, 0x00
  };

  int erg;

  int r = (verticalpos / 4) - 1;
  int c = ((anglepos + 0x7a) / 8) & 0xf;

  erg = (lev_is_empty(r, c) || lev_is_door(r, c)) ? 0 : 2;

  c = ((anglepos + 0x7a) / 8 + 1) & 0xf;

  if ((!lev_is_empty(r, c)) && (!lev_is_door(r, c))) erg++;

  erg = unter[(anglepos & 0x7) * 4 + erg];

  if (look_left)
    return erg >> 4;
  else
    return erg & 0xf;
}



/* makes the toppler fall down, the parameter specifies in
 which direction:
 0 = down start falling
 1 = backwards
 2 = forwards
 3 = down but already at a high speed (e.g. after a jump) */
static void falling(int nr) {

  state = STATE_FALLING;
  substate = 0;
  //TTSound->startsound(SND_FALL);

  switch (nr) {
    case 0:
      topplershape = 0;
      falling_direction = 0;
      falling_minimum = -1;
      falling_howmuch = 1;
      break;
  
    case 1:
      topplershape = 0xf;
      falling_direction = look_left ? -1 : 1;
      falling_minimum = -1;
      falling_howmuch = 1;
      break;
  
    case 2:
      topplershape = 0xe;
      falling_direction = look_left ? 1 : -1;
      falling_minimum = -1;
      falling_howmuch = 1;
      break;
  
    case 3:
      topplershape = 0;
      falling_direction = 0;
      falling_minimum = -1;
      falling_howmuch = 3;
      break;
  }
}

static void walking(void) {

  state = STATE_STANDING;
  substate = 0;
}

static void elevator(long dir) {

  elevator_direction = dir;
  substate = 0;
  state = STATE_ELEVATOR;

  ele_select((Uint16)verticalpos, anglepos);
}

static void shooting(void) {

  if (snb_exists()) {
    walking();
    return;
  }
  state = STATE_SHOOTING;
  substate = 0;
  topplershape = 0;
  ttsounds::instance()->startsound(SND_SHOOT);
}

static void door(void) {

  state = STATE_DOOR;
  substate = 0;
}

static void turn(void) {

  state = STATE_TURNING;
  substate = 0;
}

static void jump(int left_right) {

  state = STATE_JUMPING;
  substate = 0;
  jumping_direction = left_right;
  jumping_how = 0;
  jumping_howlong = 0xc;
}

static void step(int left_right) {

  state = STATE_JUMPING;
  substate = 0;
  jumping_direction = left_right;
  jumping_how = 1;
  jumping_howlong = 0x7;
}

static void drown(void) {

  state = STATE_DROWN;
  substate = 0;
  verticalpos = 0;

  ttsounds::instance()->setsoundvol(SND_SPLASH, 128);
  ttsounds::instance()->startsound(SND_SPLASH);
}

static void topple(void) {

  state = STATE_TOPPLING;
  substate = 0;
  targetdoor = false;

  technic -= 2;
  if (technic < 0) technic = 0;
}

/* tries to move the toppler by the given amount, returns true on
 success and false if the move is not possible (collision with
 a tower element) */
static bool movetoppler(long x, long y) {

  if (verticalpos + y < 0) {
    drown();
    return false;
  }
  if (!lev_testfigure((anglepos + x) & 0x7f, verticalpos + y, -3, 2, 0, 0, 9))
    return false;
  anglepos = (anglepos + x) & 0x7f;
  verticalpos += y;
  return true;
}

static void slidestep(int left_right, bool look_left) {

  if (left_right)
    return;

  int dir;

  if (look_left)
    dir = lev_is_sliding(verticalpos / 4 - 1, (anglepos) / 8);
  else
    dir = lev_is_sliding(verticalpos / 4 - 1, (anglepos-1) / 8);

  movetoppler((long)dir, 0);
}

/* the state machine of the animal */
void top_updatetoppler(int left_right, int up_down, bool space) {

  /* table specifying the number for the animal sprite if it is turning
   around */
  static unsigned char umdreh[7] = {
    0x8, 0x9, 0xa, 0xb, 0xa, 0x9, 0x8
  };
  
  /* the sprites if the animal turns to enter the door */
  static unsigned char door1[4] = {
    0x10, 0x11, 0x12, 0x13
  };
  
  /* and when it enters the door */
  static unsigned char door2[6] = {
    0x13, 0x14, 0x14, 0x15, 0x15, 0x16
  };
  
  /* when it leaves the door */
  static unsigned char door3[6] = {
    0x17, 0x18, 0x18, 0x19, 0x19, 0xb
  };

  /* the shapes of the toppler when it turns after leaving a door*/
  static unsigned char door4[4] = { 0xa, 0x9, 0x8, 0x0 };
  
  /* the height differences for jumping */
  static long jump0[12] = { 3, 2, 2, 1, 1, 0, 0, -1, -1, -2, -2, -3 };
  static long jump1[7] = { 2, 2, 1, 0, -1, -2, -2 };
  
  /* sprites for throwing the snowball */
  static unsigned char schiessen[3] = {
    0x00, 0x1a, 0x1b
  };
  
  /* the sprites for toppling over */
  static unsigned char toppler1[4] = {
    0x00, 0x1c, 0x1d, 0x1e
  };
  
  /* the vertical movement for toppling */
  static long toppler2[16] = {
    3, 2, 1, 1, 1, 0, 0, -1, -2, -2, -3, -3, -3, -3, -3, -4
  };

  int inh, b;

  switch (state) {

  case STATE_STANDING: 
    lev_removevanishstep(verticalpos / 4 - 1, anglepos / 8);
    switch (testunderground(verticalpos, anglepos, look_left)) {
      case 0:
        if (left_right == 0) {
          if (space)
            shooting();
          else {
            if (lev_is_up_station(verticalpos / 4 - 1, anglepos / 8) &&
                (up_down == 1)) {
              elevator(up_down);
              break;
            }
            if (lev_is_down_station(verticalpos / 4 - 1, anglepos / 8) &&
                (up_down == -1)) {
              elevator(up_down);
              break;
            }

            if (lev_is_door_upperend(verticalpos / 4, anglepos / 8) &&
                (up_down == 1)) {
              targetdoor = lev_is_targetdoor(verticalpos / 4, anglepos / 8);
              door();
              break;
            }

            slidestep(left_right, look_left);
            topplershape = 0xc;
          }
        } else {
          if ((substate == 2) || (substate == 6)) ttsounds::instance()->startsound(SND_TAP);
          if (left_right == -1) {
            if (look_left)
              turn();
            else {
              if (space)
                jump(left_right);
              else {
                if (!movetoppler(left_right, 0L))
                  step(left_right);
                else {
                  substate = (substate + 1) & 0x7;
                  topplershape = substate;
                }
              }
            }
          } else {
            if (!look_left)
              turn();
            else {
              if (space)
                jump(left_right);
              else {
                if (!movetoppler(left_right, 0L))
                  step(left_right);
                else {
                  substate = (substate + 1) & 0x7;
                  topplershape = substate;
                }
              }
            }
          }
        }
        break;
  
      case 1:
        falling(0);
        break;
  
      case 2:
        falling(1);
        break;
  
      case 3:
        falling(2);
        break;
    }
    break;

  case STATE_JUMPING: 
    topplershape = substate & 7;
    movetoppler(jumping_direction, 0L);
    if (jumping_how == 0)
      inh = jump0[substate];
    else
      inh = jump1[substate];

    if (movetoppler(0, inh)) {
      substate++;
      if (substate >= jumping_howlong) {
        if (movetoppler(0, -1))
          falling(3);
        else {
          state = STATE_FALLING;
          substate = 1;
        }
      }
    } else {
      b = inh;
      do {
        if (inh < 0)
          inh++;
        else
          inh--;
      } while (!((inh == 0) || movetoppler(0L, inh)));
      if (b < 0) {
        walking();
        ttsounds::instance()->startsound(SND_TAP);
      } else {
        substate++;
        if (substate >= jumping_howlong) {
          if (movetoppler(0L, -1L))
            falling(3);
          else {
            state = STATE_FALLING;
            substate = 1;
          }
        }
      }
    }
    break;

  case STATE_FALLING:
    if (substate == 0) {
      falling_minimum++;
      if (!movetoppler(falling_direction, 0))
        falling_direction = 0;
      if (movetoppler(0, -falling_howmuch)) {
        falling_howmuch++;
        if (falling_howmuch > 4)
          falling_howmuch = 4;
      } else {
        do {
          falling_howmuch--;
        } while (falling_howmuch && !movetoppler(0, -falling_howmuch));
        ttsounds::instance()->startsound(SND_TAP);
        if (falling_howmuch != 0) {
          falling_howmuch++;
          if (falling_howmuch > 4)
            falling_howmuch = 4;
        } else {
          if ((falling_direction == 0) || (falling_minimum >= 2)) {
            substate++;
            topplershape = 14 - substate;
            substate++;
            if (substate == 3) {
              walking();
            }
          } else {
            falling_howmuch++;
            if (falling_howmuch > 4)
              falling_howmuch = 4;
          }
        }
      }
    } else {
      topplershape = 14 - substate;
      substate++;
      if (substate == 3)
        walking();
    }
    break;

  case STATE_TURNING:
    topplershape = umdreh[substate];
    substate++;
    if ((substate == 4) || (substate == 7)) ttsounds::instance()->startsound(SND_TAP);
    if (substate == 4) look_left = !look_left;
    if (substate == 7) walking();
    break;

  case STATE_DOOR:


    switch (substate) {

    case 0:
    case 1:
    case 2:
    case 3:
      topplershape = door1[substate];
      if (((anglepos) & 7) == 4)
        substate++;
      else if (((anglepos) & 7) > 4)
        movetoppler(-1L, 0L);
      else
        movetoppler(1L, 0L);

      break;

    case 4:
    case 5:
    case 6:
      substate = 7;
      break;

    case 7:
    case 8:
    case 9:
    case 10:
    case 11:
    case 12:
      topplershape = door2[substate - 7];
      substate++;
      break;

    case 29:
    case 30:
    case 31:
    case 32:
    case 33:
    case 34:
      topplershape = door3[substate - 29];
      tvisible = true;
      substate++;
      break;

    case 35:
      if (!movetoppler(0L, -4L)) {
        if (left_right == -1)
          look_left = false;
        else
          look_left = true;
        substate++;
      }
      break;

    case 36:
    case 37:
    case 38:
    case 39:
      topplershape = door4[substate - 36];
      substate++;
      break;

    case 40:
      walking();
      break;

    default:
      if (substate >= 13 && substate <= 28) {
        if ((door_turner % 4) == 0)
          ttsounds::instance()->startsound(SND_DOORTAP);

        tvisible = false;
        if (targetdoor) {
          state = STATE_FINISHED;
          ttsounds::instance()->startsound(SND_FANFARE);
        } else {
          if (look_left)
            anglepos += 2;
          else
            anglepos -= 2;

          anglepos &= 0x7f;
          door_turner++;
          if ((door_turner & 1) == 0)
            substate++;
        }
      }
      break;
    }
    break;

  case STATE_SHOOTING:
    slidestep(left_right, look_left);
    if (substate == 3) {
      walking();
    } else {
      topplershape = schiessen[substate];
      if (substate == 1)
        snb_start(verticalpos, anglepos, look_left);
      substate++;
    }
    break;

  case STATE_ELEVATOR:

    if (substate == 0) {
      /* move the animal to the center of the elevator platform */
      if (((anglepos) & 7) != 4) {
        if (((anglepos) & 7) < 4)
          (anglepos)++;
        else
          (anglepos)--;
        anglepos &= 0x7f;
        return;
      }
      substate++;
      on_elevator = true;
      ele_activate((Sint8)elevator_direction);
      ttsounds::instance()->startsound(SND_TICK);
      return;
    }
    verticalpos += elevator_direction;

    if ((substate > 0) && (verticalpos & 3) == 0) {

      if (ele_is_atstop()) {
        on_elevator = false;
        ele_deactivate();
        walking();
      } else {
        ele_move();
        ttsounds::instance()->startsound(SND_TICK);
      }
    }
    break;

  case STATE_TOPPLING:
    if (substate < topple_min) {

      topplershape = toppler1[(substate / 2) & 3];

      if (substate < 15)
        verticalpos += toppler2[substate];
      else
        verticalpos += toppler2[15];

      if (verticalpos < 0)
        drown();
      else
        substate++;

    } else {
      topplershape = 0;
      verticalpos -= 4;
      if (verticalpos < 0)
        drown();
      else if (movetoppler(0, 0))
        falling(3);
    }
    break;

  case STATE_DROWN:
    if (substate == 0x8) ttsounds::instance()->startsound(SND_DROWN);
    if (substate < 0x18) {
      topplershape = substate / 4 + 31;
      substate++;
    } else {
      tvisible = false;
      if (substate < 0x20)
        substate++;
      else {
        state = STATE_DROWNED;
      }
    }
    break;

  case STATE_DROWNED:
  case STATE_FINISHED:
    break;

  }
}

void top_testcollision(void) {
  int nr;

  if ((state == STATE_TOPPLING) ||
      (state == STATE_DROWN) ||
      (state == STATE_DOOR) && (substate >= 10) && (substate < 31) ||
      (!tvisible))
    return;

  if (topple_delay) {
    if (state == STATE_ELEVATOR) {
      if ((verticalpos & 3) == 0) {
        ele_deactivate();
        on_elevator = false;

        topple_delay = false;
        topple();
      }
    } else {
      topple_delay = false;
      topple();
    }
    return;
  } else {
    nr = rob_topplercollision(anglepos, verticalpos + 1);
    if (nr != -1) {

      if (rob_kind(nr) == OBJ_KIND_CROSS)
        topple_min = 15;
      else
        topple_min = 20;

      if (state == STATE_ELEVATOR) {
        if ((verticalpos & 3) == 0) {
          ele_deactivate();
          topple_delay = false;
          on_elevator = false;
          topple();
        } else {
          topple_delay = true;
        }
      } else {
        topple();
      }
    } else if ((state == STATE_ELEVATOR) && !movetoppler(0L, 0L)) {

      topple_min = 20;
      if ((verticalpos & 3) == 0) {
        ele_deactivate();
        topple_delay = false;
        on_elevator = false;
        topple();
      } else {
        topple_delay = true;
      }
    }
  }
}


int top_verticalpos(void) { return verticalpos; }
int top_anglepos(void) { return anglepos; }
bool top_visible(void) { return tvisible; }
bool top_look_left(void) { return look_left; }
int top_shape(void) { return topplershape; }
bool top_onelevator(void) { return on_elevator; }
int top_technic(void) { return technic; }

bool top_died(void) { return state == STATE_DROWNED; }
bool top_targetreached(void) { return state == STATE_FINISHED; }
bool top_ended(void) { return ((state == STATE_DROWNED) || (state == STATE_FINISHED)); }
bool top_dying(void) {
  return ((state == STATE_DROWN) ||
          (state == STATE_DROWNED) ||
          (state == STATE_FINISHED));
}
bool top_walking(void) { return state == STATE_STANDING; }

void top_drop1layer(void) { verticalpos -= 4; }

void top_hide(void) { tvisible = false; }
void top_show(int shape, int vpos, int apos) {
  tvisible = true;
  topplershape = shape;
  verticalpos = vpos;
  anglepos = apos;
  look_left = true;
}

void top_sidemove(void) {
  // FIXME: the toppler needs the be brought out of the way of the elevater when they fall down
  // that needs to happen at least when standing, walking and jumping, but it might be also in other
  // cases
  // it is definitively not necessary when falling and drowning
  // i hope this if is right
  if ((state != STATE_STANDING) &&
      (state != STATE_JUMPING) &&
      (state != STATE_TURNING) &&
      (state != STATE_SHOOTING))
    return;

  if (movetoppler( 0, 0)) return;

  int i = 1;
  while (1) {
    if (movetoppler( i, 0)) break;
    if (movetoppler(-i, 0)) break;
    i++;
  }
}

