/******************************************************************************
 * Product: Adempiere ERP & CRM Smart Business Solution                       *
 * Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved.                *
 * This program is free software; you can redistribute it and/or modify it    *
 * under the terms version 2 of the GNU General Public License as published   *
 * by the Free Software Foundation. This program 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.           *
 * See the GNU General Public License for more details.                       *
 * You should have received a copy of the GNU General Public License along    *
 * with this program; if not, write to the Free Software Foundation, Inc.,    *
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.                     *
 * For the text or an alternative of this public license, you may reach us    *
 * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA        *
 * or via info@compiere.org or http://www.compiere.org/license.html           *
 *****************************************************************************/
package org.compiere.print.layout;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.geom.Point2D;
import java.awt.image.ImageObserver;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;

import org.compiere.model.MQuery;
import org.compiere.print.MPrintFormatItem;
import org.compiere.print.PrintData;
import org.compiere.util.CLogger;

/**
 *  Abstract base class for Print Element
 *
 *  @author     Jorg Janke
 *  @version    $Id: PrintElement.java,v 1.2 2006/07/30 00:53:02 jjanke Exp $
 */
public abstract class PrintElement implements ImageObserver, Serializable
{
	/**
	 * generated serial id
	 */
	private static final long serialVersionUID = 5894090289966933777L;

	/**
	 *  Constructor
	 */
	protected PrintElement ()
	{
	}   //  PrintElement

	/**	Link Color					*/
	public static final Color		LINK_COLOR = Color.blue;

	/**	Calculated Size of Element	*/
	protected float					p_width = 0f;
	protected float					p_height = 0f;
	protected boolean				p_sizeCalculated = false;
	/**	Max Size of Element			*/
	protected float					p_maxWidth = 0f;
	protected float					p_maxHeight = 0f;
	/** Field Align Type			*/
	protected String 				p_FieldAlignmentType;
	/**	Location on Page			*/
	protected Point2D.Double		p_pageLocation = null;
	/** Loading Flag				*/
	private boolean					m_imageNotLoaded = true;
	
	/**	Logger			*/
	protected CLogger	log = CLogger.getCLogger(getClass());

	private Page m_currentPage;

	private PrintData m_printData;

	private int m_rowIndex = -1;

	private String m_pageLogic;
		
	/**
	 * 	Get Calculated Width
	 * 	@return Width
	 */
	public float getWidth()
	{
		if (!p_sizeCalculated)
			p_sizeCalculated = calculateSize();
		return p_width;
	}	//	getWidth

	/**
	 * 	Get Calculated Height
	 * 	@return Height
	 */
	public float getHeight()
	{
		if (!p_sizeCalculated)
			p_sizeCalculated = calculateSize();
		return p_height;
	}	//	getHeight

	/**
	 * 	Get Calculated Height on page
	 *  @param pageNo page number
	 * 	@return Height
	 */
	public float getHeight (int pageNo)
	{
		return getHeight();
	}	//	getHeight

	/**
	 * 	Get number of pages
	 * 	@return page count (1)
	 */
	public int getPageCount()
	{
		return 1;
	}	//	getPageCount

	/**
	 * 	Layout and Calculate Size
	 * 	@return true if calculated
	 */
	protected abstract boolean calculateSize();

	/**
	 * 	Layout Element
	 * 	@param maxWidth max width
	 * 	@param maxHeight max height
	 * 	@param isHeightOneLine just one line
	 * 	@param FieldAlignmentType alignment type (MPrintFormatItem.FIELD_ALIGN_*)
	 */
	public void layout (float maxWidth, float maxHeight, boolean isHeightOneLine,
		String FieldAlignmentType)
	{
		if (isHeightOneLine)
			p_maxHeight = -1f;
		else if (maxHeight > 0f)
			p_maxHeight = maxHeight;
		p_maxWidth = maxWidth;
		//
		p_FieldAlignmentType = FieldAlignmentType;
		if (p_FieldAlignmentType == null || p_FieldAlignmentType.equals(MPrintFormatItem.FIELDALIGNMENTTYPE_Default))
			p_FieldAlignmentType = MPrintFormatItem.FIELDALIGNMENTTYPE_LeadingLeft;
		//
		p_sizeCalculated = calculateSize();
	}	//	layout

