/*
 * 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;

import java.io.*;
import java.util.*;
import java.util.logging.*;
import java.beans.*;

import javax.servlet.http.*;
import javax.servlet.*;
import javax.servlet.jsp.tagext.*;

import org.w3c.dom.*;

import com.caucho.log.Log;

import com.caucho.util.*;
import com.caucho.vfs.*;
import com.caucho.xml.*;
import com.caucho.server.http.*;
import com.caucho.java.*;
import com.caucho.xpath.*;

import com.caucho.jsp.java.JspTagFileSupport;

/**
 * Describes a single tag instance.
 */
public class TagInstance {
  static final L10N L = new L10N(TagInstance.class);
  static final Logger log = Log.open(TagInstance.class);

  // Special object to note that the attribute varies
  private static final Object VARIES = new Varies();

  public static final String TOP_TAG = null;
  public static final String FRAGMENT_WITH_TAG_PARENT = "top_tag_fragment";
  public static final String FRAGMENT_WITH_SIMPLE_PARENT = "top_simple_fragment";

  private ParseTagManager _manager;

  private TagInstance _top;
  private TagInfo _tagInfo;
  private int _maxId;
  
  private TagInstance _parent;
  private ArrayList<TagInstance> _children = new ArrayList<TagInstance>();
  private ArrayList<TagInstance> _tags = new ArrayList<TagInstance>();

  private String _tagId = null;
  private QName _qname;
  private Class _cl;
  private VariableInfo []_varInfo;
  private boolean _needsAdapter;
  private boolean _hasAdapterDeclaration;

  private AnalyzedTag _analyzedTag;

  private ArrayList<QName> _attributeNames = new ArrayList<QName>();
  private ArrayList<Object> _attributeValues = new ArrayList<Object>();

  private boolean _hasBodyContent;
  
  public TagInstance(ParseTagManager manager)
  {
    _manager = manager;
    
    _top = this;
    _tagId = null;
  }
  
  public TagInstance(ParseTagManager manager, String id)
  {
    _manager = manager;
    _top = this;
    _tagId = id;
  }
  
  TagInstance(TagInstance parent, TagInfo tagInfo, QName qname, Class cl)
  {
    _qname = qname;
    _parent = parent;
    _manager = parent._manager;
    _tagInfo = tagInfo;
    _cl = cl;
    
    _top = parent._top;
    parent._children.add(this);
    _top._tags.add(this);

    if (tagInfo != null) {
      String className = tagInfo.getTagClassName();
      int p = className.lastIndexOf('.');
      if (p >= 0)
	className = className.substring(p + 1);
      _tagId = "_jsp_" + className + "_" + _top._maxId++;

      _analyzedTag = _manager.analyzeTag(cl);
    }
  }

  /**
   * Returns the tag name
   */
  public QName getQName()
  {
    return _qname;
  }

  /**
   * Returns the tag name
   */
  public String getName()
  {
    return _qname.getName();
  }
  
  public String getId()
  {
    return _tagId;
  }

  public void setId(String id)
  {
    _tagId = id;
  }

  public boolean generateAdapterDeclaration()
  {
    if (_hasAdapterDeclaration) {
      return false;
    }
    else {
      _hasAdapterDeclaration = true;
      
      return true;
    }
  }

  public TagInstance getParent()
  {
    return _parent;
  }

  /**
   * Returns true for a top tag.
   */
  public boolean isTop()
  {
    return _tagId == null || _tagId.startsWith("top_");
  }

  /**
   * Returns the tag class.
   */
  public Class getTagClass()
  {
    return _cl;
  }

  /**
   * Returns true if it's a simple tag.
   */
  public boolean isSimpleTag()
  {
    return _cl != null && SimpleTag.class.isAssignableFrom(_cl);
  }

  /**
   * Returns true if it's a simple tag.
   */
  public boolean isTagFileTag()
  {
    return _cl != null && JspTagFileSupport.class.isAssignableFrom(_cl);
  }
  
  TagInfo getTagInfo()
  {
    return _tagInfo;
  }

  /**
   * Returns the analyzed tag.
   */
  public AnalyzedTag getAnalyzedTag()
  {
    return _analyzedTag;
  }

  void setVarInfo(VariableInfo []varInfo)
  {
    _varInfo = varInfo;
  }

  public VariableInfo []getVarInfo()
  {
    return _varInfo;
  }

  public int size()
  {
    return _top._maxId;
  }

  /**
   * Returns true if there are children.
   */
  public boolean hasChildren()
  {
    return _children != null && _children.size() > 0;
  }

  /**
   * Set true if needs an adapter.
   */
  public boolean getNeedsAdapter()
  {
    return _needsAdapter || isSimpleTag() && hasChildren();
  }

  /**
   * if needs an adapter.
   */
  public void setNeedsAdapter(boolean needsAdapter)
  {
    _needsAdapter = needsAdapter;
  }

