/*******************************************************************************
 * 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 Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package net.sourceforge.phpdt.internal.corext.util;

import java.util.Map;

import net.sourceforge.phpdt.core.ToolFactory;
import net.sourceforge.phpdt.core.formatter.DefaultCodeFormatterConstants;
import net.sourceforge.phpdt.internal.corext.Assert;
import net.sourceforge.phpeclipse.PHPeclipsePlugin;

import org.eclipse.core.runtime.Preferences;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.Position;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;

public class CodeFormatterUtil {

	/**
	 * Creates a string that represents the given number of indents (can be
	 * spaces or tabs..)
	 */
	public static String createIndentString(int indent) {
		// axelcl change start
		// String str= format(CodeFormatter.K_EXPRESSION, "x", indent, null, "",
		// (Map) null); //$NON-NLS-1$ //$NON-NLS-2$
		String str = ToolFactory.createCodeFormatter().format("x", indent,
				null, "");
		return str.substring(0, str.indexOf('x'));
		// axelcl change end
	}

	public static int getTabWidth() {
		Preferences preferences = PHPeclipsePlugin.getDefault()
				.getPluginPreferences();
		return preferences
				.getInt(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE);
	}

	// transition code

	/**
	 * Old API. Consider to use format2 (TextEdit)
	 */
	// public static String format(int kind, String string, int
	// indentationLevel, int[] positions, String lineSeparator, Map options) {
	// return format(kind, string, 0, string.length(), indentationLevel,
	// positions, lineSeparator, options);
	// }
	//	
	// public static String format(int kind, String string, int
	// indentationLevel, int[] positions, String lineSeparator, IJavaProject
	// project) {
	// Map options= project != null ? project.getOptions(true) : null;
	// return format(kind, string, 0, string.length(), indentationLevel,
	// positions, lineSeparator, options);
	// }

	/**
	 * Old API. Consider to use format2 (TextEdit)
	 */
	// public static String format(int kind, String string, int offset, int
	// length, int indentationLevel, int[] positions, String lineSeparator, Map
	// options) {
	// TextEdit edit= format2(kind, string, offset, length, indentationLevel,
	// lineSeparator, options);
	// if (edit == null) {
	// //JavaPlugin.logErrorMessage("formatter failed to format (no edit
	// returned). Will use unformatted text instead. kind: " + kind + ", string:
	// " + string); //$NON-NLS-1$ //$NON-NLS-2$
	// return string.substring(offset, offset + length);
	// }
	// String formatted= getOldAPICompatibleResult(string, edit,
	// indentationLevel, positions, lineSeparator, options);
	// return formatted.substring(offset, formatted.length() - (string.length()
	// - (offset + length)));
	// }
	/**
	 * Old API. Consider to use format2 (TextEdit)
	 */
	// public static String format(ASTNode node, String string, int
	// indentationLevel, int[] positions, String lineSeparator, Map options) {
	//		
	// TextEdit edit= format2(node, string, indentationLevel, lineSeparator,
	// options);
	// if (edit == null) {
	// //JavaPlugin.logErrorMessage("formatter failed to format (no edit
	// returned). Will use unformatted text instead. node: " +
	// node.getNodeType() + ", string: " + string); //$NON-NLS-1$ //$NON-NLS-2$
	// return string;
	// }
	// return getOldAPICompatibleResult(string, edit, indentationLevel,
	// positions, lineSeparator, options);
	// }
	private static String getOldAPICompatibleResult(String string,
			TextEdit edit, int indentationLevel, int[] positions,
			String lineSeparator, Map options) {
		Position[] p = null;

		if (positions != null) {
			p = new Position[positions.length];
			for (int i = 0; i < positions.length; i++) {
				p[i] = new Position(positions[i], 0);
			}
		}
		String res = evaluateFormatterEdit(string, edit, p);

		if (positions != null) {
			for (int i = 0; i < positions.length; i++) {
				Position curr = p[i];
				positions[i] = curr.getOffset();
			}
		}
		return res;
	}

	/**
	 * Evaluates the edit on the given string.
	 * 
	 * @throws IllegalArgumentException
	 *             If the positions are not inside the string, a
	 *             IllegalArgumentException is thrown.
	 */
	public static String evaluateFormatterEdit(String string, TextEdit edit,
			Position[] positions) {
		try {
			Document doc = createDocument(string, positions);
			edit.apply(doc, 0);
			if (positions != null) {
				for (int i = 0; i < positions.length; i++) {
					Assert.isTrue(!positions[i].isDeleted,
							"Position got deleted"); //$NON-NLS-1$
				}
			}
			return doc.get();
		} catch (BadLocationException e) {
			PHPeclipsePlugin.log(e); // bug in the formatter
			Assert
					.isTrue(
							false,
							"Fromatter created edits with wrong positions: " + e.getMessage()); //$NON-NLS-1$
		}
		return null;
	}

	/**
	 * Creates edits that describe how to format the given string. Returns
	 * <code>null</code> if the code could not be formatted for the given
	 * kind.
	 * 
	 * @throws IllegalArgumentException
	 *             If the offset and length are not inside the string, a
	 *             IllegalArgumentException is thrown.
	 */
	// public static TextEdit format2(int kind, String string, int offset, int
	// length, int indentationLevel, String lineSeparator, Map options) {
	// if (offset < 0 || length < 0 || offset + length > string.length()) {
	// throw new IllegalArgumentException("offset or length outside of string.
	// offset: " + offset + ", length: " + length + ", string size: " +
	// string.length()); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
	// }
	//		
	// return createCodeFormatter(options).format(kind, string, offset, length,
	// indentationLevel, lineSeparator);
	//		
	// }
	// public static TextEdit format2(int kind, String string, int
	// indentationLevel, String lineSeparator, Map options) {
	// return format2(kind, string, 0, string.length(), indentationLevel,
	// lineSeparator, options);
	// }
	/**
	 * Creates edits that describe how to format the given string. Returns
	 * <code>null</code> if the code could not be formatted for the given
	 * kind.
	 * 
	 * @throws IllegalArgumentException
	 *             If the offset and length are not inside the string, a
	 *             IllegalArgumentException is thrown.
	 */
	// public static TextEdit format2(ASTNode node, String str, int
	// indentationLevel, String lineSeparator, Map options) {
	// int code;
	// String prefix= ""; //$NON-NLS-1$
	// String suffix= ""; //$NON-NLS-1$
	// if (node instanceof Statement) {
	// code= CodeFormatter.K_STATEMENTS;
	// if (node.getNodeType() == ASTNode.SWITCH_CASE) {
	// prefix= "switch(1) {"; //$NON-NLS-1$
	// suffix= "}"; //$NON-NLS-1$
	// code= CodeFormatter.K_STATEMENTS;
	// }
	// } else if (node instanceof Expression && node.getNodeType() !=
	// ASTNode.VARIABLE_DECLARATION_EXPRESSION) {
	// code= CodeFormatter.K_EXPRESSION;
	// } else {
	// switch (node.getNodeType()) {
	// case ASTNode.METHOD_DECLARATION:
	// case ASTNode.TYPE_DECLARATION:
	// case ASTNode.FIELD_DECLARATION:
	// case ASTNode.INITIALIZER:
	// code= CodeFormatter.K_CLASS_BODY_DECLARATIONS;
	// break;
	// case ASTNode.ARRAY_TYPE:
	// case ASTNode.PRIMITIVE_TYPE:
	// case ASTNode.SIMPLE_TYPE:
	// suffix= " x;"; //$NON-NLS-1$
	// code= CodeFormatter.K_EXPRESSION;
	// break;
	// case ASTNode.COMPILATION_UNIT:
	// code= CodeFormatter.K_COMPILATION_UNIT;
	// break;
	// case ASTNode.VARIABLE_DECLARATION_EXPRESSION:
	// case ASTNode.SINGLE_VARIABLE_DECLARATION:
	// suffix= ";"; //$NON-NLS-1$
	// code= CodeFormatter.K_STATEMENTS;
	// break;
	// case ASTNode.VARIABLE_DECLARATION_FRAGMENT:
	// prefix= "A "; //$NON-NLS-1$
	// suffix= ";"; //$NON-NLS-1$
	// code= CodeFormatter.K_STATEMENTS;
	// break;
	// case ASTNode.PACKAGE_DECLARATION:
	// case ASTNode.IMPORT_DECLARATION:
	// suffix= "\nclass A {}"; //$NON-NLS-1$
	// code= CodeFormatter.K_COMPILATION_UNIT;
	// break;
	// case ASTNode.JAVADOC:
	// suffix= "void foo();"; //$NON-NLS-1$
	// code= CodeFormatter.K_CLASS_BODY_DECLARATIONS;
	// break;
	// case ASTNode.CATCH_CLAUSE:
	// prefix= "try {}"; //$NON-NLS-1$
	// code= CodeFormatter.K_STATEMENTS;
	// break;
	// case ASTNode.ANONYMOUS_CLASS_DECLARATION:
	// prefix= "new A()"; //$NON-NLS-1$
	// suffix= ";"; //$NON-NLS-1$
	// code= CodeFormatter.K_STATEMENTS;
	// break;
	// case ASTNode.MEMBER_REF:
	// case ASTNode.METHOD_REF:
	// case ASTNode.METHOD_REF_PARAMETER:
	// case ASTNode.TAG_ELEMENT:
	// case ASTNode.TEXT_ELEMENT:
	// // not yet supported
	// return null;
	// default:
	// Assert.isTrue(false, "Node type not covered: " +
	// node.getClass().getName()); //$NON-NLS-1$
	// return null;
	// }
	// }
	//		
	// String concatStr= prefix + str + suffix;
	// TextEdit edit= format2(code, concatStr, prefix.length(), str.length(),
	// indentationLevel, lineSeparator, options);
	// if (prefix.length() > 0) {
	// edit= shifEdit(edit, prefix.length());
	// }
	// return edit;
	// }
	private static TextEdit shifEdit(TextEdit oldEdit, int diff) {
		TextEdit newEdit;
		if (oldEdit instanceof ReplaceEdit) {
			ReplaceEdit edit = (ReplaceEdit) oldEdit;
			newEdit = new ReplaceEdit(edit.getOffset() - diff,
					edit.getLength(), edit.getText());
		} else if (oldEdit instanceof InsertEdit) {
			InsertEdit edit = (InsertEdit) oldEdit;
			newEdit = new InsertEdit(edit.getOffset() - diff, edit.getText());
		} else if (oldEdit instanceof DeleteEdit) {
			DeleteEdit edit = (DeleteEdit) oldEdit;
			newEdit = new DeleteEdit(edit.getOffset() - diff, edit.getLength());
		} else if (oldEdit instanceof MultiTextEdit) {
			newEdit = new MultiTextEdit();
		} else {
			return null; // not supported
		}
		TextEdit[] children = oldEdit.getChildren();
		for (int i = 0; i < children.length; i++) {
			TextEdit shifted = shifEdit(children[i], diff);
			if (shifted != null) {
				newEdit.addChild(shifted);
			}
		}
		return newEdit;
	}

	private static Document createDocument(String string, Position[] positions)
			throws IllegalArgumentException {
		Document doc = new Document(string);
		try {
			if (positions != null) {
				final String POS_CATEGORY = "myCategory"; //$NON-NLS-1$

				doc.addPositionCategory(POS_CATEGORY);
				doc
						.addPositionUpdater(new DefaultPositionUpdater(
								POS_CATEGORY) {
							protected boolean notDeleted() {
								if (fOffset < fPosition.offset
										&& (fPosition.offset + fPosition.length < fOffset
												+ fLength)) {
									fPosition.offset = fOffset + fLength; // deleted
																			// positions:
																			// set
																			// to
																			// end
																			// of
																			// remove
									return false;
								}
								return true;
							}
						});
				for (int i = 0; i < positions.length; i++) {
					try {
						doc.addPosition(POS_CATEGORY, positions[i]);
					} catch (BadLocationException e) {
						throw new IllegalArgumentException(
								"Position outside of string. offset: " + positions[i].offset + ", length: " + positions[i].length + ", string size: " + string.length()); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
					}
				}
			}
		} catch (BadPositionCategoryException cannotHappen) {
			// can not happen: category is correctly set up
		}
		return doc;
	}

}
