/*
 * Copyright (c) 1998-2012 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 Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.jsp;

import com.caucho.java.LineMap;
import com.caucho.jsp.cfg.JspPropertyGroup;
import com.caucho.server.webapp.WebApp;
import com.caucho.util.CharScanner;
import com.caucho.util.L10N;
import com.caucho.vfs.PersistentDependency;
import com.caucho.vfs.TempCharBuffer;
import com.caucho.vfs.Path;
import com.caucho.xml.QName;
import com.caucho.xml.Xml;

import java.util.ArrayList;
import java.util.logging.Logger;

/**
 * Represents the current state of the parser.
 */
public class ParseState {
  private static final L10N L = new L10N(ParseState.class);
  private static final Logger log
    = Logger.getLogger(ParseState.class.getName());

  private WebApp _application;

  private JspPropertyGroup _jspPropertyGroup = new JspPropertyGroup();

  private Boolean _isELIgnored;
  private Boolean _isELIgnoredDefault;
  private boolean _isELIgnoredPageSpecified = false;
  
  private boolean _isScriptingInvalid = false;
  private boolean _isLocalScriptingInvalid = false;
  
  private boolean _isVelocityEnabled;

  private boolean _isSession = true;
  private boolean _isOptionalSession = false;
  private boolean _isSessionSet = false;
  
  private boolean _isErrorPage = false;
  private boolean _isErrorPageSet = false;
  
  private boolean _isAutoFlush = true;
  private boolean _isAutoFlushSet = false;

  private boolean _isThreadSafe = true;
  private boolean _isThreadSafeSet = false;

  private boolean _isTag = false;
  private boolean _isXml = false;
  private boolean _isForbidXml = false;

  private int _buffer = TempCharBuffer.SIZE;
  private boolean _isBufferSet = false;

  private String _info;
  private String _errorPage;
  private String _contentType;
  private String _charEncoding;

  private int _bom = -1;

  private String _pageEncoding;
  private String _xmlPageEncoding;
  private Class _extends;

  private boolean _recycleTags = true;
  private boolean _isTrimWhitespace;
  private boolean _isDeferredSyntaxAllowedAsLiteral;

  private JspResourceManager _resourceManager;

  private JspBuilder _jspBuilder;

  private ArrayList<String> _importList = new ArrayList<String>();

  private String _uriPwd;
  
  private ArrayList<PersistentDependency> _depends
    = new ArrayList<PersistentDependency>();
  private LineMap _lineMap;
  
  private boolean _isPrototype;
  
  private Xml _xml;
  private Namespace _namespaces;

  private String _jspVersion = "2.1";

  /**
   * Create a new parse state instance.
   */
  public ParseState()
  {
  }

  /**
   * Sets the JSP property group.
   */
  public void setJspPropertyGroup(JspPropertyGroup group)
  {
    _jspPropertyGroup = group;
  }

  /**
   * Gets the JSP property group.
   */
  public JspPropertyGroup getJspPropertyGroup()
  {
    return _jspPropertyGroup;
  }

  /**
   * Returns true if JSP EL is ignored.
   */
  public boolean isELIgnored()
  {
    if (_isELIgnored != null)
      return _isELIgnored;
    else if (_isELIgnoredDefault != null)
      return _isELIgnoredDefault;
    else
      return false;
  }

  /**
   * Set if JSP EL is ignored.
   */
  public void setELIgnored(boolean isELIgnored)
  {
    _isELIgnored = isELIgnored;
  }

  /**
   * Set if JSP EL is ignored.
   */
  public void setELIgnoredDefault(boolean isELIgnored)
  {
    _isELIgnoredDefault = isELIgnored;
  }

  /**
   * Set if JSP EL is ignored.
   */
  public Boolean getELIgnoredDefault()
  {
    return _isELIgnoredDefault;
  }

  public boolean isELIgnoredPageSpecified()
  {
    return _isELIgnoredPageSpecified;
  }

  public void setELIgnoredPageSpecified(boolean ELIgnoredPageSpecified)
  {
    _isELIgnoredPageSpecified = ELIgnoredPageSpecified;
  }

  /**
   * Returns true if JSP scripting is invalid.
   */
  public boolean isScriptingInvalid()
  {
    return _isScriptingInvalid;
  }

  /**
   * Set if JSP scripting is ignored.
   */
  public void setScriptingInvalid(boolean isScriptingInvalid)
  {
    _isScriptingInvalid = isScriptingInvalid;
  }

