/* ###
 * IP: GHIDRA
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ghidra.feature.vt.api.markuptype;

import static ghidra.feature.vt.gui.util.VTOptionDefines.*;

import java.util.Collection;
import java.util.List;

import ghidra.feature.vt.api.impl.MarkupItemImpl;
import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.api.util.Stringable;
import ghidra.feature.vt.api.util.VersionTrackingApplyException;
import ghidra.feature.vt.gui.plugin.VTController;
import ghidra.feature.vt.gui.util.VTMatchApplyChoices.*;
import ghidra.framework.options.Options;
import ghidra.framework.options.ToolOptions;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.util.ProgramLocation;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;

public abstract class VTMarkupType {

	static final ToolOptions VT_UNAPPLY_MARKUP_OPTIONS =
		new ToolOptions(VTController.VERSION_TRACKING_OPTIONS_NAME);
	static {
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(FUNCTION_SIGNATURE, FunctionSignatureChoices.REPLACE);
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(CALLING_CONVENTION, CallingConventionChoices.NAME_MATCH);
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(INLINE, ReplaceChoices.REPLACE);
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(NO_RETURN, ReplaceChoices.REPLACE);
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(VAR_ARGS, ReplaceChoices.REPLACE);
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(CALL_FIXUP, ReplaceChoices.REPLACE);
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(FUNCTION_RETURN_TYPE, ParameterDataTypeChoices.REPLACE);
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(PARAMETER_DATA_TYPES, ParameterDataTypeChoices.REPLACE);
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(PARAMETER_NAMES, SourcePriorityChoices.REPLACE);
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(PARAMETER_COMMENTS, CommentChoices.OVERWRITE_EXISTING);

		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(FUNCTION_NAME, FunctionNameChoices.REPLACE_ALWAYS);
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(LABELS, LabelChoices.REPLACE_ALL);

		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(PLATE_COMMENT, CommentChoices.OVERWRITE_EXISTING);
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(PRE_COMMENT, CommentChoices.OVERWRITE_EXISTING);
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(END_OF_LINE_COMMENT, CommentChoices.OVERWRITE_EXISTING);
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(REPEATABLE_COMMENT, CommentChoices.OVERWRITE_EXISTING);
		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(POST_COMMENT, CommentChoices.OVERWRITE_EXISTING);

		VT_UNAPPLY_MARKUP_OPTIONS.setEnum(DATA_MATCH_DATA_TYPE,
			ReplaceDataChoices.REPLACE_ALL_DATA);
	}

	private final String name;

	public VTMarkupType(String name) {
		this.name = name;
	}

	public String getDisplayName() {
		return name;
	}

	public abstract boolean supportsAssociationType(VTAssociationType matchType);

	public abstract List<VTMarkupItem> createMarkupItems(VTAssociation associationDB);

	public abstract boolean applyMarkup(VTMarkupItem markupItem, ToolOptions markupOptions)
			throws VersionTrackingApplyException;

	public abstract void unapplyMarkup(VTMarkupItem markupItem)
			throws VersionTrackingApplyException;

	public abstract VTMarkupItemApplyActionType getApplyAction(ToolOptions options);

	public abstract boolean supportsApplyAction(VTMarkupItemApplyActionType applyAction);

	public Address validateDestinationAddress(VTAssociation association, Address sourceAddress,
			Address suggestedDestinationAddress) {
		// normal markup migrators accept any address given
		return suggestedDestinationAddress;

	}

	public abstract ProgramLocation getSourceLocation(VTAssociation association,
			Address sourceAddress);

	public abstract Stringable getSourceValue(VTAssociation association, Address source);

	public abstract ProgramLocation getDestinationLocation(VTAssociation association,
			Address destinationAddress);

	public abstract Stringable getCurrentDestinationValue(VTAssociation association,
			Address destinationAddress);

	public abstract Stringable getOriginalDestinationValue(VTAssociation association,
			Address destinationAddress);

	protected Stringable getOriginalDestinationValueForAppliedMarkupOfThisType(
			VTAssociation association, Address destinationAddress, TaskMonitor monitor)
			throws CancelledException {

		if ((destinationAddress == null) || destinationAddress == Address.NO_ADDRESS) {
			return null;
		}

		Collection<VTMarkupItem> markupItems = association.getMarkupItems(monitor);
		for (VTMarkupItem markupItem : markupItems) {
			if ((markupItem.getMarkupType() == this) && markupItem.canUnapply()) {
				Address itemDestination = markupItem.getDestinationAddress();
				if (destinationAddress.equals(itemDestination)) {
					// Return the original destination value for the first applied 
					// markup item we find of this type at this address.
					return markupItem.getOriginalDestinationValue();
				}
			}
		}

		return null;
	}

	/**
	 * Returns true if both the source and destination have the same value such that there is
	 * nothing to apply.
	 * @param markupItem the markup item to check for having the save source and desination values.
	 * @return true if both the source and destination have the same value such that there is
	 * nothing to apply.
	 */
	public abstract boolean hasSameSourceAndDestinationValues(VTMarkupItem markupItem);

