/* $Id: syb.c,v 1.12 2005/12/27 12:36:06 tom Exp $ */

#include <cdk_test.h>
#include <sybfront.h>
#include <sybdb.h>

#ifdef HAVE_XCURSES
char *XCursesProgramName="syb";
#endif

#define MAXWIDTH	5000
#define MAXHISTORY	1000

/*
 * This structure is used for keeping command history.
 */
struct history_st {
   unsigned used;
   int count;
   int current;
   char **cmd_history;
};

/*
 * Define some global variables.
 */
char *GPUsage		= "[-p Command Prompt] [-U User] [-P Password] [-S Server] [-h help]";
char *GPCurrentDatabase = 0;
extern char *dberrstr;

/*
 * Because the error/message callback do not allow you to pass in
 * data of your own, we have to make the screen pointer global. :(
 */
CDKSCREEN *GPCdkScreen = 0;

/*
 * Define function prototypes.
 */
DBPROCESS *loginToSybase (CDKSCREEN *screen, char *login, char *password);
DBPROCESS *sybaseLogin (CDKSCREEN *screen, char *login, char *password, int attempts);
char *assembleTitle (DBPROCESS *dbProcess);
char *assembleRow (DBPROCESS *dbProcess);
char *uc (char *word);
int getColWidth (DBPROCESS *dbProcess, int col);
void runIsqlCommand (CDKSCREEN *cdkscreen, CDKSWINDOW *swindow, DBPROCESS *process);
void useDatabase (CDKSWINDOW *swindow, DBPROCESS *dbProc, char *command);
void intro (CDKSCREEN *screen);
void help (CDKENTRY *entry);
void loadHistory (struct history_st *history);
void saveHistory (struct history_st *history, int count);

/*
 * Define callback prototypes.
 */
BINDFN_PROTO(viewHistoryCB);
BINDFN_PROTO(swindowHelpCB);
BINDFN_PROTO(historyUpCB);
BINDFN_PROTO(historyDownCB);
BINDFN_PROTO(listHistoryCB);

/*
 * Define Sybase error/message callbacks. This is required by DBLib.
 */
int err_handler (DBPROCESS *dbProcess, DBINT mesgNumber, int mesgState,
			int severity, char *mesgText);
int msg_handler (DBPROCESS *dbProcess, DBINT mesgNumber, int mesgState,
			int severity, char *mesgText);

/*
 * Written by:	Mike Glover
 * Purpose:
 *		This creates a very simple interface to Sybase.
 */