  public boolean isLocalScriptingInvalid()
  {
    return _isLocalScriptingInvalid;
  }

  public void setLocalScriptingInvalid(boolean isLocalScriptingInvalid)
  {
    _isLocalScriptingInvalid = isLocalScriptingInvalid;
  }

  /**
   * Set if velocity statements are enabled.
   */
  public void setVelocityEnabled(boolean isVelocity)
  {
    _isVelocityEnabled = isVelocity;
  }

  /**
   * Returns true if Velocity statements are enabled.
   */
  public boolean isVelocityEnabled()
  {
    return _isVelocityEnabled;
  }

  /**
   * Returns true if the session is enabled.
   */
  public boolean isSession()
  {
    return _isSession;
  }

  /**
   * Returns true if the optional session is enabled.
   */
  public boolean isOptionalSession()
  {
    return _isOptionalSession;
  }
  
  /**
   * Set if the session is enabled.
   */
  public boolean setSession(boolean session)
  {
    boolean isSession = _isSession;
    
    _isSession = session;
    _isOptionalSession = session;

    return (session == isSession || ! _isSessionSet);
  }

  /**
   * Mark the thread safe attribute as set.
   */
  public void markSessionSet()
  {
    _isSessionSet = true;
  }

  /**
   * Returns true if the autoFlush is enabled.
   */
  public boolean isAutoFlush()
  {
    return _isAutoFlush;
  }
  
  /**
   * Set if the autoFlush is enabled.
   */
  public boolean setAutoFlush(boolean autoFlush)
  {
    boolean isAutoFlush = _isAutoFlush;
    
    _isAutoFlush = autoFlush;

    return (autoFlush == isAutoFlush || ! _isAutoFlushSet);
  }

  /**
   * Mark the thread safe attribute as set.
   */
  public void markAutoFlushSet()
  {
    _isAutoFlushSet = true;
  }

  /**
   * Returns true if the threadSafe is enabled.
   */
  public boolean isThreadSafe()
  {
    return _isThreadSafe;
  }

  /**
   * Set if the threadSafe is enabled.
   */
  public boolean setThreadSafe(boolean threadSafe)
  {
    boolean isThreadSafe = _isThreadSafe;
    
    _isThreadSafe = threadSafe;

    return (threadSafe == isThreadSafe || ! _isThreadSafeSet);
  }

  /**
   * Mark the thread safe attribute as set.
   */
  public void markThreadSafeSet()
  {
    _isThreadSafeSet = true;
  }

  /**
   * Set if the errorPage is enabled.
   */
  public boolean setErrorPage(boolean errorPage)
  {
    boolean isErrorPage = _isErrorPage;
    
    _isErrorPage = errorPage;

    return (errorPage == isErrorPage || ! _isErrorPageSet);
  }

  /**
   * Returns true if the errorPage is enabled.
   */
  public boolean isErrorPage()
  {
    return _isErrorPage;
  }

  /**
   * Mark the error page attribute as set.
   */
  public void markErrorPage()
  {
    _isErrorPageSet = true;
  }

  /**
   * Returns the buffer size in bytes.
   */
  public int getBuffer()
  {
    return _buffer;
  }
  
  /**
   * Set the buffer size.
   */
  public boolean setBuffer(int buffer)
  {
    int oldBuffer = _buffer;
    
    _buffer = buffer;

    return (buffer == oldBuffer || ! _isBufferSet);
  }

  /**
   * Mark the buffer attribute as set.
   */
  public void markBufferSet()
  {
    _isBufferSet = true;
  }

  /**
   * Sets the JSP's error page
   */
  public void setErrorPage(String errorPage)
  {
    _errorPage = errorPage;
  }

  /**
   * Gets the JSP's error page
   */
  public String getErrorPage()
  {
    return _errorPage;
  }

  /**
   * Sets the JSP's content type
   */
  public void setContentType(String contentType)
  {
    _contentType = contentType;
  }

  /**
   * Gets the JSP's content type
   */
  public String getContentType()
  {
    return _contentType;
  }

  /**
   * Sets the XML parser
   */
  public void setXml(Xml xml)
  {
    _xml = xml;
  }

  public Xml getXml()
  {
    return _xml;
  }

  /**
   * Sets the JSP's character encoding
   */
  public void setCharEncoding(String charEncoding)
    throws JspParseException
  {
    if ("UTF-16".equalsIgnoreCase(charEncoding))
      charEncoding = "UTF-16LE";
    
    /*
    if (_charEncoding != null &&
        ! _charEncoding.equalsIgnoreCase(charEncoding))
      throw new JspParseException(L.l("Cannot change character encoding to '{0}' (old value '{1}').  The character encoding may only be set once.",
                                      charEncoding, _charEncoding));
    */
    
    _charEncoding = charEncoding;
  }

