package com.dbxml.db.admin.xmleditor;

/*
 * dbXML - Native XML Database
 * Copyright (C) 1999-2004  The dbXML Group, L.L.C.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: DefaultNodeEditor.java,v 1.2 2004/02/12 00:17:55 bradford Exp $
 */

import java.awt.*;
import javax.swing.*;
import org.w3c.dom.*;

import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.Caret;

/**
 * DefaultNodeEditor
 */

public class DefaultNodeEditor extends NodeEditorBase implements Expandable {
   boolean canScroll;
   boolean expanded;
   JPanel editors;
   int childCount;

   public DefaultNodeEditor() {
      super();
   }

   public DefaultNodeEditor(XMLEditor owner, DefaultNodeEditor parent, Node node) {
      super(owner, parent, node);
      expanded = owner.expandable ? owner.isNodeExpanded(node) : true;
   }

   public DefaultNodeEditor(XMLEditor owner) {
      super(owner);
   }

   public void setNode(Node node) {
      super.setNode(node);
      removeAll();
      childCount = 0;
      editors = null;

      if ( node == null )
         return;

      short nodeType = node.getNodeType();

      switch ( nodeType ) {

         case Node.DOCUMENT_NODE:
            node = ((Document)node).getDocumentElement();
            // Fall through to Element

         case Node.ELEMENT_NODE:
            renderElement((Element)node);
            break;

         case Node.ATTRIBUTE_NODE:
            renderAttribute((Attr)node);
            break;

         case Node.COMMENT_NODE:
            renderComment((Comment)node);
            break;

         case Node.TEXT_NODE:
            renderText((Text)node);
            break;
      }
   }