int main (int argc, char **argv)
{
   /* Declare variables. */
   CDKSWINDOW *commandOutput	= 0;
   CDKENTRY *commandEntry	= 0;
   DBPROCESS *dbProcess		= 0;
   WINDOW *cursesWin		= 0;
   char *dsquery		= 0;
   char *command		= 0;
   char *prompt			= 0;
   char *upper			= 0;
   char *login			= 0;
   char *password		= 0;
   char *server			= 0;
   int count			= 0;
   int width			= 0;
   int ret			= 0;
   struct history_st history;
   char *mesg[5], temp[1000];

   /* Set up the history. */
   GPCurrentDatabase	= copyChar ("master");
   history.used   	= 0;
   history.current	= 0;
   history.count	= 0;

   /* Check the command line for options. */
   while (1)
   {
      /* Are there any more command line options to parse. */
      if ((ret = getopt (argc, argv, "p:U:P:S:h")) == -1)
      {
	 break;
      }

      switch (ret)
      {
	 case 'p':
	      prompt = copyChar (optarg);
	      break;

	 case 'U':
	      login = copyChar (optarg);
	      break;

	 case 'P':
	      password = copyChar (optarg);
	      break;

	 case 'S':
	      server = copyChar (optarg);
	      break;

	 case 'h':
	      printf ("Usage: %s %s\n", argv[0], GPUsage);
	      ExitProgram (EXIT_SUCCESS);
	      break;
      }
   }

   /* Set up the command prompt. */
   if (prompt == 0)
   {
      if (server == 0)
      {
	 if ((dsquery = getenv ("DSQUERY")) != 0)
	 {
	    sprintf (temp, "</B/24>[%s] Command >", dsquery);
	    prompt = copyChar (temp);
	 }
	 else
	 {
	    prompt = copyChar ("</B/24>Command >");
	 }
      }
      else
      {
	 sprintf (temp, "</B/24>[%s] Command >", server);
	 prompt = copyChar (temp);
      }
   }

   /* Set up CDK. */
   cursesWin = initscr();
   GPCdkScreen = initCDKScreen (cursesWin);

   /* Start color. */
   initCDKColor();

   /* Initialize DB-Library. */
   if (dbinit() == FAIL)
   {
      mesg[0] = "<C></U>Fatal Error";
      mesg[1] = "<C>Could not connect to the Sybase database.";
      popupLabel (GPCdkScreen, mesg, 2);
      ExitProgram (EXIT_FAILURE);
   }

   /* Load the history. */
   loadHistory (&history);

   /* Create the scrolling window. */
   commandOutput = newCDKSwindow (GPCdkScreen, CENTER, TOP, -8, -2,
					"<C></B/5>Command Output Window",
					MAXWIDTH, TRUE, FALSE);

   /* Create the entry field. */
   width	= COLS - strlen (prompt) - 1;
   commandEntry = newCDKEntry (GPCdkScreen, CENTER, BOTTOM,
				0, prompt, A_BOLD|COLOR_PAIR(8),
				COLOR_PAIR(24)|'_', vMIXED,
				width, 1, 512, FALSE, FALSE);

   /* Create the key bindings. */
   bindCDKObject (vENTRY, commandEntry, KEY_UP, &historyUpCB, &history);
   bindCDKObject (vENTRY, commandEntry, KEY_DOWN, &historyDownCB, &history);
   bindCDKObject (vENTRY, commandEntry, CTRL('^'), &listHistoryCB, &history);
   bindCDKObject (vENTRY, commandEntry, KEY_TAB, &viewHistoryCB, commandOutput);
   bindCDKObject (vSWINDOW, commandOutput, '?', swindowHelpCB, commandEntry);

   /* Draw the screen. */
   refreshCDKScreen (GPCdkScreen);

   /* Display the introduction window. */
   intro (GPCdkScreen);

   /* Make them login first. */
   dbProcess = sybaseLogin (GPCdkScreen, login, password, 3);
   if (dbProcess == 0)
   {
      destroyCDKScreen (GPCdkScreen);
      endCDK ();
      ExitProgram (EXIT_FAILURE);
   }

   /* Do this forever. */
   for (;;)
   {
      /* Get the command. */
      command = activateCDKEntry (commandEntry, 0);

      /* Strip off leading and trailing white space. */
      stripWhiteSpace (vBOTH, command);

      /* Upper case the command. */
      upper = uc (command);

      /* Check the output of the command. */
      if (strcmp (upper, "QUIT") == 0 ||
		strcmp (upper, "EXIT") == 0 ||
		strcmp (upper, "Q") == 0 ||
		strcmp (upper, "E") == 0 ||
		commandEntry->exitType == vESCAPE_HIT)
      {
	 /* Save the history. */
	 saveHistory (&history, 100);

	 /* Exit. */
	 dbclose (dbProcess);
	 dbexit();

	 /* All done. */
	 destroyCDKEntry (commandEntry);
	 destroyCDKSwindow (commandOutput);
	 freeChar (upper);
	 endCDK();
	 ExitProgram (EXIT_SUCCESS);
      }
      else if (strcmp (command, "login") == 0)
      {
	 DBPROCESS *newLogin = sybaseLogin (GPCdkScreen, 0, 0, 3);
	 if (newLogin == 0)
	 {
	    addCDKSwindow (commandOutput, "Login Error: Could not switch to new user.", BOTTOM);
	 }
	 else
	 {
	    /* Close the old connection. */
	    dbclose (dbProcess);
	    dbProcess = newLogin;

	    /* Add a message to the scrolling window. */
	    addCDKSwindow (commandOutput,
				"Logged into database as new user.",
				BOTTOM);
	    count = 0;
	 }
      }
      else if (strcmp (command, "logout") == 0)
      {
	 /* Close the old connection. */
	 dbclose (dbProcess);
	 dbProcess = 0;

	 /* Add a message to the scrolling window. */
	 addCDKSwindow (commandOutput, "Logged out.", BOTTOM);
	 count = 0;
      }
      else if (strcmp (command, "clear") == 0)
      {
	 /* Clear the scrolling window. */
	 cleanCDKSwindow (commandOutput);
      }
      else if (strcmp (command, "history") == 0)
      {
	 listHistoryCB (vENTRY, (void *)commandEntry, (void *)&history, 0);
      }
      else if (strcmp (command, "tables") == 0)
      {
	 /* Check if we are logged in. */
	 if (dbProcess == 0)
	 {
	    addCDKSwindow (commandOutput, "You must login first.", BOTTOM);
	 }
	 else
	 {
	    sprintf (command, "select * from sysobjects where type = 'U'");

	    /* Put the command into the ISQL buffer. */
	    dbcmd (dbProcess, command);

	    /* Put the command into the scrolling window. */
	    sprintf (temp, "</R>%d><!R> %s", count+1, command);
	    addCDKSwindow (commandOutput, temp, BOTTOM);

	    /* Increment the counter. */
	    count++;
	 }
      }
      else if (strcmp (command, "help") == 0)
      {
	 /* Display the help. */
	 help(commandEntry);
      }
      else if (command[0] == 'u' && command[1] == 's' &&
		command[2] == 'e' && command[3] == ' ')
      {
	 /* They want to use a database. */
	 useDatabase (commandOutput, dbProcess, command);
	 count = 0;
      }
      else if (strcmp (command, "go") == 0)
      {
	 /* Check if we are logged in. */
	 if (dbProcess == 0)
	 {
	    addCDKSwindow (commandOutput, "You must login first.", BOTTOM);
	 }
	 else
	 {
	    /* Put the command into the scrolling window. */
	    sprintf (temp, "</R>%d><!R> %s", count+1, command);
	    addCDKSwindow (commandOutput, temp, BOTTOM);
	    count = 0;

	    /* Run the command. */
	    runIsqlCommand (GPCdkScreen, commandOutput, dbProcess);
	 }
      }
      else
      {
	 /* Check if we are logged in. */
	 if (dbProcess == 0)
	 {
	    addCDKSwindow (commandOutput, "You must login first.", BOTTOM);
	 }
	 else
	 {
	    /* Put the command into the ISQL buffer. */
	    dbcmd (dbProcess, command);

	    /* Put the command into the scrolling window. */
	    sprintf (temp, "</R>%d><!R> %s", count+1, command);
	    addCDKSwindow (commandOutput, temp, BOTTOM);

	    /* Increment the counter. */
	    count++;
	 }
      }

      /* Keep the history. */
      history.used = CDKallocStrings(&(history.cmd_history), command, history.count++, history.used);
      history.current = history.count;

      /* Clear the entry field. */
      cleanCDKEntry (commandEntry);

      /* Free up the memory used by the upper pointer. */
      freeChar (upper);
   }
}