	/**
	 * 	Set Maximum Height
	 * 	@param maxHeight maximum height (0) is no limit
	 */
	public void setMaxHeight (float maxHeight)
	{
		p_maxHeight = maxHeight;
	}	//	setMaxHeight

	/**
	 * 	Set Maximum Width
	 * 	@param maxWidth maximum width (0) is no limit
	 */
	public void setMaxWidth (float maxWidth)
	{
		p_maxWidth = maxWidth;
	}	//	setMaxWidth

	/**
	 * 	Set Location within page.<br/>
	 * 	Called from LayoutEngine.layoutForm(), layout(), createStandardFooterHeader()
	 * 	@param pageLocation location within page
	 */
	public void setLocation (Point2D pageLocation)
	{
		p_pageLocation = new Point2D.Double(pageLocation.getX(), pageLocation.getY());
	}	//	setLocation

	/**
	 * 	Get Location within page
	 * 	@return location within page
	 */
	public Point2D getLocation()
	{
		return p_pageLocation;
	}	//	getLocation

	/**
	 * 	Return Absolute Position
	 * 	@param pageStart start of page
	 * 	@return absolute position
	 */
	protected Point2D.Double getAbsoluteLocation(Point2D pageStart)
	{
		Point2D.Double retValue = new Point2D.Double(
			p_pageLocation.x + pageStart.getX(), p_pageLocation.y + pageStart.getY());
		return retValue;
	}	//	getAbsoluteLocation

	/**
	 * 	Get relative Bounds of Element
	 * 	@return bounds 
	 */
	public Rectangle getBounds()
	{
		if (p_pageLocation == null)
			return new Rectangle (0,0, (int)p_width, (int)p_height);
		return new Rectangle ((int)p_pageLocation.x, (int)p_pageLocation.y, (int)p_width, (int)p_height);
	}	//	getBounds

	/**
	 * 	Get Drill Down Query
	 * 	@param relativePoint point to find print element
	 *  @param pageNo page number
	 * 	@return null (subclass to overwrite)
	 */
	public MQuery getDrillDown (Point relativePoint, int pageNo)
	{
		return null;
	}	//	getDrillDown

	/**
	 * 	Get Drill Across Query
	 * 	@param relativePoint point to find print element
	 *  @param pageNo page number
	 * 	@return null (subclass to overwrite)
	 */
	public MQuery getDrillAcross (Point relativePoint, int pageNo)
	{
		return null;
	}	//	getDrillAcross
	
	/**
	 * 	Translate Content if required. <br/>
	 *  If content is translated, the element needs to stay in the bounds
	 *  of the originally calculated size and need to align the field.
	 * 	@param ctx context
	 */
	public void translate (Properties ctx)
	{
	//	noop
	}	//	translate

	/**
	 * 	Content is translated
	 * 	@return false
	 */
	public boolean isTranslated()
	{
		return false;
	}	//	translate
	
	/**
	 * 	Paint/Print.
	 * 	@param g2D Graphics
	 *  @param pageNo page number for multi page support (0 = header/footer)
	 *  @param pageStart top left Location of page
	 *  @param ctx context
	 *  @param isView true if online view (IDs are links)
	 */
	public abstract void paint (Graphics2D g2D, int pageNo, Point2D pageStart, Properties ctx, boolean isView);
	
