/*
	This is some experimental code by Shadowlord (SL).
	P.S. It doesn't really work for some reason. (It crashes)
*/

#define MEMCPP
#include "noctis-d.h"
#include "noctis-0.h"
//#include <stdio.h>
#include <stdio.h>
#include <stdarg.h>
//#include <varargs.h>
#include "mem.h"

int huge *conventional;

#define MAXHANDLES 32
unsigned int segnums[MAXHANDLES];
unsigned int numUMBHandles=0;
unsigned long paralengths[MAXHANDLES];

unsigned int handles[MAXHANDLES];
unsigned int numXMSHandles=0;
void far * convAddress[MAXHANDLES];
unsigned int lengths[MAXHANDLES];
unsigned int conventionalRecurses[MAXHANDLES];
unsigned long conventionalLengths[MAXHANDLES];

unsigned long conventionalUsed = 0;
unsigned long XmsDriverAddr = 0;
int firstRun=1;
int enabled=0;

int FindIdx(int handle) {
	for (int a=0; a<numXMSHandles; a++) {
		if (handles[a]==handle) {
			return a;
		}
	}
	return -1;
}

#pragma pack(1)
struct ExtMemMoveStruct {
	unsigned long Length;
	unsigned int SourceHandle;
	union {
		unsigned long SourceOffset;
		void far * SourcePtr;
		struct {
			unsigned short SourceSegment;
			unsigned short SourceOffset;
		} SourceGroup;
	};
	unsigned int DestHandle;
	union {
		unsigned long DestOffset;
		void far * DestPtr;
		struct {
			unsigned short DestSegment;
			unsigned short DestOffset;
		} DestGroup;
	};
};
#pragma pack()

/*asm {
	ExtMemMoveStruct struc
		Length       dd ?   ; 32-bit number of bytes to transfer
		SourceHandle dw ?   ; Handle of source block
		SourceOffset dd ?   ; 32-bit offset into source
		DestHandle   dw ?   ; Handle of destination block
		DestOffset   dd ?   ; 32-bit offset into destination block
	ExtMemMoveStruct ends
}*/

ExtMemMoveStruct ext;

unsigned long GetXMS_U8(int handle, unsigned long pos) {
	int i=FindIdx(handle);
	if (i==-1) {
		DebugPrintf("Couldn't find handle %x to make conventional.");
		return 0;
	}
	if (conventionalRecurses[i]>0) {
		unsigned long far * ptr = (unsigned long far *) convAddress;
		return ptr[pos];
	} else {
		unsigned long length = 4;
		if (length+conventionalUsed>needed_conventional_mem) {
			DebugPrintf("Not enough conventional memory available. Need at least %lu.",(unsigned long)(length+conventionalUsed));
			return 0;
		}
		ext.Length=length;
		ext.SourceHandle=handle;
		ext.SourceOffset=pos*4;
		ext.DestHandle=0;
		void far * startpos=(void far *) (conventional+conventionalUsed);
		ext.DestPtr=startpos;
		int success=0;
		unsigned char errcode=0;
		asm {
			db 0x66; pusha
			mov ah, 0bh
			lds si, dword ptr ext;
			call XmsDriverAddr
			mov success, ax
			mov errcode, bl;
			db 0x66; popa
		}
		if (success) {
			//convAddress[i]=startpos;
			//conventionalRecurses[i]=1;
			//conventionalUsed+=length;
			unsigned long far * ptr = (unsigned long far *) startpos;
			DebugPrintf("Successfully relocated 4 bytes from XMS block %x to conventional memory at %lx.",handle, startpos);
			return *ptr;
		} else {
			_80_25_C ();
			DebugPrintf("Failed to relocate 4 bytes of XMS block %x to conventional memory at %lx: Error code %x.",handle, startpos, (unsigned) errcode);
			Pause();
			return 0;
		}
	}
}

