/*
 * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source 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.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *   Free SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.jsp.java;

import java.io.IOException;

import java.util.ArrayList;

import com.caucho.util.L10N;
import com.caucho.util.LineCompileException;
import com.caucho.util.CharBuffer;

import com.caucho.vfs.WriteStream;

import com.caucho.xml.QName;
import com.caucho.xml.XmlChar;

import com.caucho.jsp.JspParseException;

/**
 * A node which can contain other nodes.
 */
public abstract class JspContainerNode extends JspNode {
  static final L10N L = new L10N(JspContainerNode.class);
  
  protected ArrayList<QName> _attributeNames = new ArrayList<QName>();
  protected ArrayList<Object> _attributeValues = new ArrayList<Object>();
  
  protected ArrayList<JspNode> _children = new ArrayList<JspNode>();

  protected boolean _hasJspAttribute;
  
  /**
   * Adds an attribute.
   *
   * @param name the name of the attribute.
   * @param value the value of the attribute.
   */
  public void addAttribute(QName name, String value)
    throws JspParseException
  {
    if (name == null)
      throw new NullPointerException();

    if (_attributeNames.indexOf(name) >= 0)
      throw error(L.l("'{0}' is a duplicate attribute name.  Attributes must occur only once in a tag.", name.getName()));
    
    _attributeNames.add(name);
    _attributeValues.add(value);
  }

  /**
   * Adds a JspAttribute attribute.
   *
   * @param name the name of the attribute.
   * @param value the value of the attribute.
   */
  public void addAttribute(QName name, JspAttribute value)
    throws JspParseException
  {
    if (name == null)
      throw new NullPointerException();
    
    if (_attributeNames.indexOf(name) >= 0)
      throw error(L.l("'{0}' is a duplicate attribute name.  Attributes must occur only once in a tag.", name.getName()));
    
    _attributeNames.add(name);
    _attributeValues.add(value);

    _gen.addFragment(value);
  }

  /**
   * Returns the named attribute.
   */
  public Object getAttribute(String name)
    throws JspParseException
  {
    for (int i = 0; i < _attributeNames.size(); i++) {
      if (name.equals(_attributeNames.get(i).getName()))
        return _attributeValues.get(i);
    }

    return null;
  }
  
  /**
   * Adds a child node.
   */
  public void addChild(JspNode node)
    throws JspParseException
  {
    node.setParent(this);
    
    if (node instanceof JspAttribute) {
      JspAttribute attr = (JspAttribute) node;

      QName name = attr.getName();

      addAttribute(name, attr);

      _hasJspAttribute = true;

      int i = 0;
      while (_children != null && i < _children.size()) {
        JspNode child = _children.get(i);

        if (child instanceof StaticText) {
          String text = ((StaticText) child).getText();

          if (isWhitespace(text))
            _children.remove(i);
          else
            throw child.error(L.l("tags using jsp:attribute must put body content in a jsp:body tag"));
        }
        else if (child instanceof JspBody)
          i++;
        else if (child instanceof JspAttribute)
          i++;
        else {
          throw child.error(L.l("tags using jsp:attribute must put body content in a jsp:body tag"));
	}
      }
    }
    else if (_hasJspAttribute && ! (node instanceof JspBody)) {
      throw node.error(L.l("tags using jsp:attribute must put body content in a jsp:body tag"));
    }
    else if (node instanceof StaticText) {
      StaticText text = (StaticText) node;
      String data = text.getText();

      boolean isXml = _gen.isXml();
      for (JspNode ptr = this; ptr != null; ptr = ptr.getParent()) {
        if (ptr instanceof JspRoot)
          isXml = true;
      }

      if (_children == null)
        _children = new ArrayList<JspNode>();
      
      for (int i = 0;
           i < data.length();
           i++) {
        if (! XmlChar.isWhitespace(data.charAt(i))) {
          _children.add(node);
          return;
        }
      }

      if (! isXml)
        _children.add(node);
    }
    else {
      if (_children == null)
        _children = new ArrayList<JspNode>();

      _children.add(node);
    }
  }
  
  /**
   * Adds a child node after its done initializing.
   */
  public void addChildEnd(JspNode node)
    throws JspParseException
  {
  }

  /**
   * Returns true if the node is empty
   */
  public boolean isEmpty()
  {
    if (_children == null || _children.size() == 0)
      return true;

    for (int i = 0; i < _children.size(); i++) {
      JspNode child = _children.get(i);

      if (child instanceof JspBody) {
	JspBody body = (JspBody) child;

	return body.isEmpty();
      }
      else if (child instanceof StaticText) {
	StaticText text = (StaticText) child;

	if (! text.isWhitespace())
	  return false;
      }
      else
	return false;
    }

    return false;
  }

  /**
   * Returns the static text.
   */
  public void getStaticText(CharBuffer cb)
  {
    if (_children == null)
      return;
    
    for (int i = 0; i < _children.size(); i++)
      _children.get(i).getStaticText(cb);
  }
  
