/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.empire.struts2.jsp.tags;

import java.io.Writer;
import java.util.Iterator;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.empire.db.DBReader;
import org.apache.struts2.components.Component;
import org.apache.struts2.components.IteratorComponent;
import org.apache.struts2.util.MakeIterator;
import org.apache.struts2.views.jsp.IteratorStatus;
import org.apache.struts2.views.jsp.IteratorTag;

import com.opensymphony.xwork2.util.ValueStack;


@SuppressWarnings("serial")
public class ItemListTag extends IteratorTag
{
    // Logger
    protected static Logger log = LoggerFactory.getLogger(ItemListTag.class);

    public static class ItemIteratorComponent extends IteratorComponent
    {
        private PageContext pageContext;
        private boolean isReader;
        private Object oldSource;
        private Object srcObject;

        public ItemIteratorComponent(ValueStack stack, PageContext pageContext, Object source, Iterator iterator)
        {
            super(stack);
            // set iterator
            this.pageContext = pageContext;
            this.iterator = iterator;
            this.isReader =(source instanceof DBReader);
            this.srcObject = source;
            // Store Reader on Page Context
            if (isReader)
            {
                oldSource = pageContext.getAttribute(EmpireValueTagSupport.RECORD_ATTRIBUTE);
                pageContext.setAttribute(EmpireValueTagSupport.RECORD_ATTRIBUTE, source);
            }
            else
            {
                oldSource = pageContext.getAttribute(EmpireValueTagSupport.BEAN_ITEM_ATTRIBUTE);
                pageContext.setAttribute(EmpireValueTagSupport.BEAN_ITEM_ATTRIBUTE, null);
            }
        }        
        
        public void dispose()
        {
            String pageAttribute;
            if (isReader)
            {   // Close Reader
                ((DBReader)srcObject).close();
                pageAttribute = EmpireValueTagSupport.RECORD_ATTRIBUTE; 
            }
            else
            {   // Bean List
                pageAttribute = EmpireValueTagSupport.BEAN_ITEM_ATTRIBUTE; 
            }
            // Remove Source
            if (oldSource!=null)
                pageContext.setAttribute(pageAttribute, oldSource);
            else
                pageContext.removeAttribute(pageAttribute);
            // done
            pageContext = null;
            oldSource = null;
            srcObject = null;
        }

        @Override
        public boolean start(Writer writer)
        {
            if (iterator==null)
            {
                log.error("No Iterator for ItemListTag supplied. Ignoring body");
                return false;
            }    
            
            // Create an iterator status if the status attribute was set.
            if (statusAttr != null) {
                statusState = new IteratorStatus.StatusState();
                status = new IteratorStatus(statusState);
            }

            // we don't need this
            /*
            if (value == null) {
                value = "top";
            }
            iterator = MakeIterator.convert(findValue(value));
            */

            // get the first
            ValueStack stack = getStack();
            if ((iterator != null) && iterator.hasNext())
            {
                Object currentValue = iterator.next();
                if (currentValue!=null)
                {
                    stack.push(currentValue);
				    /*
				     * UPGRADE-struts 2.1.6
				     * CHANGE: changed "getId()" to "getVar()"
				     * Reason: The Funktion no longer exists.
				     */
                    String id = getVar();

                    if ((id != null))
                    {
                        //pageContext.setAttribute(id, currentValue);
                        //pageContext.setAttribute(id, currentValue, PageContext.REQUEST_SCOPE);
                        stack.getContext().put(id, currentValue);
                    }

                    // Status object
                    if (statusAttr != null)
                    {
                        statusState.setLast((isReader==false) ? !iterator.hasNext() : false); 
                        oldStatus = stack.getContext().get(statusAttr);
                        stack.getContext().put(statusAttr, status);
                    }

                    // Set current Value
                    if (isReader==false)
                        pageContext.setAttribute(EmpireValueTagSupport.BEAN_ITEM_ATTRIBUTE, currentValue);
                    
                    // Return with valid iterator
                    return true;
                }
            }
            // Make sure iterator is set to null!
            iterator = null;
            // Not more Records
            super.end(writer, "");
            return false;
        }

        @Override
        public boolean end(Writer writer, String body)
        {
            if (isReader)
            {   // A reader list
                IteratorStatus tmpStatus = status;
                status = null;
                // Iterate
                boolean result = super.end(writer, body);
                status = tmpStatus;
                if (status!=null)
                {
                    if (result==true)
                    {
                        statusState.next(); // Increase counter
                        // statusState.setLast(!iterator.hasNext());
                    }
                    else 
                    {
                        if (oldStatus == null) {
                            stack.getContext().put(statusAttr, null);
                        } else {
                            stack.getContext().put(statusAttr, oldStatus);
                        }
                    }
                }
                // done
                return result;
            }
            else
            {   // A bean list
                boolean result = super.end(writer, body);
                if (result)
                {   // Set current Value
                    ValueStack stack = getStack();
                    pageContext.setAttribute(EmpireValueTagSupport.BEAN_ITEM_ATTRIBUTE, stack.peek());
                }
                return result;
            }
        }    
        
    }

    // Properties
    private Object source;
    private Object maxItems;
    private String property;

    // Temporay
    private Object sourceObject;     
    private Object oldProperty;
    private Object oldStatusAttr;

    /*
     * Constructor
     */
    public ItemListTag()
    {
        // Default constructor
    }