void * NeedConventional(int handle) {
	int i=FindIdx(handle);
	if (i==-1) {
		_80_25_C ();
		DebugPrintf("Couldn't find handle %x to make conventional.");
		DebugPrintf("[Press the space bar to continue]");
		Pause();
		return 0;
	}
	if (conventionalRecurses[i]>0) {
		conventionalRecurses[i]++;
		DebugPrintf("Conventional request on already-conventional handle %x. Don't panic! No worries, this is OK.",handle);
		return convAddress[i];
	}
	unsigned long length=lengths[i];
	length*=1024;
	if (length+conventionalUsed>needed_conventional_mem) {
		_80_25_C ();
		DebugPrintf("Not enough conventional memory available. Need at least %lu.",(unsigned long)(length+conventionalUsed));
		DebugPrintf("[Press the space bar to continue]");
		Pause();
		return 0;
	}
	ext.Length=length;
	ext.SourceHandle=handle;
	ext.SourceOffset=0;
	ext.DestHandle=0;
	void far * startpos=(void far *) (conventional+conventionalUsed);
	ext.DestPtr=startpos;
	int success=0;
	unsigned char errcode=0;
	asm {
		db 0x66; pusha
		mov ah, 0bh
		lds si, dword ptr ext;
		call XmsDriverAddr
		mov success, ax
		mov errcode, bl;
		db 0x66; popa
	}
	if (success) {
		convAddress[i]=startpos;
		conventionalRecurses[i]=1;
		conventionalUsed+=length;
		DebugPrintf("Successfully relocated XMS block %x to conventional memory at %lx.",handle, startpos);
		return startpos;
	} else {
		_80_25_C ();
		DebugPrintf("Failed to relocate XMS block %x to conventional memory at %lx: Error code %x.",handle, startpos, (unsigned) errcode);
		DebugPrintf("[Press the space bar to continue]");
		Pause();
		return 0;
	}
}

int DoneConventional(int handle) {
	int i=FindIdx(handle);
	if (i==-1) {
		_80_25_C ();
		DebugPrintf("Couldn't find handle %x to mark done with conventional.");
		DebugPrintf("[Press the space bar to continue]");
		Pause();
		return 0;
	}
	if (conventionalRecurses[i]>1) {
		conventionalRecurses[i]--;
		return 1;
	}
	unsigned long length=lengths[i];
	length*=1024;
	void far * startpos=convAddress[i];
	if (startpos==0) {
		_80_25_C ();
		DebugPrintf("Handle %x is not in conventional memory!", handle);
		DebugPrintf("[Press the space bar to continue]");
		Pause();
		return 0;
	}
	ext.Length=length;
	ext.SourceHandle=0;
	ext.SourcePtr=startpos;
	ext.DestHandle=handle;
	ext.DestOffset=0;
	int success=0;
	unsigned char errcode=0;
	asm {
		db 0x66; pusha
		mov ah, 0bh
		lds si, dword ptr ext;
		call XmsDriverAddr
		mov success, ax
		mov errcode, bl;
		db 0x66; popa
	}
	if (success) {
		if (((unsigned long) convAddress[i])+length==conventionalUsed) {
			convAddress[i]=0;
			conventionalUsed-=length;
			conventionalRecurses[i]=0;
			DebugPrintf("Successfully relocated XMS block %x back to XMS from %lx.",handle, startpos);
			return 1;
		} else {
			_80_25_C ();
			DebugPrintf("XMS blocks relocated to conventional memory must be released back to XMS in LIFO order: convAddress=%lx length=%lx conventionalUsed=%lx", convAddress, length, conventionalUsed);
			DebugPrintf("[Press the space bar to continue]");
			Pause();
			return 0;
		}
	} else {
		_80_25_C ();
		DebugPrintf("Failed to relocate XMS block %x back to XMS from %lx: Error code %x.",handle, startpos, (unsigned) errcode);
		DebugPrintf("[Press the space bar to continue]");
		Pause();
		return 0;
	}
}

