/*******************************************************************************
 * Copyright (c) 2008, 2021 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.pde.api.tools.internal.problems;

import java.util.Arrays;

import org.eclipse.core.runtime.IPath;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.provisional.descriptors.IElementDescriptor;
import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem;
import org.eclipse.pde.api.tools.internal.util.Util;

/**
 * A description of an API problem
 *
 * @since 1.0.0
 */
public class ApiProblem implements IApiProblem {

	/**
	 * Human readable message for the problem
	 */
	private String fMessage = null;
	/**
	 * The resource path for this problem
	 */
	private String fResourcePath = null;
	/**
	 * The qualified type name for this problem
	 */
	private String fTypeName = null;
	/**
	 * The composite id of the problem. Contains the category, element kind,
	 * kind, and flags for a specific problem
	 */
	private int fId = 0;
	/**
	 * The listing of extra argument ids for the problem
	 */
	private String[] fExtraArgumentIds = null;
	/**
	 * The listing of corresponding arguments for the problem
	 */
	private Object[] fExtraArguments = null;
	/**
	 * The listing of arguments used to fill localized messages
	 */
	private String[] fMessageArguments = null;
	/**
	 * The line number the problem occurred on
	 */
	private int fLineNumber = -1;
	/**
	 * The start of a character selection range
	 */
	private int fCharStart = -1;
	/**
	 * The end of a character selection range
	 */
	private int fCharEnd = -1;

	/**
	 * Masks to get the original bits out of the id
	 */
	public static final int CATEGORY_MASK = 0xF0000000;
	public static final int ELEMENT_KIND_MASK = 0x0F000000;
	public static final int KIND_MASK = 0x00F00000;
	public static final int FLAGS_MASK = 0x000FF000;
	public static final int MESSAGE_MASK = 0x00000FFF;

	/**
	 * Constant representing the name of the 'compatibilityStatus' attribute on
	 * API problem for the compatibility category. Value is:
	 * <code>compatibilityStatus</code>
	 */
	public static final String COMPATIBILITY_STATUS = "compatibilityStatus"; //$NON-NLS-1$

	/**
	 * Constructor
	 *
	 * @param path        the resource this problem occurs on / in
	 * @param typeName    the qualified type name this problem occurs on / in
	 * @param messageargs arguments to be passed into a localized message for the
	 *                    problem
	 * @param argumentids the ids of arguments passed into the problem
	 * @param arguments   the arguments that correspond to the listing of ids
	 * @param linenumber  the line this problem occurred on
	 * @param charstart   the char selection start position
	 * @param charend     the char selection end position
	 * @param id          the id of the problem
	 */
	public ApiProblem(String path, String typeName, String[] messageargs, String[] argumentids, Object[] arguments, int linenumber, int charstart, int charend, int id) {
		this.fResourcePath = path;
		this.fTypeName = typeName;
		this.fId = id;
		this.fExtraArgumentIds = argumentids;
		this.fExtraArguments = arguments;
		this.fLineNumber = linenumber;
		this.fCharStart = charstart;
		this.fCharEnd = charend;
		this.fMessageArguments = messageargs;
	}

	@Override
	public int getId() {
		return fId;
	}

	@Override
	public int getCategory() {
		return (fId & CATEGORY_MASK);
	}

	@Override
	public int getMessageid() {
		return (fId & MESSAGE_MASK) >> OFFSET_MESSAGE;
	}

	@Override
	public int getFlags() {
		return (fId & FLAGS_MASK) >> OFFSET_FLAGS;
	}

	@Override
	public int getKind() {
		return (fId & KIND_MASK) >> OFFSET_KINDS;
	}

	@Override
	public String getMessage() {
		if (fMessage == null) {
			fMessage = ApiProblemFactory.getLocalizedMessage(this);
		}
		return fMessage;
	}

	@Override
	public String getResourcePath() {
		return fResourcePath;
	}

	@Override
	public int getSeverity() {
		if (ApiPlugin.isRunningInFramework()) {
			return ApiPlugin.getDefault().getSeverityLevel(ApiProblemFactory.getProblemSeverityId(this), null);
		}
		return ApiPlugin.SEVERITY_WARNING;
	}

	@Override
	public int getElementKind() {
		return (fId & ELEMENT_KIND_MASK) >> OFFSET_ELEMENT;
	}

