/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.jdt.internal.formatter.comment;

import java.util.LinkedList;

/**
 * General comment line in a comment region.
 * 
 * @since 3.0
 */
public abstract class CommentLine implements IBorderAttributes {

	/** Prefix of non-formattable comment lines */
	protected static final String NON_FORMAT_START_PREFIX= "/*-"; //$NON-NLS-1$

	/** The attributes of this line */
	private int fAttributes= 0;

	/** The parent region of this line */
	private final CommentRegion fParent;

	/** The comment ranges in this line */
	private final LinkedList fRanges= new LinkedList();

	/**
	 * Creates a new comment line.
	 * 
	 * @param parent comment region to create the comment line for
	 */
	protected CommentLine(final CommentRegion parent) {
		fParent= parent;
	}

	/**
	 * Adapts the line attributes from the previous line in the comment
	 * region.
	 * 
	 * @param previous the previous comment line in the comment region
	 */
	protected abstract void adapt(final CommentLine previous);

	/**
	 * Appends the specified comment range to this comment line.
	 * 
	 * @param range comment range to append to this line
	 */
	protected void append(final CommentRange range) {
		fRanges.add(range);
	}

	/**
	 * Formats this comment line as content line.
	 * 
	 * @param predecessor the predecessor comment line in the comment region
	 * @param last the most recently processed comment range
	 * @param indentation the indentation of the comment region
	 * @param line the index of this comment line in the comment region
	 * @return the first comment range in this comment line
	 */
	protected CommentRange formatLine(final CommentLine predecessor, final CommentRange last, final String indentation, final int line) {

		int offset= 0;
		int length= 0;

		CommentRange next= last;
		CommentRange previous= null;

		final int stop= fRanges.size() - 1;
		final int end= fParent.getSize() - 1;

		for (int index= stop; index >= 0; index--) {

			previous= next;
			next= (CommentRange)fRanges.get(index);

			if (fParent.canFormat(previous, next)) {

				offset= next.getOffset() + next.getLength();
				length= previous.getOffset() - offset;

				if (index == stop && line != end)
					fParent.logEdit(fParent.getDelimiter(predecessor, this, previous, next, indentation), offset, length);
				else
					fParent.logEdit(fParent.getDelimiter(previous, next), offset, length);
			}
		}
		return next;
	}

	/**
	 * Formats this comment line as end line having a lower border
	 * consisting of content line prefixes.
	 * 
	 * @param range last comment range of the last comment line in the
	 *                comment region
	 * @param indentation the indentation of the comment region
	 * @param length the maximal length of text in this comment region
	 *                measured in average character widths
	 */
	protected void formatLowerBorder(final CommentRange range, final String indentation, final int length) {

		final int offset= range.getOffset() + range.getLength();

		final StringBuffer buffer= new StringBuffer(length);
		final String end= getEndingPrefix();
		final String delimiter= fParent.getDelimiter();

		if (fParent.isSingleLine() && fParent.getSize() == 1)
			buffer.append(end);
		else {

			final String filler= getContentPrefix().trim();

			buffer.append(delimiter);
			buffer.append(indentation);

			if (fParent.hasBorder(BORDER_LOWER)) {

				buffer.append(' ');
				for (int character= 0; character < length; character++)
					buffer.append(filler);

				buffer.append(end.trim());

			} else
				buffer.append(end);
		}
		fParent.logEdit(buffer.toString(), offset, fParent.getLength() - offset);
	}

	/**
	 * Formats this comment line as start line having an upper border
	 * consisting of content line prefixes.
	 * 
	 * @param range the first comment range in the comment region
	 * @param indentation the indentation of the comment region
	 * @param length the maximal length of text in this comment region
	 *                measured in average character widths
	 */
	protected void formatUpperBorder(final CommentRange range, final String indentation, final int length) {

		final StringBuffer buffer= new StringBuffer(length);
		final String start= getStartingPrefix();
		final String content= getContentPrefix();

		if (fParent.isSingleLine() && fParent.getSize() == 1)
			buffer.append(start);
		else {

			final String trimmed= start.trim();
			final String filler= content.trim();

			buffer.append(trimmed);

			if (fParent.hasBorder(BORDER_UPPER)) {

				for (int character= 0; character < length - trimmed.length() + start.length(); character++)
					buffer.append(filler);
			}

			buffer.append(fParent.getDelimiter());
			buffer.append(indentation);
			buffer.append(content);
		}
		fParent.logEdit(buffer.toString(), 0, range.getOffset());
	}

	/**
	 * Returns the line prefix of content lines.
	 * 
	 * @return line prefix of content lines
	 */
	protected abstract String getContentPrefix();

	/**
	 * Returns the line prefix of end lines.
	 * 
	 * @return line prefix of end lines
	 */
	protected abstract String getEndingPrefix();

	/**
	 * Returns the first comment range in this comment line.
	 * 
	 * @return the first comment range
	 */
	protected final CommentRange getFirst() {
		return (CommentRange)fRanges.getFirst();
	}

	/**
	 * Returns the indentation reference string for this line.
	 * 
	 * @return the indentation reference string for this line
	 */
	protected String getIndentationReference() {
		return ""; //$NON-NLS-1$
	}

	/**
	 * Returns the last comment range in this comment line.
	 * 
	 * @return the last comment range
	 */
	protected final CommentRange getLast() {
		return (CommentRange)fRanges.getLast();
	}

	/**
	 * Returns the parent comment region of this comment line.
	 * 
	 * @return the parent comment region
	 */
	protected final CommentRegion getParent() {
		return fParent;
	}

	/**
	 * Returns the number of comment ranges in this comment line.
	 * 
	 * @return the number of ranges in this line
	 */
	protected final int getSize() {
		return fRanges.size();
	}

	/**
	 * Returns the line prefix of start lines.
	 * 
	 * @return line prefix of start lines
	 */
	protected abstract String getStartingPrefix();

	/**
	 * Is the attribute <code>attribute</code> true?
	 * 
	 * @param attribute the attribute to get.
	 * @return <code>true</code> iff this attribute is <code>true</code>,
	 *         <code>false</code> otherwise.
	 */
	protected final boolean hasAttribute(final int attribute) {
		return (fAttributes & attribute) == attribute;
	}

	/**
	 * Scans this comment line for comment range boundaries.
	 * 
	 * @param line the index of this line in the comment region
	 */
	protected abstract void scanLine(final int line);

	/**
	 * Set the attribute <code>attribute</code> to true.
	 * 
	 * @param attribute the attribute to set.
	 */
	protected final void setAttribute(final int attribute) {
		fAttributes |= attribute;
	}

	/**
	 * Tokenizes this comment line into comment ranges
	 * 
	 * @param line the index of this line in the comment region
	 */
	protected void tokenizeLine(final int line) {

		int offset= 0;
		int index= offset;

		final CommentRange range= (CommentRange)fRanges.get(0);
		final int begin= range.getOffset();

		final String content= fParent.getText(begin, range.getLength());
		final int length= content.length();

		while (offset < length) {

			while (offset < length && Character.isWhitespace(content.charAt(offset)))
				offset++;

			index= offset;

			while (index < length && !Character.isWhitespace(content.charAt(index)))
				index++;

			if (index - offset > 0) {
				fParent.append(new CommentRange(begin + offset, index - offset));

				offset= index;
			}
		}
	}
}
