/*******************************************************************************
 * Copyright (c) 2000, 2008 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.wst.jsdt.internal.core;

import java.util.Map;

import org.eclipse.wst.jsdt.core.Flags;
import org.eclipse.wst.jsdt.core.IJavaScriptProject;
import org.eclipse.wst.jsdt.core.compiler.CharOperation;
import org.eclipse.wst.jsdt.core.compiler.InvalidInputException;
import org.eclipse.wst.jsdt.internal.codeassist.impl.AssistOptions;
import org.eclipse.wst.jsdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.wst.jsdt.internal.compiler.parser.Scanner;
import org.eclipse.wst.jsdt.internal.compiler.parser.ScannerHelper;
import org.eclipse.wst.jsdt.internal.compiler.parser.TerminalTokens;

public class InternalNamingConventions {
	private static final char[] DEFAULT_NAME = "name".toCharArray(); //$NON-NLS-1$

	private static Scanner getNameScanner(CompilerOptions compilerOptions) {
		return
			new Scanner(
				false /*comment*/,
				false /*whitespace*/,
				false /*nls*/,
				compilerOptions.sourceLevel /*sourceLevel*/,
				null /*taskTags*/,
				null/*taskPriorities*/,
				true/*taskCaseSensitive*/);
	}
	public static void suggestArgumentNames(IJavaScriptProject javaProject, char[] packageName, char[] qualifiedTypeName, int dim, char[] internalPrefix, char[][] excludedNames, INamingRequestor requestor) {
		Map options = javaProject.getOptions(true);
		CompilerOptions compilerOptions = new CompilerOptions(options);
		AssistOptions assistOptions = new AssistOptions(options);

		suggestNames(
			packageName,
			qualifiedTypeName,
			dim,
			internalPrefix,
			assistOptions.argumentPrefixes,
			assistOptions.argumentSuffixes,
			excludedNames,
			getNameScanner(compilerOptions),
			requestor);
	}
	public static void suggestFieldNames(IJavaScriptProject javaProject, char[] packageName, char[] qualifiedTypeName, int dim, int modifiers, char[] internalPrefix, char[][] excludedNames, INamingRequestor requestor) {
		boolean isStatic = Flags.isStatic(modifiers);

		Map options = javaProject.getOptions(true);
		CompilerOptions compilerOptions = new CompilerOptions(options);
		AssistOptions assistOptions = new AssistOptions(options);

		suggestNames(
			packageName,
			qualifiedTypeName,
			dim,
			internalPrefix,
			isStatic ? assistOptions.staticFieldPrefixes : assistOptions.fieldPrefixes,
			isStatic ? assistOptions.staticFieldSuffixes : assistOptions.fieldSuffixes,
			excludedNames,
			getNameScanner(compilerOptions),
			requestor);
	}
	public static void suggestLocalVariableNames(IJavaScriptProject javaProject, char[] packageName, char[] qualifiedTypeName, int dim, char[] internalPrefix, char[][] excludedNames, INamingRequestor requestor) {
		Map options = javaProject.getOptions(true);
		CompilerOptions compilerOptions = new CompilerOptions(options);
		AssistOptions assistOptions = new AssistOptions(options);

		suggestNames(
			packageName,
			qualifiedTypeName,
			dim,
			internalPrefix,
			assistOptions.localPrefixes,
			assistOptions.localSuffixes,
			excludedNames,
			getNameScanner(compilerOptions),
			requestor);
	}

	private static void suggestNames(
		char[] packageName,
		char[] qualifiedTypeName,
		int dim,
		char[] internalPrefix,
		char[][] prefixes,
		char[][] suffixes,
		char[][] excludedNames,
		Scanner nameScanner,
		INamingRequestor requestor){

		if(qualifiedTypeName == null || qualifiedTypeName.length == 0)
			return;

		if(internalPrefix == null) {
			internalPrefix = CharOperation.NO_CHAR;
		} else {
			internalPrefix = removePrefix(internalPrefix, prefixes);
		}

		char[] typeName = (qualifiedTypeName!=null)? CharOperation.lastSegment(qualifiedTypeName, '.'):null;

		if(prefixes == null || prefixes.length == 0) {
			prefixes = new char[1][0];
		} else {
			int length = prefixes.length;
			System.arraycopy(prefixes, 0, prefixes = new char[length+1][], 0, length);
			prefixes[length] = CharOperation.NO_CHAR;
		}

		if(suffixes == null || suffixes.length == 0) {
			suffixes = new char[1][0];
		} else {
			int length = suffixes.length;
			System.arraycopy(suffixes, 0, suffixes = new char[length+1][], 0, length);
			suffixes[length] = CharOperation.NO_CHAR;
		}

		char[][] tempNames = null;

		// compute variable name for base type
		try{
			nameScanner.setSource(typeName);
			switch (nameScanner.getNextToken()) {
				case TerminalTokens.TokenNameint :
				case TerminalTokens.TokenNamebyte :
				case TerminalTokens.TokenNameshort :
				case TerminalTokens.TokenNamechar :
				case TerminalTokens.TokenNamelong :
				case TerminalTokens.TokenNamefloat :
				case TerminalTokens.TokenNamedouble :
				case TerminalTokens.TokenNameboolean :

					if (internalPrefix != null && internalPrefix.length > 0) return;

					char[] name = computeBaseTypeNames(typeName[0], excludedNames);
					if(name != null) {
						tempNames =  new char[][]{name};
					}
					break;
			}
		} catch(InvalidInputException e){
			// ignore
		}

		// compute variable name for non base type
		if(tempNames == null) {
			tempNames = computeNames(typeName);
		}

		boolean acceptDefaultName = true;

		next : for (int i = 0; i < tempNames.length; i++) {
			char[] tempName = tempNames[i];
			if(dim > 0) {
				int length = tempName.length;
				if (tempName[length-1] == 's'){
					if(tempName.length > 1 && tempName[length-2] == 's') {
						System.arraycopy(tempName, 0, tempName = new char[length + 2], 0, length);
						tempName[length] = 'e';
						tempName[length+1] = 's';
					}
				} else if(tempName[length-1] == 'y') {
					System.arraycopy(tempName, 0, tempName = new char[length + 2], 0, length);
					tempName[length-1] = 'i';
					tempName[length] = 'e';
					tempName[length+1] = 's';
				} else {
					System.arraycopy(tempName, 0, tempName = new char[length + 1], 0, length);
					tempName[length] = 's';
				}
			}

			char[] unprefixedName = tempName;
			for (int j = 0; j <= internalPrefix.length; j++) {
				if(j == internalPrefix.length || CharOperation.prefixEquals(CharOperation.subarray(internalPrefix, j, -1), unprefixedName, false)) {
					tempName = CharOperation.concat(CharOperation.subarray(internalPrefix, 0, j), unprefixedName);
					if(j != 0) tempName[j] = ScannerHelper.toUpperCase(tempName[j]);
					for (int k = 0; k < prefixes.length; k++) {
						if(prefixes[k].length > 0
							&& ScannerHelper.isLetterOrDigit(prefixes[k][prefixes[k].length - 1])) {
							tempName[0] = ScannerHelper.toUpperCase(tempName[0]);
						} else {
							tempName[0] = ScannerHelper.toLowerCase(tempName[0]);
						}
						char[] prefixName = CharOperation.concat(prefixes[k], tempName);
						for (int l = 0; l < suffixes.length; l++) {
							char[] suffixName = CharOperation.concat(prefixName, suffixes[l]);
							suffixName =
								excludeNames(
									suffixName,
									prefixName,
									suffixes[l],
									excludedNames);
							try{
								nameScanner.setSource(suffixName);
								switch (nameScanner.getNextToken()) {
									case TerminalTokens.TokenNameIdentifier :
										int token = nameScanner.getNextToken();
										if (token == TerminalTokens.TokenNameEOF && nameScanner.startPosition == suffixName.length) {
											acceptName(suffixName, prefixes[k], suffixes[l],  k == 0, l == 0, internalPrefix.length - j, requestor);
											acceptDefaultName = false;
										}
										break;
									default:
										suffixName = CharOperation.concat(
											prefixName,
											String.valueOf(1).toCharArray(),
											suffixes[l]
										);
										suffixName =
											excludeNames(
												suffixName,
												prefixName,
												suffixes[l],
												excludedNames);
										nameScanner.setSource(suffixName);
										switch (nameScanner.getNextToken()) {
											case TerminalTokens.TokenNameIdentifier :
												token = nameScanner.getNextToken();
												if (token == TerminalTokens.TokenNameEOF && nameScanner.startPosition == suffixName.length) {
													acceptName(suffixName, prefixes[k], suffixes[l], k == 0, l == 0, internalPrefix.length - j, requestor);
													acceptDefaultName = false;
												}
										}
								}
							} catch(InvalidInputException e){
								// ignore
							}
						}
					}
					continue next;
				}
			}
		}
		// if no names were found
		if(acceptDefaultName) {
			char[] name = excludeNames(DEFAULT_NAME, DEFAULT_NAME, CharOperation.NO_CHAR, excludedNames);
			requestor.acceptNameWithoutPrefixAndSuffix(name, 0);
		}
	}

	private static void acceptName(
		char[] name,
		char[] prefix,
		char[] suffix,
		boolean isFirstPrefix,
		boolean isFirstSuffix,
		int reusedCharacters,
		INamingRequestor requestor) {
		if(prefix.length > 0 && suffix.length > 0) {
			requestor.acceptNameWithPrefixAndSuffix(name, isFirstPrefix, isFirstSuffix, reusedCharacters);
		} else if(prefix.length > 0){
			requestor.acceptNameWithPrefix(name, isFirstPrefix, reusedCharacters);
		} else if(suffix.length > 0){
			requestor.acceptNameWithSuffix(name, isFirstSuffix, reusedCharacters);
		} else {
			requestor.acceptNameWithoutPrefixAndSuffix(name, reusedCharacters);
		}
	}

	private static char[] computeBaseTypeNames(char firstName, char[][] excludedNames){
		char[] name = new char[]{firstName};

		for(int i = 0 ; i < excludedNames.length ; i++){
			if(CharOperation.equals(name, excludedNames[i], false)) {
				name[0]++;
				if(name[0] > 'z')
					name[0] = 'a';
				if(name[0] == firstName)
					return null;
				i = 0;
			}
		}

		return name;
	}

	private static char[][] computeNames(char[] sourceName){
		char[][] names = new char[5][];
		int nameCount = 0;
		boolean previousIsUpperCase = false;
		boolean previousIsLetter = true;
		for(int i = sourceName.length - 1 ; i >= 0 ; i--){
			boolean isUpperCase = ScannerHelper.isUpperCase(sourceName[i]);
			boolean isLetter = ScannerHelper.isLetter(sourceName[i]);
			if(isUpperCase && !previousIsUpperCase && previousIsLetter){
				char[] name = CharOperation.subarray(sourceName,i,sourceName.length);
				if(name.length > 1){
					if(nameCount == names.length) {
						System.arraycopy(names, 0, names = new char[nameCount * 2][], 0, nameCount);
					}
					name[0] = ScannerHelper.toLowerCase(name[0]);
					names[nameCount++] = name;
				}
			}
			previousIsUpperCase = isUpperCase;
			previousIsLetter = isLetter;
		}
		if(nameCount == 0){
			names[nameCount++] = CharOperation.toLowerCase(sourceName);
		}
		System.arraycopy(names, 0, names = new char[nameCount][], 0, nameCount);
		return names;
	}

	private static char[] excludeNames(
		char[] suffixName,
		char[] prefixName,
		char[] suffix,
		char[][] excludedNames) {
		int count = 2;
		int m = 0;
		while (m < excludedNames.length) {
			if(CharOperation.equals(suffixName, excludedNames[m], false)) {
				suffixName = CharOperation.concat(
					prefixName,
					String.valueOf(count++).toCharArray(),
					suffix
				);
				m = 0;
			} else {
				m++;
			}
		}
		return suffixName;
	}

	private static char[] removePrefix(char[] name, char[][] prefixes) {
		// remove longer prefix
		char[] withoutPrefixName = name;
		if (prefixes != null) {
			int bestLength = 0;
			int nameLength = name.length;
			for (int i= 0; i < prefixes.length; i++) {
				char[] prefix = prefixes[i];

				int prefixLength = prefix.length;
				if(prefixLength <= nameLength) {
					if(CharOperation.prefixEquals(prefix, name, false)) {
						if (prefixLength > bestLength) {
							bestLength = prefixLength;
						}
					}
				} else {
					int currLen = 0;
					for (; currLen < nameLength; currLen++) {
						if(ScannerHelper.toLowerCase(prefix[currLen]) != ScannerHelper.toLowerCase(name[currLen])) {
							if (currLen > bestLength) {
								bestLength = currLen;
							}
							break;
						}
					}
					if(currLen == nameLength && currLen > bestLength) {
						bestLength = currLen;
					}
				}
			}
			if(bestLength > 0) {
				if(bestLength == nameLength) {
					withoutPrefixName = CharOperation.NO_CHAR;
				} else {
					withoutPrefixName = CharOperation.subarray(name, bestLength, nameLength);
				}
			}
		}
//
//
//		// remove longer prefix
//		char[] withoutPrefixName = name;
//		if (prefixes != null) {
//			int bestLength = 0;
//			for (int i= 0; i < prefixes.length; i++) {
//				char[] prefix = prefixes[i];
//				int max = prefix.length < name.length ? prefix.length : name.length;
//				int currLen = 0;
//				for (; currLen < max; currLen++) {
//					if(Character.toLowerCase(prefix[currLen]) != Character.toLowerCase(name[currLen])) {
//						if (currLen > bestLength) {
//							bestLength = currLen;
//						}
//						break;
//					}
//				}
//				if(currLen == max && currLen > bestLength) {
//					bestLength = max;
//				}
//			}
//			if(bestLength > 0) {
//				if(bestLength == name.length) {
//					withoutPrefixName = CharOperation.NO_CHAR;
//				} else {
//					withoutPrefixName = CharOperation.subarray(name, bestLength, name.length);
//				}
//			}
//		}

		return withoutPrefixName;
	}

	public static final boolean prefixEquals(char[] prefix, char[] name) {

		int max = prefix.length;
		if (name.length < max)
			return false;
		for (int i = max;
			--i >= 0;
			) // assumes the prefix is not larger than the name
				if (prefix[i] != name[i])
					return false;
			return true;
	}
}