int XmsInit() {
	DebugPrintf("xms init");
	int ibx=0;
	int ies=0;
	asm {
		db 0x66; pusha
		pushf; pop AX; test AX,AX; js need286orbetter
		
		mov ah, 0x43
		mov al, 0x00
		int 0x2f
		cmp al, 0x80
		jne missingxmsdriver
		
		mov ah, 0x43
		mov al, 0x10
		int 0x2f
		mov ibx, bx
		mov ies, es
		mov ax, es
		db 0x66; shl ax, 16 //shl eax, 16
		mov ax, bx
		db 0x66; mov word ptr XmsDriverAddr, ax //mov XmsDriverAddr, eax
		db 0x66; popa
	}
	DebugPrintf("xms driver address=%lx bx=%x es=%x",XmsDriverAddr,ibx, ies);
	
	unsigned char leftVersion;
	unsigned char rightVersion;
	int subVersion;
	int hmaAccess;
	asm {
		db 0x66; pusha
		mov ah, 0
		call XmsDriverAddr
		mov leftVersion, ah
		mov rightVersion, al;
		mov subVersion, bx
		mov hmaAccess, dx
		db 0x66; popa
	}
	DebugPrintf("Version: %i.%i.%i  HMA Access: %i", leftVersion, rightVersion, subVersion, hmaAccess);
	if (leftVersion<2) {
		DebugPrintf("The XMS memory manager may not be recent enough (Version 2 or better recommended), but we're gonna try anyways.");
	}
	enabled=1;
	unsigned int freeXMSmem, freeBlockSize;
	asm {
		db 0x66; pusha
		mov ah, 8
		call XmsDriverAddr
		mov freeBlockSize, ax
		mov freeXMSmem, dx
		db 0x66; popa
	}
	DebugPrintf("largest free block: %iKB Total free XMS memory: %iKB", freeBlockSize, freeXMSmem);
	
	unsigned long i = (long) farmalloc(0);
	DebugPrintf("farmalloc returned %lu.", i);
	conventional = (int huge *) farmalloc((unsigned long)needed_conventional_mem);
	if (conventional==0) {
		_80_25_C ();
		DebugPrintf("Unable to allocate %lu conventional bytes of memory for swapping XMS.",(unsigned long)needed_conventional_mem);
		DebugPrintf("[Press the space bar to continue]");
		Pause();
		return 0;
	}
	return 1;
	
	need286orbetter:
	{
		asm {db 0x66; popa }
		_80_25_C ();
		DebugPrintf("You need a 286 or better to use this program.");
		DebugPrintf("[Press the space bar to continue]");
		Pause();
		return 0;
	}
	missingxmsdriver:
	{
		asm {db 0x66; popa }
		_80_25_C ();
		DebugPrintf("Couldn't find the XMS driver.");
		DebugPrintf("[Press the space bar to continue]");
		Pause();
		return 0;
	}
}

int XmsFree(unsigned int handle) {
	if (handle==0) {
		return 0;
	}
	for (int a=numXMSHandles-1; a>=0; a--) {
		if (handles[a]==handle) {
			if (conventionalRecurses[a]>0) {
				DebugPrintf("Umm, hey, you just asked to free some XMS memory that was mirrored in conventional... I'm now freeing the conventional memory first. This'll probably screw up if something's tries to use the conventional version of the XMS later, but at least we won't have a bigass memory leak!");
				DoneConventional(handle);
			}
			handles[a]=0;
			int success;
			unsigned char errcode;
			asm {
				db 0x66; pusha
				mov ah, 0Ah
				mov dx, handle
				call XmsDriverAddr
				mov success, ax
				mov errcode, bl
				db 0x66; popa
			}
			if (success) {
				DebugPrintf("Successfully freed XMS handle %x.",handle);
				if (handle==numXMSHandles-1) {
					numXMSHandles--;
				}
				return 1;
			} else {
				_80_25_C ();
				DebugPrintf("Unable to free XMS handle %x: Error code %x.",handle, errcode);
				DebugPrintf("[Press the space bar to continue]");
				Pause();
				return 0;
			}
		}
	}
	return 0;
}

