/** @(#)wspook.c 97/11/29 Win32s Ciphergram Solver - K L Dunn
*/
/**
    Ciphergram Solver for Windows v1.0
    Copyright (C) 1997  K. L. Dunn

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Author: K. L. Dunn    email: k.l.dunn@ieee.org
*/
/**
 * Revision history:
 *   29 Nov 97	Initial
 *   27 Mar 99	Add selfok feature
 */

#include <windows.h>
#include "wspook.h"
#define  EDITMID 1
#define  EDITAID 2
#define  EDITKID 3
#define  EDITRID 4

#define  DEFAULT_WLIST "nlist"

long FAR PASCAL WndProc(HWND, WORD, WORD, LONG);
BOOL FAR PASCAL OpDlProc(HWND hDlg, unsigned wMsg, WORD wParam, LONG lParam);
VOID ErrorBox(char *szCaption, char *szFormat, ...);
VOID spook(int nSelfOK,       LPSTR lpszWlFile, LPSTR lpszInitKey,
           LPSTR lpszCipher, LPSTR lpszAttrib, LPSTR lpszResult);
int  vsprintf(char * __buffer, const char * __format, void * __arglist);

static char *what_text = "@(#)wspook.c 970327 K L Dunn";
char    szAppName[] = "Spook";
char    szWlFile[MAX_PATH + 16];
char    szBlankKey[] = "                          ";
char    szInitKey[] = "                          ";
#ifdef BADCURSOR
HCURSOR hWaitCursor;
HCURSOR hArrowCursor;
BOOL    bWorking;
#endif
int	nSelfOK;	/* 1 = a cypher letter may represent itself */
extern  int trying;	/* in module "spook.c" */

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
		           LPSTR lpszCmdLine, int nCmdShow)
{
  MSG     msg;
  HWND    hwnd;
  HANDLE  hAccel;
  WNDCLASS wndclass;

  if (!hPrevInstance) {
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = (long (FAR PASCAL *) ()) WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(hInstance, szAppName);
#ifdef BADCURSOR
    wndclass.hCursor = NULL;
#else
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
#endif
    wndclass.hbrBackground = GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = szAppName;
    wndclass.lpszClassName = szAppName;

    RegisterClass(&wndclass);
  }
  hwnd = CreateWindow(szAppName, szAppName,
		      WS_OVERLAPPEDWINDOW,
		      GetSystemMetrics(SM_CXSCREEN) / 8,
		      GetSystemMetrics(SM_CYSCREEN) / 8,
		      (GetSystemMetrics(SM_CXSCREEN) * 3) / 4,
		      (GetSystemMetrics(SM_CYSCREEN) * 3) / 4,
		      NULL, NULL, hInstance, lpszCmdLine);

  if (!hwnd)
    return (FALSE);

#ifdef BADCURSOR
  hArrowCursor = LoadCursor(NULL, IDC_ARROW);
  hWaitCursor = LoadCursor(NULL, IDC_WAIT);
  bWorking = FALSE;
  SetCursor(hArrowCursor);
#endif

  /* First argument specifies word list.  If absent, use EXE_path/nlist. */
  if (*lpszCmdLine) {
    lstrcpyn(szWlFile, lpszCmdLine, sizeof(szWlFile) - 1);
  } else {
    if (GetModuleFileName(hInstance, szWlFile, sizeof(szWlFile) - 16)) {
      LPSTR lpszSlash;
      if ((lpszSlash = strrchr(szWlFile, '\\'))) {
        lstrcpy(CharNext(lpszSlash), DEFAULT_WLIST);
      } else {
        lstrcpy(szWlFile, DEFAULT_WLIST);
      }
    } else {
      return (FALSE);
    }
  }

  ShowWindow(hwnd, nCmdShow);

  UpdateWindow(hwnd);

  hAccel = LoadAccelerators(hInstance, szAppName);

  while (GetMessage(&msg, NULL, 0, 0)) {
    if (!TranslateAccelerator(hwnd, hAccel, &msg)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }
  return msg.wParam;
}

VOID Throttle()
{
  MSG     msg;

  while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
    if (msg.message == WM_QUIT) {
      trying = 0;			/* Cause spook to exit */
      break;
    }
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
}

BOOL FAR PASCAL AboutDlgProc(HWND hDlg, WORD message, WORD wParam, LONG lParam)
{
  switch (message) {
    case WM_INITDIALOG:
    return TRUE;

  case WM_COMMAND:
    switch (wParam) {
    case IDOK:
      EndDialog(hDlg, 0);
      return TRUE;
    }
    break;
  }
  return FALSE;
}

long FAR PASCAL WndProc(HWND hwnd, WORD message, WORD wParam, LONG lParam)
{
  static FARPROC lpfnAboutDlgProc;
  static FARPROC lpfnOpDlProc;
  static HANDLE hInst;
  static HWND hwndEditM;
  static HWND hwndEditA;
  static HWND hwndEditK;
  static HWND hwndEditR;
  static HWND hwndCtrl;
  static HGLOBAL hCipherText, hAttribText, hKeypairText, hResultText;
  static LPSTR lpszCipher, lpszAttrib, lpszKeypair, lpszPair, lpszResult;
  DWORD   dwLengthM, dwLengthA, dwLengthK, dwLengthR;
  short   cxMain, cyMain;
  LONG    lSelect;
  WORD    wEnable;

  switch (message) {
  case WM_CREATE:

    hInst = ((LPCREATESTRUCT) lParam)->hInstance;
    lpfnAboutDlgProc = MakeProcInstance(AboutDlgProc, hInst);
    lpfnOpDlProc = MakeProcInstance(OpDlProc, hInst);

    hwndEditM = CreateWindow("edit", NULL,
			    WS_CHILD | WS_BORDER | WS_VISIBLE | WS_VSCROLL |
			    ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL,
			    0, 0, 0, 0,
			    hwnd, (HMENU) EDITMID, hInst, NULL);
    hwndCtrl = hwndEditM;
    SendMessage(hwndEditM, EM_LIMITTEXT, 32000, 0L);
    SendMessage(hwndEditM, WM_SETFONT,
                (WPARAM) GetStockObject (SYSTEM_FIXED_FONT), 0);

    hwndEditA = CreateWindow("edit", NULL,
			    WS_CHILD | WS_BORDER | WS_VISIBLE | WS_VSCROLL |
			    ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL,
			    0, 0, 0, 0,
			    hwnd, (HMENU) EDITAID, hInst, NULL);
    SendMessage(hwndEditA, EM_LIMITTEXT, 32000, 0L);
    SendMessage(hwndEditA, WM_SETFONT,
                (WPARAM) GetStockObject (SYSTEM_FIXED_FONT), 0);

    hwndEditK = CreateWindow("edit", NULL,
			    WS_CHILD | WS_BORDER | WS_VISIBLE | WS_VSCROLL |
			    ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL,
			    0, 0, 0, 0,
			    hwnd, (HMENU) EDITKID, hInst, NULL);
    SendMessage(hwndEditK, EM_LIMITTEXT, 32000, 0L);
    SendMessage(hwndEditK, WM_SETFONT,
                (WPARAM) GetStockObject (SYSTEM_FIXED_FONT), 0);

    hwndEditR = CreateWindow("edit", NULL,
			    WS_CHILD | WS_BORDER | WS_VISIBLE | WS_VSCROLL |
			    ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL,
			    0, 0, 0, 0,
			    hwnd, (HMENU) EDITRID, hInst, NULL);
    SendMessage(hwndEditR, EM_LIMITTEXT, 32000, 0L);
    SendMessage(hwndEditR, WM_SETFONT,
                (WPARAM) GetStockObject (SYSTEM_FIXED_FONT), 0);

    trying = 0;
    return 0;

  case WM_SETFOCUS:
    SetFocus(hwndCtrl);
    return 0;

  case WM_SIZE:
    cxMain = LOWORD (lParam);
    cyMain = HIWORD (lParam);
    MoveWindow(hwndEditM,
	       0,
	       0,
	       cxMain,
	       cyMain / 3,
	       TRUE);
    MoveWindow(hwndEditA,
	       0,
	       cyMain / 3,
	       cxMain,
	       (cyMain / 2) - (cyMain / 3),
	       TRUE);
    MoveWindow(hwndEditK,
	       0,
	       cyMain / 2,
	       cxMain,
	       ((cyMain + cyMain) / 3) - (cyMain / 2),
	       TRUE);
    MoveWindow(hwndEditR,
	       0,
	       (cyMain + cyMain) / 3,
	       cxMain,
	       cyMain - ((cyMain + cyMain) / 3),
	       TRUE);
    return 0;

  case WM_INITMENUPOPUP:
    if (HIWORD(lParam)) {		/* System Menu coming up */
      EnableMenuItem((HMENU) wParam, 6, MF_BYPOSITION |
                     (trying ? MF_GRAYED : MF_ENABLED));
    } else if (lParam == 1) {	/* Edit Menu */
      EnableMenuItem((HMENU) wParam, IDM_UNDO,
		     SendMessage(hwndCtrl, EM_CANUNDO, 0, 0L) ?
		     MF_ENABLED : MF_GRAYED);

      EnableMenuItem((HMENU) wParam, IDM_PASTE,
		     IsClipboardFormatAvailable(CF_TEXT) ?
		     MF_ENABLED : MF_GRAYED);

      lSelect = SendMessage(hwndCtrl, EM_GETSEL, 0, 0L);

      if (HIWORD(lSelect) == LOWORD(lSelect))
	wEnable = MF_GRAYED;
      else
	wEnable = MF_ENABLED;

      EnableMenuItem((HMENU) wParam, IDM_CUT, wEnable);
      EnableMenuItem((HMENU) wParam, IDM_COPY, wEnable);
      EnableMenuItem((HMENU) wParam, IDM_DELETE, wEnable);
    } else if (lParam == 0) {	/* Command Menu */
      EnableMenuItem((HMENU) wParam, IDM_OPTIONS,
                     trying ? MF_GRAYED : MF_ENABLED);
      EnableMenuItem((HMENU) wParam, IDM_EXIT,
                     trying ? MF_GRAYED : MF_ENABLED);
      EnableMenuItem((HMENU) wParam, IDM_SABORT,
                     trying ? MF_ENABLED : MF_GRAYED);
    }
    return 0;

  case WM_COMMAND:
    if (LOWORD(lParam) &&
        (wParam == EDITMID || wParam == EDITAID
	 || wParam == EDITKID || wParam == EDITRID)) {
      switch (HIWORD(lParam)) {
      /* case EN_SETFOCUS: */
      default:
        hwndCtrl = (HWND) LOWORD(lParam);
	return 0;

      case EN_ERRSPACE:
        ErrorBox(szAppName, "%s", "Edit control out of space.");
	return 0;
      }
      /* break; */
    }
    switch (wParam) {
    case IDM_EXIT:
      SendMessage(hwnd, WM_CLOSE, 0, 0L);
      return 0;

    case IDM_OPTIONS:
      {
	if (DialogBox(hInst, "OPTIONS", hwnd, lpfnOpDlProc) == 1)
	  InvalidateRect(hwnd, NULL, TRUE);
      }
      break;
    case IDM_ABOUT:
      DialogBox(hInst, "AboutBox", hwnd,
		lpfnAboutDlgProc);
      return 0;

    case IDM_UNDO:
      SendMessage(hwndCtrl, WM_UNDO, 0, 0L);
      return 0;

    case IDM_CUT:
      SendMessage(hwndCtrl, WM_CUT, 0, 0L);
      return 0;

    case IDM_COPY:
      SendMessage(hwndCtrl, WM_COPY, 0, 0L);
      return 0;

    case IDM_PASTE:
      SendMessage(hwndCtrl, WM_PASTE, 0, 0L);
      return 0;

    case IDM_DELETE:
      SendMessage(hwndCtrl, WM_CLEAR, 0, 0L);
      return 0;

    case IDM_SELALL:
      SendMessage(hwndCtrl, EM_SETSEL, 0, -1L);
      return 0;

    case IDM_DECIPHER:
#ifndef NDEBUG
      ErrorBox("IDM_DECIPHER",
        "wParam=%5d, lParam=%5d-%5d\n"
        "WlFile=[%s]\n",
        wParam, HIWORD(lParam), LOWORD(lParam),
        szWlFile);
#endif
      /* Allocate space for and set up ciphertext */
      dwLengthM = GetWindowTextLength(hwndEditM);
      if ((hCipherText = GlobalAlloc(GPTR, dwLengthM + 2)) == NULL) {
        ErrorBox(szAppName, "%s", "Not enough memory for ciphertext");
        return FALSE;
      }
      lpszCipher = GlobalLock(hCipherText);
      GetWindowText(hwndEditM, lpszCipher, dwLengthM + 1);
      /* Allocate space for and set up attribution */
      dwLengthA = GetWindowTextLength(hwndEditA);
      if ((hAttribText = GlobalAlloc(GPTR, dwLengthA + 2)) == NULL) {
        ErrorBox(szAppName, "%s", "Not enough memory for attribution");
        return FALSE;
      }
      lpszAttrib = GlobalLock(hAttribText);
      GetWindowText(hwndEditA, lpszAttrib, dwLengthA + 1);
      /* Allocate space for and set up key pairs */
      dwLengthK = GetWindowTextLength(hwndEditK);
      if ((hKeypairText = GlobalAlloc(GPTR, dwLengthK + 4)) == NULL) {
        ErrorBox(szAppName, "%s", "Not enough memory for attribution");
        return FALSE;
      }
      lpszKeypair = GlobalLock(hKeypairText);
      GetWindowText(hwndEditK, lpszKeypair, dwLengthK + 1);
      /* Convert key pairs, if any, to an initial key */
      lstrcpy(szInitKey, szBlankKey);
      strupr(lpszKeypair);
      lstrcat(lpszKeypair, " ");
      lpszPair = strtok(lpszKeypair, " ");
      while (lpszPair) {
        if ((lstrlen(lpszPair) != 2)
            || !isalpha(*lpszPair) || !isalpha(*(lpszPair + 1))) {
          ErrorBox(szAppName, "Improper key pair: %s", lpszPair);
          return FALSE;
        } else {
          szInitKey[*lpszPair - 'A'] = *(lpszPair + 1);
        }
        lpszPair = strtok(NULL, " ");
      }
#ifndef NDEBUG
      ErrorBox("IDM_DECIPHER",
        "wParam=%5d, lParam=%5d-%5d\n"
        "Length=%5d, CipherText=[%s]\n"
        "Length=%5d, AttribText=[%s]\n"
        "Length=%5d, KeypairText=[%s]\n"
        "Length=%5d, InitKey=[%s]\n",
        wParam, HIWORD(lParam), LOWORD(lParam),
        dwLengthM, lpszCipher,
        dwLengthA, lpszAttrib,
        dwLengthK, lpszKeypair,
	sizeof(szInitKey), szInitKey);
#endif
      dwLengthR = lstrlen(lpszCipher) + lstrlen(lpszAttrib) +
                  lstrlen(szWlFile) + 500;
      if ((hResultText = GlobalAlloc(GPTR, dwLengthR + 1)) == NULL) {
        ErrorBox(szAppName, "%s", "Cannot allocate memory for result");
        return FALSE;
      }
      lpszResult = GlobalLock(hResultText);
      /* Show a message */
      SetWindowText(hwndEditR, "Working...");
#ifdef BADCURSOR
      /* Switch to hourglass cursor */
      bWorking = TRUE;
      SetCursor(hWaitCursor);
#endif
      /* Go try a solution */
      spook(nSelfOK, szWlFile, szInitKey, lpszCipher, lpszAttrib, lpszResult);
#ifdef BADCURSOR
      /* Switch back to the arrow cursor */
      bWorking = FALSE;
      SetCursor(hArrowCursor);
#endif
      /* Present the solution */
#ifndef NDEBUG
      ErrorBox("IDM_DECIPHER",
        "wParam=%5d, lParam=%5d-%5d\n"
        "Length=%5d, ResultText=[%s]\n"
        "SetWindowText=%5d\n",
        wParam, HIWORD(lParam), LOWORD(lParam),
        dwLengthR, lpszResult, SetWindowText(hwndEditR, lpszResult));
#else
      SetWindowText(hwndEditR, lpszResult);
#endif
      /* Free the memory used for text strings */
      GlobalUnlock(hResultText);
      GlobalFree(hResultText);
      GlobalUnlock(hAttribText);
      GlobalFree(hAttribText);
      GlobalUnlock(hCipherText);
      GlobalFree(hCipherText);
      return 0;

    case IDM_SABORT:
      /* Force spook to quit more or less immediately */
      trying = 0;
      return 0;
    }
    break;

  case WM_MOUSEMOVE:
  case WM_NCMOUSEMOVE:
#ifdef BADCURSOR
    if (bWorking) {
      SetCursor(hWaitCursor);
    } else {
      SetCursor(hArrowCursor);
    }
#endif
    return 0;

  case WM_CLOSE:
    DestroyWindow(hwnd);
    return 0;

  case WM_QUERYENDSESSION:
    return 0;

  case WM_DESTROY:
    PostQuitMessage(0);
    return 0;
  }
  return DefWindowProc(hwnd, message, wParam, lParam);
}

VOID ErrorBox(char *szCaption, char *szFormat, ...)
{
  char szBuffer[4096];
  char *pArguments;

  pArguments = (char *)&szFormat + sizeof szFormat;
  vsprintf(szBuffer, szFormat, pArguments);
  MessageBox(NULL, szBuffer, szCaption, MB_OK | MB_ICONSTOP);
}

BOOL FAR PASCAL OpDlProc(HWND hDlg, unsigned wMsg, WORD wParam, LONG lParam)
/* Options dialog */
{
  int     selfok_button;

  switch (wMsg) {
  case WM_INITDIALOG:
    if (nSelfOK)
      selfok_button = OD_RB_SELFOK;
    else
      selfok_button = OD_RB_SELFDIF;
    CheckRadioButton(hDlg, OD_RB_SELFOK, OD_RB_SELFDIF, selfok_button);
    return (TRUE);
  case WM_COMMAND:
    switch (wParam) {
    case OD_RB_SELFDIF:
    case OD_RB_SELFOK:
      nSelfOK = IsDlgButtonChecked(hDlg, OD_RB_SELFOK);
      break;
    case OD_OK:
      EndDialog(hDlg, 1);
      break;
    default:
      return (FALSE);
    }
    break;
  default:
    return (FALSE);
  }
  return (TRUE);
}