  /**
   * Gets the JSP's character encoding
   */
  public String getCharEncoding()
  {
    return _charEncoding;
  }

  public void setXmlPageEncoding(String pageEncoding)
    throws JspParseException
  {
    _xmlPageEncoding = pageEncoding;
    setPageEncoding(pageEncoding);
  }

  /**
   * Sets the JSP's page encoding
   */
  public void setPageEncoding(String pageEncoding)
    throws JspParseException
  {
    if (pageEncoding == null)
      return;

    if (_xml != null && _pageEncoding == null)
      _pageEncoding = _xml.getEncoding();

    if ("UTF-16".equalsIgnoreCase(pageEncoding))
      pageEncoding = "UTF-16LE";

    if (_pageEncoding == null
        || _pageEncoding.equalsIgnoreCase(pageEncoding)) {
      _pageEncoding = pageEncoding;
    }
    else if (("UTF-16".equalsIgnoreCase(_pageEncoding)
              || "UTF-16LE".equalsIgnoreCase(_pageEncoding))
             && ("UTF-16LE".equalsIgnoreCase(pageEncoding)
                 || "UTF-16BE".equalsIgnoreCase(pageEncoding))) {
      _pageEncoding = pageEncoding;
    }
    else if ("UTF-16LE".equalsIgnoreCase(pageEncoding)
             && ("UTF-16LE".equalsIgnoreCase(_pageEncoding)
                 || "UTF-16BE".equalsIgnoreCase(_pageEncoding))) {
    }
    else {
      String oldPageEncoding = _pageEncoding;
      
      //_pageEncoding = pageEncoding;

      if (_bom == -1)
        throw new JspParseException(L.l(
          "Cannot change page encoding to '{0}' (old value '{1}').  The page encoding may only be set once.",
          pageEncoding,
          oldPageEncoding));
      else
        throw new JspParseException(L.l(
          "Cannot change page encoding to '{0}' (old value is specified by BOM '{1}' -> '{2}').  The page encoding may only be set once.",
          pageEncoding,
          Integer.toHexString(_bom),
          oldPageEncoding));
    }
  }

  public void setBom(int bom) {
    _bom = bom;
  }

  /**
   * Gets the JSP's character encoding
   */
  public String getPageEncoding()
  {
    if (_pageEncoding != null)
      return _pageEncoding;
    else if (_xmlPageEncoding != null)
      return "utf-8";
    else
      return null;
  }

  /**
   * Gets the JSP's character encoding
   */
  public String getXmlPageEncoding()
  {
    return _xmlPageEncoding;
  }

  /**
   * Returns the JSP's info string.
   */
  public String getInfo()
  {
    return _info;
  }

  /**
   * Sets the JSP's info string
   */
  public void setInfo(String info)
  {
    _info = info;
  }

  /**
   * Returns the JSP's extends
   */
  public Class getExtends()
  {
    return _extends;
  }

  /**
   * Sets the JSP's extends
   */
  public void setExtends(Class extendsValue)
  {
    _extends = extendsValue;
  }

  /**
   * Returns true if parsing is a tag
   */
  public boolean isTag()
  {
    return _isTag;
  }

  /**
   * Set if parsing a tag
   */
  public void setTag(boolean isTag)
  {
    _isTag = isTag;
  }

  /**
   * Returns true if parsing is XML
   */
  public boolean isXml()
  {
    return _isXml;
  }

  /**
   * Set if parsing is xml
   */
  public void setXml(boolean isXml)
  {
    _isXml = isXml;
  }

  /**
   * Returns true if parsing forbids XML
   */
  public boolean isForbidXml()
  {
    return _isForbidXml;
  }

  /**
   * Set if parsing forbids xml
   */
  public void setForbidXml(boolean isForbidXml)
  {
    _isForbidXml = isForbidXml;
  }

  /**
   * Returns true if the print-null-as-blank is enabled.
   */
  public boolean isPrintNullAsBlank()
  {
    return _jspPropertyGroup.isPrintNullAsBlank();
  }

  /**
   * Returns true if JSP whitespace is trimmed.
   */
  public boolean isTrimWhitespace()
  {
    return _isTrimWhitespace;
  }

