/*******************************************************************************
 * Copyright (c) 2000, 2008 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.corext.refactoring.nls;

import java.util.Properties;

import org.eclipse.core.runtime.Assert;


public class NLSSubstitution {
	public static final int EXTERNALIZED= 0;
	public static final int IGNORED= 1;
	public static final int INTERNALIZED= 2;

	public static final int DEFAULT= EXTERNALIZED;
	public static final int STATE_COUNT= 3;

	private int fState;
	private String fKey;
	/**
	 * @since 3.1
	 */
	private String fCachedPrefixPlusKey;
	private String fValue;

	private int fInitialState;
	private String fInitialKey;
	private String fInitialValue;

	private NLSElement fNLSElement;
	private AccessorClassReference fAccessorClassReference;

	private String fNewAccessorClassName;

	private String fPrefix= ""; //$NON-NLS-1$


	public NLSSubstitution(int state, String value, NLSElement element) {
		fNLSElement= element;
		fValue= value;
		fState= state;
		fInitialState= state;
		fInitialValue= value;
		fCachedPrefixPlusKey= null;
		Assert.isTrue(state == EXTERNALIZED || state == IGNORED || state == INTERNALIZED);
	}

	/**
	 * @param state initial state of the substitution
	 * @param key initial key
	 * @param value the externalized value, or <b>null</b> to indicate a corrupt substitution
	 * @param element the element to externalize
	 * @param accessorClassReference the accessor class descriptor
	 */
	public NLSSubstitution(int state, String key, String value, NLSElement element, AccessorClassReference accessorClassReference) {
		this(state,value,element);
		if (state != EXTERNALIZED) {
			throw new IllegalArgumentException("Set to INTERNALIZE/IGNORED State with different Constructor"); //$NON-NLS-1$
		}
		fKey= key;
		fInitialKey= key;
		fAccessorClassReference= accessorClassReference;
		fNewAccessorClassName= null;
	}

	//util
	public static int countItems(NLSSubstitution[] elems, int task) {
		Assert.isTrue(task == NLSSubstitution.EXTERNALIZED || task == NLSSubstitution.IGNORED || task == NLSSubstitution.INTERNALIZED);
		int result= 0;
		for (NLSSubstitution substitution : elems) {
			if (substitution.fState == task) {
				result++;
			}
		}
		return result;
	}

	public NLSElement getNLSElement() {
		return fNLSElement;
	}

	public String getKeyWithoutPrefix() {
		return fKey;
	}

	/**
	 * Returns key dependent on state.
	 * @return prefix + key when
	 */
	public String getKey() {
		if ((fState == EXTERNALIZED) && hasStateChanged()) {
			if (fCachedPrefixPlusKey == null) {
				int length= 0;
				if (fPrefix != null)
					length= length + fPrefix.length();
				if (fKey != null)
					length= length + fKey.length();
				StringBuilder sb= new StringBuilder(length);
				sb.append(fPrefix);
				sb.append(fKey);
				fCachedPrefixPlusKey= sb.toString();
			}
			return fCachedPrefixPlusKey;
		}
		return fKey;
	}

	public void setKey(String key) {
		if (fState != EXTERNALIZED) {
			throw new IllegalStateException("Must be in Externalized State !"); //$NON-NLS-1$
		}
		fCachedPrefixPlusKey= null;
		fKey= key;
	}

	public void setValue(String value) {
		fValue= value;
	}

	public void setInitialValue(String value) {
		fInitialValue= value;
	}

	/**
	 * @return the value or <b>null</b> if none
	 */
	public String getValue() {
		return fValue;
	}

	public String getValueNonEmpty() {
		if (fValue == null) {
			return ""; //$NON-NLS-1$
		}
		return fValue;
	}

	public int getState() {
		return fState;
	}

	public void setState(int state) {
		fCachedPrefixPlusKey= null;
		fState= state;
	}

	public void setUpdatedAccessor(String accessorClassName) {
		fNewAccessorClassName= accessorClassName;
	}

	public String getUpdatedAccessor() {
		return fNewAccessorClassName;
	}

	public boolean hasStateChanged() {
		return fState != fInitialState;
	}

	public boolean isKeyRename() {
		return 	(fInitialKey != null && !fInitialKey.equals(fKey));
	}

	public boolean isValueRename() {
		return 	(fInitialValue != null && !fInitialValue.equals(fValue));
	}

	public boolean isAccessorRename() {
		return (fAccessorClassReference != null) && (fNewAccessorClassName != null) && !fNewAccessorClassName.equals(fAccessorClassReference.getName());
	}


	public boolean hasPropertyFileChange() {
		if (fInitialState != EXTERNALIZED && fState != EXTERNALIZED) {
			return false;
		}
		if (fInitialState != fState) {
			return true;
		}
		if (fState == EXTERNALIZED) {
			if (fInitialValue == null) {
				return true; // recreate entry in property file
			} else if (!fInitialValue.equals(fValue)) {
				return true; // change of value
			}
			if (!fInitialKey.equals(fKey)) {
				return true; // change of key
			}
		}
		return false;
	}

	public boolean hasAccessorClassChange() {
		if (fInitialState != EXTERNALIZED && fState != EXTERNALIZED) {
			return false;
		}
		if (fInitialState != fState) {
			return true;
		}
		if (fState == EXTERNALIZED) {
			if (fInitialValue == null) {
				return true; // recreate entry in property file
			} else if (!fInitialValue.equals(fValue)) {
				return false; // change of value
			}
			if (!fInitialKey.equals(fKey)) {
				return true; // change of key
			}
		}
		return false;
	}

	public boolean hasSourceChange() {
		if (hasStateChanged()) {
			return true;
		}
		if (fState == EXTERNALIZED) {
			if (!fInitialKey.equals(fKey)) {
				return true; // change of key
			}
			if (isAccessorRename()) {
				return true;
			}
		} else {
			if (!fInitialValue.equals(fValue)) {
				return true; // change of value
			}
		}
		return false;
	}

	public int getInitialState() {
		return fInitialState;
	}

	public String getInitialKey() {
		return fInitialKey;
	}

	public String getInitialValue() {
		return fInitialValue;
	}

	public AccessorClassReference getAccessorClassReference() {
		return fAccessorClassReference;
	}

	/**
	 * @param prefix the prefix to use for the key
	 */
	public void setPrefix(String prefix) {
		fPrefix= prefix;
		fCachedPrefixPlusKey= null;
	}

	public boolean isConflicting(NLSSubstitution[] substitutions) {
		if (fState == EXTERNALIZED) {
			String currKey= getKey();
			String currValue= getValueNonEmpty();
			for (NLSSubstitution substitution : substitutions) {
				if (substitution != this && substitution.getState() == EXTERNALIZED) {
					// same key but different value
					if (currKey.equals(substitution.getKey()) && !currValue.equals(substitution.getValueNonEmpty())) {
						return true;
					}
				}
			}
		}
		return false;
	}

	private String internalGetKeyWithoutPrefix() {
		if (fState == EXTERNALIZED && fPrefix != null && fKey != null && fKey.indexOf(fPrefix) == 0)
			return fKey.substring(fPrefix.length());
		return fKey;
	}

	public void generateKey(NLSSubstitution[] substitutions, Properties properties) {
		if (fState != EXTERNALIZED || ((fState == EXTERNALIZED) && hasStateChanged())) {
			int number= 0;
			while (true) {
				String ithKey= createKey(number);
				if (!properties.containsKey(fPrefix + ithKey) && !containsKey(substitutions, ithKey)) {
					fCachedPrefixPlusKey= null;
					fKey= ithKey;
					return;
				}
				number++;
			}
		}
	}

	private boolean containsKey(NLSSubstitution[] substitutions, String key) {
		for (NLSSubstitution substitution : substitutions) {
			if (substitution == this || substitution.fState != EXTERNALIZED)
				continue;

			if (key.equals(substitution.internalGetKeyWithoutPrefix()))
				return true;
		}

		return false;
	}

	public static void updateSubtitutions(NLSSubstitution[] substitutions, Properties props, String accessorClassName) {
		for (NLSSubstitution substitution : substitutions) {
			if ((substitution.getState() == NLSSubstitution.EXTERNALIZED) && !substitution.hasStateChanged()) {
				substitution.setInitialValue(props.getProperty(substitution.getKey()));
				substitution.setUpdatedAccessor(accessorClassName);
			}
		}
	}

	public void revert() {
		fState= fInitialState;
		fKey= fInitialKey;
		fCachedPrefixPlusKey= null;
		fValue= fInitialValue;
	}

	private String createKey(int counter) {
		return String.valueOf(counter);
	}

}
