/* ###
 * 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.app.cmd.data;

import ghidra.app.cmd.data.rtti.RttiUtil;
import ghidra.app.util.datatype.microsoft.DataApplyOptions;
import ghidra.app.util.datatype.microsoft.DataValidationOptions;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.Msg;
import ghidra.util.exception.*;

/**
 * This command will create a TypeDescriptor data type. Since unsized arrays are not properly
 * handled due to the current data type API limitations, this creates a dynamic RTTI0DataType.
 */
public class CreateTypeDescriptorBackgroundCmd
		extends AbstractCreateDataBackgroundCmd<TypeDescriptorModel> {

	private static final String RTTI_0_NAME = "RTTI Type Descriptor";

	/**
	 * Constructs a command for applying a TypeDescriptor data type at an address using the
	 * default validation and apply options.
	 * @param address the address where the data should be created using the data type.
	 */
	public CreateTypeDescriptorBackgroundCmd(Address address) {
		super(TypeDescriptorModel.DATA_TYPE_NAME, address, 1);
	}

	/**
	 * Constructs a command for applying a TypeDescriptor data type at an address using the
	 * indicated options.
	 * @param address the address where the data should be created using the data type.
	 * @param validationOptions the options for controlling how validation is performed when
	 * determining whether or not to create the data structure at the indicated address.
	 * @param applyOptions the options for creating the new data structure and its associated
	 * markup in the program as well as whether to follow other data references and create their
	 * data too.
	 */
	public CreateTypeDescriptorBackgroundCmd(Address address,
			DataValidationOptions validationOptions, DataApplyOptions applyOptions) {
		super(TypeDescriptorModel.DATA_TYPE_NAME, address, 1, validationOptions, applyOptions);
	}

	/**
	 * Constructs a command for applying a TypeDescriptor data type at the address indicated
	 * by the model and using the indicated options.
	 * @param model the model indicating the TypeDescriptor data to be created by this command.
	 * @param applyOptions the options for creating the new data structure and its associated
	 * markup in the program as well as whether to follow other data references and create their
	 * data too.
	 */
	public CreateTypeDescriptorBackgroundCmd(TypeDescriptorModel model,
			DataApplyOptions applyOptions) {
		super(model, applyOptions);
	}

	private void loadModel(Program program) {
		if (model == null || program != model.getProgram()) {
			model = new TypeDescriptorModel(program, getDataAddress(), validationOptions);
		}
	}

	@Override
	protected TypeDescriptorModel createModel(Program program) {
		if (model == null) {
			loadModel(program);
		}
		return model;
	}

	/**
	 * Create the data corresponding to a RTTI0 TypeDescriptor structure which contains a flexible-array
	 * as its last component ( char[0]  name ).  The string data associated with this flexible char array will
	 * be applied as a sized character array immediately following the structure whose size does not include
	 * the char array bytes.
	 * @return false if the data type was not created because it already exists, true otherwise
	 * @throws CodeUnitInsertionException if creating data throws exception
	 * @throws CancelledException if cancelled
	 */
	@Override
	protected boolean createData() throws CodeUnitInsertionException, CancelledException {
		if (!super.createData()) { // create the TypeDesciptor structure 
			return false;
		}

		// Determine the size of the flexible char array storage and create  properly sized array
		DataType dataType = model.getDataType();
		int structLen = dataType.getLength();
		Address arrayAddr = model.getAddress().add(structLen);
		DataType charArray =
			new ArrayDataType(CharDataType.dataType, model.getDataTypeLength() - structLen, 1);

		// Create 'name' char[0] data at the address immediately following structure
		Program program = model.getProgram();
		Data nameData = DataUtilities.createData(program, arrayAddr, charArray,
			charArray.getLength(), getClearDataMode());

		nameData.setComment(CommentType.EOL, "TypeDescriptor.name");

		return true;
	}

	@Override
	protected boolean createAssociatedData() throws CancelledException {

		// No associated data to create.
		return true;
	}

	@Override
	protected boolean createMarkup() throws CancelledException, InvalidInputException {

		monitor.checkCancelled();

		Program program = model.getProgram();
		String demangledName = model.getDemangledTypeDescriptor();

		// if cannot demangle then use the mangled name
		if (demangledName == null) {
			demangledName = model.getOriginalTypename();
		}
		String prefix = demangledName + " ";

		// Plate Comment
		EHDataTypeUtilities.createPlateCommentIfNeeded(program, prefix, RTTI_0_NAME, null,
			getDataAddress(), applyOptions);

		monitor.checkCancelled();

		// Label
		Namespace classNamespace = model.getDescriptorAsNamespace();

		// if cannot demangle then use the mangled name as the namespace
		if (classNamespace == null || classNamespace.isGlobal()) {
			Msg.error(RttiUtil.class, "Cannot get demangled namespace from model " +
				model.getAddress() + " so will use the mangled name for the namespace");
			try {
				classNamespace = program.getSymbolTable()
						.getOrCreateNameSpace(program.getGlobalNamespace(), demangledName,
							SourceType.IMPORTED);
			}
			catch (DuplicateNameException e) {
				// ok if it is duplicate as it was likely created in another rtti handling method
			}
			catch (InvalidInputException e) {
				Msg.error(TypeDescriptorModel.class,
					"Failed to create mangled namespace: " + e.getMessage());
				classNamespace = null;
				return false;
			}

		}

		// If PDB had been run, then the namespace here might already have been promoted to
		//  a class type.  At this point in processing, we know that the model only has a type
		//  with a "class" or "struct" tag (see TypeDescriptorModel).
		// <br>Note: For now this assumes all classes and structs with RTTI data must
		// actually be classes. In the future this might need additional checking before
		// promoting some "struct" ref types to being a class, if we can better determine
		// whether or not they are actually classes.
		if (!(classNamespace instanceof GhidraClass)) {
			classNamespace = RttiUtil.promoteToClassNamespace(program, classNamespace);
		}

		// Make the symbol even if the namespace couldn't be promoted
		// the method to promote spits out debug error if it cannot be promoted
		EHDataTypeUtilities.createSymbolIfNeeded(program, classNamespace, RTTI_0_NAME,
			getDataAddress(), applyOptions);

		return true;
	}

}