  /**
   * Set true if JSP whitespace is trimmed.
   */
  public void setTrimWhitespace(boolean trim)
  {
    _isTrimWhitespace = trim;
  }

  /**
   * Returns true if JSP whitespace is trimmed.
   */
  public boolean isDeferredSyntaxAllowedAsLiteral()
  {
    return _isDeferredSyntaxAllowedAsLiteral;
  }

  /**
   * Set true if JSP whitespace is trimmed.
   */
  public void setDeferredSyntaxAllowedAsLiteral(boolean trim)
  {
    _isDeferredSyntaxAllowedAsLiteral = trim;
  }

  /**
   * Set the version
   */
  public void setJspVersion(String version)
  {
    _jspVersion = version;
  }

  /**
   * Set the version
   */
  public String getJspVersion()
  {
    return _jspVersion;
  }
  
  /**
   * Gets the resource manager.
   */
  public JspResourceManager getResourceManager()
  {
    return _resourceManager;
  }
  
  /**
   * Sets the resource manager.
   */
  public void setResourceManager(JspResourceManager resourceManager)
  {
    _resourceManager = resourceManager;
  }
  
  /**
   * Gets the builder
   */
  public JspBuilder getBuilder()
  {
    return _jspBuilder;
  }
  
  /**
   * Sets the builder
   */
  public void setBuilder(JspBuilder jspBuilder)
  {
    _jspBuilder = jspBuilder;
  }

  private static CharScanner COMMA_DELIM_SCANNER = new CharScanner(" \t\n\r,");
  
  /**
   * Adds an import string.
   */
  public void addImport(String importString)
    throws JspParseException
  {
    String []imports = importString.split("[ \t\n\r,]+");

    for (int i = 0; i < imports.length; i++) {
      String value = imports[i];

      if (value.equals(""))
        continue;
      
      if (value.equals("static") && i + 1 < imports.length) {
        value = "static " + imports[i + 1];
        i++;
      }

      if (! _importList.contains(value))
        _importList.add(value);
    }
  }

  /**
   * Returns the import list.
   */
  public ArrayList<String> getImportList()
  {
    return _importList;
  }

  /**
   * Sets the URI pwd
   */
  public void setUriPwd(String uriPwd)
  {
    _uriPwd = uriPwd;
  }

  /**
   * Gets the URI pwd
   */
  public String getUriPwd()
  {
    return _uriPwd;
  }

  /**
   * Returns the line map.
   */
  public LineMap getLineMap()
  {
    return _lineMap;
  }

  /**
   * Add a dependency.
   */
  public void addDepend(Path path)
  {
    PersistentDependency depend = path.createDepend();
    if (! _depends.contains(depend))
      _depends.add(depend);
  }

  /**
   * Returns the dependencies
   */
  public ArrayList<PersistentDependency> getDependList()
  {
    return _depends;
  }

  /**
   * Resolves a path.
   *
   * @param uri the uri for the path
   *
   * @return the Path
   */
  public Path resolvePath(String uri)
  {
    return getResourceManager().resolvePath(uri);
  }

  /**
   * Set if recycle-tags is enabled.
   */
  public void setRecycleTags(boolean recycleTags)
  {
    _recycleTags = recycleTags;
  }

  /**
   * Returns true if recycle-tags is enabled.
   */
  public boolean isRecycleTags()
  {
    return _recycleTags;
  }

  /**
   * Returns the QName for the given name.
   */
  public QName getQName(String name)
  {
    int p = name.indexOf(':');

    if (p < 0)
      return new QName(name);
    else {
      String prefix = name.substring(0, p);
      String uri = Namespace.find(_namespaces, prefix);

      if (uri != null)
        return new QName(name, uri);
      else
        return new QName(name);
    }
  }

  public Namespace getNamespaces()
  {
    return _namespaces;
  }

  /**
   * Pushes a namespace.
   */
  public void pushNamespace(String prefix, String uri)
  {
    _namespaces = new Namespace(_namespaces, prefix, uri);
  }

  /**
   * Pops a namespace.
   */
  public void popNamespace(String prefix)
  {
    if (_namespaces._prefix.equals(prefix))
      _namespaces = _namespaces.getNext();
    else
      throw new IllegalStateException();
  }

  public String findPrefix(String uri)
  {
    return Namespace.findPrefix(_namespaces, uri);
  }

  public boolean isPrototype()
  {
    return _isPrototype;
  }

  public void setPrototype(boolean isPrototype)
  {
    _isPrototype = isPrototype;
  }
}

