package railo.runtime.tag;

import java.io.IOException;

import railo.commons.lang.StringUtil;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.ext.tag.BodyTagTryCatchFinallyImpl;
import railo.runtime.functions.string.CJustify;
import railo.runtime.functions.string.LJustify;
import railo.runtime.functions.string.RJustify;
import railo.runtime.op.Caster;

/**
* Builds a table in a CFML page. Use the cfcol tag to define table column and row 
*   characteristics. The cftable tag renders data either as preformatted text, or, with the HTMLTable 
*   attribute, as an HTML table. Use cftable to create tables if you don't want to write HTML table tag 
*   code, or if your data can be well presented as preformatted text.
*
*
*
**/
public final class Table extends BodyTagTryCatchFinallyImpl {
    
    /**
     * Field <code>ALIGN_LEFT</code>
     */
    public static final short ALIGN_LEFT=0;
    /**
     * Field <code>ALIGN_CENTER</code>
     */
    public static final short ALIGN_CENTER=1;
    /**
     * Field <code>ALIGN_RIGHT</code>
     */
    public static final short ALIGN_RIGHT=2;

	/** Name of the cfquery from which to draw data. */
	private railo.runtime.type.Query query;

	/** Maximum number of rows to display in the table. */
	private int maxrows=Integer.MAX_VALUE;

	/** Specifies the query row from which to start processing. */
	private int startrow=1;

	/** Adds a border to the table. Use only when you specify the HTMLTable attribute for the table. */
	private boolean border;

	/** Displays headers for each column, as specified in the cfcol tag. */
	private boolean colheaders;

	/** Number of spaces to insert between columns 'default is 2'. */
	private int colspacing=2;

	/** Renders the table as an HTML 3.0 table. */
	private boolean htmltable;

	/** Number of lines to use for the table header. The default is 2, which leaves one line between 
	** 		the headers and the first row of the table. */
	private int headerlines=2;

	StringBuffer header=new StringBuffer();
	StringBuffer body=new StringBuffer();

    private int initRow;

    private int count=0;
	private boolean startNewRow;

	@Override
	public void release()	{
		super.release();
		query=null;
		maxrows=Integer.MAX_VALUE;
		startrow=1;
		border=false;
		colheaders=false;
		colspacing=2;
		htmltable=false;
		headerlines=2;
		if(header.length()>0)header=new StringBuffer();
		body=new StringBuffer();
		count=0;
	}

	/** set the value query
	*  Name of the cfquery from which to draw data.
	* @param query value to set
	 * @throws PageException
	**/
	public void setQuery(String query) throws PageException	{
		this.query = Caster.toQuery(pageContext.getVariable(query));
	}

	/** set the value maxrows
	*  Maximum number of rows to display in the table.
	* @param maxrows value to set
	**/
	public void setMaxrows(double maxrows)	{
		this.maxrows=(int)maxrows;
	}

	/** set the value startrow
	*  Specifies the query row from which to start processing.
	* @param startrow value to set
	**/
	public void setStartrow(double startrow)	{
		this.startrow=(int)startrow;
		if(this.startrow<=0)this.startrow=1;
	}

	/** set the value border
	*  Adds a border to the table. Use only when you specify the HTMLTable attribute for the table.
	* @param border value to set
	**/
	public void setBorder(boolean border)	{
		this.border=border;
	}

	/** set the value colheaders
	*  Displays headers for each column, as specified in the cfcol tag.
	* @param colheaders value to set
	**/
	public void setColheaders(boolean colheaders)	{
		this.colheaders=colheaders;
	}

	/** set the value colspacing
	*  Number of spaces to insert between columns 'default is 2'.
	* @param colspacing value to set
	**/
	public void setColspacing(double colspacing)	{
		this.colspacing=(int)colspacing;
	}

	/** set the value htmltable
	*  Renders the table as an HTML 3.0 table.
	* @param htmltable value to set
	**/
	public void setHtmltable(boolean htmltable)	{
		this.htmltable=htmltable;
	}

	/** set the value headerlines
	*  Number of lines to use for the table header. The default is 2, which leaves one line between 
	* 		the headers and the first row of the table.
	* @param headerlines value to set
	**/
	public void setHeaderlines(double headerlines)	{
		this.headerlines=(int)headerlines;
		if(this.headerlines<2)this.headerlines=2;
	}


