/*
------------------------------------------------------------------------------------
Copyright (c)2000 TogetherSoft Corporation. Patents pending. All rights reserved.
------------------------------------------------------------------------------------*/

package com.togethersoft.modules.xmleditor.xmltree;

import java.util.*;
import org.w3c.dom.*;
import com.togethersoft.openapi.rwi.*;
import com.togethersoft.modules.xmleditor.dtd.*;
import com.togethersoft.modules.xmleditor.api.*;

import com.togethersoft.together.util.message.Informator;
import com.togethersoft.together.util.message.DefaultInformatorImpl;

import com.togethersoft.modules.ejbframework.utils.ConfigProperties;

public class ElementInst extends ValueHolder implements Inst, XmlElementNode
{
  // New Element Instance Allowance
  public final static int NEA_REQUIRED   = 0;
  public final static int NEA_ALLOWED    = 1;
  public final static int NEA_PROHIBITED = 2;

	/**
	full XML name like "ejb-jar.enterprise-beans.session"

	*/
	private String m_fullXMLName = null;

  /**
   * @supplierCardinality 1
   */
  private XMLTreeManager m_xmlTreeManager;

  private DTDElementDef m_elementDef;

  /**
   * @supplierCardinality 0..1
   */
  private ElementInst  m_parentElementInst = null;

  /**
   * @supplierCardinality 0..1
   */
  private InstHolder m_childrenHolder = null;

  /**
   *@link aggregation
   *     @associates <{com.togethersoft.modules.xmleditor.xmltree.AttributeInst}>
   * @supplierCardinality 0..*
   */
  private Vector m_attributes = new Vector();

  private boolean  m_storeAsCDataSection = false;


  //--------------------------------------------------------------------
  public ElementInst (XMLTreeManager xmlTreeManager, DTDElementDef elementDef, ElementInst parentElementInst)
  {
    super (xmlTreeManager, elementDef);

    m_xmlTreeManager = xmlTreeManager;
    m_elementDef = elementDef;
    m_parentElementInst = parentElementInst;

    DTDItemDef childrenItemDef = elementDef.getChildrenItemDef();
    if (childrenItemDef != null)
      m_childrenHolder = InstHolder.newInstHolder (childrenItemDef);

    // creating attributes
    Enumeration attrDefEnum = elementDef.attributeDefs();
    while (attrDefEnum.hasMoreElements())
    {
      DTDAttributeDef attrDef = (DTDAttributeDef) attrDefEnum.nextElement();
      AttributeInst attrInst = new AttributeInst (xmlTreeManager, attrDef, this);
      m_attributes.addElement (attrInst);
    }
  }

  //--------------------------------------------------------------------
  public boolean equals (Object obj)
  {
    return obj == this;
  }

  //--------------------------------------------------------------------
  public DTDElementDef getElementDef()
  {
    return m_elementDef;
  }

  //--------------------------------------------------------------------
  public XMLTreeManager getXMLTreeManager()
  {
    return m_xmlTreeManager;
  }

  //--------------------------------------------------------------------
  /**
   * This method constructs full-qualified XML name of the element
   */
  public String getFullXMLName()
  {
    if (m_fullXMLName == null)
		{
      m_fullXMLName = (m_parentElementInst != null) ?
         m_parentElementInst.getFullXMLName() + '.' + m_elementDef.getTagName() : m_elementDef.getTagName();
		}

    return m_fullXMLName;
  }

  //--------------------------------------------------------------------
  public boolean isComplete()
  {
    return m_childrenHolder == null || m_childrenHolder.isComplete();
  }

  //--------------------------------------------------------------------
  public int checkNewChildElementAllowance (DTDElementDef childElementDef)
  {
    int allowance = NEA_PROHIBITED;

    if (m_childrenHolder != null)
    {
      allowance = m_childrenHolder.checkElementInstAllowance (childElementDef);

      if (allowance != NEA_PROHIBITED)
      {
        String keySuffix = '.' + childElementDef.getTagName() + ".disableNew";

        String value = findFullXMLNameRelatedProperty (keySuffix);
        if (value == null)
        {
		      ConfigProperties props = m_elementDef.getDocumentType().getConfigProperties();
          value = props.getProperty ('*' + keySuffix);
        }

        if (value != null && (value.equalsIgnoreCase ("true") || value.trim().length() == 0))
          allowance = NEA_PROHIBITED;
      }
    }

    return allowance;
  }