void far * UmbMalloc(long bytes) {
	DebugPrintf("UmbMalloc(%li)",bytes);
	if (bytes>1048560) {
		_80_25_C ();
		DebugPrintf("Cannot UmbMalloc more than 1,048,560 bytes. Well, it can't allocate that much either, because that much can never exist, but ... Hey, this is an error if you get it. :P");
		DebugPrintf("[Press the space bar to continue]");
		Pause();
		return 0;
	}
	unsigned int paragraphs = (unsigned int) ((bytes+15)/16);	//number of paragraphs needed, rounding up
	DebugPrintf("paragraphs=%u",paragraphs);
	
	unsigned int success=0;
	//unsigned int handle=0;
	unsigned char errcode=0;
	unsigned int segnum=0;
	unsigned int parasize=0;
	asm {
		db 0x66; pusha
		mov ah, 10h
		mov dx, paragraphs
		call XmsDriverAddr
		mov success, ax
		mov errcode, bl
		mov segnum, bx
		mov parasize, dx
		db 0x66; popa
	}
	if (success) {
		int hnum=-1;
		for (int a=0; a<numUMBHandles; a++) {
			if (segnums[a]==0) {
				hnum=a;
				break;
			}
		}
		if (hnum==-1) {
			if (numUMBHandles==MAXHANDLES) {
				DebugPrintf("Out of UMB handles.");
				DebugPrintf("[Press the space bar to continue]");
				Pause();
				return 0;
			}
			hnum=numUMBHandles;
			segnums[hnum]=0;
			numUMBHandles++;
		}
		segnums[hnum]=segnum;
		paralengths[hnum]=parasize;
		return (void far *) (segnum<<16);
	} else {
		_80_25_C ();
		DebugPrintf("XMS driver returned error %x when attempting to allocate %u KB from UMB.", (unsigned)errcode, (unsigned)(paragraphs/16));
		DebugPrintf("[Press the space bar to continue]");
		Pause();
		return 0;
	}
	//return 0;
}
void PushMirror(int handle) {
	int i=FindIdx(handle);
	void far* address = convAddress[i];
	unsigned long bytes = conventionalLengths[i];
	if (conventionalRecurses[i]!=1) {
		DebugPrintf("Error in PushMirror! recurses = %i.", conventionalRecurses[i]);
		Pause();
	} else {
		ext.Length=bytes;
		ext.SourceHandle=0;
		ext.SourcePtr=address;
		ext.DestHandle=handle;
		ext.DestOffset=0;
		int success=0;
		unsigned char errcode=0;
		DebugPrintf("PushMirror %i %ul %i", bytes, address, handle);
		asm {
			db 0x66; pusha
			mov ah, 0bh
			db 0x66; push ds
			lds si, dword ptr ext
			call XmsDriverAddr
			mov success, ax
			mov errcode, bl
			db 0x66; pop ds
			db 0x66; popa	
		}
		if (success) {
			convAddress[i]=address;
			conventionalLengths[i]=bytes;
		} else {
			_80_25_C ();
			DebugPrintf("Error in PushMirror! It didn't work! Success=%i errcode=%i", success, errcode);
			Pause();
		}
	}
}
void PopMirror(int handle) {
	int i=FindIdx(handle);
	void far* address = convAddress[i];
	unsigned long bytes = conventionalLengths[i];
	if (conventionalRecurses[i]!=0) {
		_80_25_C ();
		DebugPrintf("Error in PopMirror! recurses = %i.", conventionalRecurses[i]);
		Pause();
	} else {
		ext.Length=bytes;
		ext.SourceHandle=handle;
		ext.SourceOffset=0;
		ext.DestHandle=0;
		ext.DestPtr=address;
		int success=0;
		unsigned char errcode=0;
		asm {
			db 0x66; pusha
			db 0x66; push ds
			mov ah, 0bh
			lds si, dword ptr ext
			call XmsDriverAddr
			mov success, ax
			mov errcode, bl
			db 0x66; pop ds
			db 0x66; popa
		}
		if (success) {
			conventionalRecurses[i]=1;
			DebugPrintf("Successfully demirrored XMS block %x back to conventional memory at %lx.",handle, address);
		} else {
			_80_25_C ();
			DebugPrintf("Failed to demirror XMS block %x to conventional memory at %lx: Error code %x.",handle, address, (unsigned) errcode);
			DebugPrintf("[Press the space bar to continue]");
			Pause();
		}
	}
}
void FreeMirror(int handle) {
	XmsFree(handle);
}

