/** (C) Game Page Network, Inc., Confidential, All Rights Reserved */
// Club.java -- server main()
// --paul@gamepage.net, 21jul97

import java.io.*;
import java.net.*;
import java.util.*;
import java.text.*; // for date junk (the key word here is junk)

public class Club implements Runnable {
  private final static String README =
    "(C) 1998 Game Page Network, Inc., http://gamepage.net\n"+
    "Examining this code is in violation of your license agreement.\n\n\n";

  public static final String  GPN_CLUB =
    "Game Page Network V3.1.2 22jun98";

  public static boolean DEBUG = false;
  static final int GPN_PORT   = 4998; // register w/IANA
  public static int port;
  public static String name() { return GPN_CLUB; };

  private static boolean shutdown = false; // see ShutdownCommand
  
  public final static int EOF = -1; // special logout and quit
  public final static int OK  =  0; // command executed ok
  public final static int SYN =  1; // command syntax error
  public final static int ERR =  2; // command error
  public final static int CHK =  3; // move ok and put opponent in check
  public final static int WIN =  4; // move ok & checkmate|stalemate
  
  public static Table lobby = new Table("Lobby",false);

  // -----------------------------------------------------------------
  // Stuff for remote Loader
  public Club() {
  }
  
  public void run() {
    Club.main(null);
  }

  
  // -----------------------------------------------------------------
  public static void main(String[] args) {
    port = (args!=null && args.length>0) ? atoi(args[0]) : GPN_PORT;
    
    ServerSocket serverSocket = null;
    try { 
      serverSocket = new ServerSocket(port);
    } catch (Exception e1) {
      fatal(null,"Could not create GPN server socket on port "+port); 
    }

    InetAddress localHost = null;
    try { 
      localHost = InetAddress.getLocalHost();
    } catch (UnknownHostException uh) {
      fatal(null,"Could not get the IP address of localhost from network");
    }

    try {
      log("startup "+GPN_CLUB);
      log("server " +localHost +" port " + serverSocket.getLocalPort());
      warning("startup "+GPN_CLUB);
      Command.loadCommands(); // Prepare command dispatch

      Reaper reaper = new Reaper();
      reaper.start();		// background client cleanup thread

      while (!shutdown) {
	Socket socket = null;
	try { socket = serverSocket.accept(); } // block until client request
	catch (Exception e) {	// rare but possible "Protocol error"
	  warning(e,"accept");	// could be out of file descriptors!
	  continue;
	}
	if (shutdown)	// don't let last guy in
	  break;
        /// TBD: If connection is from banned host, do not process it.
        Client client = new Client(socket); // new thread for this guy
        client.start();		// calls thread run() method
	Club.sleep(1);		// yield hack
      }
    } catch (Exception e2) {
      warning(e2,"serverSocket problem");
    } finally {
      log("server socket shutdown");
      try { serverSocket.close(); }
      catch (IOException e3) { fatal(e3,"Error closing serverSocket"); }
    }
  }

  public static void shutdown(Client c, String message) {
    shutdown = true;
    Client.shutdown(c,message);
    System.exit(0);
  }

  // -----------------------------------------------------------------
  // Trusted Computers
  private static final String[] trustedIP = {
    "127.0.0.1", // game server localhost
    "207.244.122.15", // f7.net
    "158.121.104.126", // aruba.cs.umb.edu
    "24.128.10.253", // xiangqi.ne.mediaone.net
    "203.116.95.68", // sg.xiangqi.com (ping)
    "203.116.81.61", // sg.xiangqi.com (observed)
    "203.120.47.50", // chris monitor sg.xiangqi.com
    "202.189.0.13", // my.xiangqi.com (ok)
    "202.189.2.200", // chan office pc malaysia
    "202.96.140.46", // cn.xiangqi.com (ok)
    "202.96.140.45", // cn status checker
    "202.96.140.44", // new cn pub server (11jun98)
    "207.244.113.55", // us-game.xiangqi.com
    "207.244.125.10", // landshark.shore.net (observed)
    "207.244.124.103", // shell3.shore.net (ok)
  };
  public static boolean trustedIP(InetAddress address) {
    String ipName = address.toString();
    // f7.net/207.244.122.15
    for (int i=0; i< trustedIP.length; i++) {
      if (ipName.indexOf(trustedIP[i])>=0)
	return true;
    }
    log("!trusted "+ipName);
    return false;
  }
  
  // -------------------------------------------------------------------------
  // Club Coach management:  Cf http://xiangqi.com/coach.html
  private static String[] global_coaches = {
    "paul", "seal", "ddeng", "wxy", "karl" // global coaches for all servers
  };
  private static Hashtable coaches;
  /** Is MEMBER allowed to execute a coach command? */
  public static boolean coach(Member member) {
    if (member==null) return false;
    return coach(member.lowercase());
  }

  public static boolean coach(String name) {
    if (coaches==null) {
      coaches = new Hashtable(10);
      for (int i=0; i<global_coaches.length; i++)
	coaches.put(global_coaches[i],"true");
    }
    Object val = coaches.get(name);
    if (val==null) {
      String command = "grep -i ^" +name +"$ ../data/config/coaches.txt";
      int retval = exec(command);
      log(command+"="+retval);
      val = (retval==0) ? "true" : "false";
      coaches.put(name, val);
    }
    return "true".equals(val);
  }

  // -----------------------------------------------------------------
  public static long execCount = 0;
  