  //--------------------------------------------------------------------
  public boolean putChildElementInst (ElementInst elementInst)
  {
    if (m_childrenHolder != null)
    {
      if (m_childrenHolder.putElementInst (elementInst))
      {
        m_xmlTreeManager.setModified (true);
        return true;
      }
    }

    return false;
  }

  //--------------------------------------------------------------------
  public boolean deleteChildElementInst (ElementInst elementInst)
  {
    if (m_childrenHolder != null)
    {
      if (m_childrenHolder.deleteElementInst (elementInst))
      {
        m_xmlTreeManager.setModified (true);
        return true;
      }
    }

    return false;
  }

  //--------------------------------------------------------------------
  public Enumeration children()
  {
    Vector v = (m_childrenHolder != null) ?
         m_childrenHolder.getContainedElements() : new Vector();

    return v.elements();
  }

  //--------------------------------------------------------------------
  public int getChildIndex (ElementInst elementInst)
  {
    Vector v = (m_childrenHolder != null) ?
         m_childrenHolder.getContainedElements() : new Vector();

    return v.indexOf (elementInst);
  }

  //--------------------------------------------------------------------
  public Enumeration getAttributes()
  {
    return m_attributes.elements();
  }

  //--------------------------------------------------------------------
  public boolean hasAttributes()
  {
    return m_attributes.size() > 0;
  }

  //--------------------------------------------------------------------
  public void loadFromDOMElement (Element domElement)
  {
    // loading attributes
    Enumeration attrEnum = m_attributes.elements();
    while (attrEnum.hasMoreElements())
    {
      AttributeInst attrInst = (AttributeInst) attrEnum.nextElement();
      attrInst.loadFromDOMElement (domElement);
    }

    // loading children & text value

    Node domNode = domElement.getFirstChild();
    if (domNode != null)
    {
      do
      {
        switch (domNode.getNodeType())
        {
          case Node.ELEMENT_NODE:

            Element domChildElement = (Element) domNode;
            try
            {
              ElementInst childElementInst =
                   m_xmlTreeManager.createElementInst (domChildElement.getTagName(), this);

              childElementInst.loadFromDOMElement (domChildElement);
              putChildElementInst (childElementInst);
            }
            catch (UnknownXMLElementException e) { }

            break;

          case Node.CDATA_SECTION_NODE:

            m_storeAsCDataSection = true;

          case Node.TEXT_NODE:

            try
            {
              String value = domNode.getNodeValue();

              switch (m_elementDef.getElementType())
              {
                case DTDElementDef.ET_PCDATA:

                  setStringValue (value);
                  break;

                case DTDElementDef.ET_ANY:

                  // if parent element is of "ANY" type and the text in the node
                  // consists of the only spaces we can skip this node
                  if (isEmptyText (value))
                    break;

                default:

                  // in that case create a dummy "#PCDATA" element instance
                  try
                  {
                    ElementInst childElementInst = m_xmlTreeManager.createElementInst ("#PCDATA", this);
                    childElementInst.setStringValue (value);

                    putChildElementInst (childElementInst);
                  }
                  catch (UnknownXMLElementException e) { }
                  break;
              }
            }
            catch (DOMException e) { }
            break;
        }

        domNode = domNode.getNextSibling();

      } while (domNode != null);
    }
  }

  //--------------------------------------------------------------------
  public void storeToDOMElement (Element domElement)
  {
    Document domDocument = domElement.getOwnerDocument();

    // save text value
    if (m_elementDef.getElementType() == DTDElementDef.ET_PCDATA)
    {
      try
      {
        String value = getStringValue();
        Text textNode = (m_storeAsCDataSection || (value.indexOf ('<') >= 0 || value.indexOf ('>') >= 0)
                         && value.indexOf ("]]>") < 0)
                ? (Text) domDocument.createCDATASection (value)
                : domDocument.createTextNode (value);

        domElement.appendChild ((Node) textNode);
      }
      catch (DOMException e) { }
    }

    // save attributes
    Enumeration attrEnum = m_attributes.elements();
    while (attrEnum.hasMoreElements())
    {
      AttributeInst attrInst = (AttributeInst) attrEnum.nextElement();
      attrInst.storeToDOMElement (domElement);
    }

    // add children
    Enumeration childrenEnum = children();
    while (childrenEnum.hasMoreElements())
    {
      ElementInst childInst = (ElementInst) childrenEnum.nextElement();
      try
      {
        String tagName = childInst.getElementDef().getTagName();

        // if child element is a dummy #PCDATA element
        if (tagName.equals ("#PCDATA"))
        {
          // if parent element is of "ANY" type and child element's text consits of the only spaces
          // we can skip this child
          if (m_elementDef.getElementType() == DTDElementDef.ET_ANY && isEmptyText (childInst.getStringValue()))
            continue;

          // otherwise, create a text node for it
          Text textNode = domDocument.createTextNode (childInst.getStringValue());
          domElement.appendChild ((Node) textNode);
        }
        else
        {
          Element domChild = domDocument.createElement (tagName);
          childInst.storeToDOMElement (domChild);
          domElement.appendChild (domChild);
        }
      }
      catch (DOMException e) { }
    }
  }

