/*
 * Copyright 2004-2014 ICEsoft Technologies Canada Corp.
 *
 * Licensed 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 com.icesoft.faces.component.dataexporter;

import com.icesoft.faces.component.CSS_DEFAULT;
import com.icesoft.faces.component.commandsortheader.CommandSortHeader;
import com.icesoft.faces.component.ext.RowSelector;
import com.icesoft.faces.component.ext.UIColumn;
import com.icesoft.faces.component.ext.UIColumns;
import com.icesoft.faces.component.ext.taglib.Util;
import com.icesoft.faces.component.outputresource.OutputResource;
import com.icesoft.faces.context.FileResource;
import com.icesoft.faces.context.effects.JavascriptContext;
import com.icesoft.faces.util.CoreUtils;
import com.icesoft.util.CoreComponentUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.icefaces.resources.BrowserType;
import org.icefaces.resources.ICEResourceDependencies;
import org.icefaces.resources.ICEResourceDependency;

import javax.faces.component.*;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.el.ValueBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

@ICEResourceDependencies({
	@ICEResourceDependency(name="icefaces-compat.js", library="ice.compat",target="head", browser=BrowserType.ALL, browserOverride={}),
	@ICEResourceDependency(name="compat.js", library="ice.compat",target="head", browser=BrowserType.ALL, browserOverride={})
})
public class DataExporter extends OutputResource {
    public static final String COMPONENT_FAMILY = "com.icesoft.faces.DataExporter";
	public static final String COMPONENT_TYPE = "com.icesoft.faces.DataExporter";
	public static final String DEFAULT_RENDERER_TYPE = "com.icesoft.faces.DataExporterRenderer";
	private final Log log = LogFactory.getLog(DataExporter.class);
	
    private static final OutputTypeHandler NoopOutputHandler = new OutputTypeHandler("no-data") {
        public void writeHeaderCell(String text, int col) {
        }

        public void writeCell(Object output, int col, int row) {
        }

        public void flushFile() {
        }
    };

	private boolean readyToExport = false;

	private String _for;
	private String type;
	private String clickToCreateFileText;
	private String _origType;
	private transient OutputTypeHandler outputTypeHandler;
	private String _origFor;
	private transient OutputTypeHandler _origOutputTypeHandler;
	private transient int _origDataModelHash = 0;
	public final static String EXCEL_TYPE = "excel";
	public final static String CSV_TYPE = "csv";
    public final static String PDF_TYPE = "pdf";
	private Boolean ignorePagination;
    private Boolean renderLabelAsButton;
    private String styleClass;
    private String includeColumns;
    private int rows = Integer.MIN_VALUE;
    private int first = Integer.MIN_VALUE;
    private String popupBlockerLabel;
	public DataExporter() {
	}
	
	   /*
     * (non-Javadoc)
     * 
     * @see javax.faces.component.UIComponent#getFamily()
     */
    public String getFamily() {
        return COMPONENT_FAMILY;
    }
    
    public String getComponentType() {
        return COMPONENT_TYPE;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.faces.component.UIComponent#getRendererType()
     */
    public String getRendererType() {
        return DEFAULT_RENDERER_TYPE;
    }
    
	public UIData getUIData() {
		String forStr = getFor();
		UIData forComp = (UIData) CoreComponentUtils.findComponent(forStr, this);

		if (forComp == null) {
			throw new IllegalArgumentException(
					"could not find UIData referenced by attribute @for = '"
							+ forStr + "'");
		} else if (!(forComp instanceof UIData)) {
			throw new IllegalArgumentException(
					"uiComponent referenced by attribute @for = '" + forStr
							+ "' must be of type " + UIData.class.getName()
							+ ", not type " + forComp.getClass().getName());
		}
		//compare with cached DataModel to check for updates
		if( _origDataModelHash != 0 && _origDataModelHash != forComp.getValue().hashCode()){
			reset();
		}
		if (!isIgnorePagination() && ((first != Integer.MIN_VALUE && first != forComp.getFirst()) ||
		        (rows != Integer.MIN_VALUE && rows != forComp.getRows()))  ) {
		    reset(); 
		}
		

		Object value = forComp.getValue();
		if (null != value) {
		    _origDataModelHash = forComp.getValue().hashCode();
		}
		return forComp;
	}
	
	private void reset(){
		this.readyToExport = false;
		this.resource = null;
	}
	
	public String getFor() {
		if (_for != null) {
			if( !_for.equals(this._origFor))
				reset();
			this._origFor = _for;
			return _for;
		}
		ValueBinding vb = getValueBinding("for");
		String newFor = null;
		if( vb != null ){
			newFor = (String) vb.getValue(getFacesContext());
			if( newFor != null && !newFor.equals(this._origFor))
				reset();
			this._origFor = newFor;
		}
		
		return newFor;
	}

	public void setFor(String forValue) {
		if( forValue != null && !forValue.equals(_for))
			this.resource = null;
		_for = forValue;
	}
	
	public String getType(){
		if (type != null) {
			if( !type.equals(this._origType))
				reset();
			this._origType = type;
			return type;
		}
		ValueBinding vb = getValueBinding("type");
		String newType = null;
		if( vb != null ){
			newType = (String) vb.getValue(getFacesContext());
			if( newType != null && !newType.equals(this._origType))
				reset();
			this._origType = newType;
		}
		return newType;
	}
	
	public void setType(String type){
		if( type != null && !type.equals(this.type))
			reset();
		this.type = type;
	}

	public boolean isReadyToExport() {
		return readyToExport;
	}

	public void setReadyToExport(boolean readyToExport) {
		this.readyToExport = readyToExport;
	}
	
	/**
	 * @deprecated
	 */
	public String getClickToCreateFileText() {
		if (this.clickToCreateFileText != null) {
			return clickToCreateFileText;
		}
		ValueBinding vb = getValueBinding("clickToCreateFileText");
		return vb != null ? (String) vb.getValue(getFacesContext()) : null;
	}

     /**
     * @deprecated
     */
	public void setClickToCreateFileText(String clickToCreateFileText) {
		this.clickToCreateFileText = clickToCreateFileText;
	}

	public OutputTypeHandler getOutputTypeHandler() {
		ValueBinding vb = getValueBinding("outputTypeHandler");
		OutputTypeHandler newOutputHandler = null;
		if( vb != null ){
			newOutputHandler = (OutputTypeHandler) vb.getValue(getFacesContext());
			if( newOutputHandler != null && newOutputHandler != this._origOutputTypeHandler)
				reset();
			this._origOutputTypeHandler = newOutputHandler;
		}
		return newOutputHandler;
	}

	public void setOutputTypeHandler(OutputTypeHandler outputTypeHandler) {
		if( outputTypeHandler != null && outputTypeHandler != this.outputTypeHandler)
			reset();
		this.outputTypeHandler = outputTypeHandler;
	}
	
    private transient Object values[];
    /*
     * (non-Javadoc)
     * 
     * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
     */
    public Object saveState(FacesContext context) {

        if(values == null){
            values = new Object[14];
        }
        values[0] = super.saveState(context);
        values[1] = _for;
        values[2] = type;
        values[3] = clickToCreateFileText;
        values[4] = readyToExport? Boolean.TRUE : Boolean.FALSE;
        values[5] = _origType;
        values[6] = _origFor;
        values[7] = ignorePagination; 
        values[8] = renderLabelAsButton;     
        values[9] = styleClass;
        values[10] = includeColumns;         
        values[11] = new Integer(rows); 
        values[12] = new Integer(first); 
        values[13] = popupBlockerLabel;
        return ((Object) (values));
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext,
     *      java.lang.Object)
     */
    public void restoreState(FacesContext context, Object state) {
        Object values[] = (Object[]) state;
        super.restoreState(context, values[0]);
        _for = (String) values[1];
        type = (String) values[2];
        clickToCreateFileText = (String) values[3];
        readyToExport = ((Boolean) values[4]).booleanValue();        
        _origType = (String) values[5];
        _origFor = (String)values[6];
        ignorePagination = (Boolean)values[7]; 
        renderLabelAsButton = (Boolean)values[8];  
        styleClass = (String)values[9];  
        includeColumns = (String)values[10];    
        rows = ((Integer)values[11]).intValue(); 
        first = ((Integer)values[12]).intValue();  
        popupBlockerLabel = (String)values[13];         
    }
    
    public String getLabel() {
        String label = super.getLabel();
        if (label == null) {
            if (resource instanceof FileResource)
            label = ((FileResource)resource).getFile().getName();
        }
        return label;
    }
	
    public void broadcast(FacesEvent event)
    throws AbortProcessingException {
        super.broadcast(event);
        if (event != null) {
            FacesContext facesContext = FacesContext.getCurrentInstance();
            String type = getType();
            UIData uiData = getUIData();
			File output = createFile(facesContext, type, uiData);
			setResource(new FileResource(output));
			getResource();
			JavascriptContext.addJavascriptCall(facesContext, "Ice.DataExporters['" + getClientId(facesContext) + "'].url('" + getPath() + "');");
        }
    }    

    private File createFile(FacesContext fc, String type, UIData uiData) {
        OutputTypeHandler outputHandler = null;
        String path = CoreUtils.getRealPath(fc, "/export"); 
        File exportDir = new File(path);
        if (!exportDir.exists())
            exportDir.mkdirs();
        String pathWithoutExt = path + "/export_"
                + new Date().getTime();

        if (getOutputTypeHandler() != null)
            outputHandler = getOutputTypeHandler();
        else if (DataExporter.EXCEL_TYPE.equals(getType())) {
            outputHandler = new ExcelOutputHandler(pathWithoutExt + ".xls",
                    fc, uiData.getId());
        } else if (DataExporter.CSV_TYPE.equals(getType())) {
            outputHandler = new CSVOutputHandler(pathWithoutExt + ".csv");
        } else if (DataExporter.PDF_TYPE.equals(getType())) {
            outputHandler = new PDFOutputHandler(pathWithoutExt + ".pdf",
                    uiData.getId());
        } else {
            outputHandler = NoopOutputHandler;
        }
        renderToHandler(outputHandler, uiData, fc);
        setMimeType(outputHandler.getMimeType());

        return outputHandler.getFile();
    }

    private String encodeParentAndChildrenAsString(FacesContext fc,
            UIComponent uic) {
        StringBuffer str = new StringBuffer();
        if (uic instanceof CommandSortHeader) {
            if (uic.getChildCount() > 0) {
                Iterator iter = uic.getChildren().iterator();
                while (iter.hasNext()) {
                    UIComponent child = (UIComponent) iter.next();
                    str.append(encodeParentAndChildrenAsString(fc, child));
                }
            }
        }
        Object value = uic.getAttributes().get("value");
        if (value == null) {
            ValueBinding vb = uic.getValueBinding("value");
            if (vb != null) {
                value = vb.getValue(fc);
            }
        }
        if (value == null && !(uic instanceof javax.faces.component.UIPanel)) {
        	return str.toString();
        }
		if (!(uic instanceof javax.faces.component.UIPanel)) {
			Converter converter = null;
			if(uic instanceof ValueHolder) {
				converter = ((ValueHolder)uic).getConverter();
			}
			if(converter == null) {
				converter = FacesContext.getCurrentInstance().getApplication().createConverter(value.getClass());
			}
			if(converter != null) {
				str.append(converter.getAsString(FacesContext.getCurrentInstance(), uic, value));
			} else {
				str.append(value);
			}
		}

        //don't process selectItems or f:param for uiCommand)
        if (uic instanceof UISelectBoolean || 
        		uic instanceof UISelectMany || 
        		uic instanceof UISelectOne||
        		uic instanceof UICommand){
        	return str.toString();
        }         
        
        if (uic.getChildCount() > 0) {
            Iterator iter = uic.getChildren().iterator();
            while (iter.hasNext()) {
                UIComponent child = (UIComponent) iter.next();
                str.append(encodeParentAndChildrenAsString(fc, child));
            }
        }
        return str.toString();
    }

    protected List getRenderedChildColumnsList(UIComponent component) {
        List results = new ArrayList();
        Iterator kids = component.getChildren().iterator();
        while (kids.hasNext()) {
            UIComponent kid = (UIComponent) kids.next();
            if ((kid instanceof UIColumn) && kid.isRendered()) {
                results.add(kid);
            }
            else if (kid instanceof UIColumns) {
                results.add(kid);
            }
        }
        return results;
    }

    private void renderToHandler(OutputTypeHandler outputHandler,
            UIData uiData, FacesContext fc) {

        try {
            int rowIndex = 0;
            int numberOfRowsToDisplay = 0;  
            if (!isIgnorePagination()) {
                rowIndex = uiData.getFirst();
                numberOfRowsToDisplay = uiData.getRows();
                first = rowIndex;
                rows = numberOfRowsToDisplay;
            }
 
            int countOfRowsDisplayed = 0;
            uiData.setRowIndex(rowIndex);
            String[] includeColumnsArray = null;
            String includeColumns = getIncludeColumns();
            if (includeColumns != null)
                includeColumnsArray = includeColumns.split(",");
            List columns = getRenderedChildColumnsList(uiData);
//System.out.println("DataExporter.renderToHandler()  columns.size: " + columns.size());
                
            // write header
//System.out.println("DataExporter.renderToHandler()  HEADERS");
            processAllColumns(
                fc, outputHandler, columns, includeColumnsArray, -1, false);

//System.out.println("DataExporter.renderToHandler()  ROWS");
            while (uiData.isRowAvailable()) {
                if (numberOfRowsToDisplay > 0
                        && countOfRowsDisplayed >= numberOfRowsToDisplay) {
                    break;
                }

                // render the child columns; each one in a td
                processAllColumns(fc, outputHandler, columns,
                    includeColumnsArray, countOfRowsDisplayed, false);
                
                // keep track of rows displayed
                countOfRowsDisplayed++;
                // maintain the row index property on the underlying UIData
                // component
                rowIndex++;
                uiData.setRowIndex(rowIndex);

            }
            // reset the underlying UIData component
            uiData.setRowIndex(-1);

            // write footer
//System.out.println("DataExporter.renderToHandler()  FOOTERS");
            processAllColumns(fc, outputHandler, columns,
                includeColumnsArray, countOfRowsDisplayed, true);
            
            outputHandler.flushFile();
        } catch (Exception e) {
            log.error("renderToHandler()", e);
        }
    }
    
    public void addInfo() {}
    
    /**
     * <p>Set the value of the <code>ignorePagination</code> property.</p>
     */
    public void setIgnorePagination(boolean ignorePagination) {
        this.ignorePagination = new Boolean(ignorePagination);
    }

    /**
     * <p>Return the value of the <code>ignorePagination</code> property.</p>
     */
    public boolean isIgnorePagination() {
        if (ignorePagination != null) {
            return ignorePagination.booleanValue();
        }
        ValueBinding vb = getValueBinding("ignorePagination");
        return vb != null ? ((Boolean) vb.getValue(getFacesContext()))
                .booleanValue() : false;
    }   
    
    /**
     * <p>Set the value of the <code>renderLabelAsButton</code> property.</p>
     */
    public void setRenderLabelAsButton(boolean renderLabelAsButton) {
        this.renderLabelAsButton = new Boolean(renderLabelAsButton);
    }

    /**
     * <p>Return the value of the <code>renderLabelAsButton</code> property.</p>
     */
    public boolean isRenderLabelAsButton() {
        if (renderLabelAsButton != null) {
            return renderLabelAsButton.booleanValue();
        }
        ValueBinding vb = getValueBinding("renderLabelAsButton");
        return vb != null ? ((Boolean) vb.getValue(getFacesContext()))
                .booleanValue() : false;
    }
    
    public void setStyleClass(String styleClass) {
        this.styleClass = styleClass;
    }
    
    public String getStyleClass() {
        return Util.getQualifiedStyleClass(this, 
                styleClass,
                CSS_DEFAULT.DATAEXPORTER_DEFAULT_STYLE_CLASS,
                "styleClass");
    } 
    
    protected void processAllColumns(FacesContext fc,
                                     OutputTypeHandler outputHandler,
                                     List columns,
                                     String[] includeColumnsArray,
                                     int countOfRowsDisplayed,
                                     boolean footer) {
        if (includeColumnsArray != null) {
//System.out.println("DataExporter.processAllColumns()  COLUMNS ARRAY");
            renderInUserDefinedOrder(fc, outputHandler, columns,
                includeColumnsArray, 0, countOfRowsDisplayed, footer);
        } else {
//System.out.println("DataExporter.processAllColumns()  COLUMNS ITERATING");
            int colIndex = 0;
            Iterator childColumns = columns.iterator();
            while (childColumns.hasNext()) {
                UIComponent nextColumn = (UIComponent) childColumns.next();
                if (nextColumn instanceof UIColumn) {
//System.out.println("DataExporter.processAllColumns()  UIColumn  colIndex: " + colIndex);
                    if (countOfRowsDisplayed == -1) {
//System.out.println("DataExporter.processAllColumns()    HEADER CELL");
                        processColumnHeader(
                            fc, outputHandler, nextColumn, colIndex);
                    }
                    else if (footer) {
//System.out.println("DataExporter.processAllColumns()    FOOTER CELL");
                        processColumnFooter(fc, outputHandler, nextColumn,
                            colIndex, countOfRowsDisplayed);
                    }
                    else {
//System.out.println("DataExporter.processAllColumns()    CELL");
                        processColumn(fc, outputHandler, nextColumn, colIndex,
                            countOfRowsDisplayed);                        
                    }
                    colIndex++;
                }
                else if (nextColumn instanceof UIColumns) {
                    UIColumns nextColumns = (UIColumns) nextColumn;
                    int colsSpecificIndex = nextColumns.getFirst();
                    int count = nextColumns.getRows();
                    int colsSpecificLast = (count == 0) ?
                        (Integer.MAX_VALUE - 1) :
                        (colsSpecificIndex + count - 1);
//System.out.println("DataExporter.processAllColumns()  UIColumns  first: " + colsSpecificIndex + "  rows: " + count);
                    while (colsSpecificIndex <= colsSpecificLast) {
                        nextColumns.setRowIndex(colsSpecificIndex);
                        if (!nextColumns.isRowAvailable()) {
                            break;
                        }
                        if (countOfRowsDisplayed == -1) {
//System.out.println("DataExporter.processAllColumns()    HEADER CELL  colIndex: " + colIndex);
                            processColumnHeader(
                                fc, outputHandler, nextColumn, colIndex);
                        }
                        else if (footer) {
//System.out.println("DataExporter.processAllColumns()    FOOTER CELL  colIndex: " + colIndex);
                            processColumnFooter(fc, outputHandler, nextColumn,
                                colIndex, countOfRowsDisplayed);
                        }
                        else {
//System.out.println("DataExporter.processAllColumns()    CELL  colIndex: " + colIndex);
                            processColumn(fc, outputHandler, nextColumn,
                                colIndex, countOfRowsDisplayed);
                        }
                        colsSpecificIndex++;
                        colIndex++;
                    }
                    nextColumns.setRowIndex(-1);
                }
            }
        }
    }
    
    protected void processColumnHeader(FacesContext fc, 
                                    OutputTypeHandler outputHandler,
                                    UIComponent uiColumn, int colIndex) {
        UIComponent headerComp = uiColumn.getFacet("header");
        if (headerComp != null) {
            String headerText = encodeParentAndChildrenAsString(fc, headerComp);
            if (headerText != null) {
                outputHandler.writeHeaderCell(headerText, colIndex);
            }
        }        
    }
    
    protected void processColumnFooter(FacesContext fc, 
                                    OutputTypeHandler outputHandler,
                                    UIComponent uiColumn, int colIndex,
                                    int countOfRowsDisplayed) {
        UIComponent footerComp = uiColumn.getFacet("footer");
        if (footerComp != null) {
            Object output = encodeParentAndChildrenAsString(fc, footerComp);
            if (output != null) {
                outputHandler.writeFooterCell(output, colIndex, countOfRowsDisplayed);
            }
        }        
    }
    
    protected void processColumn(FacesContext fc, 
            OutputTypeHandler outputHandler,
            UIComponent uiColumn, int colIndex,
            int countOfRowsDisplayed) {
        StringBuffer stringOutput = new StringBuffer();

        Iterator childrenOfThisColumn = uiColumn.getChildren()
                .iterator();
        while (childrenOfThisColumn.hasNext()) {

            UIComponent nextChild = (UIComponent) childrenOfThisColumn
                    .next();
            if (nextChild.isRendered() && !(nextChild instanceof RowSelector)) {
				Object visible = nextChild.getAttributes().get("visible");
				if (visible == null) {
					ValueBinding vb = nextChild.getValueBinding("visible");
					if (vb != null) {
						visible = vb.getValue(fc);
					}
				}
				if (visible != null && ((Boolean) visible) == false) {
					continue;
				}
                stringOutput.append(encodeParentAndChildrenAsString(fc,
                        nextChild));
                //a blank to separate 
                if (childrenOfThisColumn.hasNext()) {
                    stringOutput.append(' '); 
                }
            }

        }
        outputHandler.writeCell(stringOutput.toString(), colIndex, countOfRowsDisplayed);
        
    }
    
    
    /**
      */
    public String getIncludeColumns() {
        if (this.includeColumns != null) {
            return includeColumns;
        }
        ValueBinding vb = getValueBinding("includeColumns");
        return vb != null ? (String) vb.getValue(getFacesContext()) : null;
    }

     /**
     */
    public void setIncludeColumns(String includeColumns) {
        this.includeColumns = includeColumns;
    }    
    
    protected void renderInUserDefinedOrder(FacesContext fc,
            OutputTypeHandler outputHandler, 
            List columns,
            String[] includeColumnsArray,
            int colIndex,
            int countOfRowsDisplayed,
            boolean footer) {
        UIComponent previousColumn = null;
        for (int i=0; i<includeColumnsArray.length; i++) {
//System.out.println("DataExporter.renderInUserDefinedOrder()  includeColumnsArray["+i+"]: " + includeColumnsArray[i]);
            int userIndex = 0;
            try {
                userIndex = Integer.parseInt(includeColumnsArray[i].trim());
            } catch (Exception e) {
                log.error("renderInUserDefinedOrder() invalid column index ", e);
                continue;
            }
//System.out.println("DataExporter.renderInUserDefinedOrder()  userIndex: " + userIndex);
            UIComponent nextColumn = null;
            int runningIndex = 0;
            for (int listIndex = 0; listIndex < columns.size(); listIndex++) {
                UIComponent runningComp = (UIComponent) columns.get(listIndex);
                if (runningComp instanceof UIColumn) {
//System.out.println("DataExporter.renderInUserDefinedOrder()    Check UIColumn  runningIndex: " + runningIndex);
                    if (runningIndex == userIndex) {
//System.out.println("DataExporter.renderInUserDefinedOrder()      MATCH");
                        nextColumn = runningComp;
                        break;
                    }
                    runningIndex++;
                }
                else if (runningComp instanceof UIColumns) {
//System.out.println("DataExporter.renderInUserDefinedOrder()    Check UIColumns");
                    UIColumns runningCols = (UIColumns) runningComp;
                    // If rows is specified, so it's possible to know if 
                    // userIndex is contained in this UIColumns or not.
                    int runningColsSize = runningCols.getRows();
                    if (runningColsSize != 0) {
//System.out.println("DataExporter.renderInUserDefinedOrder()      rows specified");
                        if (userIndex >= runningIndex &&
                            userIndex < runningIndex + runningColsSize) {
                            int colsSpecificIndex = userIndex - runningIndex +
                                runningCols.getFirst();
                            runningIndex = userIndex;
                            runningCols.setRowIndex(colsSpecificIndex);
//System.out.println("DataExporter.renderInUserDefinedOrder()      MATCH  colsSpecificIndex: " + colsSpecificIndex);
                            nextColumn = runningCols;
                            break;
                        }
                        else {
//System.out.println("DataExporter.renderInUserDefinedOrder()      beyond rows");
                            runningIndex += runningColsSize;
                        }
                    }
                    // Have to just iterate over the UIColumns to determine
                    // its size, to know if userIndex falls within it.
                    else {
//System.out.println("DataExporter.renderInUserDefinedOrder()      rows not specified");
                        int colsSpecificIndex = runningCols.getFirst();
                        while (true) {
                            runningCols.setRowIndex(colsSpecificIndex);
                            if (!runningCols.isRowAvailable()) {
                                break;
                            }
                            if (runningIndex == userIndex) {
//System.out.println("DataExporter.renderInUserDefinedOrder()      MATCH  colsSpecificIndex: " + colsSpecificIndex);
                                nextColumn = runningCols;
                                break;
                            }
                            runningIndex++;
                            colsSpecificIndex++;
                        }
                        if (nextColumn != null) {
                            break;
                        }
                        
                        // If we just scanned through runningCols, and aren't 
                        // using it, clean it up, unless we're going to already
                        if (nextColumn == null &&
                            runningCols != previousColumn) {
                            runningCols.setRowIndex(-1);
                        }
                    }
                }
            }
            
            if (nextColumn == null) {
                log.info("["+userIndex +"] is invalid column index. Column index is 0 based and should be less then from "+ runningIndex);
                continue;
            }
            
            // If we're switching away from a UIColumns, clean it up
            if (previousColumn != null &&
                nextColumn != previousColumn &&
                previousColumn instanceof UIColumns) {
                ((UIColumns)previousColumn).setRowIndex(-1);
            }
            previousColumn = nextColumn;
            
            if (countOfRowsDisplayed == -1) {
                processColumnHeader(fc, outputHandler, nextColumn, colIndex);
            } else if (footer) {
                processColumnFooter(fc, outputHandler, nextColumn, colIndex,
                    countOfRowsDisplayed);
            } else {
                processColumn(fc, outputHandler, nextColumn, colIndex, countOfRowsDisplayed);
            }
            colIndex++; 
        }
        
        
        // Cleanup any trailing UIColumns
        if (previousColumn != null &&
            previousColumn instanceof UIColumns) {
            ((UIColumns)previousColumn).setRowIndex(-1);
        }
    }

    public String getPopupBlockerLabel() {
        if (this.popupBlockerLabel != null) {
            return popupBlockerLabel;
        }
        ValueBinding vb = getValueBinding("popupBlockerLabel");
        return vb != null ? (String) vb.getValue(getFacesContext()) : null;
    }

    public void setPopupBlockerLabel(String popupBlockerLabel) {
        this.popupBlockerLabel = popupBlockerLabel;
    }    
}
