/*
 * WBTitle
 *
 * Version 1.4
 *
 * v1.0   Original public release by Mark Thomas
 *
 * v1.3d  German locale version by Martin Kaim (Gwardar), fixes/features:
 * - removed a bug in the numeric display routine (v1.0 would print e.g. 10,274,380 as
 *   :,274,380 )
 * - now ToolType SEPARATOR is spelled correctly (instead of SEPERATOR),
 *   also the default separator for German locale now is '.' instead of ','
 * - supplied smakefile
 * - version now works with the German locale (ONLY) (different Workbench title bar)
 *   (can/must be adjusted to any other locale by slight changes in the source and
 *   recompiling)
 * - supplied Icon with a nice tooltype setting (in German only)
 * - adjusted documentation to reflect these changes
 *
 * v1.4   Multi-locale update by Gwardar, changes/features: 
 *
 *   This version *finally* supports ALL locales by using two new ToolType entries!
 *
 * - Switched back to English locale as *default* configuration (convenient for the
 *   English-speaking Amiga community :-)
 * - You can now configure this program to *whatever* your original Workbench title
 *   might read, so WBTitle is *no longer* limited to English- or German-speaking users
 *   :-)
 * - Supplied 2 icons with appropriate tooltype entries for English and German locales.
 * - Now compiled with SAS/C v6.55 (which increases the excutable size by ~120 bytes)
 *   but nevertheless the patch is worth it.
 *
 * - Adjusted docs to reflect changes.
 *
 * - NB: allows for up to 2^32 - 1 bytes (~4 GB) of memory (who knows, in a few
 *   years from now this *might* be the standard virtual memory amount on Amigas) ;->
 * 
 * Public Domain Software
 *
 * This program replaces the Amiga's Workbench title bar so that it shows the amount
 * of all types of FREE memory currently available: Chip, Fast, Public; 
 * further VMM (if installed and ACTIVE), and Retina Gfx board memory (if applicable).
 */

#include <string.h>
#include <ctype.h>

#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <dos/dos.h>

#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/dos.h>
#include <proto/retina.h>

// Gwardar begin
//#include <proto/vmm.h>                // Gwardar: if you don't have the proto file,
                                        // so what? It's not really needed.
#include <pragmas/retina_pragmas.h>     // Gwardar: you may comment this out, if
                                        // you don't have the Retina stuff
// Gwardar end

#include <clib/alib_protos.h>

// The SetWindowTitles function offset
#define SWTOffset -276

enum {
	JMPINSTR = 0x4ef9
};

typedef struct JmpEntry {
	UWORD Instr;
	APTR Func;
} JmpEntry;

static BOOL Replace(void);
static void Restore(void);
static void __asm new_SetWindowTitles(register __a0 struct Window *,
					register __a1 UBYTE *, register __a2 UBYTE *,
					register __a6 struct Library *);
static void NumberToString(ULONG number);
static void format_number(ULONG number);
static void no_lead_triple(ULONG number);
static void lead_triple(ULONG number);

// Gwardar begin
//ULONG AvailVMem(ULONG);
ULONG Retina_AvailMem(ULONG);
// Gwardar end

// Global variables
struct RetinaBase *RetinaBase;
struct Library *VMMBase;

// Local variables
static char verstring[] = "$VER: WBTitle 1.4 (multi-locale version by Gwardar) " __AMIGADATE__;
static char port_name[] = "WBTitle";

static void __asm (*old_SetWindowTitles)(register __a0 struct Window *,
	       	                        		register __a1 UBYTE *,
							register __a2 UBYTE *,
							register __a6 struct Library *);
static JmpEntry *SWTEntry;
// Gwardar begin
static char *match1_str, *match2_str;
// Gwardar end
static char *chip_str, *fast_str, *public_str, *total_str, *virtual_str;
static char *retina_str, *memorder_str, *prefix_str, *suffix_str, *labels_str;
static char *comma_str, *mem_str, *last_str, *buff_ptr;
static char num_buf[16], comma;
static int units, num_size, mem_str_len, prefix_str_len;
static BOOL sep1000, labels_after;

