/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * 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 Grendel mail/news client.
 *
 * The Initial Developer of the Original Code is Giao Nguyen
 * <grail@cafebabe.org>.  Portions created by Giao Nguyen are
 * Copyright (C) 1999 Giao Nguyen. All
 * Rights Reserved.
 *
 * Contributor(s): Morgan Schweers <morgan@vixen.com>
 */

package grendel.ui;

import java.io.InputStream;
import java.io.IOException;

import java.net.URL;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.NoSuchElementException;

import java.awt.Container;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;

import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JFrame;
import javax.swing.JDialog;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JButton;
import javax.swing.RootPaneContainer;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.SwingConstants;
import javax.swing.ImageIcon;
import javax.swing.JComboBox;

import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.sun.xml.parser.Resolver;
import com.sun.xml.parser.Parser;
import com.sun.xml.tree.XmlDocument;
import com.sun.xml.tree.TreeWalker;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;



/**
 * Build a panel from an XML data source.
 */
public class XMLPageBuilder extends XMLWidgetBuilder {
  static final String id_attr = "id";
  static final String label_attr = "label";
  static final String separator_attr = "separator";
  static final String radio_attr = "radio";
  static final String group_attr = "group";
  static final String accel_attr = "accel";
  static final String type_attr = "type";
  static final String dialog_tag = "dialog";
  static final String panel_tag = "panel";
  static final String input_tag = "input";
  static final String label_tag = "label";
  static final String image_tag = "image";
  static final String layout_attr = "layout";
  
  PageUI component;
  String title;
  String id;
  String attr;
  PageModel model;
  Hashtable group = new Hashtable();
  
  /**
   * Build a menu builder which operates on XML formatted data
   * 
   * @param attr attribute
   * @param id the value of the attribute to have a match
   * @param model the page model for the page to be created
   */
  public XMLPageBuilder(String attr, String id, PageModel model) {
    this.attr = attr;
    this.id = id;
    this.model = model;
  }
  
  /**
   * Read the input stream and build a menubar from it
   * 
   * @param stream the stream containing the XML data
   */
  public JComponent buildFrom(InputStream stream) {
    XmlDocument doc;
    TreeWalker tree;
    Node node;
    URL linkURL;
    Element current;
    
    try {
      doc = XmlDocument.createXmlDocument(stream, false);
      current = doc.getDocumentElement();
      tree = new TreeWalker(current);
      
      // get the link tag for this file
      // get into head and get the first element
      node = 
        tree.getNextElement("head").getFirstChild().getNextSibling();
      
      // set the configuration contained in this node
      setConfiguration((Element)node);
      
      // skip to the body and find the element 
      current = findTargetElement(tree.getNextElement("body"));

      if (current != null) {
        component = (PageUI)buildFrom(current);
      }
    } catch (Throwable t) {
      t.printStackTrace();
    }

    return component;
  }
  
  /**
   * Locates the element targeted.
   * @param element the element that contains all nodes to search
   * @return the element matching the target description
   */
  private Element findTargetElement(Element element) {
    Node node = element.getFirstChild().getNextSibling();
    
    while (node != null) {
      // make sure we're looking at an ELEMENT_NODE
      if (node.getNodeType() == Node.ELEMENT_NODE) {
        element = (Element)node;

        // found it
        if (element.getAttribute(attr).equals(id)) break;
      }

      // proceed to the next node in the tree
      node = node.getNextSibling();
    }

    // we didn't find it otherwise we wouldn't be here.
    return element;
  }

  /**
   * Build a page from this element.
   * @param element figure it out you twit
   * @return the component built
   */
  public JComponent buildFrom(Element element) {
    Node node;
    PageUI my_component = buildPanel((Element)element.getFirstChild().getNextSibling());
    title = getReferencedLabel(element, "title");

    return my_component;
  }