   private void renderElement(Element node) {
      ElementResourceMap rm = owner.resourceMap;

      String nodeName = node.getTagName();

      JLabel label = null;
      boolean showLabel = owner.showRootElement || owner.rootEdit != this;
      Color fg = rm.getForegroundColor(nodeName);
      Color bg = rm.getBackgroundColor(nodeName);

      java.util.List attrList = new ArrayList();
      NamedNodeMap attribs = node.getAttributes();
      for ( int i = 0; i < attribs.getLength(); i++ ) {
         // Ignore Query Result Attributes
         Attr a = (Attr)attribs.item(i);
         String ns = a.getNamespaceURI();
         String p = a.getPrefix();
         attrList.add(a);
      }

      java.util.List childList = new ArrayList();
      NodeList children = node.getChildNodes();
      for ( int i = 0; i < children.getLength(); i++ ) {
         Node n = children.item(i);
         short type = n.getNodeType();
         if ( type == Node.TEXT_NODE ) {
            Text t = (Text)n;
            if ( t.getData().trim().length() == 0 )
            continue;
         }
         if ( type == Node.COMMENT_NODE && !owner.showComments )
            continue;

         childList.add(children.item(i));
      }

      boolean textEdit = false;
      if ( showLabel && owner.collapseText && childList.size() <= 1 ) {
         Node n;
         if ( childList.size() == 0 ) {
            if ( attrList.size() == 0 ) {
               n = node.getOwnerDocument().createTextNode("");
               node.appendChild(n);
               childList.add(n);
            }
            else
               n = null;
         }
         else
            n = (Node)childList.get(0);

         if ( n != null )
            textEdit = n.getNodeType() == Node.TEXT_NODE;
      }

      java.util.List list = new ArrayList(attrList);
      list.addAll(childList);
      final boolean canExpand = (textEdit && attrList.size() > 0)
         || (!textEdit && list.size() > 0);

      if ( showLabel ) {
         String description = null;
         String toolTip = null;
         String caption = null;

         if ( owner.contentModel != null )
            description = owner.contentModel.getSchemaDoc(nodeName);
         if ( description != null ) {
            StringBuffer sb = new StringBuffer();
            sb.append("<html>");
            sb.append(nodeName);
            sb.append("<br>");
            sb.append(description);
            sb.append("</html>");
            toolTip = sb.toString();
         }
         else
            toolTip = nodeName;

         if ( expanded || textEdit || !owner.showAttribs || attrList.size() == 0 )
            caption = nodeName;
         else {
            StringBuffer sb = new StringBuffer();
            sb.append(nodeName);
            sb.append(owner.attribStart);
            for ( int i = 0; i < attrList.size(); i++ ) {
               Attr a = (Attr)attrList.get(i);
               if ( i > 0 )
                  sb.append(' ');
               sb.append(a.getName());
               sb.append("=\"");
               sb.append(a.getValue());
               sb.append('\"');
            }
            sb.append(owner.attribEnd);
            caption = sb.toString();
         }

         label = createLabel(node, caption, toolTip, canExpand, fg, bg);
      }

      if ( !expanded && textEdit ) {
         setLayout(new BorderLayout());

         Node textNode = (Node)childList.get(0);
         String textValue = textNode.getNodeValue();

         label.setVerticalAlignment(SwingConstants.TOP);

         Icon ico = rm.getIcon(nodeName);
         if ( ico != null ) {
            label.setIcon(ico);
            if ( !expanded && owner.expandable && canExpand )
               label.setFont(owner.getFont().deriveFont(Font.BOLD));
         }
         else if ( owner.expandable && canExpand )
            label.setIcon(expanded ? XMLEditor.ImgCollapse : XMLEditor.ImgExpand);
         else
            label.setIcon(XMLEditor.ImgNoExpand);

         if ( owner.attribLabelSize > 0 )
            label.setPreferredSize(new Dimension(owner.attribLabelSize, (int)label.getPreferredSize().getHeight()));
         add(label, BorderLayout.WEST);

         if ( bg == null )
            bg = owner.textColor;

         if ( owner.readOnly ) {
            /*
            if ( textValue.length() == 0 )
               textValue = " ";
            JLabel display = createLabel(null, textValue, null, canExpand, fg, bg);
            display.setBorder(owner.LeftBorder);
            if ( label != null )
               label.setLabelFor(display);
            add(display, BorderLayout.CENTER);
            */
            JTextArea t = createTextArea(textNode, textValue, label, fg, bg);
            t.setEditable(false);
            add(t, BorderLayout.CENTER);

         }
         else {
            String t = textValue.trim();
            if ( t.indexOf('\n') == -1 )
               add(createTextArea(textNode, t, label, fg, bg), BorderLayout.CENTER);
            else
               add(createTextArea(textNode, textValue, label, fg, bg), BorderLayout.CENTER);
         }
      }
      else {
         setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
         if ( showLabel ) {
            Icon ico = rm.getIcon(nodeName);
            if ( ico != null ) {
               label.setIcon(ico);
               if ( !expanded && owner.expandable && canExpand && attrList.size() == 0 )
                  label.setFont(owner.getFont().deriveFont(Font.BOLD));
            }
            else if ( owner.expandable )
               label.setIcon(expanded ? XMLEditor.ImgCollapse : XMLEditor.ImgExpand);
            else
               label.setIcon(XMLEditor.ImgNoExpand);

            if ( !canExpand )
               label.setEnabled(false);

            JPanel p = new JPanel();
            p.setAutoscrolls(true);
            p.setOpaque(bg != null);
            if ( bg != null )
               p.setBackground(bg);
            p.setLayout(new BorderLayout());
            p.add(label, BorderLayout.WEST);
            add(p);
         }
      }

      if ( !expanded )
         return;

      if ( list.size() > 0 ) {
         editors = new JPanel();
         editors.setAutoscrolls(true);
         if ( label != null )
            label.setLabelFor(editors);
         if ( bg != null ) {
            editors.setBackground(bg);
            editors.setOpaque(true);
         }
         else
            editors.setOpaque(false);

         for ( int i = 0; i < list.size(); i++, childCount++ ) {
            Node n = (Node)list.get(i);
            Class editor = rm.getEditor(n.getNodeName());
            if ( editor != null ) {
               try {
                  Object obj = editor.newInstance();
                  if ( obj instanceof NodeEditor && obj instanceof JComponent ) {
                     NodeEditor ne = (NodeEditor)obj;
                     ne.setOwner(owner);
                     ne.setNode(n);
                     editors.add((JComponent)obj);
                     continue;
                  }
               }
               catch ( Exception e ) {
                  e.printStackTrace(System.err);
               }
            }
            editors.add(new DefaultNodeEditor(owner, this, n));
         }
         add(editors);
         editors.setLayout(new BoxLayout(editors, BoxLayout.Y_AXIS));
      }
   }