    /*
     * Clears all params since tag is reused
     */
    public void resetParams()
    {
        // Optional
        source = null;
        maxItems = null;
        statusAttr = null;
        sourceObject = null;
    }
    
    @Override
    public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res)
    {
        // Make Iterator
        Iterator iterator = getIterator(sourceObject);
        // Create Compontent
        return new ItemIteratorComponent(stack, pageContext, sourceObject, iterator);
    }

    @Override
    public int doStartTag() throws JspException
    {
        // Reader
        sourceObject = getSourceObject(source);
        if (source==null)
        {   log.error("No reader or beanlist supplied for e:list tag. Skipping body.");
            property = null;
            return SKIP_BODY;
        }
        // Set Status Attribute Name and store it on page context
        oldStatusAttr = pageContext.getAttribute(TableRowTag.ITERATOR_STATUS_ATTRIBUTE);
        if (oldStatusAttr!=null)
            statusAttr = oldStatusAttr.toString() + "Nested";
        else
            statusAttr = "listStatusAttrName";
        pageContext.setAttribute(TableRowTag.ITERATOR_STATUS_ATTRIBUTE, statusAttr);
        // Parent Property
        if (property!= null)
        {
            oldProperty = pageContext.getAttribute(EmpireValueTagSupport.PARENT_PROPERTY_ATTRIBUTE);
            pageContext.setAttribute(EmpireValueTagSupport.PARENT_PROPERTY_ATTRIBUTE, property);
        }
        // do Start
        return super.doStartTag();
    }

    @Override
    public int doEndTag() throws JspException
    {
        // Parent Property
        if (property!= null)
            removePageAttribute(EmpireValueTagSupport.PARENT_PROPERTY_ATTRIBUTE, oldProperty);
        oldProperty = null;
        // Status Attribute
        if (statusAttr!= null)
            removePageAttribute(TableRowTag.ITERATOR_STATUS_ATTRIBUTE, oldStatusAttr);
        oldStatusAttr = null;
        // Remove Component
        if (component!=null)
           ((ItemIteratorComponent)component).dispose();
        // End
        resetParams();
        return super.doEndTag();
    }

    // ------- Helpers -------

    private void removePageAttribute(String name, Object oldValue)
    {
        if ( oldValue!=null )
             pageContext.setAttribute(name, oldValue);
        else pageContext.removeAttribute(name);
    }
    
    private Object getSourceObject(Object value)
    {
        // Check String
        if ((value instanceof String))
        {
            // Check Value
            String strval = value.toString();
            if (strval.startsWith("%{") && strval.endsWith("}"))
            { // OGNL
                strval = strval.substring(2, strval.length() - 1);
                value = getStack().findValue(strval);
            }
            else if (strval.startsWith("#"))
            { // OGNL
                value = getStack().findValue(strval);
            }
            else if (strval.startsWith("$"))
            { // Attribute on page, request, session or application (in this order)
                strval = strval.substring(1);
                if (strval.startsWith("$") == false)
                    value = getAttribute(strval);
                if (value == null)
                    return null;
            }
        }
        return value;
    }
    
    private Iterator getIterator(Object value)
    {
        // 
        if (value instanceof DBReader)
        {
            // Create Component
            int maxCount = getIntValue(maxItems, -1);
            return ((DBReader)value).iterator(maxCount);
        }
        // Error
        if (MakeIterator.isIterable(value)==false)
        {
            log.error("Cannot make an iterator of class " + value.getClass().getName());
            return null;
        }
        // Done
        return MakeIterator.convert(value);
    }
    
    private int getIntValue(Object value, int defValue)
    {
        if (value==null)
            return defValue;
        if (value instanceof String)
        {
            // Check Value
            String strval = value.toString();
            if (strval.startsWith("%{") && strval.endsWith("}"))
            { // OGNL
                strval = strval.substring(2, strval.length() - 1);
                value = getStack().findValue(strval, java.lang.Integer.class);
            }
            if (strval.startsWith("#"))
            { // OGNL
                value = getStack().findValue(strval, java.lang.Integer.class);
            }
            if (strval.startsWith("$"))
            { // Attribute on page, request, session or application (in this order)
                strval = strval.substring(1);
                if (strval.startsWith("$") == false)
                    value = getAttribute(strval);
                if (value == null)
                    return defValue;
            }
        }
        // Check Type
        if (value instanceof Integer)
        {
            return ((Integer)value).intValue();
        }
        // Parse String
        try 
        {   // Get Integer from String   
            return Integer.parseInt(value.toString());
            
        } catch(Exception e) {
            // failed to convert
            log.error("getIntFromString: given value is not a number!");
            return defValue;
        }
    }

    protected Object getAttribute(String attribute)
    {
        // Try Page
        Object obj = pageContext.getAttribute(attribute);
        if (obj != null)
            return obj;
        // Try Request
        obj = pageContext.getRequest().getAttribute(attribute);
        if (obj != null)
            return obj;
        // Try Session
        obj = pageContext.getSession().getAttribute(attribute);
        if (obj != null)
            return obj;
        // Try Application
        return pageContext.getServletContext().getAttribute(attribute);
    }
    
    // ------- Property accessors -------

    public void setSource(Object source)
    {
        this.source = source;
    }

    public void setMaxItems(Object maxItems)
    {
        this.maxItems = maxItems;
    }

    public void setProperty(String property)
    {
        this.property = property;
    }

}
