/*
 *	EasyScout Boy Scouts of America tracking software.
 *	http://www.easyscout.org/
 *
 *	Copyright (C) 2000 Conan C. Albrecht
 *	conan@warp.cx
 *
 *	This program is released under the GNU license so tech-savvy
 *	Boy Scouts and others can enhance the program for the use
 *	of all.  Please e-mail changes to my address above so I can
 *	reintegrate into the main program at http://www.easyscout.org/.
 *	This way, all users mutually benefit one another in the spirit of
 *	scouting.
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	as published by the Free Software Foundation; either version 2
 *	of the License, or (at your option) any later version.
 *
 * 	Any and all changes to the program code must be documented with
 *  date, author name and e-mail, and a short description.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package cx.warp.scout;

import cx.warp.scout.graphics.*;
import cx.warp.scout.lookup.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import java.util.zip.*;

/** The main entry point into the EasyScout program.

		All program access comes through this class.  I made this decision
		(rather than using multiple JSP's or servlets) because it is much simpler
		this way.  I firmly believe in the Keep It Simple principle.  Things just
		work better that way.  One entry point makes security and sessioning much
		easier and enables better control.  It also keeps things from getting
		spread all over the place.
 */
public class EasyScout extends HttpServlet {
	
	TimedHashtable sessions = new TimedHashtable();
	DataGate datagate = null;
	
	/** Initialize global variables */
	public synchronized void init(ServletConfig config) throws ServletException {
		super.init(config);
		
		// initialize the data source
		try {
			datagate = new cx.warp.scout.file.FileDataGate();
			datagate.init();
		}catch (Exception e) {
			e.printStackTrace();
			throw new ServletException("Error while initializing data source.  More information in the error log.  " + e.toString());
		}//if

	}//service