   private void renderAttribute(Attr node) {
      setLayout(new BorderLayout());

      ElementResourceMap rm = owner.resourceMap;

      StringBuffer sb = new StringBuffer();
      sb.append('@');
      sb.append(node.getName());
      String attrName = sb.toString();

      String ownerName = node.getOwnerElement().getTagName();
      String elemAttrName = ownerName + attrName;

      Color bg = rm.getBackgroundColor(elemAttrName);
      if ( bg == null )
         bg = rm.getBackgroundColor(attrName);
      if ( bg == null )
         bg = owner.attribColor;
      Color fg = rm.getForegroundColor(elemAttrName);
      if ( fg == null )
         fg = rm.getForegroundColor(attrName);

      String description = null;
      String caption = null;
      String toolTip = null;
      if ( owner.contentModel != null )
         description = owner.contentModel.getSchemaDoc(ownerName, attrName);
      if ( description != null ) {
         sb = new StringBuffer();
         sb.append("<html>");
         sb.append(elemAttrName);
         sb.append("<br>");
         sb.append(description);
         sb.append("</html>");
         toolTip = sb.toString();
      }
      else
         toolTip = elemAttrName;

      sb = new StringBuffer();
      sb.append(node.getName());
      sb.append(owner.space);
      caption = sb.toString();

      JLabel label = createLabel(null, caption, toolTip, false, fg, bg);

      Icon ico = rm.getIcon(elemAttrName);
      if ( ico == null )
         ico = rm.getIcon(attrName);
      label.setIcon(ico != null ? ico : XMLEditor.ImgAttrib);

      if ( owner.attribLabelSize > 0 )
         label.setPreferredSize(new Dimension(owner.attribLabelSize, (int)label.getPreferredSize().getHeight()));

      add(label, BorderLayout.WEST);

      if ( owner.readOnly ) {
         /*
         String nodeValue = node.getValue();
         if ( nodeValue.length() == 0 )
            nodeValue = " ";
         JLabel display = createLabel(null, nodeValue, null, false, fg, bg);
         display.setBorder(owner.LeftBorder);
         if ( label != null )
            label.setLabelFor(display);
         add(display, BorderLayout.CENTER);
         */
         JTextField text = createTextField(node, label, fg, bg);
         text.setEditable(false);
         add(text, BorderLayout.CENTER);
      }
      else
         add(createTextField(node, label, fg, bg), BorderLayout.CENTER);
   }

   private void renderComment(Comment node) {
      setLayout(new BorderLayout());

      JLabel label = createLabel(node, owner.space, "This is a comment node", false, null, owner.commentColor);
      label.setIcon(XMLEditor.ImgCommentStart);
      label.setVerticalAlignment(SwingConstants.TOP);
      add(label, BorderLayout.WEST);

      JLabel ce = createLabel(null, owner.space, "This is a comment node", false, null, owner.commentColor);
      ce.setBorder(XMLEditor.LeftBorder);
      ce.setIcon(XMLEditor.ImgCommentEnd);
      ce.setVerticalAlignment(SwingConstants.BOTTOM);
      ce.setHorizontalTextPosition(SwingConstants.LEFT);
      add(ce, BorderLayout.EAST);

      if ( owner.readOnly ) {
         /*
         String nodeValue = node.getData();
         if ( nodeValue.length() == 0 )
            nodeValue = " ";
         JLabel display = createLabel(null, nodeValue, null, false, null, owner.commentColor);
         display.setBorder(owner.LeftBorder);
         if ( label != null )
            label.setLabelFor(display);
         add(display, BorderLayout.CENTER);
         */
         JTextArea text = createTextArea(node, label, Color.black, owner.commentColor);
         text.setEditable(false);
         add(text, BorderLayout.CENTER);

      }
      else
         add(createTextArea(node, label, Color.black, owner.commentColor), BorderLayout.CENTER);
   }

