package gamecubeloader.rel;

import java.io.IOException;

import ghidra.app.util.bin.BinaryReader;
import ghidra.util.Msg;

public class RELHeader {
	private static final int SECTION_INFO_SIZE = 8;
	
	public class SectionInfo {
		public long address;
		public long size;
		
		public SectionInfo(long address, long size) {
			this.address = address;
			this.size = size;
		}
	}
	
	public long moduleId;
	public long previousModuleAddress;
	public long nextModuleAddress;
	public long sectionCount;
	
	public long sectionTableOffset;
	public long moduleNameOffset;
	public long moduleNameLength;
	public long moduleVersion; // REL Version
	
	public long bssSize;
	public long relocationTableOffset;
	public long importTableOffset;
	public long importTableSize;
	
	public int prologSectionId;
	public int epilogSectionId;
	public int unresolvedSectionId;
	public int bssSectionId;
	public long prologSectionOffset;
	public long epilogSectionOffset;
	public long unresolvedSectionOffset;
	
	public long sectionAlignment;
	public long bssSectionAlignment;
	public long fixSize;
	
	public SectionInfo[] sections;
	
	public RELHeader(BinaryReader reader) {
		this.readHeader(reader);
	}
	
	private void readHeader(BinaryReader reader) {
		try {
			reader.setPointerIndex(0);
			
			this.moduleId = reader.readNextUnsignedInt();
			this.previousModuleAddress = reader.readNextUnsignedInt();
			this.nextModuleAddress = reader.readNextUnsignedInt();
			this.sectionCount = reader.readNextUnsignedInt();
			this.sectionTableOffset = reader.readNextUnsignedInt();
			this.moduleNameOffset = reader.readNextUnsignedInt();
			this.moduleNameLength = reader.readNextUnsignedInt();
			this.moduleVersion = reader.readNextUnsignedInt();
			this.bssSize = reader.readNextUnsignedInt();
			this.relocationTableOffset = reader.readNextUnsignedInt();
			this.importTableOffset = reader.readNextUnsignedInt();
			this.importTableSize = reader.readNextUnsignedInt();
			this.prologSectionId = reader.readNextUnsignedByte();
			this.epilogSectionId = reader.readNextUnsignedByte();
			this.unresolvedSectionId = reader.readNextUnsignedByte();
			this.bssSectionId = reader.readNextUnsignedByte();
			this.prologSectionOffset = reader.readNextUnsignedInt();
			this.epilogSectionOffset = reader.readNextUnsignedInt();
			this.unresolvedSectionOffset = reader.readNextUnsignedInt();
			
			// Version specific settings
			if (this.moduleVersion > 1) {
				this.sectionAlignment = reader.readNextUnsignedInt();
				this.bssSectionAlignment = reader.readNextUnsignedInt();
			}
			else {
			    // Version 1's default values for alignment
			    this.sectionAlignment = 32;
			    this.bssSectionAlignment = 32;
			}
			
			if (this.moduleVersion > 2) {
				this.fixSize = reader.readNextUnsignedInt();
			}
			
			// Only read the sections if the header is valid.
			if (IsValid(reader)) {
				// Read sections info.
				reader.setPointerIndex(this.sectionTableOffset);
				this.sections = new SectionInfo[(int) this.sectionCount];
				for (var i = 0; i < this.sectionCount; i++) {
					this.sections[i] = new SectionInfo(reader.readNextUnsignedInt(), reader.readNextUnsignedInt());
				}
			}
		}
		catch (IOException e) {
			Msg.error(this,  "Failed to read REL header!");
		}
	}
	
	public boolean IsValid(BinaryReader reader) {
		try {
			long fileSize = reader.length();
			
			// Check section info is valid first.
			if (this.sectionTableOffset > fileSize) {
				Msg.error(this, "Unable to load REL file! Reason: Section Info Table address is past file bounds!");
				return false;
			}
			
			// Check that the relocation data offset & import info offset are valid offsets in the file.
			if (this.relocationTableOffset >= fileSize) {
				Msg.error(this, "Unable to load REL file! Reason: Relocation Data offset in header is past the file bounds!");
				return false;
			}
			
			if (this.importTableOffset + this.importTableSize > fileSize) {
				Msg.error(this, "Unable to load REL file! Reason: Import Table offset + Import Table size in header is past the file bounds!");
				return false;
			}
			
			long sectionTableSize = this.sectionCount * RELHeader.SECTION_INFO_SIZE;
			
			// Get the first section address by file address.
			long firstSectionInFileAddress = -1;
			reader.setPointerIndex(this.sectionTableOffset);
			
			for (int i = 0; i < this.sectionCount; i++) {
				long sectionAddress = reader.readNextUnsignedInt() & ~1; // Clear the executable bit-flag.
				long sectionSize = reader.readNextUnsignedInt();
				
				if (sectionAddress != 0 && sectionSize != 0 && sectionSize != this.bssSize) {
					if (firstSectionInFileAddress == -1 || sectionAddress < firstSectionInFileAddress) {
						firstSectionInFileAddress = sectionAddress;
					}
				}
			}
			
			// Ensure that the section table offset doesn't intersect the first section's data.
			if (this.sectionTableOffset + sectionTableSize > firstSectionInFileAddress) {
				Msg.error(this, "Unable to load REL file! Reason: Section Info Table intersects section data!");
				return false;
			}
			
			// TODO: Ensure that no section intersects with another. Should this include the relocation data section & import info section?
		}
		catch (IOException e) {
			return false;
		}
		
		return true;
	}
	
	public int Size() {
		switch ((int) this.moduleId) {
		case 0:
		case 1:
			return 0x40;
		
		case 2:
			return 0x48;
		
		case 3:
		default:
			return 0x4C; 
		}
	}
}