/*
 * This lets a person 'use' a database.
 */
void useDatabase (CDKSWINDOW *swindow, DBPROCESS *dbProc, char *command)
{
   char *database = 0;
   char temp[256];
   char **words;
   int wordCount, x;

   /* Split the command line up and get the database name. */
   words = CDKsplitString (command, ' ');
   wordCount = CDKcountStrings (words);

   /* Look for the name. */
   for (x=1; x < wordCount; x++)
   {
      if (strlen (words[x]) != 0)
      {
	 database = copyChar (words[x]);
      }
   }
   CDKfreeStrings(words);

   /* Try to actually use the database. */
   if (dbuse(dbProc, database) == FAIL)
   {
      /* We aren't allowed to use that database. */
      sprintf (temp, "Command: %s", command);
      addCDKSwindow (swindow, temp, BOTTOM);
      addCDKSwindow (swindow, "</B/16>Error<!B!16> You are not allowed to use that database.", BOTTOM);
      return;
   }

   /* Set the global database name. */
   if (database == 0)
   {
      /* Put a syntax error in the scrolling window. */
      sprintf (temp, "Command: %s", command);
      addCDKSwindow (swindow, temp, BOTTOM);
      addCDKSwindow (swindow, "</B/16>Error<!B!16> Syntax Error", BOTTOM);
      return;
   }

   /* Clear out the old database name and set the new one. */
   freeChar (GPCurrentDatabase);
   GPCurrentDatabase = database;

   /* Add a message into the scrolling window. */
   sprintf (temp, "Command: %s", command);
   addCDKSwindow (swindow, temp, BOTTOM);
   sprintf (temp, "Default Database set to %s", GPCurrentDatabase);
   addCDKSwindow (swindow, temp, BOTTOM);
}