   private void renderText(Text node) {
      setLayout(new BorderLayout());

      String caption = owner.space;

      JLabel label = createLabel(node, caption, "This is a text node", false, null, owner.textColor);
      label.setIcon(XMLEditor.ImgText);
      label.setVerticalAlignment(SwingConstants.TOP);
      add(label, BorderLayout.WEST);

      if ( owner.readOnly ) {
         /*
         String nodeValue = node.getData();
         if ( nodeValue.length() == 0 )
            nodeValue = " ";
         JLabel display = createLabel(null, nodeValue, null, false, null, owner.textColor);
         display.setBorder(owner.LeftBorder);
         if ( label != null )
            label.setLabelFor(display);
         add(display, BorderLayout.CENTER);
         */
         JTextArea text = createTextArea(node, label, Color.black, owner.textColor);
         text.setEditable(false);
         add(text, BorderLayout.CENTER);
      }
      else
         add(createTextArea(node, label, Color.black, owner.textColor), BorderLayout.CENTER);
   }

   private JLabel createLabel(final Node node, String label, String toolTip, final boolean canExpand, Color fg, Color bg) {
      JLabel lbl = new JLabel(label);
      lbl.setAutoscrolls(true);
      lbl.setFont(owner.font);

      if ( toolTip != null )
         lbl.setToolTipText(toolTip);

      if ( bg != null ) {
         lbl.setOpaque(true);
         lbl.setBackground(bg);
      }

      if ( fg != null )
         lbl.setForeground(fg);

      if ( node != null ) {
         lbl.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(MouseEvent ex) {
               boolean canOpen = canExpand && owner.expandable;
               if ( ex.isPopupTrigger() ) {
                  new DefaultPopupMenu(DefaultNodeEditor.this, canOpen, owner.source).show(DefaultNodeEditor.this, 0, 0);
                  ex.consume();
               }
               else if ( canExpand && owner.expandable ) {
                  setExpanded(!expanded);
                  ex.consume();
               }
            }
         });
      }

      return lbl;
   }

   private JTextField createTextField(final Node node, JLabel label, Color fg, Color bg) {
      String nodeValue = node.getNodeValue();

      final TextField t = new TextField(nodeValue);
      t.setAutoscrolls(true);
      t.setFont(owner.font);
      t.setBorder(XMLEditor.LeftBorder);

      if ( label != null )
         label.setLabelFor(t);

      if ( bg != null ) {
         t.setOpaque(true);
         t.setBackground(bg);
      }

      if ( fg != null )
         t.setForeground(fg);

      t.addCaretListener(new CaretListener() {
         public void caretUpdate(CaretEvent c) {
            t.scrollToCaret(c);
         }
      });

      t.addFocusListener(new FocusAdapter() {
         public void focusGained(FocusEvent l) {
            if ( t.getDocument().getLength() == 0 ) {
               t.setText(" ");
               t.setCaretPosition(1);
               t.setCaretPosition(0);
               t.setText("");
            }
            else {
               t.setCaretPosition(1);
               t.setCaretPosition(0);
            }
         }
      });

      t.addKeyListener(new KeyAdapter() {
         public void keyReleased(KeyEvent e) {
            node.setNodeValue(t.getText());
            owner.modified = true;
         }
      });

      return t;
   }

   private JTextArea createTextArea(final Node node, String nodeValue, JLabel label, Color fg, Color bg) {
      final TextArea t = new TextArea(nodeValue);
      t.setAutoscrolls(true);
      t.setFont(owner.font);
      t.setBorder(XMLEditor.LeftBorder);

      if ( label != null )
         label.setLabelFor(t);

      if ( bg != null ) {
         t.setOpaque(true);
         t.setBackground(bg);
      }

      if ( fg != null )
         t.setForeground(fg);

      t.addCaretListener(new CaretListener() {
         public void caretUpdate(CaretEvent c) {
            t.caretEvent = true;
         }
      });

      t.addFocusListener(new FocusAdapter() {
         public void focusGained(FocusEvent l) {
            if ( t.getDocument().getLength() == 0 ) {
               t.setText(" ");
               t.setCaretPosition(1);
               t.setCaretPosition(0);
               t.setText("");
            }
            else {
               t.setCaretPosition(1);
               t.setCaretPosition(0);
            }
         }
      });

      t.addKeyListener(new KeyAdapter() {
         public void keyReleased(KeyEvent e) {
            node.setNodeValue(t.getText());
            owner.modified = true;
         }
      });

      return t;
   }

   private JTextArea createTextArea(final Node node, JLabel label, Color fg, Color bg) {
      return createTextArea(node, node.getNodeValue(), label, fg, bg);
   }

   public boolean isExpanded() {
      return expanded;
   }

   void setExpanded(boolean expanded, boolean locked) {
      boolean expandable = owner.expandable;
      if ( expandable )
         this.expanded = expanded;
      if ( node != null )
         setNode(node);
      if ( !locked )
         scrollIntoVisiblePanel();
   }

   public void setExpanded(boolean expanded) {
      setExpanded(expanded, false);
   }

   void expandAll(boolean locked) {
      if ( !isExpanded() )
         setExpanded(true, true);
      for ( int i = 0; i < childCount; i++ ) {
         NodeEditor editor = (NodeEditor)editors.getComponent(i);
         if ( editor instanceof DefaultNodeEditor )
            ((DefaultNodeEditor)editor).expandAll(true);
         else if ( editor instanceof Expandable )
            ((Expandable)editor).expandAll();
      }
      if ( !locked )
         scrollIntoVisiblePanel();
   }

   public void expandAll() {
      expandAll(false);
   }

   void collapseAll(boolean locked) {
      for ( int i = 0; i < childCount; i++ ) {
         NodeEditor editor = (NodeEditor)editors.getComponent(i);
         if ( editor instanceof DefaultNodeEditor )
            ((DefaultNodeEditor)editor).collapseAll(true);
         else if ( editor instanceof Expandable )
            ((Expandable)editor).collapseAll();
      }
      if ( isExpanded() )
         setExpanded(false, true);
      if ( !locked )
         scrollIntoVisiblePanel();
   }

   public void collapseAll() {
      collapseAll(false);
   }

   private void scrollIntoVisiblePanel() {
      validate();
      int indent = showBorder ? owner.indentSize : 0;
      int width = 1; // getWidth()-indent;
      int height = (getHeight() * childCount) + 1;
      scrollRectToVisible(new Rectangle(indent, 0, width, height));
   }


   /**
    * TextField
    */

   private class TextField extends JTextField {
      public TextField(String text) {
         super(text);
      }

      public void scrollRectToVisible(Rectangle r) {
         // NO-OP
      }

      public void scrollToCaret(CaretEvent ev) {
         Caret c = getCaret();
         int d = ev.getDot();
         Point p = c.getMagicCaretPosition();
         if ( p != null ) {
            FontMetrics fm = getFontMetrics(getFont());
            int height = fm.getHeight();
            int left = 0;
            int width = 1;
            try {
               if ( d > 0 ) {
                  String test = getText(0, d);
                  left = fm.stringWidth(test);
               }
            }
            catch ( Exception e ) {
            }

            // Must accomodate the label and border
            width += (owner.attribLabelSize + owner.indentSize + 4);
            Rectangle r = new Rectangle(left, 0, width, height);
            DefaultNodeEditor.this.scrollRectToVisible(r);
         }
      }
   }


   /**
    * TextArea
    */

   private class TextArea extends JTextArea {
      public boolean caretEvent;

      public TextArea(String text) {
         super(text);
      }

      public void scrollRectToVisible(Rectangle r) {
         if ( caretEvent ) {
            super.scrollRectToVisible(r);
            caretEvent = false;
         }
      }
   }
}

