/*******************************************************************************
 * Copyright (c) 2000, 2013 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.problem;

import java.util.Enumeration;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import org.eclipse.jdt.core.compiler.*;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.IProblemFactory;
import org.eclipse.jdt.internal.compiler.util.HashtableOfInt;
import org.eclipse.jdt.internal.compiler.util.Util;

@SuppressWarnings("rawtypes")
public class DefaultProblemFactory implements IProblemFactory {

	public HashtableOfInt messageTemplates;
	private Locale locale;
	private static HashtableOfInt DEFAULT_LOCALE_TEMPLATES;
	private final static char[] DOUBLE_QUOTES = "''".toCharArray(); //$NON-NLS-1$
	private final static char[] SINGLE_QUOTE = "'".toCharArray(); //$NON-NLS-1$
	private final static char[] FIRST_ARGUMENT = "{0}".toCharArray(); //$NON-NLS-1$

public DefaultProblemFactory() {
	this(Locale.getDefault());
}
/**
 * @param loc the locale used to get the right message
 */
public DefaultProblemFactory(Locale loc) {
	setLocale(loc);
}
/**
 * Answer a new IProblem created according to the parameters value
 * <ul>
 * <li>originatingFileName the name of the file name from which the problem is originated
 * <li>problemId the problem id
 * <li>problemArguments the fully qualified arguments recorded inside the problem
 * <li>messageArguments the arguments needed to set the error message (shorter names than problemArguments ones)
 * <li>severity the severity of the problem
 * <li>startPosition the starting position of the problem
 * <li>endPosition the end position of the problem
 * <li>lineNumber the line on which the problem occured
 * </ul>
 * @param originatingFileName char[]
 * @param problemId int
 * @param problemArguments String[]
 * @param messageArguments String[]
 * @param severity int
 * @param startPosition int
 * @param endPosition int
 * @param lineNumber int
 * @return CategorizedProblem
 */
@Override
public CategorizedProblem createProblem(
	char[] originatingFileName,
	int problemId,
	String[] problemArguments,
	String[] messageArguments,
	int severity,
	int startPosition,
	int endPosition,
	int lineNumber,
	int columnNumber) {

	return new DefaultProblem(
		originatingFileName,
		this.getLocalizedMessage(problemId, messageArguments),
		problemId,
		problemArguments,
		severity,
		startPosition,
		endPosition,
		lineNumber,
		columnNumber);
}
@Override
public CategorizedProblem createProblem(
	char[] originatingFileName,
	int problemId,
	String[] problemArguments,
	int elaborationId,
	String[] messageArguments,
	int severity,
	int startPosition,
	int endPosition,
	int lineNumber,
	int columnNumber) {
	return new DefaultProblem(
		originatingFileName,
		this.getLocalizedMessage(problemId, elaborationId, messageArguments),
		problemId,
		problemArguments,
		severity,
		startPosition,
		endPosition,
		lineNumber,
		columnNumber);
}
private final static int keyFromID(int id) {
    return id + 1; // keys are offsetted by one in table, since it cannot handle 0 key
}
/**
 * Answer the locale used to retrieve the error messages
 * @return java.util.Locale
 */