/*
 * This does the requisite checking for failed login attempts.
 */
DBPROCESS *sybaseLogin (CDKSCREEN *screen, char *accountName, char *accountPassword, int attemptCount)
{
   DBPROCESS *dbProcess = 0;
   char *login		= accountName;
   char *password	= accountPassword;
   int count		= 0;
   int lines		= 0;
   char *mesg[5];

   /* Give them X attempts, then kick them out. */
   while (count < attemptCount)
   {
      /* Try to login. */
      dbProcess = loginToSybase (GPCdkScreen, login, password);

      /*
       * If the dbprocess is null the account/password
       * pair does not exist.
       */
       if (dbProcess == 0)
       {
	  /*
	   * If the login and account names were provided,
	   * set them to null and allow the user to enter
	   * the name and password by hand.
	   */
	   login = 0;
	   password = 0;

	  /* Spit out the login error message. */
	  lines = 0;
	  mesg[lines++] = "<C></B/5>Login Error";
	  mesg[lines++] = " ";
	  mesg[lines++] = "<C>The login/password pair does not exist.";
	  mesg[lines++] = " ";
	  mesg[lines++] = "<C>Please try again.";

	  popupLabel (GPCdkScreen, mesg, lines);

	  eraseCDKScreen (GPCdkScreen);
	  refreshCDKScreen (GPCdkScreen);
	  count++;
       }
       else
       {
	  break;
       }
   }

   /* Did we expire the login attempts? */
   if (count > attemptCount-1)
   {
      lines = 0;
      mesg[lines++] = "<C>Login Error";
      mesg[lines++] = " ";
      mesg[lines++] = "<C>Too many attempyts to login.";
      mesg[lines++] = "<C>Exiting.";

      popupLabel (GPCdkScreen, mesg, lines);

      return 0;
   }
   return dbProcess;
}

/*
 * Let the user login.
 */
DBPROCESS *loginToSybase (CDKSCREEN *screen, char *accountName, char *accountPassword)
{
   CDKENTRY *loginEntry		= 0;
   CDKENTRY *passwordEntry	= 0;
   LOGINREC *dbLogin		= 0;
   char *hostAccount		= 0;
   char *login			= accountName;
   char *password		= accountPassword;
   char *mesg[10], temp[256];

   /* Draw the screen. */
   refreshCDKScreen (screen);

   /* Define the login entry field. */
   if (login == 0)
   {
      loginEntry = newCDKEntry (screen, CENTER, CENTER,
				"\n<C></B/5>Sybase Login\n",
				"Account Name: ",
				A_BOLD|COLOR_PAIR(8),
				COLOR_PAIR(24)|'_', vMIXED,
				20, 1, 20, TRUE, FALSE);

      /* Use the current account name as the default answer. */
      hostAccount = getlogin();
      setCDKEntryValue (loginEntry, hostAccount);

      /* Get the login. */
      while (1)
      {
	 /* Redraw the screen. */
	 eraseCDKScreen (ScreenOf(loginEntry));
	 refreshCDKScreen (ScreenOf(loginEntry));

	 /* Get the login to the sybase account. */
	 login = copyChar (activateCDKEntry (loginEntry, 0));

	 /* Check if they hit escape. */
	 if (loginEntry->exitType == vESCAPE_HIT)
	 {
	    mesg[0] = "<C></U>Error";
	    mesg[1] = "A user name must be provided.";
	    popupLabel (screen, mesg, 2);
	 }
	 else
	 {
	    break;
	 }
      }

      /* Destroy the widget. */
      destroyCDKEntry (loginEntry);
   }

   /* Get the password if we need too. */
   if (password == 0)
   {
      sprintf (temp, "\n<C></B/5>%s's Password\n", login);
      passwordEntry = newCDKEntry (screen, CENTER, CENTER,
				temp, "Account Password: ",
				A_BOLD|COLOR_PAIR(8),
				COLOR_PAIR(24)|'_', vHMIXED,
				20, 0, 20, TRUE, FALSE);
      setCDKEntryHiddenChar (passwordEntry, '*');

      /* Get the password. (the account may not have a password.) */
      password = copyChar (activateCDKEntry (passwordEntry, 0));
      if ((passwordEntry->exitType == vESCAPE_HIT) ||
	(strlen(password) == 0))
      {
	 password = "";
      }

      /* Destroy the widget. */
      destroyCDKEntry (passwordEntry);
   }

   /*
    * Try to connect to the database and get a LOGINREC structre.
    */
   if ((dbLogin = dblogin()) == 0)
   {
      mesg[0] = "<C></U>Fatal Error";
      mesg[1] = "<C>Could not connect to the Sybase database.";
      popupLabel (screen, mesg, 2);
      refreshCDKScreen (screen);
      ExitProgram (EXIT_FAILURE);
   }

  /*
   * Set the login and password and try to login to the database.
   */
   DBSETLUSER (dbLogin, login);
   DBSETLPWD (dbLogin, password);
   DBSETLAPP (dbLogin, "cdk_syb");

   /* Create a dbprocess structure to communicate with the database. */
   return dbopen (dbLogin, 0);
}

