/*
 * 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 SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.jsp.java;

import com.caucho.jsp.JspParseException;
import com.caucho.server.util.CauchoSystem;
import com.caucho.util.L10N;
import com.caucho.vfs.WriteStream;
import com.caucho.xml.QName;

import javax.servlet.jsp.HttpJspPage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Locale;
import java.util.logging.*;

public class JspDirectivePage extends JspNode {
  private static final Logger log
    = Logger.getLogger(JspDirectivePage.class.getName());
  static L10N L = new L10N(JspDirectivePage.class);
  
  private static final QName IS_EL_IGNORED = new QName("isELIgnored");
  private static final QName IS_VELOCITY_ENABLED =
    new QName("isVelocityEnabled");
  private static final QName INFO = new QName("info");
  private static final QName CONTENT_TYPE = new QName("contentType");
  private static final QName PAGE_ENCODING = new QName("pageEncoding");
  private static final QName LANGUAGE = new QName("language");
  private static final QName IMPORT = new QName("import");
  private static final QName SESSION = new QName("session");
  private static final QName BUFFER = new QName("buffer");
  private static final QName ERROR_PAGE = new QName("errorPage");
  private static final QName IS_ERROR_PAGE = new QName("isErrorPage");
  private static final QName AUTO_FLUSH = new QName("autoFlush");
  private static final QName IS_THREAD_SAFE = new QName("isThreadSafe");
  private static final QName EXTENDS = new QName("extends");
  private static final QName TRIM_WS = new QName("trimDirectiveWhitespaces");
  private static final QName DEFER = new QName("deferredSyntaxAllowedAsLiteral");

  
  /**
   * Adds an attribute.
   *
   * @param name the attribute name
   * @param value the attribute value
   */
  public void addAttribute(QName name, String value)
    throws JspParseException
  {
    if (IS_EL_IGNORED.equals(name)) {
      boolean isIgnored = value.equals("true");

      if (_parseState.isELIgnoredPageSpecified() &&
          isIgnored != _parseState.isELIgnored())
        throw error(L.l("isELIgnored values conflict"));

      _parseState.setELIgnored(isIgnored);
      _parseState.setELIgnoredPageSpecified(true);
    }
    
    /*
    else if (name.equals("isScriptingInvalid"))
      _parseState.setScriptingInvalid(value.equals("true"));
    */
    else if (IS_VELOCITY_ENABLED.equals(name))
      _parseState.setVelocityEnabled(value.equals("true"));
    else if (INFO.equals(name)) {
      String oldInfo = _parseState.getInfo();
      
      if (oldInfo != null && ! value.equals(oldInfo))
        throw error(L.l("info '{0}' conflicts with previous value of info '{1}'.  Check the .jsp and any included .jsp files for conflicts.", value, oldInfo));
      
      _parseState.setInfo(value);
    }
    else if (CONTENT_TYPE.equals(name)) {
      String oldContentType = _parseState.getContentType();
      
      if (oldContentType != null && ! value.equals(oldContentType))
        throw error(L.l("contentType '{0}' conflicts with previous value of contentType '{1}'.  Check the .jsp and any included .jsp files for conflicts.", value, oldContentType));
      
      _parseState.setContentType(value);
      String charEncoding = parseCharEncoding(value);
      if (charEncoding != null)
        _parseState.setCharEncoding(charEncoding);
    }
    else if (PAGE_ENCODING.equals(name)) {
      String oldEncoding = _parseState.getPageEncoding();

      /*
      // jsp/01f1
      if (oldEncoding != null) {
        String oldCanonical = Encoding.getMimeName(oldEncoding);
        String newCanonical = Encoding.getMimeName(value);

        if (! newCanonical.equals(oldCanonical))
          throw error(L.l("pageEncoding '{0}' conflicts with previous value of pageEncoding '{1}'.  Check the .jsp and any included .jsp files for conflicts.", value, oldEncoding));
      }
      */

      try {
        _parseState.setPageEncoding(value);
        // _parseState.setCharEncoding(value);
      } catch (JspParseException e) {
        log.log(Level.FINER, e.toString(), e);

        throw error(e.getMessage());
      }
    }
    else if (LANGUAGE.equals(name)) {
      if (! value.equals("java"))
        throw error(L.l("'{0}' is not supported as a JSP scripting language.",
                        value));
    }
    else if (IMPORT.equals(name)) {
      _parseState.addImport(value);
    }
    else if (SESSION.equals(name)) {
      boolean isValid = false;
      
      if (value.equals("true"))
        isValid = _parseState.setSession(true);
      else if (value.equals("false"))
        isValid = _parseState.setSession(false);
      else
        throw error(L.l("session expects 'true' or 'false' at '{0}'",
                        value));

      _parseState.markSessionSet();

      if (! isValid)
        throw error(L.l("session is assigned different values."));
    }
    else if (BUFFER.equals(name)) {
      boolean isValid = _parseState.setBuffer(processBufferSize(value));

      _parseState.markBufferSet();
      
      if (! isValid)
        throw error(L.l("buffer is assigned different values."));

      if (_parseState.getBuffer() == 0 && ! _parseState.isAutoFlush())
        throw error(L.l("buffer must be non-zero when autoFlush is false."));
    }
    else if (ERROR_PAGE.equals(name)) {
      String errorPage = _parseState.getErrorPage();
      
      String newErrorPage = getRelativeUrl(value);

      _parseState.setErrorPage(newErrorPage);

      if (errorPage != null && ! errorPage.equals(newErrorPage)) {
        _parseState.setErrorPage(null);
        throw error(L.l("errorPage is assigned different value '{0}'.",
                        newErrorPage));
      }
    }
    else if (IS_ERROR_PAGE.equals(name)) {
      boolean isValid = false;
      
      if (value.equals("true"))
        isValid = _parseState.setErrorPage(true);
      else if (value.equals("false"))
        isValid = _parseState.setErrorPage(false);
      else
        throw error(L.l("isErrorPage expects 'true' or 'false' at '{0}'",
                        value));

      _parseState.markErrorPage();

      if (! isValid)
        throw error(L.l("isErrorPage is assigned different values."));
    }
    else if (AUTO_FLUSH.equals(name)) {
      boolean isValid = false;
      
      if (value.equals("true"))
        isValid = _parseState.setAutoFlush(true);
      else if (value.equals("false"))
        isValid = _parseState.setAutoFlush(false);
      else
        throw error(L.l("autoFlush expects 'true' or 'false' at '{0}'",
                        value));

      if (! isValid)
        throw error(L.l("autoFlush is assigned different values."));
      
      if (_parseState.getBuffer() == 0 && ! _parseState.isAutoFlush())
        throw error(L.l("buffer must be non-zero when autoFlush is false."));

      _parseState.markAutoFlushSet();
    }
    else if (IS_THREAD_SAFE.equals(name)) {
      boolean isValid = false;
      
      if (value.equals("true"))
        isValid = _parseState.setThreadSafe(true);
      else if (value.equals("false"))
        isValid = _parseState.setThreadSafe(false);
      else
        throw error(L.l("isThreadSafe expects 'true' or 'false' at '{0}'",
                        value));

      _parseState.markThreadSafeSet();

      if (! isValid)
        throw error(L.l("isThreadSafe is assigned different values."));
    }
    else if (EXTENDS.equals(name)) {
      Class cl = null;

      try {
        cl = CauchoSystem.loadClass(value);
      } catch (Exception e) {
        throw error(e);
      }
        
      if (! HttpJspPage.class.isAssignableFrom(cl))
        throw error(L.l("'{0}' must implement HttpJspPage.  The class named by jsp:directive.page extends='...' must implement HttpJspPage.", value));
      
      Class oldExtends = _parseState.getExtends();
      
      if (oldExtends != null && ! cl.equals(oldExtends))
        throw error(L.l("extends '{0}' conflicts with previous value of extends '{1}'.  Check the .jsp and any included .jsp files for conflicts.", value, oldExtends.getName()));
      
      _parseState.setExtends(cl);
    }
    else if (TRIM_WS.equals(name)) {
      if (value.equals("true"))
        _parseState.setTrimWhitespace(true);
      else if (value.equals("false"))
        _parseState.setTrimWhitespace(false);
      else
        throw error(L.l("trimDirectiveWhitespaces expects 'true' or 'false' at '{0}'",
                        value));
    }
    else if (DEFER.equals(name)) {
      if (value.equals("true"))
        _parseState.setDeferredSyntaxAllowedAsLiteral(true);
      else if (value.equals("false"))
        _parseState.setDeferredSyntaxAllowedAsLiteral(false);
      else
        throw error(L.l("deferredSyntaxAllowedAsLiteral expects 'true' or 'false' at '{0}'",
                        value));
    }
    else {
      throw error(L.l("'{0}' is an unknown JSP page directive attribute.  See the JSP documentation for a complete list of page directive attributes.",
                      name.getName()));
    }
  }

  /**
   * Parses the buffer size directive, grabbing the size out from the units.
   *
   * @param value buffer size string.
   * @return the size of the buffer in kb.
   */
  private int processBufferSize(String value) throws JspParseException
  {
    if (value.equals("none"))
      return 0;

    int i = 0;
    int kb = 0;
    for (; i < value.length(); i++) {
      char ch = value.charAt(i);
      if (ch >= '0' && ch <= '9')
        kb = 10 * kb + ch - '0';
      else
        break;
    }

    if (! value.substring(i).equals("kb"))
      throw error(L.l("Expected buffer size at '{0}'.  Buffer sizes must end in 'kb'", value));

    return 1024 * kb;
  }
  
  protected String getRelativeUrl(String value)
  {
    if (value.length() > 0 && value.charAt(0) == '/')
      return value;
    else
      return _parseState.getUriPwd() + value;
  }

  /**
   * Charset can be specific as follows:
   *  test/html; z=9; charset=utf8; w=12
   */
  static String parseCharEncoding(String type)
    throws JspParseException
  {
    type = type.toLowerCase(Locale.ENGLISH);
    int i;
    char ch;
    while ((i = type.indexOf(';')) >= 0 && i < type.length()) {
      i++;
      while (i < type.length() && ((ch = type.charAt(i)) == ' ' || ch == '\t'))
        i++;

      if (i >= type.length())
        return null;

      type = type.substring(i);
      i = type.indexOf('=');
      if (i >= 0) {
        if (! type.startsWith("charset"))
          continue;

        for (i++;
             i < type.length() && ((ch = type.charAt(i)) == ' ' || ch == '\t');
             i++) {
        }

        type = type.substring(i);
      }
      
      for (i = 0;
           i < type.length() && (ch = type.charAt(i)) != ';' && ch != ' ';
           i++) {
      }

      return type.substring(0, i);
    }

    return null;
  }
  
  /**
   * Called when the tag ends.
   */
  public void endAttributes()
    throws JspParseException
  {
    if (_gen.isTag())
      throw error(L.l("page directives are forbidden in tags."));
  }
  
  /**
   * Return true if the node only has static text.
   */
  public boolean isStatic()
  {
    return true;
  }

  /**
   * Generates the XML text representation for the tag validation.
   *
   * @param os write stream to the generated XML.
   */
  public void printXml(WriteStream os)
    throws IOException
  {
    os.print("<jsp:directive.page");
    printJspId(os);
    if (! _parseState.isELIgnored())
      os.print(" el-ignored='false'");
    /*
    if (! _parseState.isScriptingEnabled())
      os.print(" scripting-enabled='false'");
    */
    if (_parseState.getContentType() != null)
      os.print(" content-type='" + _parseState.getContentType() + "'");

    ArrayList<String> imports = _parseState.getImportList();

    if (imports != null && imports.size() != 0) {
      os.print(" import='");
      for (int i = 0; i < imports.size(); i++) {
        if (i != 0)
          os.print(',');
        os.print(imports.get(i));
      }
      os.print("'");
    }
    
    
    os.print("/>");
  }

  /**
   * Generates the code for the tag
   *
   * @param out the output writer for the generated java.
   */
  public void generate(JspJavaWriter out)
    throws Exception
  {
  }
}