  public static int exec(String command) {
    execCount++;
    int retval = -1;
    try {
      Runtime r = Runtime.getRuntime();
      Process p = r.exec(command);
      p.waitFor();
      retval = p.exitValue();
      p.destroy();
    } catch (Exception e) {
      warning(e,"Club.exec error: "+command);
    }
    return retval;
  }
  public static String execo(String command) {
    execCount++;
    // sep threads for stdout and stderr, when EOF reached
    // for both, then do waitFor()
    String retval = null;
    try {
      Runtime r = Runtime.getRuntime();
      Process p = r.exec(command);
      DataInputStream in = new DataInputStream(p.getInputStream());
      retval = in.readLine();
      p.waitFor();
      int ev = p.exitValue();
      if (ev!=0) {
	DataInputStream err = new DataInputStream(p.getErrorStream());
	Club.log(command+" error: "+err.readLine());
	err.close();
      }
      in.close();
      p.destroy();
    } catch (Exception e) {
      Club.warning(e,"Club.execo error: "+command);
    }
    return retval;
  }

  public static String execReturn(String command) {
    execCount++;
    StringBuffer buf = null;
    try {
      Runtime r = Runtime.getRuntime();
      Process p = r.exec(command);
      DataInputStream in = new DataInputStream(p.getInputStream());
      String str;
      buf = new StringBuffer(128);
      while ((str=in.readLine())!=null)
	buf.append(str+"\n");
      p.waitFor();
      int exitValue = p.exitValue();
      if (exitValue!=0) {
	DataInputStream err = new DataInputStream(p.getErrorStream());
	//Club.log(command+" stderr: "+err.readLine());
	//Club.log(command+" stdout: "+buf.toString());
	buf.setLength(0);
	err.close();
      }
      in.close();
      p.destroy();
    } catch (Exception e) {
      Club.warning(e,"Club.execo error: "+command);
    }
    if (buf==null || buf.length()==0)
      return null;
    else
      return buf.toString();
  }

  public static void log(Client client, String message) {
    log(client.id()+" "+message);
  }
  public static void log(String message) {
    System.out.println(timestamp()+" "+message);
    System.out.flush();
  }
  public static void debug(String message) {
    if (DEBUG) {
      System.out.println(message);
      System.out.flush();
    }
  }
  public static void assert(boolean truth) {
    if (!truth) {
      try { throw new Exception(); }
      catch (Exception e) { warning(e,"Assertion Failure"); }
    }
  }

  public static void warning(String message) {
    warning(null,message);
  }
  public static void warning(Exception e, String message) {
    System.err.println(timestamp()+" "+message);
    if (e != null)    // programmer wants stack dump
      e.printStackTrace();
  }
  
  public static void fatal(Exception e, String message) {
    System.err.println(timestamp()+" fatal "+message);
    if (e != null)    // programmer wants stack dump
      e.printStackTrace();
    System.exit(-1);
  }

  // ------------------------------------------------------------------------
  /** Return standard logfile time, e.g., 1997-11-21 13:56:32 */
  public static String timestamp() {
    return timestamp(System.currentTimeMillis());
  }
  public static String twoDigit(int n) {
    return ((n<10)?"0":"") + n;
  }
  public static String timestamp(long ms) {
    // BUG: PST time zone assumption, I think.
    // Return format: 1998-02-28 02:59.03
    // Maybe use a synchronized static Date, and setTime() vs new.
    Date date = new Date(ms);
    return 1900+date.getYear()+"-"+
      twoDigit(date.getMonth()+1) +"-"+
      twoDigit(date.getDate()) +" "+
      twoDigit(date.getHours()) +":"+
      twoDigit(date.getMinutes())+":"+
      twoDigit(date.getSeconds());
  }
  private static String[] days = {
    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
  };
  /** Return terse day time, e.g., Mon 14:52 */
  public static String dayTime(long ms) {
    Date date = new Date(ms);
    if (date==null) return "";
    int min=date.getMinutes();
    return days[date.getDay()]+
      " "+date.getHours()+":"+((min<10)?"0":"")+min;
  }

  /** So members can only see domain, not host, of others.
   * NOT USED; 3.0 code only shows registered country.
   */
  public static String hostDomain(String host) {
    // host23.sub13.domain.com -> sub13.domain.com
    // 204.167.98.61 -> 204.167.98
    // bug fixed? 155.69.2.5 was cut to 155.69.
    String str = null;
    try {
      char ch = host.charAt(host.length()-1); // last character
      if (!Character.isDigit(ch)) // strip leftmost word
	str = host.substring(host.indexOf('.')+1);
      else			// strip rightmost word
	str = host.substring(0,host.lastIndexOf('.'));
    } catch (Exception e) {
      str = "??";
      warning("Club.hostDomain "+host);
    }
    return str;
  }

  // -----------------------------------------------------------------
  /** Sleep for MILLISECONDS */
  public static void sleep(long ms) {
    if (ms<=0) return;
    try { Thread.currentThread().sleep(ms); }
    catch (InterruptedException e) {Club.warning(e,"Interrupted sleep.");}
  }
  /** Return a random int. */
  public static int random() {
    Random generator = new Random(System.currentTimeMillis());
    return generator.nextInt();
  }
  /** Convert String to int, return 0 if any errors. */
  public static int atoi(String str) {
    int retval = 0;
    try { retval = Integer.parseInt(str); }
    catch (NumberFormatException e) {};
    return retval;
  }
}