  /**
   * Iterates through the children.
   */
  public Iterator<TagInstance> iterator()
  {
    return _children.iterator();
  }

  /**
   * Iterates through the children.
   */
  public TagInstance get(int i)
  {
    return _top._tags.get(i);
  }

  /**
   * Returns the tag's attribute names.
   */
  public ArrayList<QName> getAttributeNames()
  {
    return _attributeNames;
  }

  /**
   * Set true if the tag has a body content.
   */
  public void setBodyContent(boolean hasBodyContent)
  {
    _hasBodyContent = hasBodyContent;
  }

  /**
   * Get true if the tag has a body content.
   */
  public boolean getBodyContent()
  {
    return _hasBodyContent;
  }

  /**
   * Adds a child tag.  If the tag exists, just reuse it.
   *
   * @param tagName the JSP name of the tag
   * @param tagInfo the TagInfo structure for the tag
   * @param cl the tag's implementation class
   * @param names the array of attribute names
   * @param values the array of attribute values
   */
  public TagInstance addTag(QName tagName, TagInfo tagInfo,
                            Class cl,
                            ArrayList<QName> names,
                            ArrayList<Object> values,
			    boolean hasBodyContent)
  {
    TagInstance child = null;//findTag(tagName, names);
    if (child == null)
      child = new TagInstance(this, tagInfo, tagName, cl);

    child.setBodyContent(hasBodyContent);

    for (int i = 0; i < names.size(); i++) {
      QName name = names.get(i);
      Object value = values.get(i);
      
      if (value instanceof String) {
        String strValue = (String) value;

        // runtime and EL expressions can't have shared values
        if (strValue.startsWith("<%=") || strValue.startsWith("%="))
          value = null;
        else if (strValue.indexOf("${") >= 0)
          value = null;

        child.addAttribute(name, value);
      } else {
        child.addAttribute(name, null);
      }
    }

    return child;
  }

  /**
   * Adds a new tag.  Always create a new tag.
   */
  public TagInstance addNewTag(QName tagName, TagInfo tagInfo,
                               Class cl,
			       ArrayList<QName> names,
                               ArrayList<String> values,
			       boolean hasBodyContent)
  {
    TagInstance child = null;
    if (child == null)
      child = new TagInstance(this, tagInfo, tagName, cl);

    child.setBodyContent(hasBodyContent);

    for (int i = 0; i < names.size(); i++) {
      QName name = names.get(i);
      String value = values.get(i);

      if (value.startsWith("<%=") || value.startsWith("%="))
        value = null;

      child.addAttribute(name, value);
    }

    return child;
  }

  /**
   * Sets the attribute.  Null values can't be pre-cached.
   */
  public void addAttribute(QName name, Object value)
  {
    for (int i = 0; i < _attributeNames.size(); i++) {
      QName attrName = _attributeNames.get(i);
      
      if (attrName.equals(name)) {
        Object oldValue = _attributeValues.get(i);
        if (value == null || oldValue == null || ! value.equals(oldValue))
          _attributeValues.set(i, null);

        return;
      }
    }

    if (name == null)
      throw new NullPointerException();
    
    _attributeNames.add(name);
    _attributeValues.add(value);
  }

  public String getAttribute(QName name)
  {
    for (int i = 0; i < _attributeNames.size(); i++) {
      if (name.equals(_attributeNames.get(i)))
        return (String) _attributeValues.get(i);
    }

    return null;
  }

  boolean canBeRequestTime(String name)
  {
    TagAttributeInfo attrs[] = _tagInfo.getAttributes();

    if (attrs == null)
      return true;

    for (int i = 0; i < attrs.length; i++) {
      if (name.equals(attrs[i].getName()))
        return attrs[i].canBeRequestTime();
    }

    return false;
  }

  /**
   * Finds the matching tag.
   */
  public TagInstance findTag(QName tagName,
			     ArrayList<QName> names,
			     boolean hasBodyContent)
  {
    for (int i = 0; i < _children.size(); i++) {
      TagInstance child = _children.get(i);

      if (child.match(tagName, names, hasBodyContent))
        return child;
    }

    return null;
  }
  
  /**
   * Returns true for matching instances.
   */
  boolean match(QName tagName, ArrayList<QName> names, boolean hasBodyContent)
  {
    if (! _qname.equals(tagName))
      return false;

    if (_attributeNames.size() != names.size())
      return false;

    if (_hasBodyContent != hasBodyContent)
      return false;
    
    for (int i = 0; i < _attributeNames.size(); i++) {
      QName attrName = _attributeNames.get(i);

      if (names.indexOf(attrName) < 0)
        return false;
    }

    for (int i = 0; i < names.size(); i++) {
      QName name = names.get(i);

      if (_attributeNames.indexOf(name) < 0)
        return false;
    }

    return true;
  }

  static class Varies {
    public String toString()
    {
      return "Value-Varies[]";
    }
  }
}