/*
 * The main method for replacing an Amiga OS function as safe as
 * possible is to place the function with a jump table that is
 * allocated.  While the function is replaced, the jump table simply
 * jumps to my routine:
 *
 * jmp  _new_SetWindowTitles
 *
 * When the user asks the program to quit, we can't simply put the
 * pointer back that SetFunction() gives us since someone else might
 * have replaced the function.  So, we first see if the pointer we
 * get back points to the out jump table.  If so, then we _can_ put
 * the pointer back like normal (no one has replaced the function
 * while we has it replaced).  But if the pointer isn't mine, then
 * we have to replace the jump table function pointer to the old
 * function pointer:
 *
 * jmp  _old_SetWindowTitles
 *
 * Finally, we only deallocate the jump table _if_ we did not have
 * to change the jump table.
 */

main(int argc, char *argv[])
{
	struct MsgPort *port;

	// FindPort() Forbid()
	Forbid();

	port = FindPort(port_name);
	if (port) {
		struct MsgPort *reply_port;

		// Create a reply port
		reply_port = CreateMsgPort();
		if (reply_port) {
			struct Message msg;

			// Set fields in message structure
			msg.mn_ReplyPort = reply_port;
			msg.mn_Length = sizeof(struct Message);

			// Send the message
			PutMsg(port, &msg);

			// Finished with port, so stop FindPort() Forbid()
			Permit();

			// Wait for a reply
			do {
				WaitPort(reply_port);
			} while (GetMsg(reply_port) == NULL);

			// Clear and Delete reply_port Forbid()
			Forbid();

			// Clear any messages
			while (GetMsg(reply_port));

			// Delete the reply port
			DeleteMsgPort(reply_port);

			// Clear and Delete reply_port stop Forbid()
			Permit();
		} else {
			// Finished with port, so stop FindPort() Forbid()
			Permit();
		}
	} else if (port = CreateMsgPort()) {
		struct Message *msg;
		char **ttypes;

		// Finished with port, so stop FindPort() Forbid()
		Permit();

		// Setup quitting port
		port->mp_Node.ln_Name = port_name;
		port->mp_Node.ln_Pri = -120;

		// Add quitting port to public list
		AddPort(port);

		// Open the Retina library
		RetinaBase = (struct RetinaBase *)OpenLibrary("retina.library", 0);

		// Check on VMM (only if ACTIVE!): look for port, and open lib
		if (FindPort("VMM_Port")) {
			VMMBase = OpenLibrary("vmm.library", 0);
		}

		// Setup to read some arguments
		ttypes = ArgArrayInit(argc, argv);

		// Read some arguments
                
                // Gwardar begin
                // these two new tooltypes allow for the multi-locale support
                // by enabling you to change the matching strings via tooltype entries
                match1_str = ArgString(ttypes, "MATCH1", "Amiga");              //default match 1 = "Amiga"
                match2_str = ArgString(ttypes, "MATCH2", "graphics mem");       //default match 2 = "graphics mem"
                // Gwardar end
		prefix_str = ArgString(ttypes, "PREFIX", "");   // Gwardar: default = ""
		suffix_str = ArgString(ttypes, "SUFFIX", "");
		labels_str = ArgString(ttypes, "LABELS", "AFTER");

		chip_str = ArgString(ttypes, "CHIP", " Chip  ");
		fast_str = ArgString(ttypes, "FAST", " Fast  ");
		public_str = ArgString(ttypes, "PUBLIC", " Public  ");
		total_str = ArgString(ttypes, "TOTAL", " Total  ");
		virtual_str = ArgString(ttypes, "VIRTUAL", " Virtual  ");
		retina_str = ArgString(ttypes, "RETINA", " Retina  ");

		memorder_str = ArgString(ttypes, "MEMORDER", "CVPR");
		sep1000 = (ArgString(ttypes, "THOUSANDSEP", NULL) != NULL);
		units = ArgInt(ttypes, "UNITS", 1);
		comma_str = ArgString(ttypes, "SEPARATOR", NULL);
		if (comma_str && comma_str[0]) {
			comma = comma_str[0];
		} else {
			comma = ',';   // separator defaults to ',' for English locale 
		}

		labels_after = (stricmp(labels_str, "BEFORE") != 0);
		if (units < 0) {
			units = 1;
		}

		// Compute the size of numbers
		{
			ULONG maxnum = 0xffffffff;

			// Compute the number of digits
			maxnum /= units;
			while (maxnum) {
				maxnum /= 10;
				num_size++;
			}

			// Compute the number of commas
			if (sep1000) {
				int com = num_size;
				while (com > 3) {
					num_size++;
					com -= 3;
				}
			}
		}

		// Compute the size of the mem string
		{
			ULONG i = 0;
			char ch;

			// Add in the prefix string size
			mem_str_len = prefix_str_len = strlen(prefix_str);

			// Add in the memory parts
			while (ch = toupper(memorder_str[i])) {
				switch (ch) {
					case 'C':
						mem_str_len += strlen(chip_str) + num_size;
						break;
					case 'F':
						mem_str_len += strlen(fast_str) + num_size;
						break;
					case 'P':
						mem_str_len += strlen(public_str) + num_size;
						break;
					case 'T':
						mem_str_len += strlen(total_str) + num_size;
						break;
					case 'V':
						if (VMMBase) {
							mem_str_len += strlen(virtual_str) + num_size;
						}
						break;
					case 'R':
						if (RetinaBase) {
							mem_str_len += strlen(retina_str) + num_size;
						}
						break;
				}
				i++;
			}

			// Add in the suffix string size
			mem_str_len += strlen(suffix_str);
		}

		if (mem_str_len > 0) {
			// Allocate the memory
			mem_str = AllocMem(mem_str_len + 1, MEMF_PUBLIC);
			if (mem_str) {
				strcpy(mem_str, prefix_str);
				last_str = &mem_str[prefix_str_len];

				// Attempt to replace function
				if (Replace()) {
					// Wait for someone to signal me to quit
                                        // e.g. WBTitle started again
					do {
						WaitPort(port);
						msg = GetMsg(port);
					} while (msg == NULL);
					ReplyMsg(msg);

					// Restore function
					Restore();
				}
				FreeMem(mem_str, mem_str_len + 1);
			}
		}

		// Cleanup from reading arguments
		ArgArrayDone();

		// Remove port from public access
		RemPort(port);

		// Clear and Delete port Forbid()
		Forbid();

		// Clear the port of messages
		while (msg = GetMsg(port)) {
			ReplyMsg(msg);
		}

		// Closedown quitting port
		DeleteMsgPort(port);

		// Clear and Delete port stop Forbid()
		Permit();
	}
}