/*
 * This actually runs the command.
 */
void runIsqlCommand (CDKSCREEN *screen, CDKSWINDOW *swindow, DBPROCESS *dbProcess)
{
   /* Declare local variables. */
   RETCODE returnCode;
   int rowCount;

   /* Add in a output seperation line. */
   addCDKSwindow (swindow, "<C><#HL(5)> Start of Output <#HL(5)>", BOTTOM);

   /* Run the command. */
   dbsqlexec (dbProcess);

   /* Check the return code of the commands. */
   while ((returnCode = dbresults (dbProcess)) != NO_MORE_RESULTS)
   {
      if (returnCode == FAIL)
      {
	 /* Oops, the command bombed. */
	 addCDKSwindow (swindow, "</5/16>Command failed.", BOTTOM);
      }
      else
      {
	 if (!(DBCMDROW (dbProcess)))
	 {
	    /* The command could not return any rows. */
	    addCDKSwindow (swindow, "</5/16>Command could not return rows.", BOTTOM);
	 }
	 else
	 {
	    /*
	     * The command returned some rows, print out the title.
	     */
	    char *row = assembleTitle (dbProcess);
	    addCDKSwindow (swindow, row, BOTTOM);
	    freeChar (row);

	    /* For each row returned, assemble the info. */
	    rowCount = 0;
	    while (dbnextrow (dbProcess) != NO_MORE_ROWS)
	    {
	       row = assembleRow (dbProcess);
	       addCDKSwindow (swindow, row, BOTTOM);
	       freeChar (row);
	    }
	 }
      }
   }

   /* Add in a output seperation line. */
   addCDKSwindow (swindow, "<C><#HL(5)>  End of Output  <#HL(5)>", BOTTOM);
   addCDKSwindow (swindow, "", BOTTOM);

   /* Can the query... */
   dbcanquery (dbProcess);
}

/*
 * This creates a single line from the column widths and values.
 */
char *assembleTitle (DBPROCESS *dbProc)
{
   char *colName	= 0;
   int colWidth		= 0;
   int colNameLen	= 0;
   int colCount		= dbnumcols (dbProc);
   int x		= 0;
   char temp[MAXWIDTH];
   char row[MAXWIDTH];

   /* Clean the row out. */
   memset (row, '\0', MAXWIDTH);

   /* Start assembling the row. */
   for (x=1; x <= colCount; x++)
   {
      colName		= dbcolname (dbProc, x);
      colWidth		= getColWidth (dbProc, x);
      colNameLen	= strlen (colName);

      /* If we need to pad, then pad. */
      if (colNameLen < colWidth)
      {
	 /* Create a string the same length as the col width. */
	 memset (temp, '\0', MAXWIDTH);
	 memset (temp, ' ', (colWidth-colNameLen));

	 /* Copy the name. */
	 sprintf (row, "%s %s%s", row, colName, temp);
      }
      else
      {
	 /* Copy the name. */
	 sprintf (row, "%s %s", row, colName);
      }
   }
   return copyChar (row);
}

/*
 * This assembles a single row.
 */
