/* ###
 * 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.util.bin.format.pe.resource;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import ghidra.app.util.bin.StructConverter;
import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;

/**
 * A class to represent the VS_VERSION_CHILD data structure which generally corresponds 
 * to either StringFileInfo or VarFileInfo.  Only a single instance of each childName
 * is expected.
 */
public class VS_VERSION_CHILD implements StructConverter {
	private String parentName;
	private long relativeOffset; // offset relative to start of parent structure
	private String childName;
	private short childSize;
	private short childValueSize;
	private short childValueType;

	private String childDataType;

	private int valueAlignment;
	private String childValue; // will be null if this has children

	private ArrayList<VS_VERSION_CHILD> children;

	VS_VERSION_CHILD(FactoryBundledWithBinaryReader reader, long relativeOffset, String parentName,
			HashMap<String, String> valueMap) throws IOException {
		this.relativeOffset = relativeOffset;
		this.parentName = parentName;
		long origIndex = reader.getPointerIndex();

		childSize = reader.readNextShort();

		if (childSize == 0) {
			return;
		}

		childValueSize = reader.readNextShort();
		childValueType = reader.readNextShort();

		childName = reader.readNextUnicodeString();

		valueAlignment = reader.align(4);

		boolean hasChildren = false;
		if (parentName == null) {
			childDataType = childName;
			hasChildren = true;
		}
		else if ("StringFileInfo".equals(parentName)) {
			childDataType = "StringTable";
			hasChildren = true;
		}
		else if ("VarFileInfo".equals(parentName)) {
			childDataType = "Var";
			if (childValueSize > 0) {
				childValue = Integer.toHexString(reader.readNextInt());
			}
		}
		else if ("StringTable".equals(parentName)) {
			// Should be called "String" but this may conflict with other String types
			// Also, we have seen some PE's where the childValueType of this is 0, so we can't
			// rely on that to know if we should read an integer or a string.  This field is 
			// always a string regardless of the specified type.
			childDataType = "StringInfo";
			if (childValueSize > 0) {
				childValue = reader.readNextUnicodeString();
			}
		}


		if (hasChildren) {
			while (reader.getPointerIndex() < origIndex + childSize) {
				VS_VERSION_CHILD child = new VS_VERSION_CHILD(reader,
					reader.getPointerIndex() - origIndex, childDataType, valueMap);
				if (children == null) {
					children = new ArrayList<VS_VERSION_CHILD>();
				}
				children.add(child);
			}
		}
		else {
			if (childValueSize > 0 && childValue != null) {
				valueMap.put(childName, childValue);
			}
		}
	}

	@Override
	public DataType toDataType() throws DuplicateNameException {
		if (childName == null || childDataType == null) {
			return null;
		}
		StructureDataType struct = new StructureDataType(childDataType, 0);
		struct.add(WORD, "wLength", null);
		struct.add(WORD, "wValueLength", null);
		struct.add(WORD, "wType", null);
		struct.setCategoryPath(new CategoryPath("/PE"));
		return struct;
	}

	/**
	 * Returns the array of children
	 * @return the array of children
	 */
	public VS_VERSION_CHILD[] getChildren() {
		VS_VERSION_CHILD[] arr = new VS_VERSION_CHILD[children.size()];
		children.toArray(arr);
		return arr;
	}

	/**
	 * Return structure offset relative to parent structure start
	 * @return relative offset
	 */
	public long getRelativeOffset() {
		return relativeOffset;
	}

	/**
	 * Returns the version child name.
	 * @return the version child name
	 */
	public String getChildName() {
		return childName;
	}

	/**
	 * Returns the version child size.
	 * @return the version child size
	 */
	public short getChildSize() {
		return childSize;
	}

	/**
	 * Return value offset relative to parent structure start.
	 * @return relative value offset or 0 if no value exists
	 */
	public long getValueRelativeOffset() {
		if (childValue == null) {
			return 0;
		}
		return ((childName.length() + 1) * 2) + valueAlignment + 6;
	}

	/**
	 * Return unicode name string offset relative to parent structure start
	 * @return relative name offset or 0 if data type is unknown
	 */
	public long getNameRelativeOffset() {
		if (childSize == 0) {
			return 0;
		}
		return 6;
	}

	/**
	 * @return true if value is unicode string
	 */
	public boolean valueIsUnicodeString() {
		return childValue != null && "StringInfo".equals(childDataType);
	}

	/**
	 * @return true if value is 4-byte integer value in memory 
	 * while string value return by {@link DataType#getValue(ghidra.program.model.mem.MemBuffer, ghidra.docking.settings.Settings, int)
	 * DataType.getValue(MemBuffer, Settings, int)} is a numeric hex string.
	 */
	public boolean valueIsDWord() {
		return childValue != null && "Var".equals(childDataType);
	}

	/**
	 * @return true if this child has children
	 */
	public boolean hasChildren() {
		return children != null;
	}

}