/*
 * 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@ricochet.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2001-2004 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.ui.helpers;

import java.io.*;
import java.util.*;
import org.apache.log4j.*;
import org.w3c.dom.*;
import com.silverwrist.util.*;
import com.silverwrist.venice.core.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.security.*;
import com.silverwrist.venice.util.*;

public class ImportHelper
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

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

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private int processed = 0;
  private int errors = 0;
  private String message = null;

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  public ImportHelper(AdminOperations adm, InputStream xml_data)
  {
    XMLLoader loader = XMLLoader.get();
    Document xmldoc;

    try
    { // load the XML data!
      xmldoc = loader.loadPostData(xml_data);

    } // end try
    catch (ValidationException e)
    { // serious parser error!
      message = "<B>Error parsing XML: " + StringUtil.encodeHTML(e.getMessage()) + "</B>\n";
      return;

    } // end catch

    Element root = xmldoc.getDocumentElement();
    if (!(root.getTagName().equals("venice-import-users")))
    { // wrong document type!
      message = "<B>XML Error: Document is not a &lt;venice-import-users/&gt; document</B>\n";
      return;

    } // end if

    SecurityInfo sinf = adm.getSecurityInfo();
    Role default_role = sinf.getDefaultRole("Global.NewUser");
    HashMap community_cache = new HashMap();
    ArrayList scroll = new ArrayList();
    NodeList nl = root.getChildNodes();
    for (int i=0; i<nl.getLength(); i++)
    { // get each node from the list, see if it's a Venice user
      Node n = nl.item(i);
      if ((n.getNodeType()==Node.ELEMENT_NODE) && n.getNodeName().equals("venice-user"))
      { // this is a Venice user record - first parse it out into binary data
	processed++;
	DOMElementHelper h = new DOMElementHelper((Element)n);
	String id = h.getElement().getAttribute("id");
	String username = null, password = null, reminder = null, description = null;
	String zonehint = null, locale = "en_US";
	Role r = default_role;
	boolean confirm = false, locked = false, hideaddr = false, hidephone = false, hidefax = false;
	boolean hideemail = false, autojoin = true, prehashed = false, post_pics = false, opt_out = false;
	boolean no_photo = false;
	VCard vcard = null;
	ArrayList join_list = null;

	// BUILD PHASE - Build the data to be used for this user.
	try
	{ // parse out the info
	  username = h.getSubElementText("username");
	  if (username==null)
	    throw new ValidationException("no <username/> element found");
	  username = username.trim();
	  if (!(IDUtils.isValidVeniceID(username)))
	    throw new ValidationException("<username/> \"" + username + "\" is not a valid Venice ID");
	  Element pelt = h.getSubElement("password");
	  if (pelt==null)
	    throw new ValidationException("no <password/> element found");
	  DOMElementHelper hh = new DOMElementHelper(pelt);
	  password = hh.getElementText();
	  if (password==null)
	    throw new ValidationException("no <password/> element found");
	  password = password.trim();
	  prehashed = loader.loadGetAttributeBoolean(pelt,"prehashed",prehashed);
	  reminder = h.getSubElementText("password-reminder");
	  if (reminder!=null)
	    reminder = reminder.trim();
	  description = h.getSubElementText("description");
	  if (description!=null)
	    description = description.trim();
	  Element opts = h.getSubElement("options");
	  if (opts!=null)
	  { // parse out the options
	    DOMElementHelper hopts = new DOMElementHelper(opts);
	    if (hopts.hasAttribute("role"))
	    { // get the role from the file and return it
	      r = sinf.getRole(opts.getAttribute("role"));
	      if (r==null)
		throw new ValidationException("role not defined: " + opts.getAttribute("role"));

	    } // end if

	    confirm = loader.loadGetAttributeBoolean(opts,"confirmed",confirm);
	    locked = loader.loadGetAttributeBoolean(opts,"locked",locked);
	    hideaddr = loader.loadGetAttributeBoolean(opts,"hideaddr",hideaddr);
	    hidephone = loader.loadGetAttributeBoolean(opts,"hidephone",hidephone);
	    hidefax = loader.loadGetAttributeBoolean(opts,"hidefax",hidefax);
	    hideemail = loader.loadGetAttributeBoolean(opts,"hideemail",hideemail);
	    autojoin = loader.loadGetAttributeBoolean(opts,"autojoin",autojoin);
	    post_pics = loader.loadGetAttributeBoolean(opts,"postpictures",post_pics);
	    opt_out = loader.loadGetAttributeBoolean(opts,"optout",opt_out);
	    no_photo = loader.loadGetAttributeBoolean(opts,"nophoto",no_photo);
	    if (hopts.hasAttribute("locale"))
	      locale = opts.getAttribute("locale").trim();
	    if (hopts.hasAttribute("zonehint"))
	      zonehint = opts.getAttribute("zonehint").trim();

	  } // end if
	  // else just leave things as the default

	  opts = h.getSubElement("vCard");
	  if (opts==null)
	    throw new ValidationException("no <vCard/> element found");
	  vcard = new VCard(opts);

	  // Now process all the child nodes which may have multiple instances.
	  NodeList nl2 = n.getChildNodes();
	  for (int j=0; j<nl2.getLength(); j++)
	  { // get each node in turn, only handle it if it's an element
	    Node cn = nl2.item(j);
	    if (cn.getNodeType()!=Node.ELEMENT_NODE)
	      continue;  // not an element, we don't care

	    if (cn.getNodeName().equals("join"))
	    { // "join" node - causes the user to join a specified community
	      h = new DOMElementHelper((Element)cn);
	      String alias = h.getElementText();
	      if (StringUtil.isStringEmpty(alias))
		continue;  // ignore this value - it doesn't make sense

	      // Translate the alias into a community context.
	      CommunityContext comm = (CommunityContext)(community_cache.get(alias));
	      if (comm==null)
	      { // call down to the AdminOperations object to get a community context
		try
		{ // attempt to get the community context
		  try
		  { // the "alias" may specify a numeric community ID - try that first
		    comm = adm.getCommunityContext(Integer.parseInt(alias));

		  } // end try
		  catch (NumberFormatException nfe)
		  { // well, it wasn't a numeric value, so fahgeddaboudit
		    comm = null;

		  } // end catch

		  if (comm==null) // OK, try to get it by alias name
		    comm = adm.getCommunityContext(alias);

		} // end try
		catch (DataException de)
		{ // this should only happen in the case of database failures
		  throw new ValidationException("database failure resolving alias \"" + alias + "\"");

		} // end catch

		if (comm!=null)  // cache it for next time
		  community_cache.put(alias,comm);
					       
	      } // end if (we needed to look up community in database)

	      if (comm==null)
		continue;  // ignore this - the community is specified wrong

	      // Figure out the role we want to join as.
	      SecurityInfo csinf = comm.getSecurityInfo();
	      Role my_role = null;
	      String str_role = h.getElement().getAttribute("role");
	      if (!(StringUtil.isStringEmpty(str_role)))
		my_role = csinf.getRole(str_role);
	      if (my_role==null)
		my_role = csinf.getDefaultRole("Community.NewUser");

	      // Save off join information in a list.
	      if (join_list==null)
		join_list = new ArrayList();
	      join_list.add(comm);
	      join_list.add(my_role);

	    } // end if ("join" element spotted)

	  } // end for

	} // end try
	catch (ValidationException e)
	{ // record the error and continue
	  logger.error("caught ValidationException in build phase(" + id + ")",e);
	  String tmp = "<B>Error in element \"" + StringUtil.encodeHTML(id) + "\":</B> "
	             + StringUtil.encodeHTML(e.getMessage());
	  scroll.add(tmp);
	  errors++;
	  continue;

	} // end catch

	// EXECUTE PHASE - make this user go!
	try
	{ // create the user context
	  AdminUserContext uc = adm.createNewAccount(username,password,prehashed,reminder,confirm,locked,r,
						     description,vcard.getBirthday(),autojoin);

	  // set up the contact info
	  ContactInfo ci = uc.getContactInfo();
	  ci.importVCard(vcard);
	  // N.B.: Since the vCard importer uses its own heuristics as to how to set the "hidden"
	  // flags, we need to reset them to the values from the <options/> element afterwards.
	  ci.setPrivateAddress(hideaddr);
	  ci.setPrivatePhone(hidephone);
	  ci.setPrivateFax(hidefax);
	  ci.setPrivateEmail(hideemail);
	  uc.putContactInfo(ci);

	  // set up the locale
	  Locale l = International.get().createLocale(locale);
	  if (l!=null)
	    uc.setLocale(l);

	  // set up the timezone
	  String tmp = vcard.getTimeZone();
	  if (tmp!=null)
	  { // get a "generic" time zone and convert it to a real one
	    TimeZone zone_raw = TimeZone.getTimeZone("GMT" + tmp);
	    String[] ids = TimeZone.getAvailableIDs(zone_raw.getRawOffset());
	    TimeZone zone = null;
	    if (ids.length>0)
	    { // OK, there's at least one time zone...which one do we pick?
	      if (zonehint!=null)
	      { // use the "zonehint" to get the right time zone
		for (int j=0; (zone==null) && (j<ids.length); j++)
		  if (ids[j].indexOf(zonehint)>=0)
		    zone = TimeZone.getTimeZone(ids[j]);

	      } // end if

	      if (zone==null)
		zone = TimeZone.getTimeZone(ids[0]);

	    } // end if
	    else  // just use the raw timezone
	      zone = zone_raw;
	    uc.setTimeZone(zone);

	  } // end if

	  // set the user properties
	  AdminUserProperties props = uc.getProperties();
	  props.setDisplayPostPictures(post_pics);
	  props.setMassMailOptOut(opt_out);
	  props.setDisallowPhoto(no_photo);
	  uc.setProperties(props);

	  if (join_list!=null)
	  { // set up the community membership
	    for (int j=0; j<join_list.size(); j+=2)
	    { // get the community and role, and set the membership status
	      CommunityContext comm = (CommunityContext)(join_list.get(j));
	      Role my_role = (Role)(join_list.get(j+1));
	      comm.setMembership(uc.getUID(),my_role.getLevel());

	    } // end for

	    join_list.clear();  // dump the excess junk

	  } // end if

	} // end try
	catch (AccessError ae)
	{ // caught an access error creating user
	  logger.error("caught AccessError in execute phase(" + id + ")",ae);
	  String tmp = "<B>Unable to create \"" + username + "\" (element \"" + StringUtil.encodeHTML(id)
	             + "\"):</B> " + StringUtil.encodeHTML(ae.getMessage());
	  scroll.add(tmp);
	  errors++;
	  continue;

	} // end catch
	catch (DataException de)
	{ // caught a database error creating the user
	  logger.error("caught DataException in execute phase(" + id + ")",de);
	  String tmp = "<B>Unable to create \"" + username + "\" (element \"" + StringUtil.encodeHTML(id)
	             + "\"):</B> database error: " + StringUtil.encodeHTML(de.getMessage());
	  scroll.add(tmp);
	  errors++;
	  continue;

	} // end catch

	// this user was created successfully!
	String tmp = "[id " + StringUtil.encodeHTML(id) + "] user \"" + username + "\" created successfully.";
	scroll.add(tmp);

      } // end if ("venice-user" element found)

    } // end for (each element in the node list)

    // Gather the scroll items together to form the message.
    message = StringUtil.join(scroll,"<BR>\n");

  } // end constructor

  /*--------------------------------------------------------------------------------
   * External operations
   *--------------------------------------------------------------------------------
   */

  public final int getProcessed()
  {
    return processed;

  } // end getProcessed

  public final int getErrors()
  {
    return errors;

  } // end getErrors

  public final String getMessage()
  {
    return message;

  } // end getMessage

} // end class ImportHelper
