/** (C) Game Page Network, Inc., Confidential, All Rights Reserved */
// Member.java -- encapsulate a Club Member (player) object
// --paul@gamepage.net, 21jul97

import java.io.*;
import java.util.*;
import java.net.*;

public class Member {
  // Class Fields:
  private static Hashtable members = new Hashtable(200);
  private static Vector banned = new Vector(10);
  // key String login, val Member

  // Instance Fields:
  private String      login;
  private String      lowercase; // all lowercase version of login
  private String      password;
  private String      country;
  private int         rating=0;	// numeric rating, default 1500
  private int	      ngames;	// number of games played
  private char	      type;	// basic, registered, computer
  
  private long        loginAt;
  private Client      client;
  private InetAddress host;
  private long        lastLogin;
  private InetAddress lastHost;
  private OutputStreamWriter out;
  private Table	      table;	// current table
  // 0=red, 1=blue, 2+=observe
  private int	      role;	// current role at table
  private Vector      muted = new Vector(4); // Members she dislikes now

  // -----------------------------------------------------------------
  public Member(String login, String password, Client client) {
    this.login     = login;
    this.lowercase = login.toLowerCase();
    this.password  = password;
    this.loginAt   = System.currentTimeMillis();
    this.lastLogin = this.loginAt;
    this.client    = client;
    this.host      = (client!=null) ? client.getAddress() : null;
    this.lastHost  = this.host;
    this.out       = (client!=null) ? client.getWriter() : null;
    this.table     = Club.lobby;
    this.type      = 'b';	// basic
    this.role      = 2;
    this.muted.removeAllElements();
    members.put(lowercase,this);
  }

  // -----------------------------------------------------------------
  // Table Management.

  public void gotoTable(Table table, int role) {
    if (out!=null) {
      this.table = table;
      this.role = role;
    } else {
      Club.log("goto "+table+" "+role+" "+this+": out=null");
    }
  }

  public boolean logout() {	// called by Table.logout
    boolean first = (out!=null); // first/real valid logout?
    if (out!=null) {
      lastLogin = loginAt;
      lastHost = host;
    }
    loginAt = 0;
    table = null;
    client = null;
    host = null;
    out = null;
    muted.removeAllElements();
    members.remove(this);
    return first;
  }

  // ---------------------------------------------------------------------
  // Member Login.

  /** Try to login, return new Member if OK else return null. */
  public static Member login(String login, String password, int id,
			     Client client) {
    if (banned.contains(login.toLowerCase())) // Coach ./BanCommand.java
      return null;
    if (!validClient(login,password,id,client))
      return null;
    
    Member member = (Member)members.get(login.toLowerCase());

    if (member != null) { // found member in cache
      if (member.out != null) {	// already have active session for member
	Club.log(client,"member login duplicate "+login);
	return null;
      }
      if (member.password.equalsIgnoreCase(password)) {
        member.loginAt = System.currentTimeMillis();
	member.client = client;
        member.host = client.getAddress(); // could be new
        member.out = client.getWriter(); // could be new
	member.getRating(true); // could be new
      } else
        member = null; // wrong password given
    }
    
    if (member==null) {
      //String command = "member --netquery --query password="+
      //password +" " +login;
      String command = "member-check "+login+" "+password;
      if (Club.port!=Club.GPN_PORT && password.equalsIgnoreCase(login))
	command = "member "+login;
      
      String matchResult = Club.execReturn(command);
      if (matchResult==null ||
	  !matchResult.regionMatches(true,0,"login|"+login,0,
				     login.length()+6))
	return null;
      member = new Member(login,password,client);
      member.country = matchField("country",matchResult);
      String val = matchField("membertype",matchResult);
      member.type = (val==null) ? 'b' : val.charAt(0);
      member.getRating(true);
      Club.debug("Member.login "+login+" "+member.getRating(false)
		 +" "+member.country);
    }
    return member;
  }