char *assembleRow (DBPROCESS *dbProcess)
{
   char *dataValue	= 0;
   int colCount		= dbnumcols (dbProcess);
   int columnType	= 0;
   int colWidth		= 0;
   int valueLen		= 0;
   char value[MAXWIDTH];
   char temp[MAXWIDTH];
   char row[MAXWIDTH];
   char format[20];
   int x;

   /* Clean out the row. */
   memset (row, '\0', MAXWIDTH);

   /* Start assembling the row. */
   for (x=1; x <= colCount; x++)
   {
      columnType	= (int)dbcoltype (dbProcess, x);
      colWidth		= (int)getColWidth (dbProcess, x);
      valueLen		= (int)dbdatlen (dbProcess, x);

      /* Check the column type. */
      if (columnType == SYBINT1)
      {
	 DBINT object_id = *((DBINT *)dbdata(dbProcess, x));
	 sprintf (format, "%%-%dd", colWidth);
	 sprintf (value, format, (int)object_id);
      }
      else if (columnType == SYBINT2)
      {
	 DBINT object_id = *((DBINT *)dbdata(dbProcess, x));
	 sprintf (format, "%%-%dd", colWidth);
	 sprintf (value, format, (int)object_id);
      }
      else if (columnType == SYBINT4)
      {
	 DBINT object_id = *((DBINT *)dbdata(dbProcess, x));
	 sprintf (format, "%%-%dd", colWidth);
	 sprintf (value, format, (int)object_id);
      }
      else if (columnType == SYBREAL)
      {
	 DBREAL object_id = *((DBREAL *)dbdata(dbProcess, x));
	 sprintf (format, "%%-%d.2f", colWidth);
	 sprintf (value, format, object_id);
      }
      else if (columnType == SYBFLT8)
      {
	 DBFLT8 object_id = *((DBFLT8 *)dbdata(dbProcess, x));
	 sprintf (format, "%%-%d.2f", colWidth);
	 sprintf (value, format, object_id);
      }
      else
      {
	 if (valueLen <= 0)
	 {
	    strcpy (value, " ");
	 }
	 else
	 {
	    memset (value, '\0', MAXWIDTH);
	    dataValue = (DBCHAR *)dbdata (dbProcess, x);
	    strncpy (value, dataValue, valueLen);
	 }
      }

      /* If we need to pad, then pad. */
      if (valueLen < colWidth)
      {
	 /* Copy the value into the string. */
	 memset (temp, '\0', MAXWIDTH);
	 memset (temp, ' ', (colWidth-valueLen));
	 sprintf  (row, "%s %s%s", row, value, temp);
      }
      else
      {
	 sprintf  (row, "%s %s", row, value);
      }
   }
   return copyChar (row);
}

/*
 * This function returns the correct width of a column, taking
 * into account the width of the title, the width of the data
 * element.
 */
int getColWidth (DBPROCESS *dbProcess, int col)
{
   char *colName	= dbcolname (dbProcess, col);
   int colNameLen	= strlen(colName);
   int colWidth		= dbcollen (dbProcess, col);
   int columnType	= (int)dbcoltype (dbProcess, col);

   /* If the colType is int/real/float adjust accordingly. */
   if (columnType == SYBINT1 || columnType == SYBINT2 ||
	columnType == SYBINT4)
   {
      colWidth = 5;
   }
   else if (columnType == SYBREAL || columnType == SYBFLT8)
   {
      colWidth = 8;
   }

   /* Is the name of the column wider than the col width? */
   if (colNameLen >= colWidth)
   {
      return (colNameLen+1);
   }
   return colWidth;
}

/*
 * This callback allows the user to play with the scrolling window.
 */
int viewHistoryCB (EObjectType cdktype, void *object, void *clientData, chtype key)
{
   CDKSWINDOW *swindow	= (CDKSWINDOW *)clientData;
   CDKENTRY *entry	= (CDKENTRY *)object;

   /* Let them play... */
   activateCDKSwindow (swindow, 0);

   /* Redraw the entry field. */
   drawCDKEntry (entry, ObjOf(entry)->box);
   return (TRUE);
}

/*
 * This displays a little introduction screen.
 */
void intro (CDKSCREEN *screen)
{
   int lines	= 0;
   char *mesg[10];

   /* Create the message. */
   mesg[lines++] = "";
   mesg[lines++] = "<C></B/16>Sybase Command Interface";
   mesg[lines++] = "<C>Written by Mike Glover";
   mesg[lines++] = "";
   mesg[lines++] = "<C>Type </B>help<!B> to get help.";

   /* Display the message. */
   popupLabel (screen, mesg, lines);
}

/*
 * This function displays help.
 */
