/*
 * JVirus is a PacMan clone, written in Java.
 *
 * Please read "http://jvirus.sourceforge.net/jvirus_licence.txt" for copyrights.
 * 
 * The sourcecode is designed and created with
 * Sun J2SDK 1.3 and Microsoft Visual J++ 6.0
 *
 * JVirus homepage: http://jvirus.sourceforge.net
 *
 * autor: Slawa Weis
 * email: slawaweis@animatronik.net
 *
 */

package org.game.JVirus;

import java.awt.event.*;

import javax.swing.JFrame;

/**
 * this class controls the movement of the avatar and the animating thread.
 */
public class ActionControl implements IFields, KeyListener
{
 /**
  * reference to the level
  *
  * @see org.game.JVirus.Matrix
  */
 protected Matrix level = null;
 /**
  * reference to the display
  *
  * @see org.game.JVirus.Display
  */
 protected Display display = null;
 /**
  * reference to the JVirusFrame
  *
  * @see org.game.JVirus.JVirusFrame
  */
 protected JFrame parent = null;

 /**
  * reference to the Avatar
  *
  * @see org.game.JVirus.Avatar
  */
 protected Avatar avatar = null;
 /**
  * callback function 
  */
 protected ActionControl.IGameListener gameListener = null;
 /**
  * thread to for animated objects
  */
 protected ActionControlRunner runner = null;

 /**
  * clear byte
  *
  * @see #nextMove()
  */
 protected int set_byte = 1;

 /**
  * game running or not
  */
 protected boolean gameStart = false;
 /**
  * game paused or not
  */
 protected boolean gamePaused = false;

 /**
  * need to escape from stones
  *
  * @see #nextMove()
  */
 protected long lastTimeAvatarMove = 0;

 /**
  * callback interface for game events
  */
 public interface IGameListener
 {
  /**
   * constant for winning the game
   */
  public static final int WIN  = 0;
  /**
   * constant for losing the game
   */
  public static final int LOSE = 1;

  /**
   * end of the game
   */
  public void gameOver(int reason);
  /**
   * game score changed
   */
  public void scoreChanged(Avatar avatar);
  /**
   * game lifes changed
   */
  public void lifesChanged(Avatar avatar);
 }

 /**
  * creates the ActionControl.
  *
  * @param display      reference to the Display
  * @param parent       reference to the JVirusFrame
  * @param gameListener reference to the callback function
  */
 public ActionControl(Display display, JFrame parent, ActionControl.IGameListener gameListener)
  {
  super();
  this.display = display;
  this.parent = parent;
  this.gameListener = gameListener;
  this.avatar = new Avatar();
  }

 /**
  * return the current avatar
  *
  * @see org.game.JVirus.Avatar
  */
 public Avatar getAvatar()
  {
  return avatar;
  }

 /**
  * start the game
  *
  * @param level playing level
  */
 public void startGame(Matrix level)
  {
  this.level = level;

  // search for start
  int i = 0, j = 0;
  boolean break_loop = false;
  for(i = 0; i < level.matrix.length; i++)
     {
     for(j = 0; j < level.matrix[i].length; j++)
        {
        if(level.matrix[i][j] == IFields.START)
          {
          break_loop = true;
          break;
          }
        }
     if(break_loop) break;
     }

  // reset the avatar
  avatar.setLevel(level, j, i);

  lastTimeAvatarMove = 0;
  // repaint the avatar
  display.moveAvatar(avatar);

  // add key listener
  parent.addKeyListener(this);

  // start animating thread
  runner = new ActionControlRunner();
  runner.start();

  gameStart = true;
  gamePaused = false;
  }

 /**
  * pause the game
  *
  * @param b pause or not
  */
 public void pauseGame(boolean b)
  {
  if(!gameStart) return;

  if(b)
    {
    runner.pauseRunner(b);
    parent.removeKeyListener(this);
    gamePaused = true;
    }
  else
    {
    runner.pauseRunner(b);
    parent.addKeyListener(this);
    gamePaused = false;
    }
  }


 /* *
  * reset the game
  */
/*
 public void resetGame()
  {
  }
*/

 /**
  * reset the scores and lifes of the avatar.
  */
 public void resetAvatar()
  {
  if(!gameStart)
    avatar = new Avatar();
  }

 /**
  * stop the game
  */
 public void stopGame()
  {
  if(gameStart)
    {
    runner.stopRunner();
    runner = null;
    parent.removeKeyListener(this);
    }
  gameStart = false;
  gamePaused = false;
  }

 /**
  * is the game started or not
  */
 public boolean isRunning()
  {
  return gameStart;
  }

