/*
 * 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) 2002 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.dynamo.dialog;

import java.io.*;
import java.util.*;
import org.w3c.dom.*;
import com.silverwrist.util.*;
import com.silverwrist.util.xml.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.except.*;

class DialogImpl implements Dialog
{
  /*--------------------------------------------------------------------------------
   * Internal class to "peel off" certain dialog items that are tags
   *--------------------------------------------------------------------------------
   */

  private class InternalFilter implements DialogItemFactory
  {
    /*====================================================================
     * Attributes
     *====================================================================
     */

    private DialogItemFactory m_inner;
    private HashMap m_rp = new HashMap();

    /*====================================================================
     * Constructor
     *====================================================================
     */

    InternalFilter(DialogItemFactory inner)
    {
      m_inner = inner;

    } // end constructor

    /*====================================================================
     * Implementations from interface DialogItemFactory
     *====================================================================
     */

    public DialogItem createDialogItem(Element elt, DialogPLAF plaf) throws DialogException
    {
      try
      { // strip out certain items we need from the element stream
	XMLLoader loader = XMLLoader.get();
	String tagname = elt.getTagName();
	if (tagname.equals("title"))
	{ // get title
	  m_default_title = loader.getText(elt);
	  if (m_default_title!=null)
	    m_default_title = m_default_title.trim();

	} // end if
	else if (tagname.equals("subtitle"))
	{ // get subtitle (not required)
	  DOMElementHelper h = new DOMElementHelper(elt);
	  m_default_subtitle = h.getElementText();
	  if (m_default_subtitle!=null)
	    m_default_subtitle = m_default_subtitle.trim();

	} // end else if
	else if (tagname.equals("action"))
	{ // get action and action type
	  m_action = loader.getText(elt);
	  if (m_action!=null)
	    m_action = m_action.trim();
	  m_action_type = loader.getAttribute(elt,"type","SERVLET");

	} // end else if
	else if (tagname.equals("render-param"))
	{ // get a rendering parameter
	  String name = loader.getAttribute(elt,"name");
	  DOMElementHelper h = new DOMElementHelper(elt);
	  String value = h.getElementText();
	  if (value!=null)
	    value = value.trim();
	  else
	    value = "";
	  m_rp.put(name,value);

	} // end else if
	else if (tagname.equals("instructions"))
	{ // get instructions (not required)
	  DOMElementHelper h = new DOMElementHelper(elt);
	  m_default_instructions = h.getElementText();
	  if (m_default_instructions!=null)
	    m_default_instructions = m_default_instructions.trim();

	} // end else if
	else  // go create a dialog item
	  return m_inner.createDialogItem(elt,plaf);

      } // end try
      catch (XMLLoadException e)
      { // translate XML Load exception to dialog exception
	throw new DialogException(e);

      } // end catch

      return null;

    } // end createDialogItem

    /*====================================================================
     * External operations
     *====================================================================
     */

    Map renderParams()
    {
      if (m_rp.isEmpty())
	return Collections.EMPTY_MAP;
      else
	return Collections.unmodifiableMap(m_rp);

    } // end renderParams

  } // end class InternalFilter

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

  private DialogPLAF m_plaf;
  private String m_name;
  private String m_default_button = null;
  private String m_default_title = null;
  private String m_default_subtitle = null;
  private String m_action = null;
  private String m_action_type = null;
  private String m_default_instructions = null;
  private boolean m_any_required;
  private boolean m_need_multipart;
  private List m_field_order;
  private Map m_field_map;
  private List m_button_order;
  private Map m_button_map;
  private List m_hidden_order;
  private Map m_hidden_map;
  private String m_title = null;
  private String m_subtitle = null;
  private String m_instructions = null;
  private String m_error_message = null;
  private Map m_default_render_params;
  private HashMap m_render_params;

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

  DialogImpl(Element dialog_element, DialogItemFactory item_factory, DialogPLAF plaf) throws DialogException
  {
    // Copy the look-and-feel object reference.
    m_plaf = plaf;

    // Get the basic stuff from the root tag itself.
    XMLLoader loader = XMLLoader.get();
    try
    { // get the dialog name
      m_name = loader.getAttribute(dialog_element,"name");
      m_default_button = dialog_element.getAttribute("defaultbutton");

    } // end try
    catch (XMLLoadException e)
    { // translate to DialogException
      throw new DialogException(e);

    } // end catch

    // Load the fields.
    ArrayList field_order = new ArrayList();
    HashMap field_map = new HashMap();
    ArrayList button_order = new ArrayList();
    HashMap button_map = new HashMap();
    ArrayList hidden_order = new ArrayList();
    HashMap hidden_map = new HashMap();
    int global_flags = 0;
    InternalFilter factory = new InternalFilter(item_factory);
    NodeList nl = dialog_element.getChildNodes();
    for (int i=0; i<nl.getLength(); i++)
    { // get each item to be implemented here
      Node n = nl.item(i);
      if (n.getNodeType()==Node.ELEMENT_NODE)
      { // create the dialog items
	DialogItem item = factory.createDialogItem((Element)n,plaf);
	if (item==null)
	  continue;
	if (item instanceof DialogField)
	{ // add the field to the field data structures
	  global_flags |= ((DialogField)item).getFlags();
	  field_order.add(item);
	  field_map.put(item.getName(),item);

	} // end if
	else if (item instanceof DialogButton)
	{ // add the button to the button data structures
	  button_order.add(item);
	  button_map.put(item.getName(),item);

	} // end else if
	else if (item instanceof DialogHiddenItem)
	{ // add the hidden item to the hidden data structures
	  hidden_order.add(item);
	  hidden_map.put(item.getName(),item);

	} // end else if

      } // end if

    } // end for

    // If anything hasn't been specified that should have been, error out now.
    if (m_default_title==null)
    { // no default title specified
      DialogException de = new DialogException(DialogImpl.class,"DialogMessages","dlg.noTitle");
      de.setParameter(0,m_name);
      throw de;

    } // end if

    if (m_action==null)
    { // no action URL specified
      DialogException de = new DialogException(DialogImpl.class,"DialogMessages","dlg.noAction");
      de.setParameter(0,m_name);
      throw de;

    } // end if

    // "Freeze" the data structures.
    field_order.trimToSize();
    m_field_order = Collections.unmodifiableList(field_order);
    m_field_map = Collections.unmodifiableMap(field_map);
    button_order.trimToSize();
    m_button_order = Collections.unmodifiableList(button_order);
    m_button_map = Collections.unmodifiableMap(button_map);
    hidden_order.trimToSize();
    m_hidden_order = Collections.unmodifiableList(hidden_order);
    m_hidden_map = Collections.unmodifiableMap(hidden_map);
    m_default_render_params = factory.renderParams();

    // Set the global flags.
    m_any_required = ((global_flags & DialogField.FLAG_REQUIRED)!=0);
    m_need_multipart = ((global_flags & DialogField.FLAG_ISFILE)!=0);

    // Copy defaults to working values.
    m_title = m_default_title;
    m_subtitle = m_default_subtitle;
    m_instructions = m_default_instructions;
    m_error_message = null;
    m_render_params = new HashMap(m_default_render_params);

  } // end constructor

  protected DialogImpl(DialogImpl other)
  {
    // Copy the base data.
    m_plaf = other.m_plaf;
    m_name = other.m_name;
    m_default_title = other.m_default_title;
    m_default_subtitle = other.m_default_subtitle;
    m_action = other.m_action;
    m_action_type = other.m_action_type;
    m_default_instructions = other.m_default_instructions;
    m_any_required = other.m_any_required;
    m_need_multipart = other.m_need_multipart;
    m_default_render_params = other.m_default_render_params;

    // Clone off the fields.
    ArrayList order = new ArrayList();
    HashMap map = new HashMap();
    Iterator it = other.m_field_order.iterator();
    while (it.hasNext())
    { // clone all the fields
      DialogField fld = (DialogField)(it.next());
      DialogField new_fld = (DialogField)(fld.clone());
      order.add(new_fld);
      map.put(new_fld.getName(),new_fld);

    } // end while

    // "Freeze" the field data structures.
    order.trimToSize();
    m_field_order = Collections.unmodifiableList(order);
    m_field_map = Collections.unmodifiableMap(map);

    // Since the buttons aren't modified, we can get away with sharing
    // the button order and button map.
    m_button_order = other.m_button_order;
    m_button_map = other.m_button_map;

    // Clone off the hidden fields.
    order = new ArrayList();
    map = new HashMap();
    it = other.m_hidden_order.iterator();
    while (it.hasNext())
    { // clone all the fields
      DialogHiddenItem fld = (DialogHiddenItem)(it.next());
      DialogHiddenItem new_fld = (DialogHiddenItem)(fld.clone());
      order.add(new_fld);
      map.put(new_fld.getName(),new_fld);

    } // end while

    // "Freeze" the field data structures.
    order.trimToSize();
    m_hidden_order = Collections.unmodifiableList(order);
    m_hidden_map = Collections.unmodifiableMap(map);

    // Copy defaults to working values.
    m_title = m_default_title;
    m_subtitle = m_default_subtitle;
    m_instructions = m_default_instructions;
    m_error_message = null;
    m_render_params = new HashMap(m_default_render_params);

  } // end constructor

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

  protected final DialogField getField(String name)
  {
    return (DialogField)(m_field_map.get(name));

  } // end getField

  protected final DialogHiddenItem getHiddenItem(String name)
  {
    return (DialogHiddenItem)(m_hidden_map.get(name));

  } // end getHiddenItem

  /*--------------------------------------------------------------------------------
   * Implementations from interface NamedObject
   *--------------------------------------------------------------------------------
   */

  public String getName()
  {
    return m_name;

  } // end getName

  /*--------------------------------------------------------------------------------
   * Implementations from interface Dialog
   *--------------------------------------------------------------------------------
   */

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

  } // end clone

  public void render(TextRenderControl control) throws IOException, RenderingException
  {
    PrintWriter wr = control.getWriter();
    URLRewriter rewriter = (URLRewriter)(control.queryService(URLRewriter.class));

    // Get the content header and render it.
    control.renderSubObject(m_plaf.getContentHeader(m_title,m_subtitle));

    // Write the error message.
    if (m_error_message!=null)
      wr.write("<p align=\"center\" class=\"content\" style=\"color: red; font-weight: bold;\">\n"
	       + StringUtils.encodeHTML(m_error_message) + "\n</p>\n");

    // Write the start of the form.
    wr.write("<form name=\"" + m_name + "\"");
    if (m_need_multipart)
      wr.write(" enctype=\"multipart/form-data\"");
    wr.write(" method=\"POST\" action=\"" + rewriter.rewriteURL(m_action_type,m_action)
	     + "\"><div class=\"content\">\n");

    // Create the render params parameter.
    Map rparam = Collections.unmodifiableMap(m_render_params);

    // Render the hidden fields.
    Iterator it = m_hidden_order.iterator();
    while (it.hasNext())
    { // render each hidden field in turn
      DialogHiddenItem hfld = (DialogHiddenItem)(it.next());
      hfld.render(control,rparam);

    } // end while

    // Render the instructions and such.
    if (m_instructions!=null)
      wr.write("<p>" + m_instructions + "</p>\n");
    if (m_any_required)
    { // write out the "required" instructions
      ResourceBundle b = ResourceBundle.getBundle("com.silverwrist.dynamo.dialog.DialogMessages",
						  Locale.getDefault());
      wr.write("<p>" + b.getString("required.msg") + "</p>");

    } // end if

    // Write the form fields out inside a table.
    wr.write("<table border=\"0\" align=\"center\" cellpadding=\"6\" cellspacing=\"0\">\n");
    it = m_field_order.iterator();
    while (it.hasNext())
    { // render each field in turn
      DialogField fld = (DialogField)(it.next());
      fld.render(control,rparam);

    } // end while

    wr.write("</table>\n");

    if (!(m_button_order.isEmpty()))
    { // render the command buttons at the button
      boolean is_first = true;
      wr.write("<p align=\"center\" class=\"content\">");
      it = m_button_order.iterator();
      while (it.hasNext())
      { // render each button in turn, spacing them out
	DialogButton bn = (DialogButton)(it.next());
	if (is_first)
	  is_first = false;
	else
	  wr.write("&nbsp;&nbsp;");
	bn.render(control,rparam);

      } // end while

      wr.write("</p>\n");

    } // end if

    wr.write("</div></form>\n");

  } // end render

  public void reset()
  {
    m_title = m_default_title;
    m_subtitle = m_default_subtitle;
    m_instructions = m_default_instructions;

    Iterator it = m_field_order.iterator();
    while (it.hasNext())
    { // call reset on each field
      DialogField fld = (DialogField)(it.next());
      fld.reset();

    } // end while

    it = m_hidden_order.iterator();
    while (it.hasNext())
    { // call reset on each hidden field
      DialogHiddenItem hfld = (DialogHiddenItem)(it.next());
      hfld.reset();

    } // end while

  } // end reset

  public void load(Request r)
  {
    Iterator it = m_field_order.iterator();
    while (it.hasNext())
    { // call setValueFrom on each field
      DialogField fld = (DialogField)(it.next());
      fld.setValueFrom(r);

    } // end while

    it = m_hidden_order.iterator();
    while (it.hasNext())
    { // call setValueFrom on each hidden field
      DialogHiddenItem hfld = (DialogHiddenItem)(it.next());
      hfld.setValueFrom(r);

    } // end while

  } // end load

  public Object getValue(String fieldname)
  {
    DialogField fld = getField(fieldname);
    if (fld==null)
    { // try looking for a hidden field
      DialogHiddenItem hfld = getHiddenItem(fieldname);
      if (hfld==null)
	return null;
      else
	return hfld.getValue();

    } // end if
    else
      return fld.getValue();

  } // end getValue

  public boolean containsValue(String fieldname)
  {
    DialogField fld = getField(fieldname);
    if (fld!=null)
      return fld.containsValue();

    DialogHiddenItem hfld = getHiddenItem(fieldname);
    if (hfld!=null)
      return hfld.containsValue();

    return false;

  } // end containsValue

  public void setValue(String fieldname, Object value)
  {
    DialogField fld = getField(fieldname);
    if (fld!=null)
      fld.setValue(value);
    else
    { // try setting a hidden value
      DialogHiddenItem hfld = getHiddenItem(fieldname);
      if (hfld!=null)
	hfld.setValue(value);

    } // end else

  } // end setValue

  public boolean isEnabled(String fieldname)
  {
    DialogField fld = getField(fieldname);
    if (fld==null)
      return false;
    else
      return fld.isEnabled();

  } // end isEnabled

  public void setEnabled(String fieldname, boolean flag)
  {
    DialogField fld = getField(fieldname);
    if (fld!=null)
      fld.setEnabled(flag);

  } // end setEnabled

  public void validate(Request r) throws ValidationException
  {
    // validate all the fields individually
    Iterator it = m_field_order.iterator();
    while (it.hasNext())
    { // call reset on each field
      DialogField fld = (DialogField)(it.next());
      fld.validate(r);

    } // end while
    
  } // end validate

  public String getTitle()
  {
    return m_title;

  } // end getTitle

  public void setTitle(String s)
  {
    m_title = s;

  } // end setTitle

  public String getSubtitle()
  {
    return m_subtitle;

  } // end getSubtitle

  public void setSubtitle(String s)
  {
    m_subtitle = s;

  } // end setSubtitle

  public String getInstructions()
  {
    return m_instructions;

  } // end getInstructions

  public void setInstructions(String s)
  {
    m_instructions = s;

  } // end setInstructions

  public void setErrorMessage(String s)
  {
    m_error_message = s;

  } // end setErrorMessage

  public String getClickedButton(Request r)
  {
    Iterator it = m_button_order.iterator();
    while (it.hasNext())
    { // look for a clicked button
      DialogButton bn = (DialogButton)(it.next());
      if (bn.isClicked(r))
	return bn.getName();

    } // end while

    return m_default_button;

  } // end getClickedButton

  public synchronized void setRenderParam(String name, String value)
  {
    if (m_render_params.containsKey(name))
      m_render_params.put(name,value);

  } // end setRenderParam

} // end class DialogImpl