  /** Does the login info all match? */
  public static boolean match(String login, String password, int id,
			      Client client) {
    if (banned.contains(login.toLowerCase()))
      return false;
    if (!validClient(login,password,id,client))
      return false;
    //int retval = Club.exec("member --netquery --query password="+
    //password+" "+login);
    int retval = Club.exec("member-check "+login+" "+password);
    return (retval==0);
  }

  /** Did Client send back the correct encrypted version of sessionID? */
  private static boolean validClient(String login, String password,
				     int id, Client client) {
    if (Club.port!=Club.GPN_PORT && password.equalsIgnoreCase(login))
      return true;		// hack
    if (id==0)
      return Club.coach(login);
    int sessionID = client.sessionID();
    //System.out.println("validClient "+login+" "+password);
    for (int i=0; i<login.length(); i++) // cheapo crypto
      sessionID ^= ((int)login.charAt(i))<<(i%3);
    return( id==sessionID );
  }

  // -----------------------------------------------------------------
  // Accessors and Predicates
  
  public int getRole() { return role; }
  public static Enumeration getMembers()        { return members.elements(); }
  public static boolean alive(Member member) {
    return (member!=null && member.client!=null);
  }
  public static boolean isOnline(String login)  { 
    Member member = (Member)members.get(login.toLowerCase());
    return (member==null) ? false : (member.out!=null);
  }
  public static boolean premium(String login) {
    Member member = findMember(login);
    return (member!=null && member.type=='p');
  }
  public static Member findMember(String login) { 
    Member member = (Member)members.get(login.toLowerCase());
    return (member==null || member.out==null) ? null : member;
  }
  /** Return a table member is playing or observing */
  public static Table findTable(String login) {
    Member member = findMember(login);
    if (member!=null)
      return member.table;
    return null;
  }

  public String lowercase() {
    return (lowercase==null) ? "<null>" : lowercase;
  }
  public String toString() {
    return (login==null) ? "<null>" : login;
  }
  
  public String getHostName() {
    if (host==null)
      return "";
    else
      return host.getHostName();
  }
  public String getLastHost() {
    return (lastHost==null) ? "" : lastHost.getHostName();
  }
  public Client getClient() 	          { return client; }
  public OutputStreamWriter getWriter()   { return out; }
  public long getLoginAt()                { return loginAt; }
  public long getLastLogin()              { return lastLogin; }

  public Table getTable() {
    return table;
  }

  public boolean isPlayingActive() {
    return role==0 && table!=null && table.isActive();
  }

  public boolean isAt(Table table) {
    return this.table == table;
  }
  public boolean isPlayer(Table table) {
    return isAt(table) && role<2;
  }
  
  public boolean isPlaying(Table table) {
    return isPlayer(table) && !table.isOpen();
  }

  public boolean isObserving(Table table) {
    return isAt(table) && role>=2;
  }
  
  /** Return idleMinutes, e.g., "3:12" for 3 hours 12 minutes idle */
  public String idleMinutes() {
    return (client==null) ? "<disconnect>" : client.idleMinutes();
  }

  public static void ban(Member badguy) { // coach BanCommand
    banned.addElement(badguy.lowercase);
  }
  public void mute(Member badguy) {
    muted.addElement(badguy);
  }
  public void unmute(Member badguy) {
    muted.removeElement(badguy);
  }
  public boolean listensTo(Member talker) {
    if (talker==null)		// anonymous system message
      return true;
    boolean retval=false;
    try {
      retval = !muted.contains(talker);
    } catch (Exception e) {
      Club.log("Member.listensTo error: "+this+" "+talker);
    }
    return retval;
  }

  public static String info(Member member) {
    if (member==null)
      return "* * *";
    else
      return member.toString()+" "+
	member.getRating(false)+" "+
	member.getCountry();
  }
  
