
// BugPanel.java
// Andrew Davison, April 2005, ad@fivedots.coe.psu.ac.th

/* Kreslící povrch hry. Používá aktivní vykreslování na panel JPanel
   s pomocí časovače z knihovny Java 3D.
   Verze generující statistiky je v aplikaci WormP.
*/

import javax.swing.*;
import java.awt.image.*;
import java.awt.event.*;
import java.awt.*;
import com.sun.j3d.utils.timer.J3DTimer;


public class BugPanel extends JPanel implements Runnable
{
  private static final int PWIDTH = 500;   // velikost panelu
  private static final int PHEIGHT = 400; 

  private static final int NO_DELAYS_PER_YIELD = 16;
  /* Počet snímků s prodlevou 0 ms před tím, než dojde k útlumu animačního vlákna
   */
  private static int MAX_FRAME_SKIPS = 5;
    // počet snímků, které lze v animačním cyklu přeskočit
    // tj. hra se aktualizuje ale nevykreslí

  // soubory s údaji o obrázcích a klipech
  private static final String IMS_INFO = "imsInfo.txt";
  private static final String SNDS_FILE = "clipsInfo.txt";


  private Thread animator;           // vlákno provádějící animaci
  private volatile boolean running = false;   // pro pozastavení animačního vlákna
  private volatile boolean isPaused = false;

  private long period;                // interval mezi vykresleními - v nanosekundách
  private BugRunner bugTop;
  private ClipsLoader clipsLoader;

  private BallSprite ball;        // naše sprity
  private BatSprite bat;

  private long gameStartTime;   // kdy hra začala
  private int timeSpentInGame;

  // pro ukončení hry
  private volatile boolean gameOver = false;
  private int score = 0;

  // zobrazování zpráv
  private Font msgsFont;
  private FontMetrics metrics;

  // vykreslování na povrch mimo obrázovku
  private Graphics dbg; 
  private Image dbImage = null;

  // uchovává obrázek s pozadím
  private BufferedImage bgImage = null;



  public BugPanel(BugRunner br, long period)
  {
    bugTop = br;
    this.period = period;

    setDoubleBuffered(false);
    setBackground(Color.black);
    setPreferredSize( new Dimension(PWIDTH, PHEIGHT));

    setFocusable(true);
    requestFocus();    // vstup od klávesnice je náš, prijímáme její události

	addKeyListener( new KeyAdapter() {
       public void keyPressed(KeyEvent e)
       { processKey(e);  }
     });

    // načti obrázek na pozadí
    ImagesLoader imsLoader = new ImagesLoader(IMS_INFO); 
    bgImage = imsLoader.getImage("bladerunner");

    // inicializuj objekt pro načítání zvukových klipů
    clipsLoader = new ClipsLoader(SNDS_FILE); 

    // vytvoř herní sprity
    bat = new BatSprite(PWIDTH, PHEIGHT, imsLoader, 
                                     (int)(period/1000000L) ); // v ms
    ball = new BallSprite(PWIDTH, PHEIGHT, imsLoader, clipsLoader, this, bat); 

    addMouseListener( new MouseAdapter() {
      public void mousePressed(MouseEvent e)
      { testPress(e.getX()); }  // zpracuj klepnutí myší
    });

    // nastav font pro zprávy
    msgsFont = new Font("SansSerif", Font.BOLD, 24);
    metrics = this.getFontMetrics(msgsFont);

  }  // konec metody: BugPanel()



  private void processKey(KeyEvent e)
  // řeší ukončení a klávesy ovlivňující dění ve hře
  {
    int keyCode = e.getKeyCode();

    // klávesy ukončující aplikaci
	// pro snadné ukončení z celoobrazovkové konfigurace
	// naslouchej klávesám esc, q, end, ctrl-c na herní obrazovce
    if ((keyCode == KeyEvent.VK_ESCAPE) || (keyCode == KeyEvent.VK_Q) ||
        (keyCode == KeyEvent.VK_END) ||
        ((keyCode == KeyEvent.VK_C) && e.isControlDown()) )
      running = false;
    
    // klávesy ovlivňující dění ve hře
    if (!isPaused && !gameOver) {
      if (keyCode == KeyEvent.VK_LEFT)
        bat.moveLeft(); 
      else if (keyCode == KeyEvent.VK_RIGHT)
        bat.moveRight();
      else if (keyCode == KeyEvent.VK_DOWN)
        bat.stayStill();
    }
  }  // konec metody: processKey()



  public void gameOver()
  // Volá se ze třídy BallSprite pro upozornění, že hra skončila
  { 
    int finalTime = 
          (int) ((J3DTimer.getValue() - gameStartTime)/1000000000L);  // nanosekundy --> sekundy
    score = finalTime;   // mohlo by být o něco propracovanější!
    clipsLoader.play("gameOver", false);   // play a game over clip once
    gameOver = true;  
  } // konec metody: gameOver()



  public void addNotify()
  // před spušťením vlastní hry vyčkej, než bude panel JPanel přidán do okna JFrame
  { super.addNotify();   // předej upozornění
    startGame();         // spusť vlákno
  }


  private void startGame()
  // inicializuj a spusť animační vlákno 
  { 
    if (animator == null || !running) {
      animator = new Thread(this);
	  animator.start();
    }
  } // konec metody: startGame()
    

