/*
 * cmdcfg.c -- a keymap configuration utility for cmdedit.com
 *
 * Version 2.0,  Jason Hood, March, 1997.
 *	   2.01, Jason Hood, 13 May, 1998.
 *	   2.02, Jason Hood, 26 May, 1999.
 *	   2.03, Jason Hood, 9 November, 2001.
 *
 * Author:  Wayne Davison (davison@borland.com)
 *
 * Compiler used: Borland C
 *
 * jmh 980513: used tab after the key when writing the keymap;
 *	       added "BkSp" as an alias for ^H;
 *	       modified the parsing of the control key aliases.
 *
 * jmh 990520: modified to reflect new position of ^BkSp.
 * jmh 990522: added "^Enter" as an alias for ^J.
 * jmh 990526: added new function "LFNToggle".
 * jmh 011109: renamed FullWord{Left,Right} to String{Left,Right}
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <io.h>
#include <string.h>
#include <conio.h>

#define BUF_SIZE (32 * 1024)

#define waitkey if (getch() == '\0') getch()

char search_text[] = "CMDEDIT key map";

struct
{
  int num;
  char* name;
}
// These must be the same keys as cmd_key_table in edit.asm.
keys[] =
{
  { 271, "#Tab"   },                    // # = Shift
  { 327, "Home"   },
  { 328, "Up"     },
  { 331, "Left"   },
  { 333, "Right"  },
  { 335, "End"    },
  { 336, "Down"   },
  { 338, "Ins"    },
  { 339, "Del"    },
  { 371, "^Left"  },
  { 372, "^Right" },
  { 373, "^End"   },
  { 375, "^Home"  },
  { 411, "@Left"  },                    // @ = Alt
  { 413, "@Right" },
};

// There must be the same number of functions, in the same order, as
// cmd_key_funcs in edit.asm.
char* functions[] =
{
  "Ignore",				// Ignore key
  "Quote",				// Quote next key
  "Default",				// Store key
  "CharLeft",				// Character left
  "CharRight",				// Character right
  "WordLeft",				// Word left
  "WordRight",				// Word right
  "StringLeft", 			// String left
  "StringRight",			// String right
  "BegLine",				// Beginning of line
  "EndLine",				// End of line
  "PrevLine",				// Previous line
  "NextLine",				// Next line
  "SearchBack",				// Search backward
  "SearchForw",				// Search forward
  "Cycle",				// Filename completion cycle
  "List",				// Filename completion list
  "DelLeft",				// Delete character left
  "DelRight",				// Delete character right
  "DelWordLeft",			// Delete word left
  "DelWordRight",			// Delete word right
  "DelBegLine",				// Delete to beginning of line
  "DelEndLine",				// Delete to end of line
  "DelEndExec",				// Delete to eol and execute
  "Erase",				// Erase line
  "StoreErase",				// Store and erase line
  "CmdSep",				// Command separator
  "Transpose",				// Transpose (swap characters)
  "CookedChar",				// Cooked characters
  "AutoRecall",				// Auto-recall
  "MacroToggle",			// Macro/symbol/brace toggling
  "VarSubst",				// Variable substitution
  "Enter",				// Enter
  "Wipe",				// Execute without storing
  "InsOvr",				// Insert/overwrite toggle
  "LFNToggle",				// Long filename completion toggling
};

int numkeys, numfuncs, largest = 0;

int print_char(int ch, FILE* file);
int write_key_map(const char* key_pos, const char* name);
int read_key_map(char* key_pos, const char* name);
void update(char* key_pos, int key, int func);

int
main(int argc, char *argv[])
{
    int fn;
    char *buf, *cp, *comfile = "cmdedit.com";
    unsigned size, left;
    struct ftime ft;
    int i;

    if (argc > 2) {
	fprintf(stderr, "Usage: cmdcfg <filename>\n");
	exit(1);
    }
    else if (argc == 2) comfile = argv[1];
    if ((fn = open(comfile, O_RDWR | O_BINARY)) == -1) {
	fprintf(stderr, "Unable to open \"%s\".\n", comfile);
	exit(1);
    }
    getftime(fn, &ft);
    if ((buf = malloc(BUF_SIZE)) == NULL) {
	fprintf(stderr, "Out of memory.\n");
	exit(1);
    }
    size = read(fn, buf, BUF_SIZE);

    numkeys = sizeof(keys) / sizeof(keys[0]);
    numfuncs = sizeof(functions) / sizeof(functions[0]);
    for (i = numfuncs-1; i >= 0; --i)
    {
      if (strlen(functions[i]) > largest)
	largest = strlen(functions[i]);
    }
    for (cp = buf, left = size; left; cp++, left--) {
	if (*cp == *search_text && !strcmp(cp, search_text)) {
	  int numfuncson2 = (numfuncs+1)/2, first;
	  int ch;
	  char* sp;
	  int changed = 0;

	  cp += sizeof search_text;
	  sp = (char*)cp;
	  for(;;)
	  {
	    clrscr();
	    for (i = 0; i < numfuncs; i++)
	    {
	      gotoxy((i/(numfuncson2))*40+1, (i%(numfuncson2))+1);
	      printf("%2d: %s", i, functions[i]);
	      gotoxy((i/(numfuncson2))*40+1+largest+6, wherey());
	      first = 1;
	      for (ch = 0; ch < 33; ch++)
	      {
		if (*(sp+ch) == i)
		{
		  if (!first) printf(", ");
		  print_char(ch, stdout);
		  first = 0;
		}
	      }
	      for (ch = 33; ch < 33+3*numkeys; ch += 3)
	      {
		if (*(sp+ch+2) == i)
		{
		  if (!first) printf(", ");
		  print_char(*(short*)(sp+ch), stdout);
		  first = 0;
		}
	      }
	    }
	    gotoxy(1, numfuncson2+2);
	    printf("Key to redefine: ");
	    ch = getch();
	    if (ch == '\0') ch = getch() + 256;
	    if (ch == ' ' || ch == 'q' || ch == 'Q' || ch == 'x' || ch == 'X')
	      break;
	    if (ch == 's' || ch == 'S' || ch == 'w' || ch == 'W')
	    {
	      char name[64];
	      printf("\rEnter the filename to write keymap: ");
	      gets(name);
	      if (*name != '\0')
		if (!write_key_map(sp, name))
		{
		  printf("Unable to create \"%s\".", name);
		  waitkey;
		}
	      continue;
	    }
	    if (ch == 'l' || ch == 'L' || ch == 'r' || ch == 'R')
	    {
	      char name[64];
	      printf("\rEnter the filename to read keymap: ");
	      gets(name);
	      if (*name != '\0')
		if (read_key_map(sp, name)) changed = 1;
		else
		{
		  printf("Unable to open \"%s\".", name);
		  waitkey;
		}
	      continue;
	    }
	    if (ch == 127) ch = 32;
	    if (!print_char(ch, stdout))
	    {
	      printf("\rThat key is unavailable.");
	      waitkey;
	    }
	    else
	    {
	      printf("\nEnter the function number: ");
	      scanf("%d", &first);
	    }
	    if (first >= 0 && first < numfuncs)
	    {
	      changed = 1;
	      update(sp, ch, first);
	    }
	  }
	  if (changed) {
	    lseek(fn, 0L, 0);
	    write(fn, buf, size);
	  }
	  break;
	}
    }
    setftime(fn, &ft);
    close(fn);
    if (!left) {
	fprintf(stderr, "Couldn't find key map table.\n");
	exit(1);
    }
    return 0;
}

// Write the key name corresponding to it's number. Return 0 if the key could
// not be found, 1 otherwise.
int
print_char(int ch, FILE* file)
{
    int rc = 1;
    if (ch < 32)
    {
      putc('^', file);
      putc(ch + '@', file);
      if (file == stdout)
      {
	char* alias;
	switch (ch)
	{
	   case  8: alias = "/BkSp";   break;
	   case  9: alias = "/Tab";    break;
	   case 10: alias = "/^Enter"; break;
	   case 13: alias = "/Enter";  break;
	   case 27: alias = "/Esc";    break;
	   default: alias = "";
	}
	fputs(alias, stdout);
      }
    }
    else if (ch == 32)
       fputs("^BkSp", file);
    else
    {
      int j = 0;
      while (keys[j].num != ch && j < numfuncs) j++;
      if (j == numfuncs) rc = 0;
      else fputs(keys[j].name, file);
    }
    return rc;
}

// Write the keys and corresponding function to file name.
int
write_key_map(const char* key_pos, const char* name)
{
  int j;
  FILE* key_file = fopen(name, "wt");
  if (key_file == NULL) return 0;

  for (j = 0; j < 33; j++)
  {
    print_char(j, key_file);
    fprintf(key_file, "\t= %s\n", functions[*(key_pos+j)]);
  }
  key_pos += 33;
  for (j = 0; j < numkeys*3; j += 3)
  {
    print_char(*(short*)(key_pos+j), key_file);
    fprintf(key_file, "\t= %s\n", functions[*(key_pos+j+2)]);
  }
  fclose(key_file);
  return 1;
}

// Read the keys and functions from file name.
// Use "^H" not "BkSp", "^I" not "Tab", "^M" not "Enter" and "^[" not "Esc".
// Blank lines and lines starting with "-" are ignored.
int
read_key_map(char* key_pos, const char* name)
{
  int j, key = -1, func, linenum = 0, error = 0;
  char line[256], keyname[10], funcname[20];

  FILE* key_file = fopen(name, "rt");
  if (key_file == NULL) return 0;

  puts("");
  for (;;)
  {
    if (fgets(line, sizeof(line), key_file) == NULL) break;
    linenum++;
    for (j = 0; line[j] == ' ' || line[j] == '\t'; j++);
    if (line[j] == '-' || line[j] == '\n') continue;
    if (sscanf(line+j, "%s = %s", keyname, funcname) != 2)
    {
      printf("Line %d: expecting \"<key> = <function>\".\n", linenum);
      error = 1;
      continue;
    }
    if (keyname[0] == '^' && keyname[2] == '\0')	// Control+letter
    {
      key = keyname[1] - '@';
      if (key > 31) key = -1;
    }
    else if (stricmp(keyname, "^BkSp") == 0)
      key = 32;
    else						// Search for a match
    {
      for (j = 0; j < numkeys; j++)
      {
	if (!stricmp(keyname, keys[j].name))
	{
	  key = keys[j].num;
	  break;
	}
      }
    }
    if (key < 0)
    {
      printf("Line %d: unrecognised key - \"%s\".\n", linenum, keyname);
      error = 1;
      continue;
    }
    for (func = 0; func < numfuncs; func++)
    {
      if (!stricmp(funcname, functions[func])) break;
    }
    if (func == numfuncs)
    {
      printf("Line %d: unrecognised function - \"%s\".\n", linenum, funcname);
      error = 1;
      continue;
    }
    update(key_pos, key, func);
  }
  fclose(key_file);
  if (error) waitkey;
  return 1;
}

// Update key with func. Both are assumed to be valid.
void
update(char* key_pos, int key, int func)
{
  if (key < 33) *(key_pos+key) = func;
  else
  {
    int j;
    for (j = 33; j < 33+numkeys*3; j += 3)
    {
      if (*(short*)(key_pos+j) == key)
      {
	*(key_pos+j+2) = func;
	break;
      }
    }
  }
}
