/*
 * Copyright (c) 2005 Matthew Hall and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Matthew Hall - initial API and implementation
 */
package net.sf.paperclips;

import net.sf.paperclips.internal.PaperClipsUtil;
import net.sf.paperclips.internal.ResourcePool;
import net.sf.paperclips.internal.Util;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;

/**
 * Displays the page number and page count within the context of a
 * {@link PagePrint}. To properly display page numbers, instances of this class
 * should be created using the {@link PageNumber} argument which is passed to
 * the {@link PageDecoration#createPrint(PageNumber)} method by PagePrint.
 * <p>
 * PageNumberPrints are never greedy with layout space, even with center- or
 * right-alignment. (Greedy prints take up all the available space on the page.)
 * Therefore, when center- or right-alignment is required, it is necessary to
 * wrap the page number in a Print which will enforce the same alignment.
 * Usually this is a center:default:grow or right:default:grow column in a
 * GridPrint.
 * 
 * @author Matthew Hall
 * @see PagePrint
 * @see PageDecoration
 * @see PageNumber
 * @see PageNumberFormat
 * @see DefaultPageNumberFormat
 */
public class PageNumberPrint implements Print {
	/** The default font data for a PageNumberPrint. Value is device-dependent. */
	public static final FontData DEFAULT_FONT_DATA = new FontData();

	/** The default alignment for a PageNumberPrint. Value is SWT.LEFT. */
	public static final int DEFAULT_ALIGN = SWT.LEFT;

	/** The default text style. Value is device-dependent. */
	public static final TextStyle DEFAULT_TEXT_STYLE = new TextStyle().font(
			DEFAULT_FONT_DATA).align(DEFAULT_ALIGN);

	PageNumber pageNumber;
	TextStyle textStyle;
	PageNumberFormat format;

	/**
	 * Constructs a PageNumberPrint for the given page number.
	 * 
	 * @param pageNumber
	 *            the page number of the page this Print will appear on.
	 */
	public PageNumberPrint(PageNumber pageNumber) {
		this(pageNumber, DEFAULT_TEXT_STYLE);
	}

	/**
	 * Constructs a PageNumberPrint for the given page number and font.
	 * 
	 * @param pageNumber
	 *            the page number of the page this Print will appear on.
	 * @param fontData
	 *            the font that this Print will appear in.
	 */
	public PageNumberPrint(PageNumber pageNumber, FontData fontData) {
		this(pageNumber, DEFAULT_TEXT_STYLE.font(fontData));
	}

	/**
	 * Constructs a PageNumberPrint for the given page number and alignment.
	 * 
	 * @param pageNumber
	 *            the page number of the page this Print will appear on.
	 * @param align
	 *            the horizontal alignment of the text.
	 */
	public PageNumberPrint(PageNumber pageNumber, int align) {
		this(pageNumber, DEFAULT_TEXT_STYLE.align(align));
	}

	/**
	 * Constructs a PageNumberPrint for the given page number, font and
	 * alignment.
	 * 
	 * @param pageNumber
	 *            the page number of the page this Print will appear on.
	 * @param fontData
	 *            the font that this Print will appear in.
	 * @param align
	 *            the horizontal alignment of the text.
	 */
	public PageNumberPrint(PageNumber pageNumber, FontData fontData, int align) {
		this(pageNumber, DEFAULT_TEXT_STYLE.font(fontData).align(align));
	}