void help (CDKENTRY *entry)
{
   int lines	= 0;
   char *mesg[25];

   /* Create the help message. */
   mesg[lines++] = "<C></B/29>Help";
   mesg[lines++] = "";
   mesg[lines++] = "</B/24>When in the command line.";
   mesg[lines++] = "<B=Up Arrow  > Scrolls back one command.";
   mesg[lines++] = "<B=Down Arrow> Scrolls forward one command.";
   mesg[lines++] = "<B=Tab       > Activates the scrolling window.";
   mesg[lines++] = "<B=help      > Displays this help window.";
   mesg[lines++] = "";
   mesg[lines++] = "</B/24>When in the scrolling window.";
   mesg[lines++] = "<B=l or L    > Loads a file into the window.";
   mesg[lines++] = "<B=s or S    > Saves the contents of the window to a file.";
   mesg[lines++] = "<B=Up Arrow  > Scrolls up one line.";
   mesg[lines++] = "<B=Down Arrow> Scrolls down one line.";
   mesg[lines++] = "<B=Page Up   > Scrolls back one page.";
   mesg[lines++] = "<B=Page Down > Scrolls forward one page.";
   mesg[lines++] = "<B=Tab or Esc> Returns to the command line.";
   mesg[lines++] = "<B=?         > Displays this help window.";
   mesg[lines++] = "";
   mesg[lines++] = "<C> (</B/24>Refer to the scrolling window online manual for more help<!B!24>.)";

   /* Pop up the help message. */
   popupLabel (ScreenOf(entry), mesg, lines);
}

/*
 * This converts a word to upper case.
 */
char *uc (char *word)
{
   int length	= strlen (word);
   char *upper	= (char *)malloc (sizeof (char *) * (length+2));
   int x;

   /* Start converting the case. */
   for (x=0; x < length; x++)
   {
      int ch = (unsigned char)(word[x]);
      if (isalpha (ch))
      {
	 upper[x] = toupper(ch);
      }
      else
      {
	 upper[x] = word[x];
      }
   }
   upper[length] = '\0';
   return upper;
}

/*
 * The following two functions are the error and message handler callbacks.
 * which will be called by Sybase when and error occurs.
 */
int err_handler (DBPROCESS *dbProcess, DBINT mesgNumber, int mesgState, int severity, char *mesgText)
{
   /* Declare local variables. */
   char *mesg[10], temp[256];
   int errorCount = 0;

   /* Check if the process is dead. */
   if ((dbProcess == 0) || (DBDEAD(dbProcess)))
   {
      mesg[0] = "</B/32>Database Process Error";
      mesg[1] = "<C>The database process seems to have died.";
      mesg[2] = "<C>Try logging in again with the </R>login<!R> command";
      popupLabel (GPCdkScreen, mesg, 3);
      return INT_EXIT;
   }
   else
   {
      mesg[0] = "</B/32>DB-Library Error";
      sprintf (temp, "<C>%s", dberrstr);
      mesg[1] = copyChar (temp);
      errorCount = 2;
   }

   /* Display the message if we have an error. */
   if (errorCount > 0)
   {
      popupLabel (GPCdkScreen, mesg, --errorCount);
   }

   /* Clean up. */
   if (errorCount == 2)
   {
      freeChar (mesg[1]);
   }
   return (1);
}
int mesg_handler (DBPROCESS *dbProcess, DBINT mesgNumber, int mesgState, int severity, char *mesgText)
{
   /* Declare local variables. */
   char *mesg[10], temp[256];

   /* Create the message. */
   mesg[0] = "<C></B/16>SQL Server Message";
   sprintf (temp, "</R>Message Number<!R> %ld", mesgNumber);
   mesg[1] = copyChar (temp);
   sprintf (temp, "</R>State         <!R> %d", mesgState);
   mesg[2] = copyChar (temp);
   sprintf (temp, "</R>Severity      <!R> %d", severity);
   mesg[3] = copyChar (temp);
   sprintf (temp, "</R>Message       <!R> %s", mesgText);
   mesg[4] = copyChar (temp);
   popupLabel (GPCdkScreen, mesg, 5);

   /* Clean up. */
   freeChar (mesg[1]); freeChar (mesg[2]);
   freeChar (mesg[3]); freeChar (mesg[4]);
   return (1);
}

/*
 * This is for the scrolling window help callback.
 */
int swindowHelpCB (EObjectType cdktype, void *object, void *clientData, chtype key)
{
   CDKENTRY *entry	= (CDKENTRY *)clientData;
   help(entry);
   return (TRUE);
}

