/* ###
 * 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.macho.commands.chained;

import java.io.IOException;

import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.app.util.bin.format.macho.MachConstants;
import ghidra.app.util.bin.format.macho.commands.dyld.BindingTable.Binding;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;

/**
 * Represents a dyld_chained_import structure.
 * 
 * @see <a href="https://github.com/apple-oss-distributions/dyld/blob/main/include/mach-o/fixup-chains.h">mach-o/fixup-chains.h</a> 
 */
public class DyldChainedImport implements StructConverter {
	private static final int DYLD_CHAINED_IMPORT = 1;
	private static final int DYLD_CHAINED_IMPORT_ADDEND = 2;
	private static final int DYLD_CHAINED_IMPORT_ADDEND64 = 3;

	private int imports_format;
	private int lib_ordinal;
	private boolean weak_import;
	private long name_offset;
	private long addend;
	private String symbolName;

	DyldChainedImport(BinaryReader reader, DyldChainedFixupHeader cfh, int imports_format)
			throws IOException {
		this.imports_format = imports_format;
		switch (imports_format) {
			case DYLD_CHAINED_IMPORT: {
				int ival = reader.readNextInt();
				int ordinal = ival & 0xff;
				lib_ordinal = ordinal > 0xf0 ? (byte) ordinal : ordinal;
				weak_import = ((ival >> 8) & 1) == 1;
				name_offset = (ival >> 9 & 0x7fffff);
				break;
			}
			case DYLD_CHAINED_IMPORT_ADDEND: {
				int ival = reader.readNextInt();
				int ordinal = ival & 0xff;
				lib_ordinal = ordinal > 0xf0 ? (byte) ordinal : ordinal;
				weak_import = ((ival >> 8) & 1) == 1;
				name_offset = (ival >> 9 & 0x7fffff);
				addend = reader.readNextInt();
				break;
			}
			case DYLD_CHAINED_IMPORT_ADDEND64: {
				long ival = reader.readNextLong();
				int ordinal = (int) (ival & 0xffff);
				lib_ordinal = ordinal > 0xfff0 ? (short) ordinal : ordinal;
				weak_import = ((ival >> 16) & 1) == 1;
				name_offset = ((ival >> 32) & 0xffffffffL);
				addend = reader.readNextLong();
				break;
			}
			default:
				throw new IOException("Bad Chained import format: " + imports_format);
		}
	}

	public DyldChainedImport(Binding binding) {
		this.imports_format = 0;
		this.lib_ordinal = binding.getLibraryOrdinal();
		this.weak_import = binding.isWeak();
		this.name_offset = 0;
		this.addend = 0;
		this.symbolName = binding.getSymbolName();
	}

	@Override
	public DataType toDataType() throws DuplicateNameException, IOException {
		StructureDataType dt = new StructureDataType("dyld_chained_import", 0);
		dt.setPackingEnabled(true);
		try {
			switch (imports_format) {
				case DYLD_CHAINED_IMPORT:
					dt.addBitField(DWORD, 8, "lib_ordinal", null);
					dt.addBitField(DWORD, 1, "weak_import", null);
					dt.addBitField(DWORD, 23, "name_offset", null);
					break;
				case DYLD_CHAINED_IMPORT_ADDEND:
					dt.addBitField(DWORD, 8, "lib_ordinal", null);
					dt.addBitField(DWORD, 1, "weak_import", null);
					dt.addBitField(DWORD, 23, "name_offset", null);
					dt.add(DWORD, "addend", null);
					break;
				case DYLD_CHAINED_IMPORT_ADDEND64:
					dt.addBitField(QWORD, 16, "lib_ordinal", null);
					dt.addBitField(QWORD, 1, "weak_import", null);
					dt.addBitField(QWORD, 15, "reserved", null);
					dt.addBitField(QWORD, 32, "name_offset", null);
					dt.add(QWORD, "addend", null);
					break;
				default:
					throw new IOException("Bad Chained import format: " + imports_format);
			}
		}
		catch (InvalidDataTypeException exc) {
			// ignore
		}
		dt.setCategoryPath(new CategoryPath(MachConstants.DATA_TYPE_CATEGORY));
		return dt;
	}

	public int getLibOrdinal() {
		return lib_ordinal;
	}

	public boolean isWeakImport() {
		return weak_import;
	}

	public long getNameOffset() {
		return name_offset;
	}

	public long getAddend() {
		return addend;
	}

	public String getName() {
		return symbolName;
	}

	public void initString(BinaryReader reader) throws IOException {
		symbolName = reader.readNextAsciiString();
	}

}