//==================================================================================================
// Program Object Convenience Methods
//==================================================================================================    

	public Program getDestinationProgram(VTAssociation association) {
		VTSession session = association.getSession();
		return session.getDestinationProgram();
	}

	public Program getSourceProgram(VTAssociation association) {
		VTSession session = association.getSession();
		return session.getSourceProgram();
	}

	public Listing getDestinationListing(VTAssociation association) {
		Program program = getDestinationProgram(association);
		return program.getListing();
	}

	public Listing getSourceListing(VTAssociation association) {
		Program program = getSourceProgram(association);
		return program.getListing();
	}

	public Function getSourceFunction(VTAssociation association) {
		VTSession session = association.getSession();
		Program program = session.getSourceProgram();
		Address sourceAddress = association.getSourceAddress();
		FunctionManager functionManager = program.getFunctionManager();
		return functionManager.getFunctionAt(sourceAddress);
	}

	public Function getDestinationFunction(VTAssociation association) {
		VTSession session = association.getSession();
		Program program = session.getDestinationProgram();
		Address destinationAddress = association.getDestinationAddress();
		FunctionManager functionManager = program.getFunctionManager();
		return functionManager.getFunctionAt(destinationAddress);
	}

	/**
	 * Get the address for the specified program location that is appropriate for this markup
	 * type to use as a source or destination address.
	 * @param loc the program location.
	 * @param program the program the location is associated with. This must be provided
	 * here as a parameter since the location usually doesn't contain the program and
	 * some markup types will need it to obtain an address.
	 * @return the appropriate address for this markup type (or null if there is no appropriate type.)
	 */
	public Address getAddress(ProgramLocation loc, Program program) {
		return loc.getAddress();
	}

	/**
	 * Determines whether applying a markup item of this type conflicts with any other markup
	 * items that are already applied.
	 * @param markupItem the markup item for this markup type.
	 * @param markupItems the markup items to check to see if any conflict with this markup type.
	 * @return true if another markup items conflicts with the one of this markup type.
	 */
	public boolean conflictsWithOtherMarkup(MarkupItemImpl markupItem,
			Collection<VTMarkupItem> markupItems) {
		return false;
	}

	/**
	 * Creates a new options object from the options that are passed to this method. The options 
	 * will be modified so that the specified apply action will occur for this markup type if
	 * it is a valid action for this markup.
	 * @param applyAction the desired apply action (ADD, ADD_AS_PRIMARY, REPLACE_DEFAULT_ONLY, 
	 * and REPLACE) for the markup type.
	 * @param applyOptions the original options settings that are to be modified.
	 * @return a new Options object that has been changed to result in the action for this markup type.
	 */
	public abstract Options convertOptionsToForceApplyOfMarkupItem(
			VTMarkupItemApplyActionType applyAction, ToolOptions applyOptions);
}