/*
 * This is the callback for the down arrow.
 */
int historyUpCB (EObjectType cdktype, void *object, void *clientData, chtype key)
{
   CDKENTRY *entry = (CDKENTRY *)object;
   struct history_st *history = (struct history_st *) clientData;

   /* Make sure we don't go out of bounds. */
   if (history->current == 0)
   {
      Beep();
      return (TRUE);
   }

   /* Decrement the counter. */
   history->current--;

   /* Display the command. */
   setCDKEntryValue (entry, history->cmd_history[history->current]);
   drawCDKEntry (entry, ObjOf(entry)->box);
   return (TRUE);
}

/*
 * This is the callback for the down arrow.
 */
int historyDownCB (EObjectType cdktype, void *object, void *clientData, chtype key)
{
   CDKENTRY *entry = (CDKENTRY *)object;
   struct history_st *history = (struct history_st *) clientData;

   /* Make sure we don't go out of bounds. */
   if (history->current == history->count)
   {
      Beep();
      return (TRUE);
   }

   /* Increment the counter... */
   history->current++;

   /* If we are at the end, clear the entry field. */
   if (history->current == history->count)
   {
      cleanCDKEntry (entry);
      drawCDKEntry (entry, ObjOf(entry)->box);
      return (TRUE);
   }

   /* Display the command. */
   setCDKEntryValue (entry, history->cmd_history[history->current]);
   drawCDKEntry (entry, ObjOf(entry)->box);
   return (TRUE);
}

/*
 * This callback allows the user to pick from the history list from a
 * scrolling list.
 */
int listHistoryCB (EObjectType cdktype, void *object, void *clientData, chtype key)
{
   CDKSCROLL *scrollList	= 0;
   CDKENTRY *entry		= (CDKENTRY *)object;
   struct history_st *history	= (struct history_st *) clientData;
   int height			= (history->count < 10 ? history->count+3 : 13);
   int selection;

   /* No history, no list. */
   if (history->count == 0)
   {
      /* Popup a little window telling the user there are no commands. */
      char *mesg[] = {"<C></B/16>No Commands Entered", "<C>No History"};
      popupLabel (ScreenOf(entry), mesg, 2);

      /* Redraw the screen. */
      eraseCDKEntry (entry);
      drawCDKScreen (ScreenOf(entry));

      /* And leave... */
      return (TRUE);
   }

   /* Create the scrolling list of previous commands. */
   scrollList = newCDKScroll (ScreenOf(entry), CENTER, CENTER, RIGHT,
				height, -10, "<C></B/29>Command History",
				history->cmd_history, history->count,
				NUMBERS, A_REVERSE, TRUE, FALSE);

   /* Get the command to execute. */
   selection = activateCDKScroll (scrollList, 0);
   destroyCDKScroll (scrollList);

   /* Check the results of the selection. */
   if (selection >= 0)
   {
      /* Get the command and stick it back in the entry field. */
      setCDKEntryValue (entry, history->cmd_history[selection]);
   }

   /* Redraw the screen. */
   eraseCDKEntry (entry);
   drawCDKScreen (ScreenOf(entry));
   return (TRUE);
}

/*
 * This loads the history into the editor from the RC file.
 */
void loadHistory (struct history_st *history)
{
   char *home	= 0;
   char filename[1000];

   /* Create the RC filename. */
   if ((home = getenv ("HOME")) == 0)
   {
      home = ".";
   }
   sprintf (filename, "%s/.sybrc", home);

   /* Set some variables. */
   history->current	= 0;
   history->count	= 0;

   /* Read the file. */
   if ((history->count = CDKreadFile (filename, &(history->cmd_history))) != -1)
   {
      history->current = history->count;
   }
   return;
}

/*
 * This saves the history into RC file.
 */
void saveHistory (struct history_st *history, int count)
{
   FILE *fd	= 0;
   char *home	= 0;
   char filename[1000];
   int x;

   /* Create the RC filename. */
   if ((home = getenv ("HOME")) == 0)
   {
      home = ".";
   }
   sprintf (filename, "%s/.sybrc", home);

   /* Open the file for writing. */
   if ((fd = fopen (filename, "w")) == 0)
   {
      return;
   }

   /* Start saving the history. */
   for (x=0; x < history->count; x++)
   {
      fprintf (fd, "%s\n", history->cmd_history[x]);
   }
   fclose (fd);
   return;
}