	/**
	 * 	Image Observer
	 * 	@param img image
	 * 	@param infoflags Observer flags
	 * 	@param x x coordinate
	 * 	@param y y coordinate
	 * 	@param width image width
	 * 	@param height image height
	 * 	@return false if the infoflags indicate that the image is completely loaded; true otherwise
	 */
	@Override
	public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height)
	{
		//	copied from java.awt.component
		m_imageNotLoaded = (infoflags & (ALLBITS|ABORT)) == 0;
		if (log.isLoggable(Level.FINEST)) log.finest("Flags=" + infoflags
				+ ", x=" + x + ", y=" + y + ", width=" + width + ", height=" + height 
				+ " - NotLoaded=" + m_imageNotLoaded);
		return m_imageNotLoaded;
	}	//	imageUpdate

	/**
	 * 	Wait until Image is loaded.
	 * 	@param image image
	 * 	@return true if loaded
	 */
	public boolean waitForLoad (Image image)
	{
		long start = System.currentTimeMillis();
		Thread.yield();
		int count = 0;
		try
		{
			Toolkit toolkit = Toolkit.getDefaultToolkit();
			while (!toolkit.prepareImage(image, -1, -1, this))	//	ImageObserver calls imageUpdate
			{
				//	Timeout
				if (count > 1000)	//	about 20+ sec overall
				{
					log.severe (this + " - Timeout - " 
						+ (System.currentTimeMillis()-start) + "ms - #" + count);
					return false;
				}
				try
				{
					if (count < 10)
						Thread.sleep(10);
					else if (count < 100)
						Thread.sleep(15);
					else
						Thread.sleep(20);
				}
				catch (InterruptedException ex)
				{
					log.log(Level.SEVERE, "", ex);
					break;
				}
				count++;
			}
		}
		catch (Exception e)		//	java.lang.SecurityException
		{
			log.log(Level.SEVERE, "", e);
			return false;
		}
		if (count > 0)
			if (log.isLoggable(Level.FINE)) log.fine((System.currentTimeMillis()-start) + "ms - #" + count);
		return true;
	}	//	waitForLoad

	/**
	 * 	Get Detail Info
	 *	@return detail info (empty string, subclass to overwrite)
	 */
	protected String getDetailInfo()
	{
		return "";
	}	//	getDetailInfo
	
	/**
	 * 	String Representation
	 * 	@return info
	 */
	@Override
	public String toString()
	{
		String cn = getClass().getName();
		StringBuilder sb = new StringBuilder();
		sb.append(cn.substring(cn.lastIndexOf('.')+1)).append("[");
		sb.append("Bounds=").append(getBounds())
			.append(",Height=").append(p_height).append("(").append(p_maxHeight)
			.append("),Width=").append(p_width).append("(").append(p_maxHeight)
			.append("),PageLocation=").append(p_pageLocation);
		sb.append("]");
		return sb.toString();
	}	//	toString

	/**
	 * Set current page
	 * @param page
	 */
	public void setCurrentPage(Page page) 
	{
		m_currentPage = page;
	}

	/**
	 * Get current page
	 * @return page
	 */
	protected Page getCurrentPage()
	{
		return m_currentPage;
	}

	/**
	 * Set print data
	 * @param printData
	 */
	public void setPrintData(PrintData printData) 
	{
		m_printData = printData;
	}
	
	/**
	 * Get print data
	 * @return print data
	 */
	public PrintData getPrintData() 
	{
		return m_printData;
	}

	/**
	 * Set row index
	 * @param row
	 */
	public void setRowIndex(int row) 
	{
		m_rowIndex = row;
	}
	
	/**
	 * Get row index
	 * @return row index
	 */
	public int getRowIndex()
	{
		return m_rowIndex;
	}

	/**
	 * Set page logic expression
	 * @param displayLogic
	 */
	public void setPageLogic(String displayLogic) 
	{
		m_pageLogic = displayLogic;
	}
	
	/**
	 * Get page logic expression
	 * @return page logic expression
	 */
	public String getPageLogic()
	{
		return m_pageLogic;
	}
}   //  PrintElement