static BOOL Replace(void)
{
	// Allocate the jump table
	SWTEntry = AllocMem(sizeof(JmpEntry), 0);
	if (SWTEntry) {
		// Replacement Forbid()
		Forbid();

		// Replace the function with pointer to jump table
		old_SetWindowTitles = SetFunction((struct Library *)IntuitionBase,
											SWTOffset, (ULONG (*)())SWTEntry);

		// Setup the jump table
		SWTEntry->Instr = JMPINSTR;
		SWTEntry->Func = new_SetWindowTitles;

		// Clear the CPU's cache so the execution cache is valid
		CacheClearU();

		// Stop the replacement Forbid()
		Permit();

		return TRUE;
	} else {
		return FALSE;
	}
}

static void Restore(void)
{
	BOOL my_table;
	ULONG (*func)();

	// Fix back Forbid()
	Forbid();

	// Put old pointer back and get current pointer at same time
	func = SetFunction((struct Library *)IntuitionBase, SWTOffset,
					(ULONG (*)())old_SetWindowTitles);

	// Check to see if the pointer we get back is ours
	if ((JmpEntry *)func != SWTEntry) {
		// If not, leave jump table in place
		my_table = FALSE;
		SetFunction((struct Library *)IntuitionBase, SWTOffset,
					func);
		SWTEntry->Func = old_SetWindowTitles;
	} else {
		// If so, free the jump table
		my_table = TRUE;
		FreeMem(SWTEntry, sizeof(JmpEntry));
	}

	// Clear the CPU's cache so the execution cache is valid
	CacheClearU();

	// Stop fix back Forbid()
	Permit();

	// Let the user know if the jump table couldn't be freed
	if (!my_table) {
		DisplayBeep(NULL);
	}

	// Wait 5 seconds to try and guarantee that all tasks have
	// finished executing inside my replacement function before
	// quitting.  There's no real way to guarantee, though.
	Delay(250);
}