  public String getCountry() {
    return country;
  }
  public String getRating(boolean force) {
    if (force || rating==0) {
      String results = Club.execo("rating "+login);
      StringTokenizer st = new StringTokenizer(results==null?"":results);
      if (st.countTokens()!=3) {
	rating = 1500;
	ngames = 0;
      } else {
	if (!login.equalsIgnoreCase(st.nextToken()))
	  Club.debug(login+" rating mismatch: "+results);
	rating = Club.atoi(st.nextToken());
	ngames = Club.atoi(st.nextToken());
      }
    }
    String suffix = "";
    if (ngames<20)
      suffix = "P";		// provisional
    else if (this.type=='p')
      suffix = "R";		// registered/premium
    else if (this.type=='c')	// computer robot
      suffix = "C";
    return "" + rating + suffix;
  }
  /** Table just recorded results and called rating DB;
   * Update the cached rating for member as well.
   * Result is like: paul 1500 23
   */
  public static void updateRating(String results) {
    StringTokenizer st = new StringTokenizer(results);
    if (st.countTokens()!=3) {
      Club.log("Member.updateRating error: "+results);
    } else {
      Member member = Member.findMember(st.nextToken());
      if (member!=null) {
	member.rating = Club.atoi(st.nextToken());
	member.ngames = Club.atoi(st.nextToken());
      }
    }
  }


  /** Match FIELD from RESULTS list and return value.
   * FIELD is like "country".
   * RESULTS is like "login|paul\nlocale|Boston\ncountry|US"
   * Field list is: login, locale, interests, ctime, password,
   * country, homepage, email, skill, membertype, fullname, published.
   */
  private static String matchField(String field, String results) {
    if (results==null || results.length() <= (field.length()+1))
      return null;
    String resultsLC = results.toLowerCase();
    String fieldLC = field.toLowerCase();
    int b,e;
    if (resultsLC.startsWith(fieldLC+"|"))
      b = 0;
    else {
      b = resultsLC.indexOf("\n"+fieldLC+"|");
      if (b<0)
	return null;
      b++;			// past \n
    }
    // b is begin of field, advance to begin of value:
    b += field.length() + 1;
    e = results.indexOf("\n",b);
    if (e<0)
      e = results.length();
    return results.substring(b,e);
  }

  // -----------------------------------------------------------------
  // Member Who What Where
  
  /** Get info about MEMBERNAME, append to BUFFER.
   * Called by ProfileCommand.
   */
  public static String profile(String name) {
    Member member = findMember(name);	// online now?
    if (member!=null) {
      return(""+name+": "+
	     member.getRating(false)+" "+
	     member.getCountry()+" "+
	     Club.dayTime(member.loginAt)+" "+	     
	     member.idleMinutes()+" "+
	     member.roleName()+
	     "@"+member.table);
    } else {
      return(name+": not online");
    }
  }

  /** Show who is logged in right now. Called by WhoCommand.process()
   * See also Table.showWho()
   */
  public static void showWho(Client c) {
    Enumeration e = members.keys();
    // who name rating cc day login idle table role

    StringBuffer buf = new StringBuffer(80*members.size());
    while (e.hasMoreElements()) {
      String login = (String)e.nextElement();
      Member member = (Member)members.get(login);
      showMember(member,buf,false);
    }
    buf.append("%%");
    c.send(buf);
  }
  private String roleName() {
    if (role==0) return "R";
    if (role==1) return "B";
    return "";
  }

  public static boolean zombie(Member member) {
    return member==null || member.out==null || member.table==null;
  }
  
  public static void showMember(Member member, StringBuffer buf,
				boolean verbose) {
    if (zombie(member)) return;
    buf.append("% "+member.toString()+" "+
	       member.getRating(false)+" "+
	       member.getCountry()+" "+
	       (verbose ?
		(Club.dayTime(member.loginAt)+" "+
		 member.idleMinutes()+" ") : "")+
	       member.roleName()+
	       "@"+member.table+"\n");
  }
  public void dump() {
    Club.log("dump "+login+" "+out+" "+client+" "+table);
  }
}