 /**
  * is the game paused or not
  */
 public boolean isPaused()
  {
  return gamePaused;
  }

 /**
  * edit the avatar and call the callback funktion
  */
 protected void gameOver(int reason)
  {
  // stop the game
  stopGame();
  if(reason == IGameListener.WIN)
    {
    Sound.playFX(Sound.FX_WIN);

    // add current level scores to whole game scores    
    avatar.gameScore += avatar.gameScoreCurrentLevel;
    avatar.gameLifes += avatar.gameLifesCurrentLevel;

    // this level solved
    avatar.solvedLevels[level.levelNumber] = Avatar.LEVEL_SOLVED;
    // unlock the next locked level
    for(int i = 0; i < avatar.solvedLevels.length; i++)
       {
       if(avatar.solvedLevels[i] == Avatar.LEVEL_CAN_NOT_SOLVE)
         {
         avatar.solvedLevels[i] = Avatar.LEVEL_CAN_SOLVE;
         break;
         }
       }
    }

  // buuuhhhh... :)
  if(reason == IGameListener.LOSE)
    {
    Sound.playFX(Sound.FX_LOSE);
    avatar.gameLifes--;
    }

  // call the callback function
  gameListener.gameOver(reason);
  }

 /**
  * get the key events
  */
 public void keyPressed (KeyEvent e)
  {
  // the last time, the avatar ist moving
  lastTimeAvatarMove = System.currentTimeMillis();
       // move the position on display
       if(e.getKeyCode() == KeyEvent.VK_LEFT)
         {
//         if(avatar.x - display.mapOnScreen.x < 1 && display.mapOnScreen.x > 0)
         if((avatar.x - display.mapOnScreen.x) < Display.DISPLAY_TILES_WIDTH2 &&
            display.mapOnScreen.x > 0)
           {
           display.mapOnScreen.x--;
           display.repaintMatrix();
           }
         moveAvatar(avatar.x, avatar.y, avatar.x-1, avatar.y);
         }
  else if(e.getKeyCode() == KeyEvent.VK_RIGHT)
         {
//         if((avatar.x - display.mapOnScreen.x) >= Display.DISPLAY_TILES_WIDTH-1 &&
//            display.mapOnScreen.x+Display.DISPLAY_TILES_WIDTH < level.matrix[0].length)
         if((avatar.x - display.mapOnScreen.x) >= Display.DISPLAY_TILES_WIDTH2 &&
            display.mapOnScreen.x+Display.DISPLAY_TILES_WIDTH < level.matrix[0].length)
           {
           display.mapOnScreen.x++;
           display.repaintMatrix();
           }
         moveAvatar(avatar.x, avatar.y, avatar.x+1, avatar.y);
         }
  else if(e.getKeyCode() == KeyEvent.VK_UP)
         {
//         if(avatar.y - display.mapOnScreen.y < 1 && display.mapOnScreen.y > 0)
         if((avatar.y - display.mapOnScreen.y) < Display.DISPLAY_TILES_HEIGHT2 &&
            display.mapOnScreen.y > 0)
           {
           display.mapOnScreen.y--;
           display.repaintMatrix();
           }
         moveAvatar(avatar.x, avatar.y, avatar.x, avatar.y-1);
         }
  else if(e.getKeyCode() == KeyEvent.VK_DOWN)
         {
//         if((avatar.y - display.mapOnScreen.y) >= Display.DISPLAY_TILES_HEIGHT-1 &&
//            display.mapOnScreen.y+Display.DISPLAY_TILES_HEIGHT < level.matrix.length)
         if((avatar.y - display.mapOnScreen.y) >= Display.DISPLAY_TILES_HEIGHT2 &&
            display.mapOnScreen.y+Display.DISPLAY_TILES_HEIGHT < level.matrix.length)
           {
           display.mapOnScreen.y++;
           display.repaintMatrix();
           }
         moveAvatar(avatar.x, avatar.y, avatar.x, avatar.y+1);
         }

  }
 /**
  * not used
  */
 public void keyReleased(KeyEvent e)
  {
  }
 /**
  * not used
  */
 public void keyTyped   (KeyEvent e)
  {
  }