  /**
   * Build a panel.
   * @param element the panel element 
   * @return the panel object as a PageUI type
   */
  public PageUI buildPanel(Element element) {
    Node node = element.getFirstChild().getNextSibling();
    
    PageUI my_component = new PageUI();

    // every pageui in a dialog shares the same model. pageui just 
    // spec the layout. 
    my_component.setModel(model);

    // in the future, we'll determine what the layout manager is. for now,
    // the only one we support is GridBagLayout
    my_component.setLayout(new GridBagLayout());

    while (node != null) {
      if (node.getNodeType() == Node.ELEMENT_NODE) {
        element = (Element)node;
        JComponent child = buildComponent(element);
        GridBagConstraints cons = buildConstraints(element);
        my_component.addCtrl(element.getAttribute("ID"), child, cons);
      }

      node = node.getNextSibling();
    }

    return my_component;
  }
  
  private int parseGridConstant(String data) {
    int result = 1;
    
    try {
      data = data.trim();
      result = Integer.parseInt(data);
    } catch (NumberFormatException nfe) {
      if (data.equals("remainder")) {
        result = GridBagConstraints.REMAINDER;
      } else if (data.equals("relative")) {
        result = GridBagConstraints.RELATIVE;
      }
    } finally {
      return result;
    }
  }

  private int parseFillConstant(String fill) {
    int result = GridBagConstraints.NONE;
    
    fill = fill.trim();
    
    if (fill.equals("horizontal")) {
      result = GridBagConstraints.HORIZONTAL;
    } else if (fill.equals("vertical")) {
      result = GridBagConstraints.VERTICAL;
    } else if (fill.equals("both")) {
      result = GridBagConstraints.BOTH;
    }

    return result;
  }

  private float parseWeightConstant(String weight) {
    float result = 0.0f;
    
    try {
      result = Float.valueOf(weight).floatValue();
    } catch (NumberFormatException nfe) {
    } finally {
      return result;
    }
  }
  
  /**
   * Build a constraint from the element's details.
   *
   * @param current the XML element containing constraint information.
   * @return extracted constraints
   */
  protected GridBagConstraints buildConstraints(Element current) {
    GridBagConstraints constraints = new GridBagConstraints();
    CustomInsets inset = 
      new CustomInsets(current.getAttribute("insets"));

    constraints.weightx = 
      parseWeightConstant(current.getAttribute("weightx"));
    
    constraints.weighty = 
      parseWeightConstant(current.getAttribute("weighty"));
    
    // fill
    constraints.fill = parseFillConstant(current.getAttribute("fill"));
    
    constraints.gridwidth = 
      parseGridConstant(current.getAttribute("gridwidth"));
    
    constraints.gridheight = 
      parseGridConstant(current.getAttribute("gridheight"));
    
    constraints.insets = inset;
    
    return constraints;
  }
  
  protected JComponent buildComponent(Element current) {
    JComponent item = null;
    String tag = current.getTagName();
    
    if (tag.equals(input_tag)) { // input tag
      item = buildInput(current);
    } else if (tag.equals(label_tag)) {
      item = buildLabel(current);
    } else if (tag.equals(panel_tag)) {
      item = buildPanel(current);
    } else if (tag.equals(image_tag)) {
      item = buildImage(current);
    }
    
    return item;
  }
  
  protected JPasswordField buildPasswordField(Element current) {
    JPasswordField item = null;
    
    item = new JPasswordField();
    textFieldAdjustments(item, current);
    return item;
  }
  
  private void textFieldAdjustments(JTextField item, Element current) {
    String s = current.getAttribute("columns");
    s = s.trim();
    try {
      int column = Integer.parseInt(s);
      item.setColumns(column);
    } catch (NumberFormatException nfe) {
    }
  }
  
  protected JTextField buildTextField(Element current) {
    JTextField item = new JTextField();
    textFieldAdjustments(item, current);
    
    return item;
  }
  