	@Override
	public int getLineNumber() {
		return fLineNumber;
	}

	@Override
	public int getCharStart() {
		return fCharStart;
	}

	@Override
	public int getCharEnd() {
		return fCharEnd;
	}

	@Override
	public String[] getExtraMarkerAttributeIds() {
		if (fExtraArguments == null || fExtraArgumentIds == null) {
			return new String[0];
		}
		if (fExtraArgumentIds.length != fExtraArguments.length) {
			return new String[0];
		}
		return fExtraArgumentIds;
	}

	@Override
	public Object[] getExtraMarkerAttributeValues() {
		if (fExtraArguments == null || fExtraArgumentIds == null) {
			return new String[0];
		}
		if (fExtraArgumentIds.length != fExtraArguments.length) {
			return new String[0];
		}
		return fExtraArguments;
	}

	@Override
	public String[] getMessageArguments() {
		if (fMessageArguments == null) {
			return new String[0];
		}
		return fMessageArguments;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof IApiProblem problem) {
			if (problem.getId() == fId && argumentsEqual(problem.getMessageArguments())) {
				String resourcePath = problem.getResourcePath();
				if (resourcePath == null) {
					if (this.fResourcePath != null) {
						return false;
					}
				} else if (this.fResourcePath == null) {
					return false;
				} else if (!IPath.fromOSString(resourcePath).equals(IPath.fromOSString(fResourcePath))) {
					return false;
				}
				String typeName = problem.getTypeName();
				if (typeName == null) {
					if (this.fTypeName != null) {
						return false;
					}
					return true;
				} else if (this.fTypeName == null) {
					return false;
				}
				return typeName.equals(this.fTypeName);
			}
			return false;
		}
		return super.equals(obj);
	}

	/**
	 * Compares the complete list of message arguments
	 *
	 * @return true if all of the arguments are equal, false otherwise
	 */
	private boolean argumentsEqual(String[] arguments) {
		String[] currentArguments = getMessageArguments();
		if ((currentArguments == null && arguments != null) || (currentArguments != null && arguments == null)) {
			return false;
		}
		boolean equal = true;
		if (currentArguments.length != arguments.length) {
			return false;
		}
		for (int i = 0; i < currentArguments.length; i++) {
			equal &= currentArguments[i].equals(arguments[i]);
		}
		return equal;
	}


	@Override
	public String toString() {
		StringBuilder buffer = new StringBuilder();
		buffer.append("API problem: "); //$NON-NLS-1$
		buffer.append(fResourcePath != null ? fResourcePath : "no path").append("\n"); //$NON-NLS-1$//$NON-NLS-2$
		buffer.append("\tseverity: "); //$NON-NLS-1$
		buffer.append(Util.getSeverity(getSeverity())).append("\n"); //$NON-NLS-1$
		buffer.append("\tcategory: "); //$NON-NLS-1$
		buffer.append(ApiProblem.getProblemCategory(getCategory())).append("\n"); //$NON-NLS-1$
		buffer.append("\telement kind: "); //$NON-NLS-1$
		buffer.append(ApiProblem.getProblemElementKind(getCategory(), getElementKind())).append("\n"); //$NON-NLS-1$
		buffer.append("\tkind: "); //$NON-NLS-1$
		buffer.append(ApiProblem.getProblemKind(getCategory(), getKind())).append("\n"); //$NON-NLS-1$
		buffer.append("\tflags: "); //$NON-NLS-1$
		buffer.append(ApiProblem.getProblemFlagsName(getCategory(), getFlags())).append("\n"); //$NON-NLS-1$
		buffer.append("\tmessage id: "); //$NON-NLS-1$
		buffer.append(getMessageid());
		buffer.append("\n\tmessage: "); //$NON-NLS-1$
		buffer.append(getMessage());
		return buffer.toString();
	}

	@Override
	public int hashCode() {
		return getId() + (fResourcePath != null ? fResourcePath.hashCode() : 0) + Arrays.hashCode(fMessageArguments)
				+ (fTypeName != null ? fTypeName.hashCode() : 0);
	}

	@Override
	public String getTypeName() {
		return this.fTypeName;
	}

	/**
	 * Returns the string representation of an element descriptor type or
	 * <code>null</code> if the kind is unknown
	 *
	 * @return the string of the kind or <code>null</code>
	 */
	public static String getDescriptorKind(int kind) {
		switch (kind) {
			case IElementDescriptor.PACKAGE: {
				return "PACKAGE"; //$NON-NLS-1$
			}
			case IElementDescriptor.FIELD: {
				return "FIELD"; //$NON-NLS-1$
			}
			case IElementDescriptor.METHOD: {
				return "METHOD"; //$NON-NLS-1$
			}
			case IElementDescriptor.TYPE: {
				return "REFERENCE_TYPE"; //$NON-NLS-1$
			}
			case IElementDescriptor.RESOURCE: {
				return "RESOURCE"; //$NON-NLS-1$
			}
			default: {
				return Util.UNKNOWN_ELEMENT_KIND;
			}
		}
	}

	/**
	 * Returns the string representation of the kind of since tab api problem
	 *
	 * @return the string for the since tag api problem kind
	 */
	public static String getTagsProblemKindName(int kind) {
		switch (kind) {
			case IApiProblem.SINCE_TAG_INVALID:
				return "INVALID_SINCE_TAGS"; //$NON-NLS-1$
			case IApiProblem.SINCE_TAG_MALFORMED:
				return "MALFORMED_SINCE_TAGS"; //$NON-NLS-1$
			case IApiProblem.SINCE_TAG_MISSING:
				return "MISSING_SINCE_TAGS"; //$NON-NLS-1$
			default:
				return Util.UNKNOWN_KIND;
		}
	}

	/**
	 * Returns the string representation of the kind of usage problem for an
	 * {@link IApiProblem} kind
	 *
	 * @return the string for the usage API problem kind
	 */
	public static String getUsageProblemKindName(int kind) {
		switch (kind) {
			case IApiProblem.ILLEGAL_EXTEND:
				return "ILLEGAL_EXTEND"; //$NON-NLS-1$
			case IApiProblem.ILLEGAL_IMPLEMENT:
				return "ILLEGAL_IMPLEMENT"; //$NON-NLS-1$
			case IApiProblem.ILLEGAL_INSTANTIATE:
				return "ILLEGAL_INSTANTIATE"; //$NON-NLS-1$
			case IApiProblem.ILLEGAL_REFERENCE:
				return "ILLEGAL_REFERENCE"; //$NON-NLS-1$
			case IApiProblem.ILLEGAL_OVERRIDE:
				return "ILLEGAL_OVERRIDE"; //$NON-NLS-1$
			case IApiProblem.API_LEAK:
				return "API_LEAK"; //$NON-NLS-1$
			case IApiProblem.UNSUPPORTED_TAG_USE:
				return "UNSUPPORTED_TAG_USE"; //$NON-NLS-1$
			case IApiProblem.UNSUPPORTED_ANNOTATION_USE:
				return "UNSUPPORTED_ANNOTATION_USE"; //$NON-NLS-1$
			case IApiProblem.INVALID_REFERENCE_IN_SYSTEM_LIBRARIES:
				return "INVALID_REFERENCE_IN_SYSTEM_LIBRARIES"; //$NON-NLS-1$
			case IApiProblem.UNUSED_PROBLEM_FILTERS:
				return "UNUSED_PROBLEM_FILTERS"; //$NON-NLS-1$
			default:
				return Util.UNKNOWN_KIND;
		}
	}

	/**
	 * Returns the string representation of the version problem kind.
	 *
	 * @return the string of the version API problem kind
	 */
	public static String getVersionProblemKindName(int kind) {
		switch (kind) {
			case IApiProblem.MINOR_VERSION_CHANGE:
				return "MINOR_VERSION_CHANGE"; //$NON-NLS-1$
			case IApiProblem.MAJOR_VERSION_CHANGE:
				return "MAJOR_VERSION_CHANGE"; //$NON-NLS-1$
			case IApiProblem.MAJOR_VERSION_CHANGE_NO_BREAKAGE:
				return "MAJOR_VERSION_CHANGE_NO_BREAKAGE"; //$NON-NLS-1$
			case IApiProblem.MINOR_VERSION_CHANGE_NO_NEW_API:
			case IApiProblem.MICRO_VERSION_CHANGE_UNNECESSARILY:
			case IApiProblem.MINOR_VERSION_CHANGE_UNNECESSARILY:
				return "MINOR_VERSION_CHANGE_NO_NEW_API"; //$NON-NLS-1$
			case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE:
				return "REEXPORTED_MAJOR_VERSION_CHANGE"; //$NON-NLS-1$
			case IApiProblem.REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE:
				return "REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE"; //$NON-NLS-1$
			case IApiProblem.REEXPORTED_MINOR_VERSION_CHANGE:
				return "REEXPORTED_MINOR_VERSION_CHANGE"; //$NON-NLS-1$
			default:
				return Util.UNKNOWN_KIND;
		}
	}

	/**
	 * Returns the string representation of the API component problem kind
	 *
	 * @return the string of the API component problem kind
	 */
	public static String getApiComponentResolutionProblemKindName(int kind) {
		switch (kind) {
			case IApiProblem.API_COMPONENT_RESOLUTION:
				return "API_COMPONENT_RESOLUTION"; //$NON-NLS-1$
			default:
				return Util.UNKNOWN_KIND;
		}

	}

	/**
	 * Returns the string representation of the API baseline problem kind
	 *
	 * @return the string of the API baseline problem kind
	 */
	public static String getApiBaselineProblemKindName(int kind) {
		switch (kind) {
			case IApiProblem.API_BASELINE_MISSING:
			case IApiProblem.API_BASELINE_MISMATCH:
				return "API_BASELINE_MISSING"; //$NON-NLS-1$
			case IApiProblem.API_PLUGIN_NOT_PRESENT_IN_BASELINE:
				return "API_NOT_BASELINE"; //$NON-NLS-1$

			default:
				return Util.UNKNOWN_KIND;
		}

	}

	/**
	 * Returns the string representation of the API Use Scan breakage problem
	 * kind
	 */
	public static String getApiUseScanProblemKindName(int kind) {
		switch (kind) {
			case IApiProblem.API_USE_SCAN_TYPE_PROBLEM:
				return "API_USE_SCAN_TYPE_PROBLEM"; //$NON-NLS-1$
			case IApiProblem.API_USE_SCAN_METHOD_PROBLEM:
				return "API_USE_SCAN_METHOD_PROBLEM"; //$NON-NLS-1$
			case IApiProblem.API_USE_SCAN_FIELD_PROBLEM:
				return "API_USE_SCAN_FIELD_PROBLEM"; //$NON-NLS-1$
			default:
				return Util.UNKNOWN_KIND;
		}

	}

	/**
	 * Returns the string representation of the kind of an {@link IApiProblem},
	 * given its category
	 *
	 * @return the string of the {@link IApiProblem} kind
	 */
	public static String getProblemKind(int category, int kind) {
		switch (category) {
			case IApiProblem.CATEGORY_COMPATIBILITY:
				return Util.getDeltaKindName(kind);
			case IApiProblem.CATEGORY_SINCETAGS:
				return ApiProblem.getTagsProblemKindName(kind);
			case IApiProblem.CATEGORY_USAGE:
				return ApiProblem.getUsageProblemKindName(kind);
			case IApiProblem.CATEGORY_VERSION:
				return ApiProblem.getVersionProblemKindName(kind);
			case IApiProblem.CATEGORY_API_BASELINE:
				return ApiProblem.getApiBaselineProblemKindName(kind);
			case IApiProblem.CATEGORY_API_COMPONENT_RESOLUTION:
				return ApiProblem.getApiComponentResolutionProblemKindName(kind);
			case IApiProblem.CATEGORY_API_USE_SCAN_PROBLEM:
				return ApiProblem.getApiUseScanProblemKindName(kind);
			default:
				return Util.UNKNOWN_KIND;
		}
	}

	/**
	 * Return the string representation of the flags for a problem
	 *
	 * @return the string for the problem flags
	 */
	public static String getProblemFlagsName(int category, int flags) {
		switch (category) {
			case IApiProblem.CATEGORY_COMPATIBILITY:
				return Util.getDeltaFlagsName(flags);
			case IApiProblem.CATEGORY_SINCETAGS:
			case IApiProblem.CATEGORY_USAGE:
			case IApiProblem.CATEGORY_VERSION:
			case IApiProblem.CATEGORY_API_BASELINE:
			case IApiProblem.CATEGORY_API_COMPONENT_RESOLUTION: {
				switch (flags) {
					case IApiProblem.LEAK_EXTENDS:
						return "LEAK_EXTENDS"; //$NON-NLS-1$
					case IApiProblem.LEAK_FIELD:
						return "LEAK_FIELD"; //$NON-NLS-1$
					case IApiProblem.LEAK_IMPLEMENTS:
						return "LEAK_IMPLEMENTS"; //$NON-NLS-1$
					case IApiProblem.LEAK_METHOD_PARAMETER:
						return "LEAK_METHOD_PARAMETER"; //$NON-NLS-1$
					case IApiProblem.LEAK_CONSTRUCTOR_PARAMETER:
						return "LEAK_CONSTRUCTOR_PARAMETER"; //$NON-NLS-1$
					case IApiProblem.LEAK_RETURN_TYPE:
						return "LEAK_RETURN_TYPE"; //$NON-NLS-1$
					case IApiProblem.CONSTRUCTOR_METHOD:
						return "CONSTRUCTOR_METHOD"; //$NON-NLS-1$
					case IApiProblem.NO_FLAGS:
						return "NO_FLAGS"; //$NON-NLS-1$
					case IApiProblem.INDIRECT_REFERENCE:
						return "INDIRECT_REFERENCE"; //$NON-NLS-1$
					case IApiProblem.METHOD:
						return "METHOD"; //$NON-NLS-1$
					case IApiProblem.FIELD:
						return "FIELD"; //$NON-NLS-1$
					default:
						return Util.UNKNOWN_FLAGS;
				}
			}
			case IApiProblem.CATEGORY_API_USE_SCAN_PROBLEM: {
				switch (flags) {
					case IApiProblem.API_USE_SCAN_DELETED:
						return "DELETED"; //$NON-NLS-1$
					case IApiProblem.API_USE_SCAN_UNRESOLVED:
						return "UNRESOLVED"; //$NON-NLS-1$
					default:
						return Util.UNKNOWN_FLAGS;
				}
			}
			default:
				return Util.UNKNOWN_FLAGS;
		}
	}

	/**
	 * Returns the string representation of the element kind of an
	 * {@link IApiProblem}, given its category
	 *
	 * @return the string of the {@link IApiProblem} element kind
	 */
	public static String getProblemElementKind(int category, int kind) {
		switch (category) {
			case IApiProblem.CATEGORY_COMPATIBILITY:
			case IApiProblem.CATEGORY_SINCETAGS: {
				return Util.getDeltaElementType(kind);
			}
			case IApiProblem.CATEGORY_USAGE:
			case IApiProblem.CATEGORY_VERSION:
			case IApiProblem.CATEGORY_API_BASELINE:
			case IApiProblem.CATEGORY_API_COMPONENT_RESOLUTION: {
				return ApiProblem.getDescriptorKind(kind);
			}
			case IApiProblem.CATEGORY_API_USE_SCAN_PROBLEM: {
				return Util.getApiElementType(kind);
			}
			default:
				return Util.UNKNOWN_KIND;
		}

	}

	/**
	 * Returns a string representation of the category of an API problem
	 *
	 * @return the string of the API problem category
	 */
	public static String getProblemCategory(int category) {
		switch (category) {
			case IApiProblem.CATEGORY_COMPATIBILITY:
				return "COMPATIBILITY"; //$NON-NLS-1$
			case IApiProblem.CATEGORY_SINCETAGS:
				return "SINCETAGS"; //$NON-NLS-1$
			case IApiProblem.CATEGORY_USAGE:
				return "USAGE"; //$NON-NLS-1$
			case IApiProblem.CATEGORY_VERSION:
				return "VERSION"; //$NON-NLS-1$
			case IApiProblem.CATEGORY_API_BASELINE:
				return "API_BASELINE"; //$NON-NLS-1$
			case IApiProblem.CATEGORY_API_COMPONENT_RESOLUTION:
				return "API_COMPONENT_RESOLUTION"; //$NON-NLS-1$
			case IApiProblem.CATEGORY_API_USE_SCAN_PROBLEM:
				return "API_USE_SCAN_PROBLEM"; //$NON-NLS-1$
			default:
				return "UNKNOWN_CATEGORY"; //$NON-NLS-1$
		}
	}
}