  // ------------- metody životního cyklu hry ------------
  // volají se z metody okna JFrame implementující rozhraní WindowListener

  public void resumeGame()
  // volá se při aktivaci / de-ikonifikaci okna JFrame
  {  isPaused = false;  } 


  public void pauseGame()
  // volá se při deaktivaci / ikonifikaci okna JFrame
  { isPaused = true;   } 


  public void stopGame() 
  // volá se při uzavírání okna JFrame
  {  running = false;   }

  // ----------------------------------------------


  private void testPress(int x)
  // ovládá pálku pomocí stisků tlačítek myši
  { if (!isPaused && !gameOver)
      bat.mouseMove(x);
  }  // konec metody: testPress()


  public void run()
  /* Snímky animace vykreslujeme uvnitř cyklu while. */
  {
    long beforeTime, afterTime, timeDiff, sleepTime;
    long overSleepTime = 0L;
    int noDelays = 0;
    long excess = 0L;

    gameStartTime = J3DTimer.getValue();
    beforeTime = gameStartTime;

	running = true;

	while(running) {
	  gameUpdate();
      gameRender();
      paintScreen();

      afterTime = J3DTimer.getValue();
      timeDiff = afterTime - beforeTime;
      sleepTime = (period - timeDiff) - overSleepTime;  

      if (sleepTime > 0) {   // v této iteraci zbyl ještě nějaký čas
        try {
          Thread.sleep(sleepTime/1000000L);  // nanosekundy -> milisekundy
        }
        catch(InterruptedException ex){}
        overSleepTime = (J3DTimer.getValue() - afterTime) - sleepTime;
      }
      else {    // sleepTime <= 0; snímek trval déle než-li interval
        excess -= sleepTime;  // ulož překročený čas
        overSleepTime = 0L;

        if (++noDelays >= NO_DELAYS_PER_YIELD) {
          Thread.yield();   // dej jinému vláknu příležitost k běhu
          noDelays = 0;
        }
      }

      beforeTime = J3DTimer.getValue();

      /* Trvá-li snímek příliš dlouho, aktualizuj herní stav bez vykreslení,
         aby se rychlost aktualizací za sekundu přiblížila požadované hodnotě FPS. */
      int skips = 0;
      while((excess > period) && (skips < MAX_FRAME_SKIPS)) {
        excess -= period;
	    gameUpdate();    // aktualizuj stav hry, ale nevykresluj ji
        skips++;
      }
	}
    System.exit(0);   // rozlučme se z okýnkem
  } // konec metody: run()


  private void gameUpdate() 
  { if (!isPaused && !gameOver) {
      ball.updateSprite();  
      bat.updateSprite();  
    }
  }  // konec metody: gameUpdate()


  private void gameRender()
  {
    if (dbImage == null){
      dbImage = createImage(PWIDTH, PHEIGHT);
      if (dbImage == null) {
        System.out.println("dbImage má hodnotu null");
        return;
      }
      else
        dbg = dbImage.getGraphics();
    }

    // vykresli pozadí: použij obrázek nebo černou barvu
    if (bgImage == null) {
      dbg.setColor(Color.black);
      dbg.fillRect (0, 0, PWIDTH, PHEIGHT);
    }
    else
      dbg.drawImage(bgImage, 0, 0, this);

    // nakresli herní prvky
    ball.drawSprite(dbg); 
    bat.drawSprite(dbg);

    reportStats(dbg);

    if (gameOver)
      gameOverMessage(dbg);
  }  // konec metody: gameRender()


  private void reportStats(Graphics g)
  // Zobrazí čas strávený hraním a počet vrácených míčků
  {
    if (!gameOver)    // po skončení hry zastav zvyšování časovače
      timeSpentInGame = 
          (int) ((J3DTimer.getValue() - gameStartTime)/1000000000L);  // nanosekundy --> sekundy

	g.setColor(Color.yellow);
    g.setFont(msgsFont);

    ball.drawBallStats(g, 15, 25);  // the ball sprite reports the ball stats
	g.drawString("Čas: + timeSpentInGame + " sekund", 15, 50);
   
	g.setColor(Color.black);
  }  // konec metody: reportStats()


  private void gameOverMessage(Graphics g)
  // V panelu vycentruj zprávu o ukončení hry.
  {
    String msg = "Hra skončila. Dosažený počet bodů: " + score;

	int x = (PWIDTH - metrics.stringWidth(msg))/2; 
	int y = (PHEIGHT - metrics.getHeight())/2;
	g.setColor(Color.red);
    g.setFont(msgsFont);
	g.drawString(msg, x, y);
  }  // konec metody: gameOverMessage()


  private void paintScreen()
  // obrázek z paměti na obrazovku přenášíme pomocí aktivního vykreslování
  { 
    Graphics g;
    try {
      g = this.getGraphics();
      if ((g != null) && (dbImage != null))
        g.drawImage(dbImage, 0, 0, null);
      // v některých systémech musíme použít synchronizaci obrazovky.
      // (na Linuxu takto opravíme problémy s frontou událostí)
      Toolkit.getDefaultToolkit().sync();

      g.dispose();
    }
    catch (Exception e)
    { System.out.println("Chyba při práci s grafickým kontextem: " + e);  }
  } // konec metody: paintScreen()


}  // konec metody: BugPanel