  //--------------------------------------------------------------------
  public void updateTogetherModel()
  {
    // write value of the element
    RwiElement rwiElement = getRwiElement();
    
	  String rwiPropertyName = getRwiPropertyToUpdate();

    if (rwiElement != null && rwiPropertyName != null)
      rwiElement.setProperty (rwiPropertyName, getValue());

    // process attributes
    Enumeration attrEnum = m_attributes.elements();
    while (attrEnum.hasMoreElements())
    {
      AttributeInst attrInst = (AttributeInst) attrEnum.nextElement();
      attrInst.updateTogetherModel();
    }

    // process child elements
    Enumeration childrenEnum = children();
    while (childrenEnum.hasMoreElements())
    {
      ElementInst childInst = (ElementInst) childrenEnum.nextElement();
      childInst.updateTogetherModel();
    }
  }

  //--------------------------------------------------------------------
  /**
    defines property by full XML name like

    ejb-jar.enterprise-beans.session.description.rwiProperty = $doc
  */
	protected String getRwiPropertyToUpdate()
	{
    if (m_rwiPropertyName == IS_UNDEFINED)
		  m_rwiPropertyName = findFullXMLNameRelatedProperty (".rwiProperty");

	 	return m_rwiPropertyName;
	}

  private String m_rwiPropertyName = IS_UNDEFINED;
  private static String IS_UNDEFINED = "undefined";

  //--------------------------------------------------------------------
  public boolean hasReadonlyValue()
  {
    boolean readOnly = super.hasReadonlyValue();

    if (! readOnly)
    {
  		String s = findFullXMLNameRelatedProperty (".readOnly");
      readOnly = (s != null && (s.equalsIgnoreCase ("true") || s.trim().length() == 0));
    }

    return readOnly;
  }

  //--------------------------------------------------------------------
  /**
   * Searches for dtd-config property specified relatively to Full XML Name of this element.
   * 
   * At the first it seeks key: <ful_xml_name><keySuffix>
   * 
   * If not found, it seeks key: *.<element_name><keySuffix>
   * then, the key is: *.<parent_element_name>.<element_name><keySuffix>
   * then: *.<parent_parent_element_name>.<parent_element_name>.<element_name><keySuffix>
   * ...
   * until the searched key again is: <ful_xml_name><keySuffix>
   */
  final String findFullXMLNameRelatedProperty (String keySuffix)
  {
		ConfigProperties props = m_elementDef.getDocumentType().getConfigProperties();

    String foundValue = props.getProperty (getFullXMLName() + keySuffix);

    if (foundValue == null)
    {
      String key = keySuffix;
      for (ElementInst elementInst = this; elementInst != null; elementInst = elementInst.m_parentElementInst)
      {
        key = '.' + elementInst.m_elementDef.getTagName() + key;
        String value = props.getProperty ('*' + key);
        if (value != null)
          foundValue = value;
      }
    }

    return foundValue;
  }

  //--------------------------------------------------------------------
  /**
   * Tests if the text is empty (i.e. consists of only spaces, tabs, nls, crs)
   */
  private static boolean isEmptyText (String text)
  {
    char[] chrs = text.toCharArray();
    for (int i = chrs.length; -- i >= 0;)
      if (" \n\r\t".indexOf (chrs[i]) < 0)
        return false;

    return true;
  }

  //--------------------------------------------------------------------
  //  Implementation of XmlElementNode interface
  //--------------------------------------------------------------------

  public String getName()
  {
    return m_elementDef.getTagName();
  }

  //--------------------------------------------------------------------
  public String getValue()
  {
    return getStringValue();
  }

  //--------------------------------------------------------------------
  /**
   * Returns the model RWI-element associated with this XML-element
   */
  public RwiElement getRwiElement()
  {
    if (m_rwiElement == null && ! m_providerIsBusy)
    {
      RwiElementProvider rwiElementProvider = getRwiElementProvider();
      if (rwiElementProvider != null)
      {
        m_providerIsBusy = true;
        m_rwiElement = m_rwiElementProvider.getRwiElement (this, m_elementDef.getRwiElementProviderParam());
        m_providerIsBusy = false;
      }
    }

    return m_rwiElement;
  }