	/**
	 * Constructs a PageNumberPrint for the given page number and text style.
	 * 
	 * @param pageNumber
	 *            the page number of the page this Print will appear on.
	 * @param textStyle
	 *            the text style that this Print will appear in.
	 */
	public PageNumberPrint(PageNumber pageNumber, TextStyle textStyle) {
		setPageNumber(pageNumber);
		setTextStyle(textStyle);
		setPageNumberFormat(new DefaultPageNumberFormat());
	}

	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((pageNumber == null) ? 0 : pageNumber.hashCode());
		result = prime * result
				+ ((textStyle == null) ? 0 : textStyle.hashCode());
		result = prime * result + ((format == null) ? 0 : format.hashCode());
		return result;
	}

	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		PageNumberPrint other = (PageNumberPrint) obj;

		if (pageNumber == null) {
			if (other.pageNumber != null)
				return false;
		} else if (!pageNumber.equals(other.pageNumber))
			return false;

		if (textStyle == null) {
			if (other.textStyle != null)
				return false;
		} else if (!textStyle.equals(other.textStyle))
			return false;

		if (format == null) {
			if (other.format != null)
				return false;
		} else if (!format.equals(other.format))
			return false;

		return true;
	}

	/**
	 * Sets the page number to the argument.
	 * 
	 * @param pageNumber
	 *            the new page number.
	 */
	public void setPageNumber(PageNumber pageNumber) {
		Util.notNull(pageNumber);
		this.pageNumber = pageNumber;
	}

	/**
	 * Returns the page number of this Print.
	 * 
	 * @return the page number of this Print.
	 */
	public PageNumber getPageNumber() {
		return pageNumber;
	}

	/**
	 * Sets the text font to the argument.
	 * 
	 * @param fontData
	 *            the new text font.
	 */
	public void setFontData(FontData fontData) {
		Util.notNull(fontData);
		setTextStyle(textStyle.font(fontData));
	}

	/**
	 * Returns the text font.
	 * 
	 * @return the text font.
	 */
	public FontData getFontData() {
		return textStyle.getFontData();
	}

	/**
	 * Sets the horizontal text alignment to the argument.
	 * 
	 * @param align
	 *            the horizontal alignment. Must be one of {@link SWT#LEFT },
	 *            {@link SWT#CENTER } or {@link SWT#RIGHT }.
	 */
	public void setAlign(int align) {
		setTextStyle(textStyle.align(checkAlign(align)));
	}

	/**
	 * Returns the horizontal text alignment.
	 * 
	 * @return the horizontal text alignment.
	 */
	public int getAlign() {
		return textStyle.getAlignment();
	}

	private int checkAlign(int align) {
		return PaperClipsUtil.firstMatch(align, new int[] { SWT.LEFT,
				SWT.CENTER, SWT.RIGHT }, SWT.LEFT);
	}

	/**
	 * Returns the text style that will be used to render the page number
	 * 
	 * @return the text style that will be used to render the page number
	 */
	public TextStyle getTextStyle() {
		return textStyle;
	}

	/**
	 * Sets the text style that will be used to render the page number
	 * 
	 * @param textStyle
	 *            the text style
	 */
	public void setTextStyle(TextStyle textStyle) {
		Util.notNull(textStyle);
		this.textStyle = textStyle;
	}

	/**
	 * Sets the format that will be used to convert the page number to a text
	 * string.
	 * 
	 * @param format
	 *            the new page number format.
	 */
	public void setPageNumberFormat(PageNumberFormat format) {
		Util.notNull(format);
		this.format = format;
	}

	/**
	 * Returns the page number format. This property determines how the
	 * PageNumber will be converted into a String representing the page number.
	 * The default value of this property formats page numbers as follows:<br>
	 * 
	 * <pre>
	 * Page 1 of 5
	 * </pre>
	 * 
	 * @return the page number format.
	 */
	public PageNumberFormat getPageNumberFormat() {
		return format;
	}

	/**
	 * Sets the text color.
	 * 
	 * @param foreground
	 *            the new text color.
	 */
	public void setRGB(RGB foreground) {
		Util.notNull(foreground);
		setTextStyle(textStyle.foreground(foreground));
	}

	/**
	 * Returns the text color.
	 * 
	 * @return the text color.
	 */
	public RGB getRGB() {
		return textStyle.getForeground();
	}

	public PrintIterator iterator(Device device, GC gc) {
		return new PageNumberIterator(this, device, gc);
	}
}

class PageNumberIterator extends AbstractIterator {
	final PageNumber pageNumber;
	final TextStyle textStyle;
	final PageNumberFormat format;
	final Point size;

	boolean hasNext = true;

	PageNumberIterator(PageNumberPrint print, Device device, GC gc) {
		super(device, gc);

		this.pageNumber = print.pageNumber;
		this.textStyle = print.textStyle;
		this.format = print.format;

		// Calculate the size for the largest possible page number string.
		Font oldFont = gc.getFont();
		try {
			gc.setFont(ResourcePool.forDevice(device).getFont(
					textStyle.getFontData()));

			size = gc.textExtent(format.format(new PageNumber() {
				public int getPageCount() {
					return 9999;
				}

				public int getPageNumber() {
					return 9998;
				} // (zero-based index)
			}));
		} finally {
			gc.setFont(oldFont);
		}
	}

	PageNumberIterator(PageNumberIterator that) {
		super(that);
		this.pageNumber = that.pageNumber;
		this.textStyle = that.textStyle;
		this.format = that.format;
		this.size = that.size;
		this.hasNext = that.hasNext;
	}

	public boolean hasNext() {
		return hasNext;
	}

	public Point minimumSize() {
		return size;
	}

	public Point preferredSize() {
		return size;
	}

	public PrintPiece next(int width, int height) {
		if (width < size.x || height < size.y)
			return null;

		Point size = new Point(this.size.x, this.size.y);
		int align = textStyle.getAlignment();
		if (align == SWT.CENTER || align == SWT.RIGHT)
			size.x = width;

		PageNumberPiece piece = new PageNumberPiece(this, size);
		hasNext = false;

		return piece;
	}

	public PrintIterator copy() {
		return new PageNumberIterator(this);
	}
}

class PageNumberPiece extends AbstractPiece {
	private final PageNumber pageNumber;
	private final TextStyle textStyle;
	private final PageNumberFormat format;

	PageNumberPiece(PageNumberIterator iter, Point size) {
		super(iter, size);
		this.pageNumber = iter.pageNumber;
		this.textStyle = iter.textStyle;
		this.format = iter.format;
	}

	public void paint(final GC gc, final int x, final int y) {
		Font oldFont = gc.getFont();
		Color oldForeground = gc.getForeground();

		Point size = getSize();

		try {
			ResourcePool resources = ResourcePool.forDevice(device);
			gc.setFont(resources.getFont(textStyle.getFontData()));
			gc.setForeground(resources.getColor(textStyle.getForeground()));

			String text = format.format(pageNumber);
			gc.drawText(text, x
					+ getHorzAlignmentOffset(gc.textExtent(text).x, size.x), y,
					true);
		} finally {
			gc.setFont(oldFont);
			gc.setForeground(oldForeground);
		}
	}

	private int getHorzAlignmentOffset(int textWidth, int totalWidth) {
		int offset = 0;
		switch (textStyle.getAlignment()) {
		case SWT.CENTER:
			offset = (totalWidth - textWidth) / 2;
			break;
		case SWT.RIGHT:
			offset = totalWidth - textWidth;
			break;
		}
		return offset;
	}

	public void dispose() {
	} // Shared resources, nothing to dispose
}