/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at <http://www.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.servlets;

import java.io.*;
import java.net.URLEncoder;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.log4j.*;
import com.silverwrist.util.StringUtil;
import com.silverwrist.venice.core.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.servlets.format.*;

public class Account extends VeniceServlet
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static final String DISPLAY_LOGIN_ATTR = "com.silverwrist.venice.servlets.internal.DisplayLogin";

  private static final int COOKIE_LIFETIME = 60*60*24*365;  // one year

  private static Category logger = Category.getInstance(Account.class);

  /*--------------------------------------------------------------------------------
   * Internal functions
   *--------------------------------------------------------------------------------
   */

  private void setDisplayLogins(ServletRequest request)
  {
    request.setAttribute(DISPLAY_LOGIN_ATTR,new Integer(0));

  } // end setDisplayLogins

  private NewAccountDialog makeNewAccountDialog() throws ServletException
  {
    final String desired_name = "NewAccountDialog";
    DialogCache cache = DialogCache.getDialogCache(getServletContext());

    if (!(cache.isCached(desired_name)))
    { // create a template and save it off
      NewAccountDialog template = new NewAccountDialog();
      cache.saveTemplate(template);

    } // end if

    // return a new copy
    return (NewAccountDialog)(cache.getNewDialog(desired_name));

  } // end makeNewAccountDialog

  private LoginDialog makeLoginDialog()
  {
    final String desired_name = "LoginDialog";
    DialogCache cache = DialogCache.getDialogCache(getServletContext());

    if (!(cache.isCached(desired_name)))
    { // create a template and save it off
      LoginDialog template = new LoginDialog();
      cache.saveTemplate(template);

    } // end if

    // return a new copy
    return (LoginDialog)(cache.getNewDialog(desired_name));

  } // end makeLoginDialog

  private VerifyEmailDialog makeVerifyEmailDialog()
  {
    final String desired_name = "VerifyEmailDialog";
    DialogCache cache = DialogCache.getDialogCache(getServletContext());

    if (!(cache.isCached(desired_name)))
    { // create a template and save it off
      VerifyEmailDialog template = new VerifyEmailDialog();
      cache.saveTemplate(template);

    } // end if

    // return a new copy
    return (VerifyEmailDialog)(cache.getNewDialog(desired_name));

  } // end makeVerifyEmailDialog

  private EditProfileDialog makeEditProfileDialog() throws ServletException
  {
    final String desired_name = "EditProfileDialog";
    DialogCache cache = DialogCache.getDialogCache(getServletContext());

    if (!(cache.isCached(desired_name)))
    { // create a template and save it off
      EditProfileDialog template = new EditProfileDialog();
      cache.saveTemplate(template);

    } // end if

    // return a new copy
    return (EditProfileDialog)(cache.getNewDialog(desired_name));

  } // end makeEditProfileDialog

  /*--------------------------------------------------------------------------------
   * Overrides from class HttpServlet
   *--------------------------------------------------------------------------------
   */

  public String getServletInfo()
  {
    String rc = "Account servlet - Handles login, logout, account creation, and profiles\n"
              + "Part of the Venice Web Communities System\n";
    return rc;

  } // end getServletInfo

  /*--------------------------------------------------------------------------------
   * Overrides from class VeniceServlet
   *--------------------------------------------------------------------------------
   */

  protected String getMyLocation(HttpServletRequest request, VeniceEngine engine, UserContext user,
				 RenderData rdat)
  {
    String rc = super.getMyLocation(request,engine,user,rdat);
    if (rc!=null)
      return rc;
    if (request.getAttribute(DISPLAY_LOGIN_ATTR)!=null)
      return "account?cmd=" + getStandardCommandParam(request);
    else
      return null;

  } // end getMyLocation

  protected VeniceContent doVeniceGet(HttpServletRequest request, VeniceEngine engine,
				      UserContext user, RenderData rdat)
    throws ServletException, IOException, VeniceServletResult
  {
    String tgt = request.getParameter("tgt");  // target location
    if (tgt==null)
      tgt = "top";  // go back to the Top screen if nothing else
    String cmd = getStandardCommandParam(request);

    if (cmd.equals("L"))
    { // "L" = Log In/Out
      if (user.isLoggedIn())
      { // this is a Logout command
	clearUserContext(request);

	// delete the "login" cookie
	Cookie del_login_info = rdat.createCookie(Variables.LOGIN_COOKIE,"",0);
	Variables.saveCookie(request,del_login_info);

	throw new RedirectResult("top");  // take 'em back to the "top" page

      } // end if

      // this is a Login command - display the Login page
      LoginDialog dlg = makeLoginDialog();
      dlg.setupNew(tgt);
      setMyLocation(request,tgt);  // fake out the location so we can display the "Log In/Create Account" links
      return dlg;

    } // end if ("L" command)

    if (cmd.equals("C"))
    { // "C" = create new account
      if (user.isLoggedIn()) // you can't create a new account while logged in on an existing one!
	return new ErrorBox("Error","You cannot create a new account while logged in on an existing "
			    + "one.  You must log out first.",tgt);

      final String yes = "yes";
      if (yes.equals(request.getParameter("agree")))
      { // display the "Create Account" dialog
	NewAccountDialog dlg = makeNewAccountDialog();
	dlg.setEngine(engine);
	dlg.setTarget(tgt);
	dlg.setFieldValue("country","US");
	return dlg;

      } // end if
      else
      { // display the account terms dialog
	String[] choices = new String[2];
	choices[0] = "account?cmd=C&agree=yes&tgt=" + URLEncoder.encode(tgt);
	choices[1] = "top";
	return new TextMessageDialog(TextMessageDialog.TYPE_ACCEPT_DECLINE,
				     rdat.getStockMessage("user-agreement-title"),
				     rdat.getStockMessage("user-agreement"),choices);

      } // end else

    } // end if ("C" command)

    if (cmd.equals("P"))
    { // "P" = user profile
      if (!(user.isLoggedIn()))  // you have to be logged in for this one!
	return new ErrorBox("Error","You must log in before you can modify the profile "
			    + "on your account.",tgt);
      
      // set up the Edit Profile dialog
      EditProfileDialog dlg = makeEditProfileDialog();
      try
      { /// set up the profile information for the dialog
	dlg.setupDialog(user,tgt);

      } // end try
      catch (DataException e)
      { // unable to load the contact info for the profile
	return new ErrorBox("Database Error","Database error retrieving profile: " + e.getMessage(),tgt);

      } // end catch

      return dlg;  // display dialog

    } // end if ("P" command)

    if (cmd.equals("V"))
    { // "V" = verify email address
      setDisplayLogins(request);
      if (!(user.isLoggedIn()))  // you have to be logged in for this one!
	return new ErrorBox("Error","You must log in before you can verify your account's "
			       + "email address.",tgt);

      // display the "verify email" dialog
      VerifyEmailDialog dlg = makeVerifyEmailDialog();
      dlg.setTarget(tgt);
      return dlg;

    } // end if ("V" command)

    // command not found!
    logger.error("invalid command to Account.doGet: " + cmd);
    return new ErrorBox("Internal Error","Invalid command to Account.doGet",tgt);

  } // end doVeniceGet

  protected VeniceContent doVenicePost(HttpServletRequest request, VeniceEngine engine,
				       UserContext user, RenderData rdat)
    throws ServletException, IOException, VeniceServletResult
  {
    changeMenuTop(request);   // this is "top" menus
    String tgt = request.getParameter("tgt");  // target location
    if (tgt==null)
      tgt = "top";  // go back to the Top screen if nothing else
    String cmd = getStandardCommandParam(request);

    if (cmd.equals("L"))
    { // "L" = login the user
      LoginDialog dlg = makeLoginDialog();

      if (dlg.isButtonClicked(request,"cancel"))
	throw new RedirectResult(tgt);  // we decided not to log in - go back where we came from

      if (dlg.isButtonClicked(request,"remind"))
      { // this will email a password reminder to the owner's account
	dlg.loadValues(request);  // load the dialog values

	try
	{ // send the password reminder
	  engine.sendPasswordReminder(dlg.getFieldValue("user"));

	  // recycle and redisplay the dialog box
	  dlg.resetOnError("Password reminder has been sent to your e-mail address.");

	} // end try
	catch (DataException de)
	{ // there was a database error finding your email address
	  return new ErrorBox("Database Error","Database error finding user: " + de.getMessage(),tgt);

	} // end catch
	catch (AccessError ae)
	{ // this indicates a problem with the user account or password
	  return new ErrorBox("User E-mail Address Not Found",ae.getMessage(),tgt);

	} // end catch
	catch (EmailException ee)
	{ // error sending the confirmation email
	  return new ErrorBox("E-mail Error","E-mail error sending reminder: " + ee.getMessage(),tgt);

	} // end catch

	setMyLocation(request,tgt);  // fake out the location
	return dlg;  // redisplay the dialog

      } // end if ("remind" button clicked)

      if (dlg.isButtonClicked(request,"login"))
      { // we actually want to try and log in!  imagine that!
	dlg.loadValues(request);  // load the values

	try
	{ // use the user context to authenticate
	  user.authenticate(dlg.getFieldValue("user"),dlg.getFieldValue("pass"));

	  // If they want a cookie, give it to them!
	  final String yes = "Y";
	  if (yes.equals(dlg.getFieldValue("saveme")))
	  { // create the authentication cookie and save it
	    Cookie auth_cookie = rdat.createCookie(Variables.LOGIN_COOKIE,user.getAuthenticationToken(),
						   COOKIE_LIFETIME);
	    Variables.saveCookie(request,auth_cookie);

	  } // end if

	  // assuming it worked OK, redirect them back where they came from
	  // (or to the verification page if they need to go there)
	  clearMenu(request);
	  if (user.isEmailVerified())
	    throw new RedirectResult(tgt);
	  else // they haven't verified yet - remind them to do so
	    throw new RedirectResult("account?cmd=V&tgt=" + URLEncoder.encode(tgt));

	} // end try
	catch (DataException de)
	{ // this indicates a database error - display an ErrorBox
	  return new ErrorBox("Database Error","Database error logging in: " + de.getMessage(),tgt);

	} // end catch
	catch (AccessError ae)
	{ // this indicates a problem with the user account or password
	  dlg.resetOnError(ae.getMessage());

	} // end catch

	setMyLocation(request,tgt);  // fake out the location
	return dlg;  // go back and redisplay the dialog

      } // end if ("login" button clicked)

      // the button must be wrong!
      logger.error("no known button click on Account.doPost, cmd=L");
      return new ErrorBox("Internal Error","Unknown command button pressed",tgt);

    } // end if ("L" command)

    if (cmd.equals("C"))
    { // "C" = Create New Account
      NewAccountDialog dlg = makeNewAccountDialog();
      dlg.setEngine(engine);

      if (dlg.isButtonClicked(request,"cancel"))
	throw new RedirectResult(tgt);  // we decided not to create account - go back where we came from

      if (dlg.isButtonClicked(request,"create"))
      { // OK, actually want to create the account!
	dlg.loadValues(request);  // load field values

	try
	{ // attempt to create the account
	  UserContext uc = dlg.doDialog(request);
	  putUserContext(request,uc);  // effectively logging in the new user

	  // now jump to the Verify page
	  throw new RedirectResult("account?cmd=V&tgt=" + URLEncoder.encode(tgt));

	} // end try
	catch (ValidationException ve)
	{ // unable to validate some of the data in the form
	  dlg.resetOnError(ve.getMessage() + " Please try again.");

	} // end catch
	catch (DataException de)
	{ // data error in setting up account
	  return new ErrorBox("Database Error","Database error creating account: " + de.getMessage(),tgt);

	} // end catch
	catch (AccessError ae)
	{ // access error in setting up the account
	  dlg.resetOnError(ae.getMessage());

	} // end catch
	catch (EmailException ee)
	{ // error sending the confirmation email
	  return new ErrorBox("E-mail Error","E-mail error creating account: " + ee.getMessage(),tgt);

	} // end catch

	return dlg;  // redisplay me

      } // end if ("create" button pressed)

      // the button must be wrong!
      logger.error("no known button click on Account.doPost, cmd=C");
      return new ErrorBox("Internal Error","Unknown command button pressed",tgt);

    } // end if ("C" command)

    if (cmd.equals("V"))
    { // "V" = Verify E-mail Address
      setDisplayLogins(request);
      VerifyEmailDialog dlg = makeVerifyEmailDialog();

      if (dlg.isButtonClicked(request,"cancel"))
	throw new RedirectResult(tgt);  // we decided not to bother - go back where we came from

      if (dlg.isButtonClicked(request,"again"))
      { // send the confirmation message again
	try
	{ // resend the email confirmation
	  user.resendEmailConfirmation();

	  // now force the form to be redisplayed
	  dlg.clearAllFields();
	  dlg.setTarget(tgt);

	} // end try
	catch (DataException de)
	{ // database error resending confirmation
	  return new ErrorBox("Database Error","Database error sending confirmation: " + de.getMessage(),tgt);

	} // end catch
	catch (EmailException ee)
	{ // error sending the confirmation email
	  return new ErrorBox("E-mail Error","E-mail error sending confirmation: " + ee.getMessage(),tgt);

	} // end catch

	return dlg;  // redisplay the dialog

      } // end if ("again" button clicked)

      if (dlg.isButtonClicked(request,"ok"))
      { // try to verify the confirmation number
	dlg.loadValues(request);  // load field values

	try
	{ // validate that we have a good number
	  dlg.validate();
	  user.confirmEmail(dlg.getConfirmationNumber());

	  // go back to our desired location
	  throw new RedirectResult(tgt);

	} // end try
	catch (ValidationException ve)
	{ // there was a validation error...
	  dlg.resetOnError(ve.getMessage() + " Please try again.");

	} // end catch
	catch (DataException de)
	{ // data error in confirming account
	  return new ErrorBox("Database Error","Database error verifying email: " + de.getMessage(),tgt);

	} // end catch
	catch (AccessError ae)
	{ // invalid confirmation number, perhaps?
	  dlg.resetOnError(ae.getMessage());

	} // end catch

	return dlg;  // redisplay the dialog

      } // end if ("ok" button clicked)

      // the button must be wrong!
      logger.error("no known button click on Account.doPost, cmd=V");
      return new ErrorBox("Internal Error","Unknown command button pressed",tgt);

    } // end if ("V" command)

    if (cmd.equals("P"))
    { // "P" = Edit User Profile
      setDisplayLogins(request);
      EditProfileDialog dlg = makeEditProfileDialog();

      if (dlg.isButtonClicked(request,"cancel"))
	throw new RedirectResult(tgt);  // we decided not to bother - go back where we came from

      if (dlg.isButtonClicked(request,"update"))
      { // we're ready to update the user profile
	dlg.loadValues(request);  // load field values

	try
	{ // validate the dialog and reset profile info
	  if (dlg.doDialog(user))  // need to reconfirm email address
	    throw new RedirectResult("account?cmd=V&tgt=" + URLEncoder.encode(tgt));
	  else
	    throw new RedirectResult(tgt);  // just go straight on back

	} // end try
	catch (ValidationException ve)
	{ // there was a validation error...
	  dlg.resetOnError(ve.getMessage() + " Please try again.");

	} // end catch
	catch (DataException de)
	{ // data error in changing profile
	  return new ErrorBox("Database Error","Database error updating profile: " + de.getMessage(),tgt);

	} // end catch
	catch (EmailException ee)
	{ // error sending the confirmation email
	  return new ErrorBox("E-mail Error","E-mail error sending confirmation: " + ee.getMessage(),tgt);

	} // end catch

	return dlg;  // redisplay dialog

      } // end if ("update" button clicked)

      // the button must be wrong!
      logger.error("no known button click on Account.doPost, cmd=P");
      return new ErrorBox("Internal Error","Unknown command button pressed",tgt);

    } // end if

    // unknown command
    logger.error("invalid command to Account.doPost: " + cmd);
    return new ErrorBox("Internal Error","Invalid command to Account.doPost",tgt);

  } // end doVenicePost

} // end class Account