  private RwiElementProvider getRwiElementProvider()
  {
    if (m_rwiElementProvider == null && ! m_providerCreationFailed)
    {
      String providerClassName = m_elementDef.getRwiElementProviderClassName();

      if (providerClassName == null || providerClassName.trim().length() == 0)
      {
        m_rwiElementProvider = new DefaultRwiElementProvider();
        return m_rwiElementProvider;
      }

      String errMsg = new String();
      m_providerCreationFailed = false;

      try
      {
        Class providerClass = Class.forName (providerClassName);
        m_rwiElementProvider = (RwiElementProvider) providerClass.newInstance();
      }
      catch (ClassNotFoundException e)
      {
        errMsg = "No such class: '" + providerClassName + '\'';
        m_providerCreationFailed = true;
      }
      catch (ClassCastException e)
      {
        errMsg = "Class '" + providerClassName + "' doesn't implement interface '" +
                 RwiElementProvider.class.getName() + '\'';
        m_providerCreationFailed = true;
      }
      catch (Throwable e)
      {
        errMsg = "Can't create/initialize instance of class '" +
                 providerClassName + "': " + e.getMessage();
        m_providerCreationFailed = true;
      }

      if (m_providerCreationFailed)
      {
        Informator informator = new DefaultInformatorImpl();
        informator.printError (errMsg);
      }
    }

    return m_rwiElementProvider;
  }

  private RwiElement         m_rwiElement = null;
  private RwiElementProvider m_rwiElementProvider = null;
  private boolean            m_providerIsBusy = false;
  private boolean            m_providerCreationFailed = false;

  //--------------------------------------------------------------------
  public XmlElementNode getRootElement()
  {
    return m_xmlTreeManager.getRootElementInst();
  }

  //--------------------------------------------------------------------
  public XmlElementNode getParentElement()
  {
    return m_parentElementInst;
  }

  //--------------------------------------------------------------------
  public Enumeration getChildElements()
  {
    return children();
  }

  //--------------------------------------------------------------------
  public Enumeration getChildElements (String name)
  {
    Vector v = new Vector();

    Enumeration childEnum = children();
    while (childEnum.hasMoreElements())
    {
      XmlElementNode childElement = (XmlElementNode) childEnum.nextElement();
      if (childElement.getName().equals (name))
        v.addElement (childElement);
    }

    return v.elements();
  }

  //--------------------------------------------------------------------
  public XmlElementNode findChildElement (String name)
  {
    Enumeration e = getChildElements (name);
    return (e.hasMoreElements()) ? (XmlElementNode) e.nextElement() : null;
  }

  //--------------------------------------------------------------------
  public Enumeration getSubtreeElements()
  {
    Vector v = new Vector();

    Enumeration childEnum = children();
    while (childEnum.hasMoreElements())
    {
      XmlElementNode childElement = (XmlElementNode) childEnum.nextElement();
      v.addElement (childElement);

      for (Enumeration e = childElement.getSubtreeElements(); e.hasMoreElements();)
        v.addElement (e.nextElement());
    }

    return v.elements();
  }

  //--------------------------------------------------------------------
  public Enumeration getSubtreeElements (String name)
  {
    Vector v = new Vector();

    Enumeration childEnum = children();
    while (childEnum.hasMoreElements())
    {
      XmlElementNode childElement = (XmlElementNode) childEnum.nextElement();

      if (childElement.getName().equals (name))
        v.addElement (childElement);

      for (Enumeration e = childElement.getSubtreeElements (name); e.hasMoreElements();)
        v.addElement (e.nextElement());
    }

    return v.elements();
  }

  //--------------------------------------------------------------------
  public XmlElementNode findSubtreeElement (String name)
  {
    Enumeration e = getSubtreeElements (name);
    return (e.hasMoreElements()) ? (XmlElementNode) e.nextElement() : null;
  }

  //--------------------------------------------------------------------
  // Defined earlier:

  // public boolean     hasAttributes();
  // public Enumeration getAttributes();

  //--------------------------------------------------------------------
  public XmlAttributeNode findAttribute (String name)
  {
    Enumeration attrEnum = getAttributes();
    while (attrEnum.hasMoreElements())
    {
      XmlAttributeNode attrNode = (XmlAttributeNode) attrEnum.nextElement();

      if (attrNode.getName().equals (name))
        return attrNode;
    }

    return null;
  }
}
