package admin;

import http.server.*;
import http.gen.HTTPReq;
import http.gen.HTTPResp;
import gen.*;
import java.io.*;
import java.util.Date;
import java.util.TimeZone;
import java.text.DateFormat;
import logging.Logger;
import edu.neu.ccs.demeterf.demfgen.lib.List;
import edu.neu.ccs.demeterf.demfgen.lib.Map;

/** Player/Competition Registration Server.  Waits for registration requests, and
 *    responds accordingly.
 *  <p> The registration protocol is as follows:
 *  <ul>
 *     <li>The Palyer sends a {@link gen.PlayerSpec PlayerSpec} as the body of
 *         a POST to the RegServer, including a URL argument.</li>
 *     <li>The full URL request will be:
 *        <quote>
 *           <tt>RegServer.PATH_ENTRY+"?"+RegServer.PASS_URL_ARG+"="+PASSWORD</tt>
 *        </quote>
 *        where <tt>PASSWORD</tt> is the Player/Team password as registered with
 *        the Teaching Staff (to be described separately).</li>
 *        
 *     <li>If registration succeeds, an HTTP Response with code 200 (OK) will be
 *         sent back, with a short description "Player '...' has been Registered".<li>
 *  </ul>
 *  </p>
 */
@Server(RegServer.PORT)
public class RegServer{
    
    /** Path the RegServer Listens On */
    public static final String PATH_ENTRY = "/register";
    /** Admins Listening (incoming) Port */
    public static final int PORT = 7000;
    /** Hashed Passwords File */
    private static final String PASS_FILE = "files/passwords.pwd";
    /** Password Argument Name */
    public static final String PASS_URL_ARG = "password";
    /** Date Format for parsing Dates */
    public static final DateFormat DateFmt =
        DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.SHORT);
    
    /** Set the current Time Zone for Date Parsing */
    static{
        DateFmt.setTimeZone(TimeZone.getTimeZone("EST5EDT"));
        //DateFmt.setTimeZone(TimeZone.getTimeZone("EST"));
    }
    
    /** Password List */
    private Map<String,String> passwds;
    /** Currently registered Players */
    private List<PlayerSpec> players = List.create();
    /** Logger for the Registration Process */
    private Logger log;
    
    /** Create a RegServer with the given Passwords.  Players/Teams will submit a
     *    Passsword, which we will 'encrypt' and store to verify the registration
     *    for competitions. */
    private RegServer(Map<String,String> ps, Logger l){ passwds = ps; log = l; }
    /** Create a RegServer with the given Passwords.  Players/Teams will submit a
     *    Passsword, which we will 'encrypt' and store to verify the registration
     *    for competitions. */
    public static RegServer create(Map<String,String> ps, Logger l){
        l.notify(PrintHeapToString.PrintHeapToStringM(ps));
        return new RegServer(ps,l);    
    }
    /** Create a RegServer with the given Logger.  Passwords will be loaded from
     *    the usual File. */
    public static RegServer create(Logger l) throws ParseException,IOException{
        PasswordFile file = PasswordFile.parse(new FileInputStream(PASS_FILE));
        return create(file.getPasswordMap(),l);
    }
    
    /** Main URL handler for the RegServer */
    @Path(PATH_ENTRY)
    public HTTPResp registerResp(HTTPReq req){
        log.event("Registration Request");
        synchronized(players){
            try{
                PlayerSpec spec = PlayerSpec.parse(req.getBody());
                if(!passwds.containsKey(spec.getName()))
                    return HTTPResp.error(log.error("Unknown Team Name '"+spec.getName()+"'"));
                if(players.contains(spec.sameNamePred()))
                    return HTTPResp.error(log.error("Player '"+spec.getName()+"' Already Registered"));
                
                Map<String,String> args = req.urlArgs();
                if(!args.containsKey(PASS_URL_ARG))
                    return HTTPResp.error(log.error("No Password Given"));
                if(verify(passwds.get(spec.getName()),args.get(PASS_URL_ARG))){
                    players = players.push(spec);
                    return HTTPResp.textResponse(log.event("Player '"+spec.getName()+"' has been Registered"));
                }
                return HTTPResp.error(log.error("Password Does not Match Player '"+spec.getName()+"'"));
            }catch(ParseException pe){
                return HTTPResp.error(log.error("Error Parsing Registration (gen.PlayerSpec)"));
            }
        }
    }
    
    /** Encrypt a given Password (consistently) */
    public static String encrypt(String s){
        // PlainText for now
        return s;    
    }
    /** Encrypt and Verify the given Password agains the known 'Hash' */
    private static boolean verify(String known, String unknownPlain){
        return known.equals(encrypt(unknownPlain));
    }
    
    @Path()
    public HTTPResp defaultResp(HTTPReq req){
        return HTTPResp.error("Unhandled Server Path '"+req.trimmedUrl()+"'");
    }
    
    /** How long to wait before re-checking the competition strat */
    private static final int TIMEOUT = 20;
    
    /** Run the registration server, until the competition starts. */
    public List<PlayerSpec> runRegistration(String startDate) throws IOException,java.text.ParseException{
        return runRegistration(DateFmt.parse(startDate));
    }
    /** Run the registration server, until the competition starts. */
    public List<PlayerSpec> runRegistration(Date start) throws IOException{
        ServerThread server = Factory.create(this);
        log.event("Registration Opened");
        log.notify("Competition will start at: "+DateFmt.format(start));
        while(start.compareTo(new Date()) > 0){
            try{
                Thread.sleep(1000*TIMEOUT);
            }catch(InterruptedException ie){}
        }
        server.shutdown();
        log.event("Registration Closed");
        return players;
    }
    
    /** Main Test for the Server. */
    public static void main(String[] args) throws Exception{
        RegServer server = RegServer.create(Logger.text(System.err));
        System.err.println(server.runRegistration("7/2/2009 12:50 pm").toString());
        System.err.println("\n\n ******* DONE : "+DateFmt.format(new Date())+" ************");
    }
}