/** (C) 1999 World Xiangqi League, Confidential, All Rights Reserved */

import java.awt.*;
import java.awt.image.*;
import java.net.*;
import java.util.*;

// paul 1536P    [USA]    13:04 3:02

class PlayerCanvas extends Canvas implements Runnable {
  private Color color;		// red or blue
  private String name = "*";	// open
  private String rating = "1500P";
  private String country;
  private int flag = -1;	// index into flags.gif, -1 for no flag
  
  // Timer stuff:
  private int gameSeconds;	// #secs player has for rest of game
  private int moveSeconds;	// #secs player has for this move
  private int gameMinutes = 30;	// total start minutes
  private int moveMinutes = 3;	// total start minutes
  private long turnStart;	// current time at start of this turn
  private int secondsStart;	// seconds left at start of turn

  // Hotlink endpoints:
  private int namex1, namex2;	// left and right edge of name field
  private int ratingx1, ratingx2;
  private int countryx1, countryx2;
  private int timerx1, timerx2;

  // Other objects:
  private Thread thread;	// for timer
  private GamePanel gamePanel;
  private Club club;


  // -----------------------------------------------------------------
  // Static Class Flag Images
  
  private static Image flagStrip; // flags.gif from our webpage footer
  private static final int FLAGX1 = 62;	// startx for US flag
  private static final int FLAGY1 = 2; // cut off a couple pixels
  private static final int FLAGW = 40; // width in pixels of each flag
  private static final int FLAGH = 20; // height in pixels of each flag
  private static final int FLAGY2 = FLAGY1 + FLAGH;
  private static String[] COUNTRIES = {
    // Uppercase countries have active servers!
    "US", "CN", "SG", "MY", "ca", "hk", "au", "uk", "tw", "de", "jp"
    // Flag bounds in flags.gif:
    // US:  66,2, 102,22
    // CN: 103,2, 143,22
  };
  private static Image[] flagImages;

  public static void loadFlags(Club club) {
    flagStrip = ImageButton.load("flags.gif",club,true);
    ImageProducer source = flagStrip.getSource();    
    Toolkit tk = Toolkit.getDefaultToolkit();
    flagImages = new Image[COUNTRIES.length];
    for (int i=0; i<COUNTRIES.length; i++) {
      ImageFilter filter = new CropImageFilter(FLAGX1+(FLAGW+1)*i,2,
					       FLAGW,FLAGH);
      ImageProducer producer = new FilteredImageSource(source, filter);
      flagImages[i] = tk.createImage(producer);
    }
  }

  // -----------------------------------------------------------------
  PlayerCanvas(Color color, Club club) {
    super();
    this.color = color;
    this.club = club;
    this.gamePanel = null;

    this.thread = null;
    this.gameSeconds = 60*gameMinutes;
    this.moveSeconds = 60*moveMinutes;
    this.country = null;
    this.flag = -1;
    this.rating = "1500P";
  }

  public void gamePanel(GamePanel gamePanel) {
    this.gamePanel = gamePanel;
    resize(gamePanel.boardWidth(),24);
  }

  public void setupPlayer(String player) {
    setupPlayer(player,null,null);
  }
  public void setupPlayer(String player, String rating, String country) {
    setName(player);
    setRating(rating);
    this.country = ("*".equals(country)) ? null : country;
    this.flag = -1;
    for (int i=0; i<COUNTRIES.length; i++) {
      if (COUNTRIES[i].equalsIgnoreCase(country))
	flag = i;
    }
    this.thread = null;
    this.gameMinutes = 30;
    this.moveMinutes = 3;
    this.gameSeconds = 60*gameMinutes;
    this.moveSeconds = 60*moveMinutes;
    repaint();
  }

  public String getName()  { return name;  }
  public String toString() { return color()+" "+name;  }
  private String color()   { return ((color==Color.red)?"red":"blue");  }
  public void setName(String value) {
    this.name = (value==null) ?  "*" : value;
    // Player leave, make sure timer stops!
    if ("*".equals(this.name))
      thread = null;
    repaint();
  }

  /** Handle club broadcast of game results. */
  public void newRating(String player, String rating) {
    if (player.equalsIgnoreCase(name))
      setRating(rating);
  }
  public void setRating(String rating) {
    this.rating = (rating==null || rating.equals("*")) ? "1500P" : rating;
    repaint();
  }

