/*
 * Copyright (c) 2007 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.Util;

import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;

/**
 * A abstract GridLookPainter which simplifies implementation of custom
 * GridLooks.
 * <p>
 * Subclasses must have the following methods implemented:
 * <ul>
 * <li>getMargins() - these margins are referenced by GridPrint for determining
 * proper layout of the cells, as well as by the paint() method.
 * <li>paintHeaderCell() - will be called by the paint() method for each header
 * cell.
 * <li>paintBodyCell() - will be called by the paint() method for each body
 * cell.
 * <li>paintFooterCell() - will be called by the paint() method for each footer
 * cell.
 * <li>dispose() - must dispose any SWT resources created by the subclass.
 * </ul>
 * 
 * @author Matthew Hall
 */
public abstract class BasicGridLookPainter implements GridLookPainter {
	/**
	 * The printer device on which the look is being painted. This is the device
	 * that was passed as an argument to the constructor.
	 */
	protected final Device device;

	/**
	 * Constructs a BasicGridLook painter.
	 * 
	 * @param device
	 *            the printer device (may not be null). This argument will be
	 *            saved in the protected {@link #device} field.
	 */
	public BasicGridLookPainter(Device device) {
		Util.notNull(device);
		this.device = device;
	}

	public void paint(GC gc, int x, int y, int[] columns, int[] headerRows,
			int[][] headerColSpans, int firstRowIndex, boolean topOpen,
			int[] bodyRows, int[][] bodyColSpans, boolean bottomOpen,
			int[] footerRows, int[][] footerColSpans) {
		GridMargins margins = getMargins();

		final boolean headerPresent = headerRows.length > 0;
		final boolean footerPresent = footerRows.length > 0;

		x += margins.getLeft();

		if (headerPresent)
			y = paintHeader(gc, x, y, columns, headerRows, headerColSpans);

		y += margins.getBodyTop(headerPresent, topOpen);
		y = paintBody(gc, x, y, columns, bodyRows, bodyColSpans, firstRowIndex,
				topOpen, bottomOpen);
		y += margins.getBodyBottom(footerPresent, bottomOpen);

		if (footerPresent)
			paintFooter(gc, x, y, columns, footerRows, footerColSpans);
	}

	private int paintHeader(GC gc, int x, int y, int[] columns, int[] rows,
			int[][] colSpans) {
		GridMargins margins = getMargins();

		y += margins.getHeaderTop();

		for (int i = 0; i < rows.length; i++) {
			int h = rows[i];

			paintHeaderRow(gc, x, y, columns, h, i, colSpans[i]);

			y += h;
			if (i < rows.length - 1)
				y += margins.getHeaderVerticalSpacing();
		}

		return y;
	}

	private int paintBody(GC gc, int x, int y, int[] columns, int[] rows,
			int[][] colSpans, int firstRowIndex, boolean topOpen,
			boolean bottomOpen) {
		GridMargins margins = getMargins();

		for (int i = 0; i < rows.length; i++) {
			final int h = rows[i];

			paintBodyRow(gc, x, y, columns, h, colSpans[i], firstRowIndex + i,
					i == 0 && topOpen, i == rows.length - 1 && bottomOpen);

			y += h;
			if (i < rows.length - 1)
				y += margins.getBodyVerticalSpacing();
		}
		return y;
	}

	private void paintFooter(GC gc, final int x, int y, int[] columns,
			int[] rows, int[][] colSpans) {
		GridMargins margins = getMargins();

		for (int i = 0; i < rows.length; i++) {
			final int h = rows[i];

			paintFooterRow(gc, x, y, columns, h, i, colSpans[i]);

			y += h;
			y += margins.getFooterVerticalSpacing();
		}
	}

	private void paintHeaderRow(GC gc, int x, int y, int[] columns,
			final int h, int rowIndex, int[] colSpans) {
		GridMargins margins = getMargins();

		int col = 0;
		for (int i = 0; i < colSpans.length; i++) {
			final int colSpan = colSpans[i];
			final int w = sum(columns, col, colSpan) + (colSpan - 1)
					* margins.getHorizontalSpacing();

			paintHeaderCell(gc, new Rectangle(x, y, w, h), rowIndex, col,
					colSpan);

			col += colSpan;
			x += w + margins.getHorizontalSpacing();
		}
	}

	private void paintBodyRow(GC gc, int x, int y, int[] columns, final int h,
			int[] colSpans, int rowIndex, final boolean topOpen,
			final boolean bottomOpen) {
		GridMargins margins = getMargins();

		int col = 0;
		for (int i = 0; i < colSpans.length; i++) {
			final int colSpan = colSpans[i];
			final int w = sum(columns, col, colSpan) + (colSpan - 1)
					* margins.getHorizontalSpacing();

			paintBodyCell(gc, new Rectangle(x, y, w, h), rowIndex, col,
					colSpan, topOpen, bottomOpen);

			col += colSpan;
			x += w + margins.getHorizontalSpacing();
		}
	}

	private void paintFooterRow(GC gc, int x, int y, int[] columns,
			final int h, int rowIndex, int[] colSpans) {
		GridMargins margins = getMargins();

		int col = 0;
		for (int i = 0; i < colSpans.length; i++) {
			final int colSpan = colSpans[i];
			final int w = sum(columns, col, colSpan) + (colSpan - 1)
					* margins.getHorizontalSpacing();

			paintFooterCell(gc, new Rectangle(x, y, w, h), rowIndex, col,
					colSpan);

			col += colSpan;
			x += w + margins.getHorizontalSpacing();
		}
	}

	private int sum(int[] elements, int start, int length) {
		int sum = 0;
		for (int j = 0; j < length; j++)
			sum += elements[start + j];
		return sum;
	}

	/**
	 * Paint the decorations for the described header cell.
	 * 
	 * @param gc
	 *            the graphics context to use for painting.
	 * @param bounds
	 *            the bounds of the cell, excluding margins.
	 * @param row
	 *            the row offset of the cell within the header.
	 * @param col
	 *            the column offset of the cell within the header.
	 * @param colspan
	 *            the number of columns that this cell spans.
	 */
	protected abstract void paintHeaderCell(GC gc, Rectangle bounds, int row,
			int col, int colspan);

	/**
	 * Paint the decorations for the described body cell.
	 * 
	 * @param gc
	 *            the graphics context to use for painting.
	 * @param bounds
	 *            the bounds of the cell, excluding margins.
	 * @param row
	 *            the row offset of the cell within the header.
	 * @param col
	 *            the column offset of the cell within the header.
	 * @param colspan
	 *            the number of columns that this cell spans.
	 * @param topOpen
	 *            whether the cell should be drawn with the top edge of the cell
	 *            border "open." An open top border is a visual cue that the
	 *            cell is being continued from the previous page.
	 * @param bottomOpen
	 *            whether the cell should be drawn with the bottom edge of the
	 *            cell border "open." An open bottom border is a visual cue that
	 *            the cell will be continued on the next page.
	 */
	protected abstract void paintBodyCell(GC gc, Rectangle bounds, int row,
			int col, int colspan, boolean topOpen, boolean bottomOpen);

	/**
	 * Paint the decorations for the described footer cell.
	 * 
	 * @param gc
	 *            the graphics context to use for painting.
	 * @param bounds
	 *            the bounds of the cell, excluding margins.
	 * @param row
	 *            the row offset of the cell within the header.
	 * @param col
	 *            the column offset of the cell within the header.
	 * @param colspan
	 *            the number of columns that this cell spans.
	 */
	protected abstract void paintFooterCell(GC gc, Rectangle bounds, int row,
			int col, int colspan);
}