	/** Service the request */               
	public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		PrintWriter out = null;
		String error = "Invalid parameters.";
		try {
			// set the response type (since all reponses are HTML pages)
			String settype = request.getParameter("settype");
			boolean skeleton = !(settype != null && settype.equals("0"));

			response.setContentType("text/html");
			out = new PrintWriter (response.getOutputStream());

			boolean printerfriendly = false;
			if (!skeleton) {
				String pf = request.getParameter("printerfriendly");
				if (pf != null && pf.equals("1")) {
					printerfriendly = true;
					Utils.minimalHeader(request, out);
				}//if
			}//if
			
			// get the action request
			Session session = null;
			String action = request.getParameter("action");
			if (action == null) {
				action = "ShowLogin";
			}//if
			
			// first look for action items that require no session
			boolean validate = true;
			if (action.equals("ShowSignup")) {
				signupScreen(request, out);
				validate = false;
				
			}else if (action.equals("Signup")) {
				session = signup(request, out);
				if (session == null) {
					validate = false;
					signupScreen(request, out);
				}//if
				
			}else if (action.equals("ShowLogin")) {
				validate = false;
			  loginScreen(request, out, null);
				
			}else if (action.equals("shutdown")) {
				shutdown(request, response, out);
				return;
				
			}//if

			// now try to validate the session
			if (validate) {
				/*
			      User validation occurs in 3 steps and occurs with EVERY call that comes in:
						  1. The "z" parameter (session id) is looked up in the sessions hashtable.  If found, no other
							   parameters are looked at for validation.  User is good.
							2. The "username" and "password" parameters are looked up in the Users table using validateUser().
							   If found, a new session is created and used.
							3. If 1 & 2 fail, the user is presented back to the login screen to try again.
				*/
				// get the session object
				try {
				  session = (Session)sessions.get(request.getParameter("z"));
				}catch(NullPointerException e) { }
				if (session == null) { // try to validate
					session = validate(request, out);
				}//if
				if (session == null) { // if still haven't validated, let the user log back in
					loginScreen(request, out, "Invalid username and/or password.");
				}//if
			}//if
			
			// log the session out if requested
			if (action.equals("logout")) {
				if (session != null) {
					sessions.remove(session.getZ());
					session = null;
				}//if
				loginScreen(request, out, null);
			}//if

			// handle superuser
			if (session != null && session.isRoot() && session.getTroop() == null) {
				String troopname = request.getParameter("troop");
				if (troopname == null || troopname.equals("")) { // don't have a troop yet, so show system status			
					status(request, response, out, session);
					return;
				}//if
				
				// create a new troop with a new session, then continue as normal
				Troop troop = datagate.getTroop(troopname, out);
				session = createSession(troop);
			}//if
			
			
			// if we have a valid session, switch based upon the action
			if (session != null) {
				if (skeleton) {
					Utils.header(request, out, session);
				}//if
				
				// switch based upon the action
				// again, switch based upon the action
			  if (action.equalsIgnoreCase("Signup") || action.equalsIgnoreCase("Login")) {
					if (session.isScout()) {
						ScoutUI.scoutDetail(request, out, session);
					}else{
						TroopUI.menu(request, out, session);
					}//if

				}else if (action.equals("menu")) { // show the main menu
				  TroopUI.menu(request, out, session);
				
				}else if (action.equals("troop") && session.isOwner()) { // shows troop information
					TroopUI.mainScreen(request, out, session);
					
				}else if (action.equals("phonelist")) {  // print the phone list
					TroopUI.phoneList(request, out, session);
					
				}else if (action.equals("createscoutscreen") && session.isOwner()) {  // create a new scout screen
					ScoutUI.createScreen(request, out, session);
					
				}else if (action.equals("createscout") && session.isOwner() && session.isWrite()) {  // creates a scout after he has been submitted
					ScoutUI.createScout(request, out, session);
					if (session.isOwner()) {
						TroopUI.scoutSummary(request, out, session);
					}else{
						ScoutUI.scoutDetail(request, out, session);
					}//if

				}else if (action.equals("scoutdelete") && session.isOwner() && session.isWrite()) {  // deletes a scout from the troop records
				  ScoutUI.deleteScout(request, out, session);
					TroopUI.scoutSummary(request, out, session);

				}else if (action.equals("scoutdetail")) { // shows detail of a scout
					ScoutUI.scoutDetail(request, out, session);

				}else if (action.equals("leaders") && session.isOwner()) { // shows current leaders
					TroopUI.leaders(request, out, session);
					
				}else if (action.equals("createleaderscreen") && session.isOwner()) { // create a new leader screen
					LeaderUI.createScreen(request, out, session);
					
				}else if (action.equals("createleader") && session.isOwner() && session.isWrite()) { // creates a leader after he has been submitted
					LeaderUI.createLeader(request, out, session);
					TroopUI.leaders(request, out, session);
					
				}else if (action.equals("leaderdetail") && session.isOwner()) { // shows detail of a leader
					LeaderUI.leaderDetail(request, out, session);
					
				}else if (action.equals("leaderdelete") && session.isOwner() && session.isWrite()) { // delete a leader
					LeaderUI.deleteLeader(request, out, session);
					TroopUI.leaders(request, out, session);
					
				}else if (action.equals("scouts") && session.isOwner()) { // shows current scouts & summary
					TroopUI.scoutSummary(request, out, session);
					
				}else if (action.equals("export") && session.isOwner() && session.isWrite()) { // shows the export screen
					TroopUI.export(request, out, session);
					
				}else if (action.equals("doexport") && session.isOwner() && session.isWrite()) { // does the actual export
					TroopUI.doexport(request, response, session);
					
				}else if (action.equals("piegraph") && session.isOwner()) { // show a pie graph 
					PieChart.doChart(request, response, session);
					
				}else if (action.equals("print")) { // print screen
					TroopUI.printScreen(request, out, session);
					
				}else if (action.equals("viewscouts") && session.isOwner()) { // user selected view scouts on the menu 
					TroopUI.viewScoutScreen(request, out, session);
					
				}else if (action.equals("addactivityscreen") && session.isOwner() && session.isWrite()) { // add a blanket activity for all scouts
					TroopUI.addActivityScreen(request, out, session);

				}else if (action.equals("createblanketactivity") && session.isOwner() && session.isWrite()) { // do the actual adding of the activity
					TroopUI.addActivity(request, out, session);
					
				}else if (action.equals("tripreference") && session.isOwner()) { // print a trip reference listing scouts and medical/contact info
					TroopUI.tripReference(request, out, session);

				}else if (action.equals("customoptions") && session.isOwner()) { // show the options for a custom report
					TroopUI.customOptions(request, out, session);

				}else if (action.equals("customreport") && session.isOwner()) { // create the custom report
					TroopUI.customReport(request, out, session);
					
				}else if (action.equals("viewpass") && session.isOwner() && session.isWrite()) { // view the troop passwords
					TroopUI.viewPasswords(request, out, session);
					
				}else if (action.equals("setpass") && session.isOwner() && session.isWrite()) { // change the troop passwords after new submission on the view page
					TroopUI.setPasswords(request, out, session);
					
				}//if
			
			}//if

			if (skeleton) {
				Utils.footer(out, session);
			}//if
			
			if (skeleton || printerfriendly) {
				out.println("</body>");
				out.println("</html>");
			}//if
			
			out.close();

		}catch(Exception e) { // all inclusive error catching
			try {
				out.println("<h2>Error:</h2>");
				out.println("An error has occurred within EasyScout Web.  Please report");
				out.println("this error to us");
				out.println("and we will resolve it soon as possible:<p>");
				out.println("<pre>");
				e.printStackTrace(out);
				out.println("</pre>");
				out.println("</table></table></table></table></table>");
				out.close(); // flush to the client
			}catch (Exception ex) {    // couldn't print to screen, so give back to the web server
				e.printStackTrace(System.out);
				throw new ServletException(e.toString());
			}//try
		}//catch
	}//service
		
	/** Shows the login screen */
	public void loginScreen(HttpServletRequest request, PrintWriter out, String msg) throws IOException, DataException {
		Utils.header(request, out, null);
		
		// show the error message, if there is one
		if (msg != null) {
			out.println("<center><font size=+1 color=#FF0000>" + msg + "</font></center><p>");
		}//if
		
	  // show the login form
		out.println("<h2><center>Please Provide Login Information:</center></h2>");
		out.println("<form action=" + Utils.servletdir() + " method=post>");
		out.println("<input type=hidden name=action value=Login><p>");
		out.println("<table border=0>");
		out.println("<tr><td>" + Utils.font() + "Troop Name:</td><td>" + Utils.font() + "<input type=text size=40 name=name></td></tr>");
		out.println("<tr><td>" + Utils.font() + "Password:</td><td>" + Utils.font() + "<input type=password size=40 name=password></td></tr>");
		out.println("</table>");
		out.println("<input type=submit value=Login><p>");
		out.println("</form>");
				
	}//loginScreen

	/** Shows the signup form */
	public void signupScreen(HttpServletRequest request, PrintWriter out) throws IOException, DataException {
		Utils.header(request, out, null);
		
	  out.println("<center><h2>Member Signup</h2></center>");
	  out.println("Please fill out the following information and press submit:<p>");
		out.println("<form action=" + Utils.servletdir() + " method=post>");
		out.println("<input type=hidden name=action value=Signup>");
		out.println("<table border=0>");
		out.println("<tr><td valign=top>" + Utils.font() + "Troop Name:</td>");
		out.println("<td valign=top>" + Utils.font());
	  out.println("<input type=text size=40 name=name>");
		out.println("</td><td valign=top>" + Utils.font());
		out.println("<font size=-1>Select a name that is unique to your troop and that you can remember.  You will need");
		out.println("to enter this name letter-for-letter to log back into your troop.");
		out.println("A good troop name is about 10-15 characters long.");
		out.println("Troop names should not include *, $, <, >, |, /, \\, or &."); 
		out.println("</td></tr>");
		out.println("<tr><td valign=top>" + Utils.font() + "Password:</td>");
		out.println("<td valign=top>" + Utils.font() + "<input type=password size=40 name=password></td>");
		out.println("<td valign=top rowspan=2>" + Utils.font() + "<font size=-1>");
		out.println("Your password <i>is</i> case sensitive.  Enter a password that is about 10-15 characters long.");
		out.println("This will be the only password that gives access to this file.");
		out.println("</td></tr>");
		out.println("<tr><td>" + Utils.font() + "Reenter Password:</td><td>" + Utils.font() + "<input type=password size=40 name=password2></td></tr>");
		out.println("<tr><td>" + Utils.font() + "Owner Name:</td><td>" + Utils.font() + "<input type=text size=40 name=owner></td></tr>");
		out.println("<tr><td valign=top>" + Utils.font() + "Owner E-mail:</td>");
		out.println("<td valign=top>" + Utils.font() + "<input type=text size=40 name=email></td>");
		out.println("<td valign=top><font size=-1>" + Utils.font());
		out.println("Please enter a valid e-mail as this is our only contact with you.  We are not responsible for miscommunication");
		out.println("about data or application changes if an invalid e-mail is supplied.");
		out.println("</td></tr>");
		out.println("</table>");
		out.println("<p>");
		out.println("<font size=-1>");
		out.println("The use of the EasyScout system implies your acceptance of the policies and rules given on the EasyScout");
		out.println("home page, FAQ, and other pages on this system.  We will not tolerate abuse of EasyScout as it affects");
		out.println("other users of the service.  We reserve the right to remove any client for any reason we deem necessary.");
		out.println("You are responsible for backing up your data.  We promise to give the best service and security we can,");
		out.println("but since EasyScout is free and not backed by a business, you forfeit all right to sue us for any reason");
		out.println("(not that we have deep pockets anyway). :)  We hope you enjoy using EasyScout.  Please e-mail us if");
		out.println("you find bugs or if you have suggestions for new features.");
		out.println("</font><p>");
		out.println("<input type=submit value=Login><p>");
		out.println("</form>");

	}//signupScreen
	
	/** Signs a troop up and then logs them in */
	public Session signup(HttpServletRequest request, PrintWriter out) throws IOException, DataException {
	  // get the variables
		String name = request.getParameter("name");
		String password = request.getParameter("password");
		String password2 = request.getParameter("password2");
		String owner = request.getParameter("owner");
		String email = request.getParameter("email");
	
	  // check the data for consistency
		String error = "";
		if (error.equals("") && (name == null || name.equals(""))) error = "Name field was blank.";
		if (error.equals("") && (password == null || password.equals(""))) error = "Password field was blank.";
		if (error.equals("") && (password2 == null || password2.equals(""))) error = "Password confirmation field was blank.";
		if (error.equals("") && (password == null || !password.equals(password2))) error = "The entered passwords did not match.";
		if (error.equals("") && (owner == null || owner.equals(""))) error = "Owner field was blank.";
		if (error.equals("") && (email == null || email.equals(""))) error = "E-mail field was blank.";
		
		// check to see if this troop already exists
		Troop troop = null;
		if (error.equals("")) {
		  troop = datagate.getTroop(name, out);
			if (troop != null) {  // if the troop exists, check the password and show that troop if the password matches
				if (troop.verifyPassword(password)) { // do they match?
					return createSession(troop);
				}else if (password.equals(troop.getReadPassword())) { // is it a read-only password login
				  Session sess = createSession(troop);
					sess.setReadOnly(true);
					return sess;
				}//if
				error = "A troop with this name already exists.  Please choose another name.";
			}//if
		}//if
		  
		if (!error.equals("")) {
		  Utils.error(out, error + " Please try again.");
			return null;
		}//if

		// record the new troop
		try {
			troop = datagate.createTroop(name, password);
			troop.setOwner(owner);
			troop.setEmail(email);
		}catch (DataException e) {
		  Utils.error(out, "An error occurred while saving registering your troop information.");
			out.println("<pre>");
			e.printStackTrace(out);
			out.println("</pre>");
			out.println("Please check the following:<p>");
			out.println("<ul>");
			out.println("<li>You specified a valid troop name.</li>");
			out.println("<li>Your troop name does not contain *, $, <, >, |, /, \\, or &.");
		  out.println("<li>Your troop name does not start with a dash (-).");
			out.println("</ul>");
			out.println("<p>");
			out.println("<b>Please press your browser's back button, correct any problems, and try again.</b>");
			return null;
		}//try
	
		// log in
		return createSession(troop);
	}//signup
	
	/** Verifies a troop name/password and returns a session object if successful */
	public Session validate(HttpServletRequest request, PrintWriter out) throws IOException, DataException {
		String name = request.getParameter("name");
		String password = request.getParameter("password");

		// short circuit
		if (name == null || name.equals("") || password == null || password.equals("")) {
			return null;
		}//if

		try {
			Troop troop = datagate.getTroop(name, out);

			// check the superuser first
			if (name.equals("root") && datagate.validateRoot(password)) {
				Session session = createSession(null);
				session.setSuperUser(true);
				return session;
			}//if

			// try to load the troop owner with full rights
			if (troop.verifyPassword(password)) {
				return createSession(troop);
			}//if
			
			// try to load the troop read-only password
			if (troop.getReadPassword().equals(password)) {
				Session session = createSession(troop);
				session.setReadOnly(true);
				return session;
			}//if
			
			// not a troop, so try to load scout access
			for (Enumeration enum = troop.getScouts(); enum.hasMoreElements(); ) {
				Scout scout = (Scout)enum.nextElement();
				String scoutpass = scout.getPassword();
				if (scoutpass != null && !scoutpass.equals("") && scoutpass.equals(password)) { // found him!
					Session session = createSession(troop);
					session.setScout(scout);
					return session;
				}//if
			}//for
			
		}catch (NullPointerException e) {
		  // thrown if the user wasn't found
		}//try
		
		return null;
	}//validate
	
	/** Creates a new session */
	public Session createSession(Troop troop) {
		Session session = new Session();
		session.setTroop(troop);
		sessions.put(session.getZ(), session);
		return session;
	}//createSession
	
	/* Get Servlet information */
	public String getServletInfo() {
		return "The servlet entry point for EasyScout 2.0 Web Edition.";
	}//service
	
	/** Displays status information */
	public void status(HttpServletRequest request, HttpServletResponse response, PrintWriter out, Session session) throws Exception {
		// header
		Utils.minimalHeader(request, out);
		Utils.printSection(out, "STATUS");
		out.println("<p>");
		
		// print the logout button
		out.println("<font size=+1><b>");
		Utils.printMenuItem(request, out, session, "Logout", "", "logout");
	  out.println("</font></b><p>");		
		
		// print the number of active clients
		out.println("<b>Active sessions:</b> " + sessions.size());
		out.println("<table border=1>");
		int index = 1;
		for (Enumeration enum = sessions.keys(); enum.hasMoreElements(); ) {
			Session sess = (Session)sessions.get((String)enum.nextElement());
			Troop troop = sess.getTroop();
			out.println("<tr>");
			out.println("<td>" + String.valueOf(index) + "</td>");
			if (troop != null) {
				out.println("<td>" + troop.getName() + "</td>");
			}else{
				out.println("<td>Superuser</td>");
			}//if
			out.println("<td>" + sess.getCreated().toString() + "</td>");
			out.println("</tr>");
			index++;
		}//for
		out.println("</table>");
		out.println("<p>");

		// print info on clients
		out.println("<b>All Clients:</b>");
		datagate.printClients(request, out, session);
		
		// footer
		Utils.footer(out, null);
		out.flush();
	}//status
	
	/** Shuts the system down cleanly  by saving sessions so they can be reloaded */
	public void shutdown(HttpServletRequest request, HttpServletResponse response, PrintWriter out) throws Exception {
		File sessionsfile = new File(new File(XMLLookup.directory("Sessions")), "sessions.save");
		ObjectOutputStream oos = new ObjectOutputStream(new GZIPOutputStream(new FileOutputStream(sessionsfile)));
		oos.writeObject(sessions);
		oos.close();
		
		Utils.minimalHeader(request, out);
		Utils.printSection(out, "SHUTDOWN");
		out.println("<p>");
//		out.println("All sessions were saved to disk and will be reloaded upon startup.");
		out.println("<p>");
		out.println("Feel free to shut down / restart the system.");
		Utils.footer(out, null);
		out.flush();
	}//shutdown
	

}//EasyScout class