 /**
  * next move from thread
  */
 protected void nextMove()
  {
  int i, j;
  int set = (set_byte == 1 ? 2 : 1);
  boolean matrix_changed = false;
  // search in the whole matrix
  for(i = 0; i < level.matrix.length; i++)
     for(j = 0; j < level.matrix[i].length; j++)
        {
        // if set, then this field is visited
        if(level.bufferMatrix1[i][j] == set) continue;

        // if object is a fall object
        if((Matrix.actionTyp[level.matrix[i][j]] & AT_FAll) == AT_FAll &&
           (Matrix.actionTyp[level.matrix[i+1][j]] & AT_HOLD) == 0)
          {
               // if under the object is free
               if(level.matrix[i+1][j] == EMPTY && (avatar.x != j || avatar.y != i+1))
                 {
                 matrix_changed = true;
                 level.matrix       [i+1][j] = level.matrix[i][j];
                 level.bufferMatrix1[i+1][j] = set;
                 level.matrix       [i][j]   = EMPTY;
                 level.bufferMatrix1[i][j]   = set;

                 // avatar die or not
                 if((j == avatar.x) && (i+2 == avatar.y) &&
                    (System.currentTimeMillis() - lastTimeAvatarMove) > 250)
                   gameOver(IGameListener.LOSE);
                 }
               // if right under the object is free
          else if(level.matrix[i][j+1] == EMPTY && level.matrix[i+1][j+1] == EMPTY &&
                  (avatar.x != j+1 || avatar.y != i) && (avatar.x != j+1 || avatar.y != i+1))
                 {
                 matrix_changed = true;
                 level.matrix       [i+1][j+1] = level.matrix[i][j];
                 level.bufferMatrix1[i+1][j+1] = set;
                 level.matrix       [i][j]     = EMPTY;
                 level.bufferMatrix1[i][j]     = set;

                 // avatar die or not
                 if((j+1 == avatar.x) && (i+2 == avatar.y) &&
                    (System.currentTimeMillis() - lastTimeAvatarMove) > 250)
                   gameOver(IGameListener.LOSE);
                 }
               // if left under the object is free
          else if(level.matrix[i][j-1] == EMPTY && level.matrix[i+1][j-1] == EMPTY &&
                  (avatar.x != j-1 || avatar.y != i) && (avatar.x != j-1 || avatar.y != i+1))
                 {
                 matrix_changed = true;
                 level.matrix       [i+1][j-1] = level.matrix[i][j];
                 level.bufferMatrix1[i+1][j-1] = set;
                 level.matrix       [i][j]     = EMPTY;
                 level.bufferMatrix1[i][j]     = set;

                 // avatar die or not
                 if((j-1 == avatar.x) && (i+2 == avatar.y) &&
                    (System.currentTimeMillis() - lastTimeAvatarMove) > 250)
                   gameOver(IGameListener.LOSE);
                 }
          }
        else
          {
          // position visited
          level.bufferMatrix1[i][j] = set;
          }
        }

  set_byte = set;

  // if matrix changed, repaint
  if(matrix_changed)
    {
    Sound.playFX(Sound.FX_FALL);
    display.repaintMatrix(avatar);
    display.repaint();
    }
  // if matrix not changed, repaint only the animated fields
  else
    {
    display.repaintAnimTiles(avatar);
    display.repaint();
    }
  }

 /**
  * next move from user
  */
 protected void moveAvatar(int sx, int sy, int tx, int ty)
  {
  Sound.playFX(Sound.FX_MOVE);

  // for the avatar image
  avatar.move_direction = (sx > tx);
  avatar.eat_sequence = !avatar.eat_sequence;

  if(sx != tx &&
     tx >= 0   &&
     tx < level.matrix[ty].length &&
     actionAvatar(sx, sy, tx, ty))
    {
    avatar.lx = avatar.x;
    avatar.ly = avatar.y;

    avatar.x = tx;
    avatar.y = ty;
    }

  if(sy != ty &&
     ty >= 0   &&
     ty < level.matrix.length &&
     actionAvatar(sx, sy, tx, ty))
    {
    avatar.lx = avatar.x;
    avatar.ly = avatar.y;

    avatar.x = tx;
    avatar.y = ty;
    }

  // repaint only the avatar
  display.moveAvatar(avatar);
  display.repaint();

  // if avatar in the end field
  if(level.matrix[avatar.y][avatar.x] == END)
    gameOver(IGameListener.WIN);
  }

