/*
 * 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.ui.dlg;

import java.io.IOException;
import java.util.*;
import org.w3c.dom.*;
import com.silverwrist.util.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.ui.*;
import com.silverwrist.venice.ui.config.RootConfig;
import com.silverwrist.venice.ui.helpers.HTMLRendering;
import com.silverwrist.venice.util.XMLLoader;

public class Dialog implements Cloneable, ContentDirect
{
  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private int menu_sel = MENU_SELECTOR_NOCHANGE;  // menu selector
  private String name;                    // name of the dialog
  private String title;                   // dialog title
  private String subtitle = null;         // dialog subtitle
  private String formname;                // name of the built-in form
  private boolean file_form = false;      // is this a file form?
  private String action;                  // action URL
  private int action_type = LinkTypes.SERVLET;   // action URL type
  private String error_message = null;    // error message
  private String instructions = null;     // instructions
  private Map command_buttons;            // the command buttons
  private List command_order;             // the command buttons in declaration order
  private Map form_fields;                // the form fields
  private List form_order;                // the form fields in declaration order
  private boolean any_required = false;   // are any fields marked required?
  private boolean writeable = true;       // is this copy of the form writeable?

  /*--------------------------------------------------------------------------------
   * Constructors
   *--------------------------------------------------------------------------------
   */

  public Dialog(String name, String title, String subtitle, String formname, String action, int type)
  {
    this.name = name;
    this.title = title;
    this.subtitle = subtitle;
    this.formname = formname;
    this.action = action;
    this.action_type = type;
    command_buttons = new HashMap();
    command_order = new ArrayList();
    form_fields = new HashMap();
    form_order = new ArrayList();

  } // end constructor

  public Dialog(RootConfig cfg, Element elt) throws ConfigException
  {
    XMLLoader loader = XMLLoader.get();
    loader.configVerifyNodeName(elt,"dialog");
    this.name = loader.configGetAttribute(elt,"name");
    this.formname = loader.configGetAttribute(elt,"formname");
    DOMElementHelper h = new DOMElementHelper(elt);
    if (h.hasAttribute("menusel"))
    { // get the menu selector attribute
      String s = elt.getAttribute("menusel").trim().toLowerCase();
      if (s.equals("nochange"))
	menu_sel = MENU_SELECTOR_NOCHANGE;
      else if (s.equals("top"))
	menu_sel = MENU_SELECTOR_TOP;
      else if (s.equals("community"))
	menu_sel = MENU_SELECTOR_COMMUNITY;
      else  // no valid value
	throw new ConfigException("invalid <dialog/> menusel= attribute");

    } // end if

    // Initialize the remaining dialog fields.
    this.title = null;
    this.subtitle = null;
    this.action = null;
    HashMap tmp_cmd = new HashMap();
    ArrayList tmp_cmd_order = new ArrayList();
    HashMap tmp_fld = new HashMap();
    ArrayList tmp_fld_order = new ArrayList();
    
    NodeList nl = elt.getChildNodes();
    DialogElementLoader dlg_loader = DialogElementLoader.get();
    for (int i=0; i<nl.getLength(); i++)
    { // look at the element
      Node n = nl.item(i);
      if (n.getNodeType()==Node.ELEMENT_NODE)
      { // figure out what happened
	h = new DOMElementHelper((Element)n);
	String tname = n.getNodeName();
	if (tname.equals("title"))
	{ // fill in the title
	  if (this.title!=null)
	    throw new ConfigException("<title/> element specified twice");
	  this.title = loader.configGetText(h);

	} // end if
	else if (tname.equals("subtitle"))
	{ // fill in the subtitle
	  if (this.subtitle!=null)
	    throw new ConfigException("<subtitle/> element specified twice");
	  this.subtitle = loader.configGetText(h);

	} // end else if
	else if (tname.equals("action"))
	{ // fill in the action
	  if (this.action!=null)
	    throw new ConfigException("<action/> element specified twice");
	  this.action = loader.configGetText(h);
	  String stype = h.getElement().getAttribute("type");
	  if (!(StringUtil.isStringEmpty(stype)))
	  { // get the action type element
	    this.action_type = cfg.convertLinkType(stype);
	    if (this.action_type<0)
	      throw new ConfigException("<action/> type= attribute not defined");

	  } // end if

	} // end else if
	else if (tname.equals("instructions"))
	{ // fill in the instructions
	  if (this.instructions!=null)
	    throw new ConfigException("<instructions/> element specified twice");
	  this.instructions = loader.configGetText(h);

	} // end else if
	else
	{ // create a dialog element
	  RenderDirect component = dlg_loader.createElement(h.getElement());
	  if (component instanceof DialogField)
	  { // add to the fields list
	    DialogField fld = (DialogField)component;
	    tmp_fld.put(fld.getName(),fld);
	    tmp_fld_order.add(fld);
	    if (fld.isFile())
	      this.file_form = true;
	    if (fld.isRequired())
	      this.any_required = true;

	  } // end if
	  else if (component instanceof DialogCommandButton)
	  { // add to the command buttons list
	    DialogCommandButton bn = (DialogCommandButton)component;
	    tmp_cmd.put(bn.getName(),bn);
	    tmp_cmd_order.add(bn);

	  } // end else if
	  else // not a valid thingy
	    throw new ConfigException("<" + tname + "/> element not a proper dialog element");

	} // end else

      } // end if
      // else ignore it

    } // end for

    // check the title and action attributes
    if (this.title==null)
      throw new ConfigException("<title/> element not specified");
    if (this.action==null)
      throw new ConfigException("<action/> element not specified");

    if (tmp_cmd.isEmpty())
    { // null out the command lists
      command_buttons = Collections.EMPTY_MAP;
      command_order = Collections.EMPTY_LIST;

    } // end if
    else
    { // freeze the lists
      tmp_cmd_order.trimToSize();
      command_buttons = Collections.unmodifiableMap(tmp_cmd);
      command_order = Collections.unmodifiableList(tmp_cmd_order);

    } // end else

    if (tmp_fld.isEmpty())
    { // null out the command lists
      form_fields = Collections.EMPTY_MAP;
      form_order = Collections.EMPTY_LIST;

    } // end if
    else
    { // freeze the command lists
      tmp_fld_order.trimToSize();
      form_fields = Collections.unmodifiableMap(tmp_fld);
      form_order = Collections.unmodifiableList(tmp_fld_order);

    } // end else

    writeable = false;  // not writeable

  } // end constructor

  protected Dialog(Dialog other)
  {
    this.menu_sel = other.menu_sel;
    this.name = other.name;
    this.title = other.title;
    this.subtitle = other.subtitle;
    this.formname = other.formname;
    this.file_form = other.file_form;
    this.action = other.action;
    this.action_type = other.action_type;
    this.instructions = other.instructions;

    Iterator it;
    if (other.writeable)
    { // need to make the arrays un-writeable
      if (other.command_buttons.isEmpty())
      { // make the copied elements empty
	this.command_buttons = Collections.EMPTY_MAP;
	this.command_order = Collections.EMPTY_LIST;

      } // end if
      else
      { // copy the contents of the data structures
	HashMap tmp_cmd = new HashMap();
	ArrayList tmp_cmd_order = new ArrayList();
	it = other.command_order.iterator();
	while (it.hasNext())
	{ // copy all elements across
	  DialogCommandButton bn = (DialogCommandButton)(it.next());
	  tmp_cmd.put(bn.getName(),bn);
	  tmp_cmd_order.add(bn);

	} // end while

	tmp_cmd_order.trimToSize();
	this.command_buttons = Collections.unmodifiableMap(tmp_cmd);
	this.command_order = Collections.unmodifiableList(tmp_cmd_order);

      } // end else

    } // end if
    else
    { // just reference the data structures directly
      this.command_buttons = other.command_buttons;
      this.command_order = other.command_order;

    } // end else
    
    // duplicate the fields 
    HashMap tmp_fld = new HashMap();
    ArrayList tmp_fld_order = new ArrayList();
    it = other.form_order.iterator();
    while (it.hasNext())
    { // duplicate the fields and copy them over
      DialogField fld_old = (DialogField)(it.next());
      DialogField fld = fld_old.duplicate();
      tmp_fld.put(fld.getName(),fld);
      tmp_fld_order.add(fld);

    } // end while

    tmp_fld_order.trimToSize();
    this.form_fields = Collections.unmodifiableMap(tmp_fld);
    this.form_order = Collections.unmodifiableList(tmp_fld_order);

    this.any_required = other.any_required;
    this.writeable = false;  // no write on this copy

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal operations
   *--------------------------------------------------------------------------------
   */

  protected DialogField getFieldRef(String name)
  {
    return (DialogField)(form_fields.get(name));

  } // end getFieldRef

  /*--------------------------------------------------------------------------------
   * Overrideable operations
   *--------------------------------------------------------------------------------
   */

  protected void validateWholeForm() throws ValidationException
  { // do nothing at this level
  } // end validateWholeForm

  /*--------------------------------------------------------------------------------
   * Overrides from class Object
   *--------------------------------------------------------------------------------
   */

  public Object clone()
  {
    return new Dialog(this);

  } // end clone

  /*--------------------------------------------------------------------------------
   * Implementations from interface Content
   *--------------------------------------------------------------------------------
   */

  public boolean needFrame()
  {
    return true;

  } // end needFrame

  public int getMenuSelector()
  {
    return menu_sel;

  } // end getMenuSelector

  public String getPageTitle(RequestOutput ro)
  {
    if (subtitle!=null)
      return title + " " + subtitle;
    else
      return title;

  } // end getPageTitle

  public String getPageQID()
  {
    return null;

  } // end getPageQID

  /*--------------------------------------------------------------------------------
   * Implementations from interface ContentDirect
   *--------------------------------------------------------------------------------
   */

  public void render(RequestOutput out) throws IOException
  {
    HTMLRendering html = (HTMLRendering)(out.queryService(HTMLRendering.class));

    out.writeContentHeader(title,subtitle);

    // write the error message
    if (error_message!=null)
      out.write("<DIV ALIGN=\"center\" CLASS=\"content\">\n" + html.getFontTag(html.CONTENT_ERROR,"dlg-error")
		+ "<B>" + StringUtil.encodeHTML(error_message) + "</B></FONT>\n</DIV><P>\n");

    // Output the start of the form.
    out.write("<FORM NAME=\"" + formname + "\"");
    if (file_form)
      out.write(" ENCTYPE=\"multipart/form-data\"");
    out.write(" METHOD=POST ACTION=\"" + html.formatURL(action,action_type) + "\"><DIV CLASS=\"content\">"
	      + html.getFontTag(html.CONTENT_FOREGROUND,"content") + "\n");

    // Render the hidden fields.
    Iterator it = form_order.iterator();
    while (it.hasNext())
    { // render all the hidden fields first
      DialogField fld = (DialogField)(it.next());
      if (fld.isHidden())
	fld.render(out);

    } // end while

    if (instructions!=null)
      out.write(instructions);
    if (any_required)
    { // write out the "required" instructions
      if (instructions!=null)
	out.write("<P>");
      out.write("Required fields are marked with a " + html.getFontTag("red","content") + "*</FONT>.<BR>\n");

    } // end if
    else if (instructions!=null)
      out.write("<BR>\n");

    out.write("<TABLE BORDER=0 ALIGN=CENTER CELLPADDING=6 CELLSPACING=0>\n");
    it = form_order.iterator();
    while (it.hasNext())
    { // render all the non-hidden fields
      DialogField fld = (DialogField)(it.next());
      if (!(fld.isHidden()))
	fld.render(out);

    } // end while

    out.write("</TABLE>\n");

    if (!(command_order.isEmpty()))
    { // render the command buttons at the bottom
      boolean is_first = true;
      out.write("<DIV ALIGN=CENTER CLASS=\"content\">");
      it = command_order.iterator();
      while (it.hasNext())
      { // render each of the command buttons in the list
	DialogCommandButton bn = (DialogCommandButton)(it.next());
	if (is_first)
	  is_first = false;
	else
	  out.write("&nbsp;&nbsp;");
	bn.render(out);

      } // end while

      out.write("</DIV>\n");

    } // end if

    out.write("</FONT></DIV></FORM>\n");

  } // end render

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

  public final String getName()
  {
    return name;

  } // end getName

  public final synchronized void setMenuSelector(int sel_type)
  {
    if (writeable)
      menu_sel = sel_type;

  } // end setMenuSelector

  public final synchronized void setTitle(String title)
  {
    this.title = title;

  } // end setTitle

  public final synchronized void setSubtitle(String subtitle)
  {
    this.subtitle = subtitle;

  } // end setSubtitle

  public final synchronized void setFormName(String s)
  {
    formname = s;

  } // end setFormName

  public final synchronized void setAction(String action, int type)
  {
    if (writeable)
    { // set the action
      this.action = action;
      this.action_type = type;

    } // end if

  } // end setAction

  public final void setAction(String action)
  {
    this.setAction(action,LinkTypes.SERVLET);

  } // end setAction

  public final synchronized void setErrorMessage(String message)
  {
    this.error_message = message;

  } // end setErrorMessage

  public final synchronized void setInstructions(String instructions)
  {
    if (writeable)
      this.instructions = instructions;

  } // end setInstructions

  public final synchronized void addButton(DialogCommandButton bn, int position)
  {
    if (!writeable)
      return;
    command_buttons.put(bn.getName(),bn);
    if ((position<0) || (position>command_order.size()))
      command_order.add(bn);
    else
      command_order.add(position,bn);
    
  } // end addButton

  public final void addButton(DialogCommandButton bn)
  {
    this.addButton(bn,-1);

  } // end addButton

  public final synchronized void addField(DialogField fld, int position)
  {
    if (!writeable)
      return;
    form_fields.put(fld.getName(),fld);
    if ((position<0) || (position>form_order.size()))
      form_order.add(fld);
    else
      form_order.add(position,fld);
    if (fld.isFile())
      file_form = true;
    if (fld.isRequired())
      any_required = true;
    
  } // end addField

  public final void addField(DialogField fld)
  {
    this.addField(fld,-1);

  } // end addField

  public final synchronized void freeze()
  {
    if (!writeable)
      return;
    if (command_buttons.isEmpty())
    { // null out the lists
      command_buttons = Collections.EMPTY_MAP;
      command_order = Collections.EMPTY_LIST;

    } // end if
    else
    { // freeze the lists
      command_buttons = Collections.unmodifiableMap(command_buttons);
      command_order = Collections.unmodifiableList(command_order);

    } // end else

    if (form_fields.isEmpty())
    { // null out the lists
      form_fields = Collections.EMPTY_MAP;
      form_order = Collections.EMPTY_LIST;

    } // end if
    else
    { // freeze the lists
      form_fields = Collections.unmodifiableMap(form_fields);
      form_order = Collections.unmodifiableList(form_order);

    } // end else

    writeable = false;

  } // end freeze

  public final String whichButton(RequestInput ri)
  {
    Iterator it = command_order.iterator();
    while (it.hasNext())
    { // check each button in turn
      DialogCommandButton bn = (DialogCommandButton)(it.next());
      if (bn.isClicked(ri))
	return bn.getName();

    } // end while

    return null;

  } // end whichButton

  public final boolean isButtonClicked(RequestInput ri, String name)
  {
    DialogCommandButton bn = (DialogCommandButton)(command_buttons.get(name));
    return ((bn==null) ? false : bn.isClicked(ri));

  } // end isButtonClicked

  public final void load(RequestInput ri)
  {
    Iterator it = form_order.iterator();
    while (it.hasNext())
    { // set the field values
      DialogField fld = (DialogField)(it.next());
      fld.setValueFrom(ri);

    } // end while

  } // end load

  public final void clear()
  {
    Iterator it = form_order.iterator();
    while (it.hasNext())
    { // set the field values
      DialogField fld = (DialogField)(it.next());
      fld.setValue(null);

    } // end while

  } // end clear

  public final Object getValue(String fieldname)
  {
    DialogField fld = (DialogField)(form_fields.get(fieldname));
    return ((fld==null) ? null : fld.getValue());

  } // end getValue

  public final synchronized void setValue(String fieldname, Object value)
  {
    DialogField fld = (DialogField)(form_fields.get(fieldname));
    if (fld!=null)
      fld.setValue(value);

  } // end setValue

  public final boolean isEnabled(String fieldname)
  {
    DialogField fld = (DialogField)(form_fields.get(fieldname));
    return ((fld==null) ? false : fld.isEnabled());

  } // end isEnabled

  public final synchronized void setEnabled(String fieldname, boolean flag)
  {
    DialogField fld = (DialogField)(form_fields.get(fieldname));
    if (fld!=null)
      fld.setEnabled(flag);

  } // end setEnabled

  public final synchronized void sendMessage(String fieldname, String message, Object data)
  {
    DialogField fld = (DialogField)(form_fields.get(fieldname));
    if (fld!=null)
      fld.sendMessage(message,data);

  } // end sendMessage

  public final void validate() throws ValidationException
  {
    // validate each field in isolation
    Iterator it = form_order.iterator();
    while (it.hasNext())
    { // set the field values
      DialogField fld = (DialogField)(it.next());
      fld.validate();

    } // end while

    // now validate the form in toto
    validateWholeForm();

  } // end validate

} // end class Dialog