@Override
public Locale getLocale() {
	return this.locale;
}
public void setLocale(Locale locale) {
	if (locale == this.locale) return;
	this.locale = locale;
	if (Locale.getDefault().equals(locale)){
		if (DEFAULT_LOCALE_TEMPLATES == null){
			DEFAULT_LOCALE_TEMPLATES = loadMessageTemplates(locale);
		}
		this.messageTemplates = DEFAULT_LOCALE_TEMPLATES;
	} else {
		this.messageTemplates = loadMessageTemplates(locale);
	}
}
@Override
public final String getLocalizedMessage(int id, String[] problemArguments) {
	return getLocalizedMessage(id, 0, problemArguments);
}
@Override
public final String getLocalizedMessage(int id, int elaborationId, String[] problemArguments) {
	String rawMessage = (String) this.messageTemplates.get(keyFromID(id & IProblem.IgnoreCategoriesMask));
	if (rawMessage == null) {
		return "Unable to retrieve the error message for problem id: " //$NON-NLS-1$
			+ (id & IProblem.IgnoreCategoriesMask) + ". Check compiler resources.";  //$NON-NLS-1$
	}
	char[] message = rawMessage.toCharArray();
	if (elaborationId != 0) {
		String elaboration = (String) this.messageTemplates.get(keyFromID(elaborationId));
		if (elaboration == null) {
			return "Unable to retrieve the error message elaboration for elaboration id: " //$NON-NLS-1$
				+ elaborationId + ". Check compiler resources.";  //$NON-NLS-1$
		}
		message = CharOperation.replace(message, FIRST_ARGUMENT, elaboration.toCharArray());
	}

	// for compatibility with MessageFormat which eliminates double quotes in original message
	message = CharOperation.replace(message, DOUBLE_QUOTES, SINGLE_QUOTE);

	if (problemArguments == null) {
		return new String(message);
	}

	int length = message.length;
	int start = 0;
	int end = length;
	StringBuilder output = null;
	if ((id & IProblem.Javadoc) != 0) {
		output = new StringBuilder(10+length+problemArguments.length*20);
		output.append((String) this.messageTemplates.get(keyFromID(IProblem.JavadocMessagePrefix & IProblem.IgnoreCategoriesMask)));
	}
	while (true) {
		if ((end = CharOperation.indexOf('{', message, start)) > -1) {
			if (output == null) output = new StringBuilder(length+problemArguments.length*20);
			output.append(message, start, end - start);
			if ((start = CharOperation.indexOf('}', message, end + 1)) > -1) {
				try {
					output.append(problemArguments[CharOperation.parseInt(message, end + 1, start - end - 1)]);
				} catch (NumberFormatException nfe) {
					output.append(message, end + 1, start - end);
				} catch (ArrayIndexOutOfBoundsException e) {
					return "Cannot bind message for problem (id: " //$NON-NLS-1$
						+ (id & IProblem.IgnoreCategoriesMask)
						+ ") \""  //$NON-NLS-1$
						+ new String(message)
						+ "\" with arguments: {" //$NON-NLS-1$
						+ Util.toString(problemArguments)
						+"}"; //$NON-NLS-1$
				}
				start++;
			} else {
				output.append(message, end, length);
				break;
			}
		} else {
			if (output == null) {
				return new String(message);
			}
			output.append(message, start, length - start);
			break;
		}
	}

	return output.toString();
}
/**
 * @param problem CategorizedProblem
 * @return String
 */
public final String localizedMessage(CategorizedProblem problem) {
	return getLocalizedMessage(problem.getID(), problem.getArguments());
}

/**
 * This method initializes the MessageTemplates class variable according
 * to the current Locale.
 * @param loc Locale
 * @return HashtableOfInt
 */
public static HashtableOfInt loadMessageTemplates(Locale loc) {
	ResourceBundle bundle = null;
	String bundleName = "org.eclipse.jdt.internal.compiler.problem.messages"; //$NON-NLS-1$
	try {
		bundle = ResourceBundle.getBundle(bundleName, loc);
	} catch(MissingResourceException e) {
		System.out.println("Missing resource : " + bundleName.replace('.', '/') + ".properties for locale " + loc); //$NON-NLS-1$//$NON-NLS-2$
		throw e;
	}
	HashtableOfInt templates = new HashtableOfInt(700);
	Enumeration keys = bundle.getKeys();
	while (keys.hasMoreElements()) {
	    String key = (String)keys.nextElement();
	    try {
	        int messageID = Integer.parseInt(key);
			templates.put(keyFromID(messageID), bundle.getString(key));
	    } catch (NumberFormatException | MissingResourceException e) {
			// available ID
	    }
	}
	return templates;
}

}
