package gamecubeloader.dol;

import java.io.IOException;

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

public class DOLHeader {
	public static final long SIZE = 0xE4;
	
	public static final String[] TEXT_NAMES = {
			".init", ".text", ".text1", ".text2", ".text3", ".text4", ".text5"
	};
	
	public static final String[] DATA_NAMES = {
		"extab", "extabindex", ".ctors", ".dtors", ".rodata", ".data", ".sdata", ".sdata2", ".bss", ".sbss", ".sbss2"	
	};
	
	public long[] textSectionOffsets;
	public long[] dataSectionOffsets;
	public long[] textSectionMemoryAddresses;
	public long[] dataSectionMemoryAddresses;
	public long[] textSectionSizes;
	public long[] dataSectionSizes;
	public long bssMemoryAddress;
	public long bssSize;
	public long entryPoint;
	
	// Not part of the DOL header.
	public long memoryEndAddress;
	
	public DOLHeader(BinaryReader reader) {
		this.readHeader(reader);
	}
	
	private void readHeader(BinaryReader reader) {
		try {
			reader.setPointerIndex(0);
			
			textSectionOffsets = new long[7];
			for (int i = 0; i < 7; i++) {
				textSectionOffsets[i] = reader.readNextUnsignedInt();
			}
			
			dataSectionOffsets = new long[11];
			for (int i = 0; i < 11; i++) {
				dataSectionOffsets[i] = reader.readNextUnsignedInt();
			}
			
			textSectionMemoryAddresses = new long[7];
			for (int i = 0; i < 7; i++) {
				textSectionMemoryAddresses[i] = reader.readNextUnsignedInt();
			}
			
			dataSectionMemoryAddresses = new long[11];
			for (int i = 0; i < 11; i++) {
				dataSectionMemoryAddresses[i] = reader.readNextUnsignedInt();
			}
			
			textSectionSizes = new long[7];
			for (int i = 0; i < 7; i++) {
				textSectionSizes[i] = reader.readNextUnsignedInt();
			}
			
			dataSectionSizes = new long[11];
			for (int i = 0; i < 11; i++) {
				dataSectionSizes[i] = reader.readNextUnsignedInt();
			}
			
			bssMemoryAddress = reader.readNextUnsignedInt();
			bssSize = reader.readNextUnsignedInt();
			entryPoint = reader.readNextUnsignedInt();
		}
		catch (IOException e) {
			Msg.error(this,  "DOL Header failed to read!");
		}
	}
	
	private long alignAddress(long address) {
		if ((address & 0x1F) == 0) {
			return address;
		}
		
		return address + (0x20 - (address & 0x1F));
	}
	
	private boolean CheckAddressIntersectsOtherAddresses() {
		for (int i = 0; i < textSectionMemoryAddresses.length; i++) {
			long address = textSectionMemoryAddresses[i];
			long size = textSectionSizes[i];
			long endAddress = address + size;
			
			if (size == 0) continue;
			
			// Align end address since all sections in the DOL file must be aligned to 32 bytes.
			endAddress = alignAddress(endAddress);
			
			// Check against text section violations first.
			for (int x = 0; x < textSectionMemoryAddresses.length; x++) {
				if (x == i || textSectionSizes[x] == 0) continue;
				
				long otherAddress = textSectionMemoryAddresses[x];
				long otherSize = textSectionSizes[x];
				long otherEndAddress = otherAddress + otherSize;
				
				otherEndAddress = alignAddress(otherEndAddress);
				
				if ((address >= otherAddress && address < otherEndAddress) || (endAddress > otherAddress && endAddress < otherEndAddress)) {
					return true;
				}
			}
			
			
			// Now check against data section & text section violations.
			for (int x = 0; x < dataSectionMemoryAddresses.length; x++) {
				if (dataSectionSizes[x] == 0) continue;
				
				long otherAddress = dataSectionMemoryAddresses[x];
				long otherSize = dataSectionSizes[x];
				long otherEndAddress = otherAddress + otherSize;
				
				otherEndAddress = alignAddress(otherEndAddress);
				
				var a = address >= otherAddress && address < otherEndAddress;
				var b = endAddress > otherAddress && endAddress < otherEndAddress;
				if ((a) || (b)) {
					return true;
				}
			}
		}
		
		// Now check for data section violations.
		for (int i = 0; i < dataSectionMemoryAddresses.length; i++) {
			long address = dataSectionMemoryAddresses[i];
			long size = dataSectionSizes[i];
			long endAddress = address + size;
			
			// Align end address since all sections in the DOL file must be aligned to 32 bytes.
			endAddress = alignAddress(endAddress);
			
			if (dataSectionSizes[i] == 0) continue;
			
			// Check against text section violations first.
			for (int x = 0; x < dataSectionMemoryAddresses.length; x++) {
				if (x == i || dataSectionSizes[x] == 0) continue;
				
				long otherAddress = dataSectionMemoryAddresses[x];
				long otherSize = dataSectionSizes[x];
				long otherEndAddress = otherAddress + otherSize;
				
				otherEndAddress = alignAddress(otherEndAddress);
				
				if ((address >= otherAddress && address < otherEndAddress) || (endAddress > otherAddress && endAddress < otherEndAddress)) {
					return true;
				}
			}
		}
		
		return false;
	}
	
	public boolean CheckHeaderIsValid() {
		// Check that no section intersect any other sections.
		if (this.CheckAddressIntersectsOtherAddresses()) {
			return false;
		}
		
		if (this.entryPoint < 0x80000000L || this.entryPoint > 0x817FFFFFL) {
			return false;
		}
		
		// TODO: Check each section is within valid memory bounds. (0x80000000 - 0x817FFFFF)
		// TODO: Check to make sure that the file entries don't overlap each other.
		
		return true;
	}
	
	public long GetTextSectionEndAddress() {
		long lastTextSectionStartAddress = 0;
		long size = 0;
		
		for (int i = 0; i < 7; i++ ) {
			if (textSectionMemoryAddresses[i] != 0 && textSectionMemoryAddresses[i] > lastTextSectionStartAddress && textSectionSizes[i] != 0) {
				lastTextSectionStartAddress = textSectionMemoryAddresses[i];
				size = textSectionSizes[i];
			}
		}
		
		if (lastTextSectionStartAddress != 0) {
			long endAddress = lastTextSectionStartAddress + size;
			return endAddress + (0x20 - (endAddress & 0x1F));
		}
		
		return -1;
	}
}