  // -----------------------------------------------------------------
  /** Called by GamePanel.handleTimerMenu() */
  public void setTimers(int gameMinutes, int moveMinutes) {
    setTimers(gameMinutes,moveMinutes,gameMinutes*60);
  }
  /** Called by GamePanel.processTimer() */
  public void setTimers(String gameMinutes, String moveMinutes, String sec) {
    setTimers(Club.atoi(gameMinutes), Club.atoi(moveMinutes), Club.atoi(sec));
  }
  private void setTimers(int gameMinutes, int moveMinutes, int seconds) {
    //Club.debug("setTimers("+gameMinutes+","+moveMinutes+","+seconds+") "+
    //gamePanel.gameState()+" "+secondsStart+" "+thread);
    this.gameMinutes = max(gameMinutes,0);
    this.moveMinutes = max(moveMinutes,0);
    if (seconds==0 && gamePanel.gameState()=='b')
      seconds = this.gameMinutes*60;
    this.gameSeconds = max(seconds,0);
    this.moveSeconds = max(moveMinutes*60,0);
    repaint();
  }
  
  private void setTimer(int seconds) {
    //Club.debug(this.toString()+" setTimer "+seconds+" "+moveSeconds);
    gameSeconds = max(seconds,0);
    repaint();
  }
  /** Called by GamePanel.gameSync() */
  public long getTimers() {
    return (((long)gameSeconds)<<32) | moveSeconds;
  }
  /** Called by GamePanel.processReady() */
  public void restoreTimers(long timers) {
    gameSeconds = (int)(timers>>32);
    moveSeconds = (int)(timers&0xffff);
    long now = System.currentTimeMillis();
    turnStart = now - (60*moveMinutes - moveSeconds)*1000;
    secondsStart = gameSeconds - 2;
    repaint();
  }

  // -----------------------------------------------------------------
  // Game Timers. Note resume() causes Netscape deadlocks!
  // Thus we must create a new thread each move!
  public void start() {
    if (gameSeconds<=0)
      return;
    if (thread==null) {
      thread = new Thread(club.threadGroup, this, getName()+" timer");
      //thread.setDaemon(true);
      turnStart = System.currentTimeMillis();
      secondsStart = gameSeconds;
      moveSeconds = 60*moveMinutes;
      thread.start(); // will cause run() method to be invoked
    }
  }
  public int stop(int seconds) {
    thread = null;
    moveSeconds = 60*moveMinutes;
    if (seconds>=0)
      setTimer(seconds);
    return gameSeconds;
  }
  public void run() {
    while (thread!=null) {
      Club.sleep(1000);		// could take longer
      long now = System.currentTimeMillis();
      int elapsedSeconds = (int)((now - turnStart) / 1000);
      if (elapsedSeconds < 0) {	// player try to cheat by reset PC clock?
	Club.debug("PlayerCanvas.run elapsed = "+elapsedSeconds);
	turnStart = now - (60*moveMinutes - moveSeconds + 2)*1000;
	elapsedSeconds = (int)((now - turnStart) / 1000);
	Club.debug("PlayerCanvas.run elapsed now = "+elapsedSeconds);
      }
      moveSeconds = max(60*moveMinutes - elapsedSeconds, 0);
      gameSeconds = max(secondsStart - elapsedSeconds, 0);
      repaint();
      if (gameSeconds==0 || moveSeconds==0) {
	Club.debug("Out of time "+getName()+" "+gamePanel);
	if (gamePanel!=null && thread!=null)
	  gamePanel.timeout(getName());
	return;
      }
    }
  }

  public boolean unplay(String player) {
    if (player!=null && player.equalsIgnoreCase(name)) {
      name = "*";
      rating = "1500P";
      country = null;
      flag = -1;
      repaint();
      return true;
    }
    return false;
  }
  public boolean isHere() {
    return name != "*";
  }

  // -----------------------------------------------------------------
  public boolean mouseEnter(Event event, int x, int y) {
    hotZone = false;
    hotZone(event);
    return true;
  }
  public boolean mouseExit(Event event, int x, int y) {
    //club.cursorReset("PlayerCanvas");
    return true;
  }
  private boolean hotZone = false;
  private boolean hotZone(Event event) {
    boolean retval = (within(event.x,namex1,namex2) ||
		      within(event.x,ratingx1,ratingx2) ||
		      ((flag>=0) && within(event.x,countryx1,countryx2)) ||
		      within(event.x,timerx1,timerx2));
    if (retval!=hotZone)	// changed zones
      club.cursor("PlayerCanvas",
		  retval ? Frame.HAND_CURSOR : Frame.DEFAULT_CURSOR);
    return(hotZone = retval);
  }