void GetAddress(int i, void far *address) {
	unsigned short mseg, moff;
	asm {
		db 0x66; pusha
		db 0x66; push ds
		lds si, address
		mov mseg, ds
		mov moff, si
		db 0x66; pop ds
		db 0x66; popa
	}
	if (i) {
		ext.DestGroup.DestSegment = mseg;
		ext.DestGroup.DestOffset = moff;
	} else {
		ext.SourceGroup.SourceSegment = mseg;
		ext.SourceGroup.SourceOffset = moff;
	}
}

int AllocMirror(void far* address, unsigned long bytes) {
	int handle = XmsMalloc(bytes);
	int i=FindIdx(handle);
	GetAddress(0, address);
	convAddress[i]=ext.SourcePtr;
	conventionalRecurses[i]=1;
	conventionalLengths[i]=bytes;
	return handle;
}

int XmsMalloc(long bytes) {
	unsigned int kilobytes = (bytes+1023)>>10;	//number of KB needed, rounding up
	unsigned int freeXMSmem, freeBlockSize;
	asm {
		mov ah, 8
		call XmsDriverAddr
		mov freeBlockSize, ax
		mov freeXMSmem, dx;
	}
	if (freeBlockSize<kilobytes) {
		_80_25_C ();
		DebugPrintf("Could not allocate %li KB from XMS, only %li available linearly (%li total).",kilobytes, freeBlockSize, freeXMSmem);
		DebugPrintf("[Press the space bar to continue]");
		Pause();
		return 0;
	} else {
		unsigned int success=0;
		unsigned int handle=0;
		unsigned char errcode=0;
		asm {
			db 0x66; pusha
			mov ah, 9
			mov dx, kilobytes
			call XmsDriverAddr
			mov success, ax
			mov errcode, bl
			mov handle, dx
			db 0x66; popa
		}
		if (success) {
			int hnum=-1;
			for (int a=0; a<numXMSHandles; a++) {
				if (handles[a]==0) {
					hnum=a;
					break;
				}
			}
			if (hnum==-1) {
				if (numXMSHandles==MAXHANDLES) {
					_80_25_C ();
					DebugPrintf("Out of XMS handles.");
					DebugPrintf("[Press the space bar to continue]");
					Pause();
					return 0;
				}
				hnum=numXMSHandles;
				handles[hnum]=0;
				conventionalRecurses[hnum]=0;
				
				numXMSHandles++;
			}
			DebugPrintf("Success: handle=%x", handle);
			handles[hnum]=handle;
			lengths[hnum]=kilobytes;
			convAddress[hnum]=0;
			return handle;
		} else {
			_80_25_C ();
			DebugPrintf("XMS driver returned error when attempting to allocate %li KB from XMS: %i.",kilobytes, errcode);
			DebugPrintf("[Press the space bar to continue]");
			Pause();
			return 0;
		}
	}
	//return 0;
}
void XmsFreeAll() {
	for (int a=numXMSHandles-1; a>=0; a--) {
		XmsFree(handles[a]);
	}
	if (conventional!=0) {
		farfree(conventional);
	}
}



#undef MEMCPP