	@Override
	public int doStartTag() throws PageException	{
		startNewRow=true;
		initRow=query.getRecordcount();
		query.go(startrow,pageContext.getId());
		pageContext.undefinedScope().addQuery(query);
		return query.getRecordcount()>=startrow?EVAL_BODY_INCLUDE:SKIP_BODY;
	}

	@Override
	public void doInitBody()	{
		//if(htmltable) body.append("<tr>\n");
	}

	@Override
	public int doAfterBody() throws PageException	{
	    if(htmltable) body.append("</tr>\n");
	    else body.append('\n');
	    startNewRow=true;
	    //print.out(query.getCurrentrow()+"-"+query.getRecordcount());
		return ++count<maxrows && query.next()?EVAL_BODY_AGAIN:SKIP_BODY;
	}

	@Override
	public int doEndTag() throws PageException	{
	    try {
            _doEndTag();
        } catch (IOException e) {
            throw Caster.toPageException(e);
        }
		return EVAL_PAGE;
	}
	private void _doEndTag() throws IOException	{
	    if(htmltable) {
		    pageContext.forceWrite("<table colspacing=\""+colspacing+"\"");
		    if(border)	{
		        pageContext.forceWrite(" border=\"1\"");
		    }
		    pageContext.forceWrite(">\n");
		    if(header.length()>0) {
		        pageContext.forceWrite("<tr>\n");
		        pageContext.forceWrite(header.toString());
		        pageContext.forceWrite("</tr>\n");
		    }
		    pageContext.forceWrite(body.toString());
		    pageContext.forceWrite("</table>");
		}
	    else {
	        pageContext.forceWrite("<pre>");
	        if(header.length()>0) {
		        pageContext.forceWrite(header.toString());
		        pageContext.forceWrite(StringUtil.repeatString("\n",headerlines-1));
		    }
	        pageContext.forceWrite(body.toString());
	        pageContext.forceWrite("</pre>");
	    }
	}

    @Override
    public void doFinally() {
		try {
		    pageContext.undefinedScope().removeQuery();
			if(query!=null)query.go(initRow,pageContext.getId());
		} 
		catch (PageException e) {	}
    }

    /**
     * @param strHeader
     * @param text
     * @param align
     * @param width 
     * @throws ExpressionException
     */
    public void setCol(String strHeader, String text, short align, int width) throws ExpressionException {
    // HTML
        if(htmltable) {
            if(colheaders && count==0 && strHeader.trim().length()>0) {
                header.append("\t<th");
                addAlign(header,align);
                addWidth(header,width);
                header.append(">");
                header.append(strHeader);
                header.append("</th>\n");
            }
            if(htmltable && startNewRow) {
            	body.append("<tr>\n");
            	startNewRow=false;
            }
            
            body.append("\t<td");
            addAlign(body,align);
            addWidth(body,width);
            body.append(">");
            body.append(text);
            body.append("</td>\n");
        }
    // PRE
        else {
            if(width<0) width=20;
            if(colheaders && count==0 && strHeader.trim().length()>0) {
                addPre(header,align,strHeader,width);
            }
            addPre(body,align,text,width);
            
        }
    }

    private void addAlign(StringBuffer data,short align) {
        data.append(" align=\"");
        data.append(toStringAlign(align));
        data.append("\"");
    }
    
    private void addWidth(StringBuffer data,int width) {
        if(width>=-1) {
            data.append(" width=\"");
            data.append(width);
            data.append("%\"");
        }
    }
    
    private void addPre(StringBuffer data,short align,String value, int length) throws ExpressionException {
        if(align==ALIGN_RIGHT) data.append(RJustify.call(pageContext,value,length));
        else if(align==ALIGN_CENTER) data.append(CJustify.call(pageContext,value,length));
        else data.append(LJustify.call(pageContext,value,length));
        
    }
    
    private String toStringAlign(short align) {
        if(align==ALIGN_RIGHT) return "right";
        if(align==ALIGN_CENTER) return "center";
        return "left";
    }
}