  public boolean handleEvent(Event event) {
    //Club.debugEvent("PlayerCanvas",event);
    if (event.id==Event.KEY_ACTION || event.id==Event.KEY_PRESS)
      return club.handleKey(event);
    hotZone(event);
    if (event.id==Event.MOUSE_DOWN) {
      if (within(event.x,namex1,namex2))
	club.showMemberPage(name);
      else if (within(event.x,ratingx1,ratingx2))
	club.showDoc("ratings.html");
      else if (within(event.x,timerx1,timerx2))
	club.showDoc("timers.html");
      else if (within(event.x,countryx1,countryx2) && flag>=0) {
	String domain = COUNTRIES[flag];
	if (Character.isUpperCase(domain.charAt(0)))
	  club.showDoc("http://"+domain.toLowerCase()+".xiangqi.com");
	else
	  club.showDoc("expansion.html");
      }
    }
    return super.handleEvent(event);
  }

  // -----------------------------------------------------------------
  private Image offScreenImage;
  private Graphics offScreenGraphics;
  private Dimension offScreenSize;
  private boolean imagesOk = false;
  
  public void paint(Graphics g) { update(g);  }
  public void update(Graphics g) {
    Dimension d = size();    
    if (offScreenImage==null || !imagesOk ||
	offScreenSize.width!=d.width ||
	offScreenSize.height!=d.height)
      prepareImageBuffer();
    if (offScreenImage!=null)
      g.drawImage(offScreenImage,0,0,this);
  }
  
  public void prepareImageBuffer() {
    if (gamePanel==null) return;
    
    Dimension d = offScreenSize = size();
    if (offScreenImage!=null)
      offScreenImage.flush();
    offScreenImage = createImage(d.width, d.height);
    offScreenGraphics = offScreenImage.getGraphics();
    Graphics g = offScreenGraphics;
    
    int dwall = d.width; // this width extends beyond board to buttonpanel
    int dw = gamePanel.boardWidth();
    int dh = d.height;
    int gap = max(10,dw/20);
    
    //Club.debug("paint "+dw+"x"+dh);
    g.setFont(Club.font(Font.BOLD,-1));
    g.setColor(getBackground());    
    g.fillRect(0,0,dwall,dh);
    g.setColor(color);
    //g.setForeground(color);
    FontMetrics fm = getFontMetrics(getFont());
    int baseline = (dh+fm.getHeight())/2 - 1;
    int startx;

    // 1. Left side: name and rating:
    startx = namex1 = gap;
    namex2 = namex1 + fm.stringWidth(name);
    g.drawString(name,startx,baseline);
    if (isHere())
      g.drawLine(namex1,baseline+1,namex2,baseline+1); // hyperlink
    if (dw>250) {		// enough room
      startx = ratingx1 = namex2 + gap;
      ratingx2 = ratingx1 + fm.stringWidth(rating);
      g.drawString(rating,startx,baseline);
    } else {			// not enough room
      ratingx1 = ratingx2 = 0;
    }

    // 2. Right side: game and move timers:
    String timer = timer(gameSeconds);
    startx = timerx1 = dw - 2*fm.stringWidth(timer) - gap*2;
    g.drawString(timer,startx,baseline);

    startx += fm.stringWidth(timer) + gap;
    timer = timer(moveSeconds);
    timerx2 = startx + fm.stringWidth(timer);
    g.drawString(timer,startx,baseline);

    // 3. Middle: flag clipped from flagStrip:
    if (dw>250) {		// enough room
      startx = countryx1 = dw/2 - FLAGW/2;
      if (flag>=0 && flagStrip.getWidth(this)>1) { // draw from flagStrip
	countryx2 = countryx1 + FLAGW;
	int y = (dh-FLAGH)/2;
	g.drawImage(flagImages[flag],startx,y,this);
	//Club.debugImage("flag "+flag+" "+startx+","+y,flagImages[flag]);
      } else if (country!=null) {
	countryx2 = countryx1 + fm.stringWidth(country);
	//Club.debug("paint country name "+country);
	g.drawString(country,startx,baseline);
      } else if (isHere()) {
	//Club.debug("PlayerCanvas null country for player "+name);
      }
    } else {			// not enough room
      countryx1 = countryx1 = 0;
    }

    g.dispose();
  }

  public boolean imageUpdate(Image image, int flags,
			     int x, int y, int w, int h) {
    if ((flags & (ImageObserver.FRAMEBITS | ImageObserver.ALLBITS)) != 0) {
      repaint();		// flag image just became ready
      return false;
    } else {
      return true;
    }
  }
  
  private String timer(int seconds) {
    if (seconds<0)
      seconds = 0;
    int min = seconds / 60;
    seconds = seconds % 60;
    return (""+min+":"+ ((seconds<10) ? "0" : "") + seconds);
  }

  private boolean within(int x, int x1, int x2) {
    return (x>=x1 && x<=x2);
  }
  private int abs(int n) { return (n<0) ? -n : n; }
  private int min(int a, int b) { return ((a<b) ? a : b); }  
  private int max(int a, int b) { return ((a>b) ? a : b); }
}