  /**
   * Set true if the node contains a child tag.
   */
  public boolean hasCustomTag()
  {
    for (int i = 0; _children != null && i < _children.size(); i++) {
      JspNode child = _children.get(i);

      if (child instanceof CustomTag)
        return true;

      if (child.hasCustomTag())
        return true;
    }
    
    for (int i = 0;
         _attributeValues != null && i < _attributeValues.size();
         i++) {
      Object value = _attributeValues.get(i);

      if (value instanceof CustomTag)
        return true;
      else if (value instanceof JspNode &&
               ((JspNode) value).hasCustomTag())
        return true;
    }

    return false;
  }
  
  /**
   * Set true if the node contains a child tag.
   */
  public boolean hasTag()
  {
    for (int i = 0; _children != null && i < _children.size(); i++) {
      JspNode child = _children.get(i);

      if (child instanceof CustomTag ||
	  child instanceof CustomSimpleTag)
        return true;

      if (child.hasTag())
        return true;
    }
    
    for (int i = 0;
         _attributeValues != null && i < _attributeValues.size();
         i++) {
      Object value = _attributeValues.get(i);

      if (value instanceof CustomTag || value instanceof CustomSimpleTag)
        return true;

      else if (value instanceof JspNode &&
               ((JspNode) value).hasTag())
        return true;
    }

    return false;
  }
  
  /**
   * Adds a text node.
   */
  public JspNode addText(String text)
    throws JspParseException
  {
    if (! _hasJspAttribute) {
      JspNode node = new StaticText(_gen, text, this);
      
      addChild(node);

      return node;
    }
    else if (! isWhitespace(text)) {
      throw error(L.l("tags using jsp:attribute must put body content in a jsp:body tag"));
    }

    return null;
  }

  protected boolean isWhitespace(String text)
  {
    for (int i = 0; i < text.length(); i++) {
      if (! Character.isWhitespace(text.charAt(i)))
        return false;
    }

    return true;
  }

  /**
   * Returns the children.
   */
  public ArrayList<JspNode> getChildren()
  {
    return _children;
  }

  /**
   * Has children.
   */
  public boolean hasChildren()
  {
    return ! isEmpty();
  }
  
  /**
   * True if the node has scripting
   */
  public boolean hasScripting()
  {
    for (int i = 0; _children != null && i < _children.size(); i++) {
      if (_children.get(i).hasScripting())
	return true;
    }
    
    return false;
  }

  /**
   * Returns true if the children are static.
   */
  public boolean isChildrenStatic()
  {
    if (_children == null)
      return true;
    
    for (int i = 0; i < _children.size(); i++) {
      if (! _children.get(i).isStatic())
	return false;
    }

    return true;
  }

  /**
   * Generates the XML text representation for the tag validation.
   *
   * @param os write stream to the generated XML.
   */
  public void printXmlChildren(WriteStream os)
    throws IOException
  {
    if (_children == null)
      return;

    for (int i = 0; i < _children.size(); i++) {
      JspNode child = _children.get(i);

      child.printXml(os);
    }
  }

  /**
   * generates data for prologue children.
   */
  public void generatePrologueChildren(JspJavaWriter out)
    throws Exception
  {
    if (_children == null)
      return;

    for (int i = 0; i < _children.size(); i++) {
      JspNode child = _children.get(i);

      child.generatePrologue(out);
    }
  }

  /**
   * generates data for declaration children.
   */
  public void generateDeclarationChildren(JspJavaWriter out)
    throws IOException
  {
    if (_children == null)
      return;

    for (int i = 0; i < _children.size(); i++) {
      JspNode child = _children.get(i);

      child.generateDeclaration(out);
    }
  }

  /**
   * Generates the code for the children.
   *
   * @param out the output writer for the generated java.
   */
  public void generateChildren(JspJavaWriter out)
    throws Exception
  {
    if (_children == null)
      return;

    for (int i = 0; i < _children.size(); i++) {
      JspNode child = _children.get(i);

      child.generateStartLocation(out);
      try {
        child.generate(out);
      } catch (Exception e) {
        if (e instanceof LineCompileException)
          throw e;
        else
          throw child.error(e);
      }
      child.generateEndLocation(out);
    }
  }

  /**
   * Generates the code for the children.
   *
   * @param out the output writer for the generated java.
   */
  public void generateChildrenEmpty()
    throws Exception
  {
    if (_children == null)
      return;

    for (int i = 0; i < _children.size(); i++) {
      JspNode child = _children.get(i);

      child.generateEmpty();
    }
  }

  /**
   * Generates static text.
   *
   * @param out the output writer for the generated java.
   */
  public void generateStatic(JspJavaWriter out)
    throws Exception
  {
    if (_children == null)
      return;

    for (int i = 0; i < _children.size(); i++) {
      JspNode child = _children.get(i);

      out.setLocation(child.getFilename(), child.getStartLine());
      child.generateStatic(out);
      // out.setLocation(child.getFilename(), child.getEndLine());
    }
  }
}