static void __saveds __asm new_SetWindowTitles(
						register __a0 struct Window *window,
						register __a1 UBYTE *win_title,
						register __a2 UBYTE *scr_title,
						register __a6 struct Library *lib)
{
	// try to make sure only the Workbench title bar gets fixed
      
        // Gwardar begin
        // compares the title bar to contain both the string specified by
        // tooltype MATCH1 (defaults to: "Amiga") and the string specified by
        // tooltype MATCH2 (defaults to: "graphics mem") and may thus be adjusted
        // to ANY locale/Workbench environment *without* the need to change the
        // source and recompile.
        
        // the default matches '#??Amiga (#??.)#??.??? graphics mem#??' 
        // (contained in the original Workbench's title bar in English locale)
        // Gwardar end
        
        if (scr_title && (scr_title != (UBYTE *)0xffffffff) &&
			strstr(scr_title, match1_str) && strstr(scr_title, match2_str)) {
		ULONG i = 0;
		char ch;

		// Set last_str to nothing
		*last_str = 0;

		// Add in the memory parts
		while (ch = toupper(memorder_str[i])) {
			switch (ch) {
				case 'C':
					if (!labels_after) {
						strcat(last_str, chip_str);
					}
					NumberToString(AvailMem(MEMF_CHIP));
					strcat(last_str, num_buf);
					if (labels_after) {
						strcat(last_str, chip_str);
					}
					break;
				case 'F':
					if (!labels_after) {
						strcat(last_str, fast_str);
					}
					NumberToString(AvailMem(MEMF_FAST));
					strcat(last_str, num_buf);
					if (labels_after) {
						strcat(last_str, fast_str);
					}
					break;
				case 'P':
					if (!labels_after) {
						strcat(last_str, public_str);
					}
					NumberToString(AvailMem(MEMF_PUBLIC));
					strcat(last_str, num_buf);
					if (labels_after) {
						strcat(last_str, public_str);
					}
					break;
				case 'T':
					if (!labels_after) {
						strcat(last_str, total_str);
					}
					NumberToString(AvailMem(0));
					strcat(last_str, num_buf);
					if (labels_after) {
						strcat(last_str, total_str);
					}
					break;
				case 'V':
					if (VMMBase) {
						if (!labels_after) {
							strcat(last_str, virtual_str);
						}
						NumberToString(AvailVMem(0));
						strcat(last_str, num_buf);
						if (labels_after) {
							strcat(last_str, virtual_str);
						}
					}
					break;
				case 'R':
					if (RetinaBase) {
						if (!labels_after) {
							strcat(last_str, retina_str);
						}
						NumberToString(Retina_AvailMem(0));
						strcat(last_str, num_buf);
						if (labels_after) {
							strcat(last_str, retina_str);
						}
					}
					break;
			}
			i++;
		}
		strcat(last_str, suffix_str);
		old_SetWindowTitles(window, win_title, mem_str, lib);
	}
        else    // leave other windows' titles untouched
        {
                old_SetWindowTitles(window, win_title, scr_title, lib);
	}
        
}

/*
 * Convert the number to a string in the format the user specified.
 * The string generated is 0 terminated.
 *
 * Uses global variables: buff_ptr, num_buff
 *
 */

static void NumberToString(ULONG number)
{
	number /= units;
	if (sep1000) {
		buff_ptr = num_buf;
		format_number(number);
		*buff_ptr = 0;
	} else {
		stcul_d(num_buf, number);
	}
}

// Gwardar: Functions below use recursive calls, so take heed when changing anything
// within 'format_number' or 'no_lead_triple' (danger of trashing unallocated memory
// when the string buffer boundary is not respected)

static void format_number(ULONG number)
{
	if (number > 1000) {
		format_number(number / 1000);
		lead_triple(number % 1000);
	} else {
		no_lead_triple(number);
	}
}

static void no_lead_triple(ULONG number)
{
	if (number > 10) {
		no_lead_triple(number / 10);
		*buff_ptr++ = ((number % 10) | 0x30);
	} 
        // Gwardar begin
        else if (number == 10) { 
                *buff_ptr++ = (1 | 0x30); // fixes the ':' numeric display bug that v1.0
                *buff_ptr++ = (0 | 0x30); // showed on some memory amounts
        }
        // Gwardar end
        else  // if (number < 10) // removed by Gwardar (smaller code, faster)
        {
		*buff_ptr++ = (number | 0x30);
	}
}

static void lead_triple(ULONG number)
{
	*buff_ptr++ = comma;
	*buff_ptr++ = ((number / 100) | 0x30);
	number %= 100;
	*buff_ptr++ = ((number / 10) | 0x30);
	number %= 10;
	*buff_ptr++ = (number | 0x30);
}