 /**
  * select what Action to do in this field
  */
 protected boolean actionAvatar(int sx, int sy, int tx, int ty)
  {
       // remove field
       if((Matrix.actionTyp[level.matrix[ty][tx]] & AT_EAT) == AT_EAT)
         {
         avatar.gameScoreCurrentLevel += Matrix.scoreTab[level.matrix[ty][tx]];
              if((Matrix.actionTyp[level.matrix[ty][tx]] & AT_EAT_SOUND1) == AT_EAT_SOUND1)
                {
                Sound.playFX(Sound.FX_EAT1);
                }
         else if((Matrix.actionTyp[level.matrix[ty][tx]] & AT_EAT_SOUND2) == AT_EAT_SOUND2)
                {
                Sound.playFX(Sound.FX_EAT2);
                }

              if(level.matrix[ty][tx] == REDKEY || 
                 level.matrix[ty][tx] == GREENKEY ||
                 level.matrix[ty][tx] == BLUEKEY)
                {
                Sound.playFX(Sound.FX_GATE_OPEN);
                removeGate(level.matrix[ty][tx]);
                display.repaintMatrix();
                }
         else if(level.matrix[ty][tx] == LIFE)
                {
                avatar.gameLifesCurrentLevel += 1;
                gameListener.lifesChanged(avatar);
                }

         level.matrix[ty][tx] = EMPTY;
         gameListener.scoreChanged(avatar);
         }

       // transport in the beam
       if((Matrix.actionTyp[level.matrix[ty][tx]] & AT_BEAM) == AT_BEAM)
         {
         Sound.playFX(Sound.FX_BEAM);
         beamAvatar(level.matrix[ty][tx], tx, ty);
         display.mapOnScreen.x = avatar.x - Display.DISPLAY_TILES_WIDTH2;
         display.mapOnScreen.y = avatar.y - Display.DISPLAY_TILES_HEIGHT2;

         if(display.mapOnScreen.x < 0)
           display.mapOnScreen.x = 0;
         if(display.mapOnScreen.x + Display.DISPLAY_TILES_WIDTH >= level.matrix[0].length)
           display.mapOnScreen.x = level.matrix[0].length - Display.DISPLAY_TILES_WIDTH;

         if(display.mapOnScreen.y < 0)
           display.mapOnScreen.y = 0;
         if(display.mapOnScreen.y + Display.DISPLAY_TILES_HEIGHT >= level.matrix.length)
           display.mapOnScreen.y = level.matrix.length - Display.DISPLAY_TILES_HEIGHT;

         display.repaintMatrix();

         return false;
         }

       // can moving into this field or not
       if((Matrix.actionTyp[level.matrix[ty][tx]] & AT_END) == AT_END)
         {
         return true;
         }
  else if((Matrix.actionTyp[level.matrix[ty][tx]] & AT_MOVE) == AT_MOVE)
         {
         return true;
         }
  else if((Matrix.actionTyp[level.matrix[ty][tx]] & AT_ACTION) == AT_ACTION)
         {
         return true;
         }
  else if((Matrix.actionTyp[level.matrix[ty][tx]] & AT_WALL) == AT_WALL)
         {
         return false;
         }
  else if((Matrix.actionTyp[level.matrix[ty][tx]] & AT_PUSH) == AT_PUSH)
         {
         return false;
         }
  else if((Matrix.actionTyp[level.matrix[ty][tx]] & AT_ANIM) == AT_ANIM)
         {
         }

  return false;
  }

 /**
  * search for the gate that pass and remove that
  */
 protected void removeGate(char key)
  {
  char gate = ' ';

       if(key == REDKEY)
         gate = REDGATE;
  else if(key == GREENKEY)
         gate = GREENGATE;
  else if(key == BLUEKEY)
         gate = BLUEGATE;

  int i, j;
  for(i = 0; i < level.matrix.length; i++)
     for(j = 0; j < level.matrix[i].length; j++)
        {
        if(level.matrix[i][j] == gate)
          {
          level.matrix[i][j] = EMPTY;
          break;
          }
        }
  }

 /**
  * search for other beam field and move the avatar
  */
 protected void beamAvatar(char key, int x, int y)
  {
  int i, j;
  for(i = 0; i < level.matrix.length; i++)
     for(j = 0; j < level.matrix[i].length; j++)
        {
        if(level.matrix[i][j] == key && (x != j || y != i))
          {
          avatar.x = j;
          avatar.y = i;
          break;
          }
        }
  }

 /**
  * Animating thread
  */
 protected class ActionControlRunner extends Thread
 {
  protected boolean run = true;
  protected boolean pause = false;

  public void run()
   {
   while(run)
        {
        if(!pause) nextMove();
        try { sleep(500); } catch(InterruptedException e) {}
        }
   }

  /**
   * pauses the while thread loop
   */
  public void pauseRunner(boolean b)
   {
   pause = b;
   }

  /**
   * stop and exit from thread loop
   */
  public void stopRunner()
   {
   System.out.println("Thread term.");
   run = false;
   }
 }
}