  protected JRadioButton buildRadioButton(Element current) {
    JRadioButton item = new JRadioButton();
    String label = getReferencedLabel(current, "title");
    String group_str = current.getAttribute("group");
    
    if (label.length() > 0) {
      item.setText(label);
    }
    
    // button group matters
    if (group_str.length() > 0) {
      ButtonGroup bg;
      if (group.containsKey(group_str)) {
        bg = (ButtonGroup)group.get(group_str);
      } else {
        bg = new ButtonGroup();
        group.put(group_str, bg);
      }
      bg.add(item);
    }
    
    return item;
  }
  
  private JComponent buildInput(Element current) {
    JComponent item = null;
    String type = current.getAttribute("type");
    
    String ID = current.getAttribute("ID");
    if (type.equals("radio")) { // radio type
      item = buildRadioButton(current);
    } else if (type.equals("text")) { // text type
      item = buildTextField(current);
    } else if (type.equals("password")) {
      item = buildPasswordField(current);
    } else if (type.equals("button")) { // buttons
      item = buildButton(current);
    } else if (type.equals("checkbox")) {
      item = buildCheckBox(current);
    } else if (type.equals("jlist")) {
      item = buildList(current);
    } else if (type.equals("combobox")) {
      item = buildComboBox(current);
    }
    
    return item;
  }
  
  protected JCheckBox buildCheckBox(Element current) {
    return new JCheckBox(getReferencedLabel(current, "title"));
  }
  
  protected JButton buildButton(Element current) {
    JButton button =  new JButton(getReferencedLabel(current, "title"));
    button.addActionListener(model);
    button.setActionCommand(getReferencedLabel(current, "command"));
    return button;
  }
  
  protected JList buildList(Element current) {
    JList list = new JList();
    return list;
  }
  
  protected JComboBox buildComboBox(Element current) {
    JComboBox combobox = new JComboBox();
    return combobox;
  }
  
  protected JLabel buildLabel(Element current) {
    JLabel label = new JLabel(getReferencedLabel(current, "title"));
    return label;
  }
  
  protected JLabel buildImage(Element current) {
    URL iconURL = ref.getResource(getReferencedLabel(current, "href"));
    JLabel label = new JLabel(new ImageIcon(iconURL));
    return label;
  }
  
  public JPanel getComponent() {
    return component;
  }
  
  public String getTitle() {
    return title;
  }
  
  public static void main(String[] args) throws Exception {
    javax.swing.JFrame frame = new javax.swing.JFrame("Foo bar");
    PageModel model = new PageModel();
    XMLPageBuilder builder = new XMLPageBuilder(args[0], args[1], model);
    URL url = builder.getClass().getResource("dialogs.xml");
    builder.buildFrom(url.openStream());
    JPanel panel = builder.getComponent();
    JDialog dialog = new JDialog();
    
    dialog.getContentPane().setLayout(new java.awt.GridBagLayout());
    dialog.getContentPane().add(panel, null);
    dialog.setTitle(builder.getTitle());
    dialog.pack();
    dialog.setVisible(true);
  }
  
}

class CustomInsets extends Insets {
  CustomInsets(String inset) {
    super(0, 0, 0, 0);
    
    inset = inset.trim();
    
    if (inset != null && inset.length() >= 9 
        && (inset.charAt(0) == '[' 
	    || inset.charAt(inset.length() - 1) == ']')) {
      inset = inset.substring(1, inset.length() - 1);
      int[] val = parseInset(inset); // guaranteed to be a length of 4
      
      top = val[0];
      left = val[1];
      bottom = val[2];
      right = val[3];
    }
  }
  
  private int[] parseInset(String inset) {
    // if all else fails, it's 0
    int[] val = { 0, 0, 0, 0 };
    StringTokenizer tok = new StringTokenizer(inset, ",");
    
    // assign the values into
    for (int i = 0; i < val.length; i++) {
      try {
        String s = tok.nextToken();
        val[i] = Integer.parseInt(s);
      } catch (NoSuchElementException nse) {
        // ignore. if it's fubared, we use 0 as the value
      } catch (NumberFormatException nfe) {
        // ignore. if it's fubared, we use 0 as the value
      }
    }
    return val;
  }
}
