/*
 * 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.jstl.el;

import com.caucho.el.Expr;
import com.caucho.jsp.PageContextImpl;
import com.caucho.jstl.NameValueTag;
import com.caucho.util.L10N;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.Vfs;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.el.ELContext;
import javax.el.ELException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.logging.Logger;

public class XmlTransformTag extends BodyTagSupport implements NameValueTag {
  private static final Logger log
    = Logger.getLogger(XmlTransformTag.class.getName());
  private static final L10N L = new L10N(XmlTransformTag.class);
  
  private Expr _xml;
  private Expr _xslt;

  private Expr _xmlSystemId;
  private Expr _xsltSystemId;
  
  private String _var;
  private String _scope;

  private Expr _result;

  private ArrayList<String> _paramNames = new ArrayList<String>();
  private ArrayList<String> _paramValues = new ArrayList<String>();
  
  /**
   * Sets the JSP-EL XML value.
   */
  public void setXml(Expr xml)
  {
    setDoc(xml);
  }
  
  /**
   * Sets the JSP-EL XML value.
   */
  public void setDoc(Expr xml)
  {
    _xml = xml;
  }

  /**
   * Sets the JSP-EL XML value.
   */
  public void setXslt(Expr xslt)
  {
    _xslt = xslt;
  }

  /**
   * Sets the JSP-EL XML system id expr.
   */
  public void setXmlSystemId(Expr xmlSystemId)
  {
    _xmlSystemId = xmlSystemId;
  }

  /**
   * Sets the JSP-EL XML system id expr.
   */
  public void setDocSystemId(Expr xmlSystemId)
  {
    _xmlSystemId = xmlSystemId;
  }

  /**
   * Sets the JSP-EL XSLT system id.
   */
  public void setXsltSystemId(Expr xsltSystemId)
  {
    _xsltSystemId = xsltSystemId;
  }

  /**
   * Sets the variable
   */
  public void setVar(String var)
  {
    _var = var;
  }

  /**
   * Sets the scope
   */
  public void setScope(String scope)
  {
    _scope = scope;
  }

  /**
   * Sets the result
   */
  public void setResult(Expr result)
  {
    _result = result;
  }
  
  /**
   * Adds a parameter.
   */
  public void addParam(String name, String value)
  {
    _paramNames.add(name);
    _paramValues.add(value);
  }

  /**
   * Process the tag.
   */
  public int doStartTag()
    throws JspException
  {
    _paramNames.clear();
    _paramValues.clear();

    return EVAL_BODY_BUFFERED;
  }

  /**
   * Process the tag.
   */
  public int doEndTag()
    throws JspException
  {
    try {
      PageContextImpl pageContext = (PageContextImpl) this.pageContext;
      ELContext env = pageContext.getELContext();
      
      JspWriter out = pageContext.getOut();

      TransformerFactory factory = TransformerFactory.newInstance();

      Source source = getSource(_xslt, _xsltSystemId);

      Transformer transformer = factory.newTransformer(source);

      for (int i = 0; i < _paramNames.size(); i++) {
        String name = _paramNames.get(i);
        String value = _paramValues.get(i);

        transformer.setParameter(name, value);
      }

      if (_xml != null)
        source = getSource(_xml, _xmlSystemId);
      else {
        BodyContent bodyContent = getBodyContent();

        source = new StreamSource(bodyContent.getReader());
        source.setSystemId(((HttpServletRequest) pageContext.getRequest()).getRequestURI());
      }

      Result result;
      Node top = null;

      if (_result != null) {
        result = (Result) _result.evalObject(env);
      }
      else if (_var != null) {
        top = new com.caucho.xml.QDocument();
        
        result = new DOMResult(top);
      }
      else
        result = new StreamResult(out);

      transformer.transform(source, result);

      if (_var != null)
        CoreSetTag.setValue(pageContext, _var, _scope, top);
    } catch (Exception e) {
      throw new JspException(e);
    }

    return SKIP_BODY;
  }

  private Source getSource(Expr xmlExpr, Expr systemIdExpr)
    throws JspException
  {
    try {
      PageContextImpl pageContext = (PageContextImpl) this.pageContext;
      
      Object xmlObj = xmlExpr.evalObject(pageContext.getELContext());
      String systemId = null;
      Source source = null;

      if (systemIdExpr != null)
        systemId = systemIdExpr.evalString(pageContext.getELContext());
      
      source = convertToSource(xmlObj, systemId);

      return source;
    } catch (ELException e) {
      throw new JspException(e);
    }
  }

  public static Source convertToSource(Object xmlObj, String systemId)
    throws JspException
  {
    if (xmlObj instanceof String) {
      ReadStream is = Vfs.openString((String) xmlObj);

      return new StreamSource(is, systemId);
    }
    else if (xmlObj instanceof InputStream) {
      return new StreamSource((InputStream) xmlObj, systemId);
    }
    else if (xmlObj instanceof Reader) {
      return new StreamSource((Reader) xmlObj, systemId);
    }
    else if (xmlObj instanceof Node) {
      return new DOMSource((Node) xmlObj, systemId);
    }
    else if (xmlObj instanceof NodeList) {
      return new DOMSource(((NodeList) xmlObj).item(0), systemId);
    }
    else if (xmlObj instanceof Source)
      return  (Source) xmlObj;
    else if (xmlObj instanceof ArrayList) {
      ArrayList list = (ArrayList) xmlObj;

      if (list.size() > 0)
        return convertToSource(list.get(0), systemId);
    }
    
    throw new JspException(L.l("unknown xml object type '{0}' '{1}'",
                               xmlObj, xmlObj.getClass()));
  }
}
