/**************************************************************************
 * WinPoem                                                                *
 *                                                                        *
 * A small C++ program which displays a random poem on execution.         *
 * It also allows search for poems containing a string.                   *
 * It requires winpoem.dat and creates winpoem.idx.                       *
 *                                                                        *
 * Copyright 1992 Julian Smart, email jacs@aiai.ed.ac.uk                  *
 *                                                                        *
 * Filename: poetry.cpp                                                   *
 * Purpose:  Main source file for WinPoem.                                *
 * Version:  1.0                                                          *
 *************************************************************************/

#include <windows.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define         buf_size 3000
#define         DEFAULT_POETRY_DAT "winpoem"
#define         DEFAULT_POETRY_IND "winpoem"
#define         DEFAULT_CHAR_HEIGHT 18
#define         DEFAULT_FONT "Swiss"
#define         DEFAULT_X_POS 0
#define         DEFAULT_Y_POS 0
#define         BORDER_SIZE 30
#define         THIN_LINE_BORDER 10
#define         THICK_LINE_BORDER 16
#define         THICK_LINE_WIDTH 2
#define         SHADOW_OFFSET 1
#define         X_SIZE 30
#define         Y_SIZE 20

static char     poem_buffer[buf_size];          // Storage for each poem
static char     line[120];                      // Storage for a line
static char     title[120];                     // Remember the title
static char     search_string[120];             // The search string
static int      pages[20];                      // For multipage poems -
                                                // store the start of each page
static long     last_poem_start = 0;            // Start of last found poem
static long     last_find = -1;                 // Point in file of last found
                                                // search string
static BOOL     search_ok = FALSE;              // Search was successful
static BOOL     same_search = FALSE;            // Searching on same string

static long     index[600];                     // Index of poem starts
static long     nitems = 0;                     // Number of poems
static int      desired_char_height = DEFAULT_CHAR_HEIGHT; // Desired height
static char     DesiredFont[64];                // Chosen font
static int      char_height = DEFAULT_CHAR_HEIGHT; // Actual height
static int      index_ptr = -1;                 // Pointer into index
static int      poem_height, poem_width;        // Size of poem window
static HWND     GlobalWindow;                   // Window handle
static HANDLE   bitmap = NULL;                  // Window backing bitmap
static HMENU    PopupMenu;                      // WinPoem menu
static int      XPos;                           // Startup X position
static int      YPos;                           // Startup Y position

static char     index_filename[100];            // Index filename
static char     data_filename[100];             // Data filename
static char     error_buf[200];                 // Error message buffer
static BOOL     loaded_ok = FALSE;              // Poem loaded ok
static BOOL     index_ok = FALSE;               // Index loaded ok

static BOOL     paging = FALSE;                 // Are we paging?
static int      current_page = 0;               // Currently viewed page

static HICON    Corner1;                        // Shell corner icons
static HICON    Corner2;
static HICON    Corner3;
static HICON    Corner4;

static HPEN     WhitePen;                       // Pens
static HPEN     BlackPen;
static HPEN     GreyPen;
static HBRUSH   GreyBrush;
static HBRUSH   DarkGreyBrush;

// Fonts
static HFONT    NormalFont = NULL;              // Fonts
static HFONT    BoldFont = NULL;
static HFONT    ItalicFont = NULL;

void            PoetryError(char *);
void            PoetryNotify(char *Msg);
void            InitPoetry();
void            TryLoadIndex();
BOOL            LoadPoem(char *, long);
int             GetIndex();
int             LoadIndex(char *);
void            LogMessage(char *Msg);
void            LogInitialize();
BOOL            Compile();
void            WritePreferences();
void            ReadPreferences();
void            DeleteFonts(void);
void            FindMax(int *max_thing, int thing);
void            CreateFonts();
void            CopyToClipboard(HWND, char *);

BOOL FAR PASCAL _export AboutDlgProc(HWND hDlg, unsigned message, WORD wParam, LONG lParam);
long FAR PASCAL _export WndProc( HWND hWnd, WORD iMessage,
                                 WORD wParam, LONG lParam );
BOOL FAR PASCAL _export SearchDlgProc(HWND hDlg, unsigned message,
                                      WORD wParam, LONG lParam);
class Main
{
    public:
    static HANDLE hInstance;
    static HANDLE hPrevInstance;
    static int nCmdShow;
    static int MessageLoop( void );
};

HANDLE Main::hInstance = 0;
HANDLE Main::hPrevInstance = 0;
int Main::nCmdShow = 0;

// Message loop
int Main::MessageLoop( void )
{
    MSG msg;

    while( GetMessage( &msg, NULL, 0, 0 ) )
    {
        TranslateMessage( &msg );
        DispatchMessage( &msg );
    }
    return msg.wParam;
}

// Base class
class Window
{
    protected:
        HWND hWnd;
    public:
        // Provide (read) access to the window's handle in case it is needed
        // elsewhere.
        HWND GetHandle( void ) { return hWnd; }

        BOOL Show( int nCmdShow ) { return ShowWindow( hWnd, nCmdShow ); }
        void Update( void ) { UpdateWindow( hWnd ); }
        // Pure virtual function makes Window an abstract class.
        virtual long WndProc( WORD iMessage, WORD wParam, LONG lParam ) = 0;
};

class MainWindow : public Window
{
    private:
        static char szClassName[14];
    public:
        // Register the class only AFTER WinMain assigns appropriate
        // values to static members of Main and only if no previous
        // instances of the program exist (a previous instance would
        // have already performed the registration).
        static void Register( void )
        {
            WNDCLASS wndclass;   // Structure used to register Windows class.

            wndclass.style         = CS_HREDRAW | CS_VREDRAW;
            wndclass.lpfnWndProc   = ::WndProc;
            wndclass.cbClsExtra    = 0;
            // Reserve extra bytes for each instance of the window;
            // we will use these bytes to store a pointer to the C++
            // (MainWindow) object corresponding to the window.
            // the size of a 'this' pointer depends on the memory model.
            wndclass.cbWndExtra    = sizeof( MainWindow * );
            wndclass.hInstance     = Main::hInstance;
            wndclass.hIcon         = LoadIcon( Main::hInstance, "winpoem" );
            wndclass.hCursor       = LoadCursor( NULL, IDC_ARROW );
            wndclass.hbrBackground = GetStockObject( LTGRAY_BRUSH );
            wndclass.lpszMenuName  = NULL;
            wndclass.lpszClassName = szClassName;

            if ( ! RegisterClass( &wndclass ) )
                exit( FALSE );
        }


        // Do not create unless previously registered.
        MainWindow( void )
        {
            // Pass 'this' pointer in lpParam of CreateWindow().
            hWnd = CreateWindow( szClassName,
                szClassName,
                WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU | WS_VISIBLE,
                XPos, YPos,
                X_SIZE, SW_HIDE,
                NULL, NULL, Main::hInstance,
                (LPSTR) this );

            GlobalWindow = hWnd;

            if ( ! hWnd )
                exit( FALSE );

            Show( Main::nCmdShow );
        }
        long WndProc( WORD iMessage, WORD wParam, LONG lParam );

        // Print a message in the client rectangle.
        void Paint( void );

        // Display next page or poem
        void NextPage(HWND);

        // Display previous page
        void PreviousPage(HWND);

        // User search
        void Search(HWND, BOOL);

        // Look in file for string
        long DoSearch();

        // Do the actual drawing of text (or just calculate size needed)
        void ScanBuffer(BOOL DrawIt, int *max_x, int *max_y);

        // Load the poem
        void GetIndexLoadPoem(void);
        void Resize();

        // Respond to a command menu message
        BOOL Command(WORD);

        // Respond to a keypress
        void KeyDown(WORD);
        void Char(WORD);

        // Change text height (refresh screen)
        void ChangeHeight(int);

};

char MainWindow::szClassName[] = "WinPoem";

// Paint procedure
void MainWindow::Paint( void )
{
    int max_x, max_y;
    PAINTSTRUCT ps;
    RECT rect;
    HDC hdcMem = NULL;
    HDC hdc;

    // If we have a bitmap with a poem drawn on it,
    // whack it onto the window.
    if (bitmap)
    {
      GetClientRect( hWnd, (LPRECT) &rect );
      hdc = BeginPaint( hWnd, &ps );
      hdcMem = CreateCompatibleDC(hdc);
      SelectObject(hdcMem, bitmap);
      BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);
      DeleteDC(hdcMem);
      EndPaint(hWnd, &ps);
    }
    else
    {
      // Necessary default behaviour
      (void)BeginPaint( hWnd, &ps );
      EndPaint(hWnd, &ps);
    }
}

// Change the text height and redraw
void MainWindow::ChangeHeight(int inc)
{
    PAINTSTRUCT ps;
    TEXTMETRIC lpTextMetric;
    HDC hdc = BeginPaint( hWnd, &ps );

    SelectObject(hdc, NormalFont);
    GetTextMetrics(hdc, &lpTextMetric);

    int old_height = lpTextMetric.tmHeight;
    int actual_height = old_height;

    // Keep changing font size until it really changes height
    while (actual_height == old_height && desired_char_height > 1
                                       && desired_char_height < 40)
    {
      desired_char_height += inc;
      CreateFonts();

      // See what ACTUAL char height is
      SelectObject(hdc, NormalFont);
      GetTextMetrics(hdc, &lpTextMetric);
      actual_height = lpTextMetric.tmHeight;
    }
    // Stop ChangeHeight from getting stuck next time
    if (desired_char_height == 1)
      desired_char_height = 2;
    if (desired_char_height == 40)
      desired_char_height = 39;
    EndPaint(hWnd, &ps);
    Resize();
    InvalidateRect(hWnd, NULL, TRUE);
    UpdateWindow(hWnd);
}

// Create the fonts
void CreateFonts()
{
  // Create fonts
  BYTE Font;
  char *TypeFace = "Any";

  if (strcmp(DesiredFont, "Modern") == 0)
    Font = FF_MODERN;
  else if (strcmp(DesiredFont, "Script") == 0)
    Font = FF_SCRIPT;
  else if (strcmp(DesiredFont, "Swiss") == 0)
    Font = FF_SWISS;
  else if (strcmp(DesiredFont, "Roman") == 0)
    Font = FF_ROMAN;
  else if (strcmp(DesiredFont, "Decorative") == 0)
    Font = FF_DECORATIVE;
  else
    Font = FF_DONTCARE;

  if (!NormalFont)
    DeleteObject(NormalFont);
  NormalFont = CreateFont(desired_char_height, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET,
                       OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
                       PROOF_QUALITY, VARIABLE_PITCH | Font,
                       TypeFace);
  if (!ItalicFont)
    DeleteObject(ItalicFont);
  ItalicFont = CreateFont(desired_char_height, 0, 0, 0, FW_BOLD, 1, 0, 0, ANSI_CHARSET,
                       OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
                       PROOF_QUALITY, VARIABLE_PITCH | Font,
                       TypeFace);
  if (!BoldFont)
    DeleteObject(BoldFont);
  BoldFont = CreateFont(desired_char_height, 0, 0, 0, FW_BOLD, 0, 0, 0, ANSI_CHARSET,
                       OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
                       PROOF_QUALITY, VARIABLE_PITCH | Font,
                       TypeFace);
}

// Delete fonts
void DeleteFonts(void)
{
  DeleteObject(NormalFont);
  DeleteObject(BoldFont);
  DeleteObject(ItalicFont);
}

// Read the poetry buffer, either for finding the size
// or for writing to a bitmap (not to the window directly,
// since that displays messily)
// If DrawIt is true, we draw, otherwise we just determine the
// size the window should be.
void MainWindow::ScanBuffer(BOOL DrawIt, int *max_x, int *max_y)
{
    PAINTSTRUCT ps;
    RECT rect;
    TEXTMETRIC lpTextMetric;
    HDC hdc = BeginPaint( hWnd, &ps );
    HDC hdcMem = NULL;

    int i = pages[current_page];
    int ch = -1;
    short x = 10;
    short y = 0;
    int j;
    char *line_ptr;
    int strsize;
    int loword;
    int x1;
    DWORD extent;
    int curr_width = 0;
    BOOL page_break = FALSE;

    short width = 0;
    short height = 0;

    SetBkMode(hdc, TRANSPARENT);

    if (DrawIt)
    {
      width = *max_x;
      height = *max_y;
      rect.left = 0; rect.top = 0;
      rect.right = width; rect.bottom = height;

      y = (height - (poem_height))/2;

      if (bitmap)
      {
        DeleteObject(bitmap);
        bitmap = NULL;
      }

      hdcMem = CreateCompatibleDC(hdc);
      if (hdcMem)
      {
        bitmap = CreateCompatibleBitmap(hdc, width, height);
        if (bitmap)
        {
          SelectObject(hdcMem, bitmap);
          SetBkMode(hdcMem, TRANSPARENT);
          FillRect( hdcMem, &rect, GetStockObject( LTGRAY_BRUSH ) );
        }
        else
        {
          DeleteDC(hdcMem);
          hdcMem = hdc;
        }
      }
    }

    // See what ACTUAL char height is
    SelectObject(hdc, NormalFont);
    GetTextMetrics(hdc, &lpTextMetric);
    char_height = lpTextMetric.tmHeight;

    if (current_page == 0)
      title[0] = 0;
    else if (title[0] != 0)
    {
       SelectObject(hdc, BoldFont);
       strsize = strlen(title);
       extent = GetTextExtent(hdc, title, strsize);
       loword = LOWORD(extent);
       FindMax(&curr_width, loword);

       if (DrawIt)
       {
         x1 = width - loword;
         x = x1/2;
         SelectObject(hdcMem, BoldFont);

         // Change text to WHITE!
         SetTextColor(hdcMem, RGB(0, 0, 0));
         TextOut(hdcMem, x, y, title, strsize);
         // Change text to BLACK!
         SetTextColor(hdcMem, RGB(255, 255, 255));
         TextOut(hdcMem, x-SHADOW_OFFSET, y-SHADOW_OFFSET, title, strsize);
         SetTextColor(hdcMem, RGB(0, 0, 0));
       }
       y += char_height;
       y += char_height;
    }

    while (ch != 0 && !page_break)
    {
        j = 0;
        while (((ch = poem_buffer[i]) != 13) && (ch != 0))
        {
            line[j] = ch;
            j ++;
            i ++;
        }

        if (ch == 13)
        {
            ch = -1;
            i ++;
            // Add another to skip the linefeed
            i ++;
            // If a single newline on its own, put a space in
            if (j == 0)
            {
              line[j] = ' ';
              j ++;
              line[j] = 0;
            }
        }

        if (j > 0)
        {
          line[j] = 0;
          if (line[0] == '@')
          {
            switch (line[1])
            {
              case 'P':
                paging = TRUE;
                page_break = TRUE;
                break;

              case 'T':
                SelectObject(hdc, BoldFont);
                line_ptr = line+3;

                strsize = strlen(line_ptr);
                strcpy(title, line_ptr);
                strcat(title, " (cont'd)");

                extent = GetTextExtent(hdc, line_ptr, strsize);
                loword = LOWORD(extent);
                FindMax(&curr_width, loword);

                if (DrawIt)
                {
                  x1 = width - loword;
                  x = x1/2;
                  SelectObject(hdcMem, BoldFont);

                  // Change text to WHITE!
                  SetTextColor(hdc, RGB(0, 0, 0));
                  TextOut(hdcMem, x, y, line_ptr, strsize);
                  // Change text to BLACK!
                  SetTextColor(hdcMem, RGB(255, 255, 255));
                  TextOut(hdcMem, x-SHADOW_OFFSET, y-SHADOW_OFFSET, line_ptr, strsize);
                  SetTextColor(hdcMem, RGB(0, 0, 0));
                }
                break;

              case 'A':
                line_ptr = line+3;
                SelectObject(hdc, ItalicFont);
                strsize = strlen(line_ptr);

                extent = GetTextExtent(hdc, line_ptr, strsize);
                loword = LOWORD(extent);
                FindMax(&curr_width, loword);

                if (DrawIt)
                {
                  x1 = width - loword;
                  x = x1/2;
                  SelectObject(hdcMem, ItalicFont);
                  SetTextColor(hdcMem, RGB(0, 0, 0));
                  TextOut(hdcMem, x, y, line_ptr, strsize);
                }
                break;

              // Default: just ignore this line
              default:
                y -= char_height;
            }
           }
           else
           {
              SelectObject(hdc, NormalFont);
              strsize = strlen(line);

              extent = GetTextExtent(hdc, line, strsize);
              int loword = LOWORD(extent);
              FindMax(&curr_width, loword);
    
              if (DrawIt)
              {
                x1 = width  - loword;
                int x = x1/2;
                SelectObject(hdcMem, NormalFont);
                SetTextColor(hdcMem, RGB(0, 0, 0));
                TextOut(hdcMem, x, y, line, strsize);
              }
           }
        }
        y += char_height;
    }

    // Write (cont'd)
    if (page_break)
    {
       char *cont = "(cont'd)";

       SelectObject(hdc, NormalFont);
       strsize = strlen(cont);

       extent = GetTextExtent(hdc, cont, strsize);
       int loword = LOWORD(extent);
       FindMax(&curr_width, loword);

       if (DrawIt)
       {
         x1 = width  - loword;
         int x = x1/2;
         SelectObject(hdcMem, NormalFont);
         SetTextColor(hdcMem, RGB(255, 255, 255));
         TextOut(hdcMem, x, y, cont, strsize);
       }
       y += 2*char_height;
    }

    *max_x = curr_width;
    *max_y = y-char_height;

    if (page_break)
      pages[current_page+1] = i;
    else
      paging = FALSE;

    if (DrawIt)
    {
    // Draw dark grey thick border
    SelectObject(hdcMem, DarkGreyBrush);
    SelectObject(hdcMem, GreyPen);

    // Left side
    Rectangle(hdcMem, 0, THIN_LINE_BORDER, THIN_LINE_BORDER, height-THIN_LINE_BORDER);
    // Top side
    Rectangle(hdcMem, THIN_LINE_BORDER, 0, width-THIN_LINE_BORDER, THIN_LINE_BORDER);
    // Right side
    Rectangle(hdcMem, width-THIN_LINE_BORDER, THIN_LINE_BORDER, width, height-THIN_LINE_BORDER);
    // Bottom side
    Rectangle(hdcMem, THIN_LINE_BORDER, height-THIN_LINE_BORDER, width-THIN_LINE_BORDER, height);

    // Draw border
    // Have grey background, plus 3-d border -
    // One black rectangle.
    // Inside this, left and top sides - dark grey. Bottom and right -
    // white.

    // Change pen to black
    SelectObject(hdcMem, BlackPen);
    MoveTo(hdcMem, THIN_LINE_BORDER, THIN_LINE_BORDER);
    LineTo(hdcMem, width-THIN_LINE_BORDER, THIN_LINE_BORDER);
    LineTo(hdcMem, width-THIN_LINE_BORDER, height-THIN_LINE_BORDER);
    LineTo(hdcMem, THIN_LINE_BORDER, height-THIN_LINE_BORDER);
    LineTo(hdcMem, THIN_LINE_BORDER, THIN_LINE_BORDER);

    // Right and bottom white lines
    SelectObject(hdcMem, WhitePen);
    MoveTo(hdcMem, width-THICK_LINE_BORDER,
                THICK_LINE_BORDER);
    LineTo(hdcMem, width-THICK_LINE_BORDER,
                height-THICK_LINE_BORDER);
    LineTo(hdcMem, THICK_LINE_BORDER,
                height-THICK_LINE_BORDER);

    // Left and top grey lines
    SelectObject(hdcMem, GreyPen);
    LineTo(hdcMem, THICK_LINE_BORDER, THICK_LINE_BORDER);
    LineTo(hdcMem, width-THICK_LINE_BORDER, THICK_LINE_BORDER);

    // Draw icons
    SelectObject(hdcMem, BlackPen);
    DrawIcon(hdcMem, 0, 0, Corner1);
    DrawIcon(hdcMem, width-32, 0, Corner2);

    int y2 = height - 32;
    int x2 = (width-32);
    DrawIcon(hdcMem, 0, y2, Corner3);
    DrawIcon(hdcMem, x2, y2, Corner4);

    DeleteDC(hdcMem);

    }

    EndPaint(hWnd, &ps);
}

// Get an index (randomly generated) and load the poem
void MainWindow::GetIndexLoadPoem(void)
{
    if (index_ok)
      index_ptr = GetIndex();

    if (index_ptr > -1)
      loaded_ok = LoadPoem(data_filename, -1);
}

// Find the size of the poem and resize the window accordingly
void MainWindow::Resize()
{
    int max_x, max_x1;
    int max_y, max_y1;
    RECT Rect;
    RECT ClientRect;
    GetWindowRect(GetHandle(), &Rect);
    GetClientRect( hWnd, (LPRECT) &ClientRect );

    // Calculate what to add on for the window borders & menu bar
    int DiffX = (Rect.right - Rect.left) - ClientRect.right;
    int DiffY = (Rect.bottom - Rect.top) - ClientRect.bottom;

    // Get the poem size
    ScanBuffer(FALSE, &poem_width, &poem_height);
    max_x = poem_width + (2*BORDER_SIZE);
    max_y = poem_height + (2*BORDER_SIZE);
    max_x1 = max_x;
    max_y1 = max_y;

    // Actually draw it
    ScanBuffer(TRUE, &max_x1, &max_y1);

    MoveWindow(GetHandle(), Rect.left, Rect.top, max_x+DiffX, max_y+DiffY, TRUE);
    SetFocus(GetHandle());
}

// Which is more?
void FindMax(int *max_thing, int thing)
{
  if (thing > *max_thing)
    *max_thing = thing;
}

// Process menu commands
// Return TRUE if processed one
BOOL MainWindow::Command(WORD Item)
{
  HWND handle = GetHandle();
  FARPROC lpProcAbout;

  switch (Item)
  {
     case 101:
       // Another poem/page
       NextPage(handle);
       break;
     case 113:
       // Previous page
       PreviousPage(handle);
       break;
     case 114:
       // Search - with dialog
       Search(handle, TRUE);
       break;
     case 115:
       // Search - without dialog (next match)
       Search(handle, FALSE);
       break;
     case 116:
       // Copy current poem to the clipboard
       CopyToClipboard(handle, poem_buffer);
       break;
     case 105:
       // Bigger text
       ChangeHeight(1);
       break;
     case 106:
       // Smaller text
       ChangeHeight(-1);
       break;
     case 107:
       strcpy(DesiredFont, "Roman");
       DeleteFonts();
       CreateFonts();
       Resize();
       InvalidateRect(handle, NULL, TRUE);
       UpdateWindow(handle);
       break;

     case 108:
       strcpy(DesiredFont, "Swiss");
       DeleteFonts();
       CreateFonts();
       Resize();
       InvalidateRect(handle, NULL, TRUE);
       UpdateWindow(handle);
       break;

     case 109:
       strcpy(DesiredFont, "Modern");
       DeleteFonts();
       CreateFonts();
       Resize();
       InvalidateRect(handle, NULL, TRUE);
       UpdateWindow(handle);
       break;

     case 110:
       strcpy(DesiredFont, "Script");
       DeleteFonts();
       CreateFonts();
       Resize();
       InvalidateRect(handle, NULL, TRUE);
       UpdateWindow(handle);
       break;

     case 111:
       strcpy(DesiredFont, "Decorative");
       DeleteFonts();
       CreateFonts();
       Resize();
       InvalidateRect(handle, NULL, TRUE);
       UpdateWindow(handle);
       break;

     case 112:
       strcpy(DesiredFont, "Any");
       DeleteFonts();
       CreateFonts();
       Resize();
       InvalidateRect(handle, NULL, TRUE);
       UpdateWindow(handle);
       break;

     case 102:
       // Compile index
       Compile();
       break;
     case 103:
       // About
       lpProcAbout = MakeProcInstance((FARPROC)AboutDlgProc, Main::hInstance);
       DialogBox(Main::hInstance, "DIALOG_1", handle, lpProcAbout);
       FreeProcInstance(lpProcAbout);
       break;
     case 104:
       // Exit
       SendMessage(handle, WM_CLOSE, 0, 0L);
       break;
     default:
       return FALSE;
  }
  return TRUE;
}

// Next page/poem
void MainWindow::NextPage(HWND handle)
{
  if (paging)
    current_page ++;
  else
  {
    current_page = 0;
    GetIndexLoadPoem();
  }
  Resize();
  InvalidateRect(handle, NULL, TRUE);
  UpdateWindow(handle);
}

// Previous page
void MainWindow::PreviousPage(HWND handle)
{
  if (current_page > 0)
  {
    current_page --;
    Resize();
    InvalidateRect(handle, NULL, TRUE);
    UpdateWindow(handle);
  }
}

// Search for a string
void MainWindow::Search(HWND handle, BOOL ask)
{
  FARPROC lpProcSearch;
  long position;

  if (ask || (search_string[0] == 0))
  {
    lpProcSearch = MakeProcInstance((FARPROC)SearchDlgProc, Main::hInstance);
    DialogBox(Main::hInstance, "DIALOG_2", handle, lpProcSearch);
    FreeProcInstance(lpProcSearch);
  }
  else
  {
    same_search = TRUE;
    search_ok = TRUE;
  }

  if (search_ok)
  {
    position = DoSearch();
    if (position > -1)
    {
       loaded_ok = LoadPoem(data_filename, position);
       Resize();
       InvalidateRect(handle, NULL, TRUE);
       UpdateWindow(handle);
    }
    else
    {
      last_poem_start = 0;
      PoetryNotify("Search string not found.");
    }
  }
}

// Process non-character key presses
void MainWindow::KeyDown(WORD Item)
{
  HWND handle = GetHandle();

  switch (Item)
  {
    case VK_SPACE:
    case VK_NEXT:
       // Another poem
       NextPage(handle);
       break;
    case VK_ESCAPE:
       SendMessage(handle, WM_CLOSE, 0, 0L);
       break;
    case VK_PRIOR:
       PreviousPage(handle);
       break;
    default:
       break;
   }
 }

// Process characters
void MainWindow::Char(WORD Item)
{
  HWND handle = GetHandle();

  switch (Item)
  {
    case 'n':
    case 'N':
      // Next match
      Search(handle, FALSE);
      break;
    case 's':
    case 'S':
      // New search
      Search(handle, TRUE);
      break;
    default:
       break;
   }
 }

// Copy a string to the clipboard
void CopyToClipboard(HWND handle, char *s)
{
  int length = strlen(s);
  HANDLE hGlobalMemory = GlobalAlloc(GHND, (DWORD) length + 1);
  if (hGlobalMemory)
  {
    LPSTR lpGlobalMemory = GlobalLock(hGlobalMemory);
    int i, j = 0;
    for (i = 0; i < length; i ++)
    {
      if (s[i] == '@')
      {
        i++;
        switch (s[i])
        {
          case 'P':
            break;
          case 'T':
          case 'A':
          default:
            i ++;
            break;
        }
      }
      else
      {
        lpGlobalMemory[j] = s[i];
        j ++;
      }
    }

    GlobalUnlock(hGlobalMemory);
    OpenClipboard(handle);
    EmptyClipboard();
    SetClipboardData(CF_TEXT, hGlobalMemory);
    CloseClipboard();
  }
}

// Maind window procedure
long MainWindow::WndProc( WORD iMessage, WORD wParam, LONG lParam )
{
  RECT ClientRect;
  POINT point;
  switch (iMessage)
  {
	case WM_CREATE:
	    break;

	case WM_PAINT:
            Paint();
            break;

        // Right button - popup the menu
        case WM_RBUTTONDOWN:
            point = MAKEPOINT(lParam);
            ClientToScreen(GetHandle(), &point);
            TrackPopupMenu(PopupMenu, 0, point.x, point.y, 0, GetHandle(), NULL);
            break;

        case WM_DESTROY:
            WritePreferences();
            DeleteFonts();
            DeleteObject(GreyPen);
            DeleteObject(WhitePen);
//            DestroyMenu(PopupMenu);
            if (bitmap)
              DeleteObject(bitmap);
            PostQuitMessage( 0 );
            break;
        case WM_SYSCOMMAND:
            // For some reason this doesn't work, i.e.
            // the window is shown before this can take effect
            if(wParam == SC_RESTORE)
              ShowWindow(hWnd, SW_HIDE);

            if (!Command(wParam))
            {
              DefWindowProc( hWnd, iMessage, wParam, lParam );
              // When we restore, get another poem
              if(wParam == SC_RESTORE)
              {
                NextPage(hWnd);
                ShowWindow(hWnd, SW_SHOW);
                SetFocus(hWnd);
              }
            }
            break;
        case WM_COMMAND:
            Command(wParam);
            break;
        case WM_KEYDOWN:
            KeyDown(wParam);
            break;
        case WM_CHAR:
            Char(wParam);
            break;
        default:
            return DefWindowProc( hWnd, iMessage, wParam, lParam );
    }
}

// If data pointers are near pointers
#if defined(__SMALL__) || defined(__MEDIUM__)
inline Window *GetPointer( HWND hWnd )
{
    return (Window *) GetWindowWord( hWnd, 0 );
}
inline void SetPointer( HWND hWnd, Window *pWindow )
{
    SetWindowWord( hWnd, 0, (WORD) pWindow );
}

// else pointers are far
#elif defined(__LARGE__) || defined(__COMPACT__)
inline Window *GetPointer( HWND hWnd )
{
    return (Window *) GetWindowLong( hWnd, 0 );
}
inline void SetPointer( HWND hWnd, Window *pWindow )
{
    SetWindowLong( hWnd, 0, (LONG) pWindow );
}

#else
    #error Choose another memory model!
#endif

// Main window procedure - C function calls C++ version
long FAR PASCAL _export WndProc( HWND hWnd, WORD iMessage, WORD wParam,
                                 LONG lParam )
{
    // Pointer to the (C++ object that is the) window.
    Window *pWindow = GetPointer( hWnd );

    // The pointer pWindow will have an invalid value if the WM_CREATE
    // message has not yet been processed (we respond to the WM_CREATE
    // message by setting the extra bytes to be a pointer to the
    // (C++) object corresponding to the Window identified
    // by hWnd).  The messages that
    // precede WM_CREATE must be processed without using pWindow so we
    // pass them to DefWindowProc.
    // How do we know in general if the pointer pWindow is invalid?
    // Simple: Windows allocates the window extra bytes using LocalAlloc
    // which zero initializes memory; thus, pWindow will have a value of
    // zero before we set the window extra bytes to the 'this' pointer.
    // Caveat emptor: the fact that LocalAlloc will zero initialize the
    // window extra bytes is not documented; therefore, it could change
    // in the future.

    if ( pWindow == 0 )
    {
        if ( iMessage == WM_CREATE )
        {
            LPCREATESTRUCT lpcs;

            lpcs = (LPCREATESTRUCT) lParam;
            pWindow = (Window *) lpcs->lpCreateParams;

            // Store a pointer to this object in the window's extra bytes;
            // this will enable to access this object (and its member
            // functions) in WndProc where we are
            // given only a handle to identify the window.
            SetPointer( hWnd, pWindow );
            // Now let the object perform whatever
            // initialization it needs for WM_CREATE in its own
            // WndProc.
            return pWindow->WndProc( iMessage, wParam, lParam );
        }
        else
            return DefWindowProc( hWnd, iMessage, wParam, lParam );
    }
    else
        return pWindow->WndProc( iMessage, wParam, lParam );
}

// Turn off warning: Parameter 'lpszCmdLine' is never used in function WinMain(unsigned int,unsigned int,char far*,int)
#pragma argsused

// Turn off warning: 'MainWnd' is assigned a value that is never used in function WinMain(unsigned int,unsigned int,char far*,int)
#pragma option -w-aus

// Main windows entry point
int PASCAL WinMain( HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine,
                    int nCmdShow )
{
    // Initialize the debugging log - development only
//    LogInitialize();
    ReadPreferences();

    Main::hInstance = hInstance;
    Main::hPrevInstance = hPrevInstance;
    Main::nCmdShow = nCmdShow;

    // A Windows class should be registered with Windows before any windows
    // of that type are created.
    // Register here all Windows classes that will be used in the program.
    // Windows classes should not be registered if an instance of
    // the program is already running.
    if ( ! Main::hPrevInstance ) {
        MainWindow::Register();
    }

    randomize();
    pages[0] = 0;
    search_string[0] = 0;

    MainWindow MainWnd;

    PopupMenu = LoadMenu(Main::hInstance, "MENU_1");
    HMENU SystemMenu = GetSystemMenu(MainWnd.GetHandle(), FALSE);
    DeleteMenu(SystemMenu, SC_SIZE, MF_BYCOMMAND);
    DeleteMenu(SystemMenu, SC_MAXIMIZE, MF_BYCOMMAND);
    AppendMenu(SystemMenu, MF_SEPARATOR, 0, NULL);
    AppendMenu(SystemMenu, MF_POPUP | MF_STRING, PopupMenu, "WinPoem Options");

    InitPoetry();

    CreateFonts();
    Corner1 = LoadIcon(hInstance, "ICON_1");
    Corner2 = LoadIcon(hInstance, "ICON_2");
    Corner3 = LoadIcon(hInstance, "ICON_3");
    Corner4 = LoadIcon(hInstance, "ICON_4");

    BlackPen = GetStockObject(BLACK_PEN);
    GreyPen = CreatePen(PS_SOLID, THICK_LINE_WIDTH, RGB(100, 100, 100));
    WhitePen = CreatePen(PS_SOLID, THICK_LINE_WIDTH, RGB(255, 255, 255));

    GreyBrush = GetStockObject(LTGRAY_BRUSH);
    DarkGreyBrush = GetStockObject(GRAY_BRUSH);

    if (nCmdShow == SW_SHOWNORMAL)
    {
      MainWnd.GetIndexLoadPoem();
      MainWnd.Resize();
    }

    return Main::MessageLoop();
}


// Load index file
int LoadIndex(char *file_name)
{
    long data;
    FILE *index_file;

    int i = 0;
    char buf[100];

    if (file_name)
      sprintf(buf, "%s.idx", file_name);
    if (! (file_name && (index_file = fopen(buf, "r"))))
      return 0;
    else
    {
      fscanf(index_file, "%ld", &nitems);

      for (i = 0; i < nitems; i++)
      {
        fscanf(index_file, "%ld", &data);
        index[i] = data;
      }
      fclose(index_file);

      return 1;
    }
}

// Get index
int GetIndex()
{
    int indexn = 0;
    double a, b;

    indexn = rand() % nitems;

    if ((indexn < 0) || (indexn > nitems))
    { PoetryError("No such poem!");
      return -1;
    }
    else
      return indexn;
}

// Read preferences
void ReadPreferences()
{
  GetProfileString("WinPoem", "Font", DEFAULT_FONT, DesiredFont, 64);
  desired_char_height = GetProfileInt("WinPoem", "TextSize", DEFAULT_CHAR_HEIGHT);
  XPos = GetProfileInt("WinPoem", "X", DEFAULT_X_POS);
  YPos = GetProfileInt("WinPoem", "Y", DEFAULT_Y_POS);
}

// Write preferences to disk
void WritePreferences()
{
  char ProfileString[50];
  RECT Rect;

  sprintf(ProfileString, "%d", desired_char_height);
  WriteProfileString("WinPoem", "TextSize", ProfileString);
  WriteProfileString("WinPoem", "Font", DesiredFont);

  GetWindowRect(GlobalWindow, &Rect);
  sprintf(ProfileString, "%d", Rect.left);
  WriteProfileString("WinPoem", "X", ProfileString);
  sprintf(ProfileString, "%d", Rect.top);
  WriteProfileString("WinPoem", "Y", ProfileString);
}

// Load a poem from given file, at given point in file.
// If position is > -1, use this for the position in the
// file, otherwise use index[index_ptr] to find the correct position.
BOOL LoadPoem(char *file_name, long position)
{
    char ch = 0;
    int i = 0;
    int j = 0;
    int indexn = 0;
    char buf[100];
    long data;
    FILE *data_file;

    paging = FALSE;
    current_page = 0;

    if (file_name)
      sprintf(buf, "%s.dat", file_name);

    if (! (file_name && (data_file = fopen(buf, "r"))))
    {
      sprintf(error_buf, "Data file %s not found.", buf);
      PoetryError(error_buf);
      return FALSE;
    }
    else
    {
      if (position > -1)
        data = position;
      else
        data = index[index_ptr];

      fseek(data_file, data, SEEK_SET);

      ch = 0;
      i = 0;
      while ((ch != EOF) && (ch != '#'))
      {
        ch = getc(data_file);
        // Add a linefeed so it will copy to the clipboard ok
        if (ch == 10)
        {
          poem_buffer[i] = 13;
          i++;
        }

        poem_buffer[i] = ch;
        i ++;

        if (i == buf_size)
        {
           sprintf(error_buf, "%s", "Poetry buffer exceeded.");
           PoetryError(error_buf);
           return FALSE;
        }
      }
      fclose(data_file);
      poem_buffer[i-1] = 0;
      return TRUE;
  }
}

// Do the search
long MainWindow::DoSearch()
{
    FILE *file;
    long i = 0;
    char ch = 0;
    char buf[100];
    long find_start;
    long previous_poem_start;

    BOOL found = FALSE;
    int search_length = strlen(search_string);

    if (same_search)
    {
      find_start = last_find + 1;
      previous_poem_start = last_poem_start;
    }
    else
    {
      find_start = 0;
      last_poem_start = 0;
      previous_poem_start = -1;
    }

    if (data_filename)
      sprintf(buf, "%s.dat", data_filename);

    if (! (data_filename && (file = fopen(buf, "r"))))
    {
      sprintf(error_buf, "Poetry data file %s not found\n", buf);
      PoetryError(error_buf);
      return FALSE;
    }

    fseek(file, find_start, SEEK_SET);

    while ((ch != EOF) && !found)
    {
        ch = getc(file);
        ch |= 0x0020;   // Make lower case

        // Only match if we're looking at a different poem
        // (no point in displaying the same poem again)
        if ((ch == search_string[i]) && (last_poem_start != previous_poem_start))
        {
          if (i == 0)
            last_find = ftell(file);
          if (i == search_length-1)
            found = TRUE;
          i ++;
        }
        else
          i = 0;

        if (ch == '#')
        {
            ch = getc(file);
              last_poem_start = ftell(file);
        }
    }
    fclose(file);
    if (ch == EOF)
      last_find = -1;

    if (found)
    {
      return last_poem_start;
    }
    else
      return -1;
}

// Set up poetry filenames, preferences, load the index
void InitPoetry()
{
  strcpy(index_filename, DEFAULT_POETRY_IND);
  strcpy(data_filename, DEFAULT_POETRY_DAT);

  TryLoadIndex();
}

// Load index (or compile it if none found)
void TryLoadIndex()
{
  index_ok = LoadIndex(index_filename);
  if (!index_ok)
  {
      PoetryError("Index file not found; will compile new one");
      index_ok = Compile();
  }
}

// Error message
void PoetryError(char *Msg)
{
  MessageBox(GlobalWindow, (LPSTR)Msg, (LPSTR)"Error", MB_OK | MB_ICONEXCLAMATION);
}

// Notification (change icon to something appropriate!)
void PoetryNotify(char *Msg)
{
  MessageBox(GlobalWindow, (LPSTR)Msg, (LPSTR)"Notification", MB_OK |
                                                        MB_ICONINFORMATION);
}

// Initialize a log file (for debugging purposes)
void LogInitialize()
{
  char *logfile_name = "c:\\log";
  OFSTRUCT OfStruct;         // Information from OpenFile()
  int WinFile;

  WinFile = OpenFile(logfile_name, &OfStruct, OF_CREATE);
  _lclose(WinFile);
}

// Write a message to the log file
void LogMessage(char *Msg)
{
  char *logfile_name = "c:\\log";
  FILE *logfile;
  OFSTRUCT OfStruct;         // Information from OpenFile()
  int WinFile;

  if ((WinFile = OpenFile(logfile_name, &OfStruct, OF_READWRITE)) != -1)
  {
   logfile = fdopen(WinFile, "a");
   fprintf(logfile, "%s\n", Msg);
   fclose(logfile);
  }
}

// 'About' dialog box
BOOL FAR PASCAL AboutDlgProc(HWND hDlg, unsigned message, WORD wParam, LONG lParam)
{
  switch (message)
  {
    case WM_INITDIALOG:
      return TRUE;
    case WM_COMMAND:
      switch (wParam)
      {
        case 101:
          EndDialog(hDlg, 0);
          return TRUE;
      }
      break;
  }
  return FALSE;
}

// Build up and save an index into the poetry data file, for
// rapid random access
BOOL Compile()
{
    FILE *file;
    long i = 0;
    int j;
    char ch = 0;
    char buf[100];

    if (data_filename)
      sprintf(buf, "%s.dat", data_filename);

    if (! (data_filename && (file = fopen(buf, "r"))))
    {
      sprintf(error_buf, "Poetry data file %s not found\n", buf);
      PoetryError(error_buf);
      return FALSE;
    }

    nitems = 0;

    // Do first one (?)
    index[nitems] = 0;
    nitems ++;

    // Do rest
    while (ch != EOF)
    {
        ch = getc(file);
        i ++;
        if (ch == '#')
        {
            ch = getc(file);
            long data;
            data = ftell(file);
            index[nitems] = data;
            nitems ++;
        }
    }
    fclose(file);

    if (index_filename)
      sprintf(buf, "%s.idx", index_filename);
    if (! (data_filename && (file = fopen(buf, "w"))))
    {
      sprintf(error_buf, "Poetry index file %s cannot be created\n", buf);
      PoetryError(error_buf);
      return FALSE;
    }

    fprintf(file, "%ld\n\n", nitems);
    for (j = 0; j < nitems; j++)
      fprintf(file, "%ld\n", index[j]);
 
    fclose(file);
    PoetryNotify("Poetry index compiled.");
    return TRUE;
}

// Dialog proc for entering search string
BOOL FAR PASCAL SearchDlgProc(HWND hDlg, unsigned message, WORD wParam, LONG lParam)
{
  char Text[120];

  switch (message)
  {
    case WM_INITDIALOG:
      if (search_string[0] != 0)
        SetDlgItemText(hDlg, 101, search_string);
      return TRUE;
    case WM_COMMAND:
      switch (wParam)
      {
        case 101:
        // Edit
        return TRUE;

        // Ok
        case 102:
          // Find string
          GetDlgItemText(hDlg, 101, Text, 40);
          if (strcmp(Text, search_string) == 0)
            same_search = TRUE;
          else
            same_search = FALSE;
          strcpy(search_string, Text);
          search_ok = TRUE;
          EndDialog(hDlg, 0);
          return TRUE;

        // Cancel
        case 103:
          EndDialog(hDlg, 0);
          search_ok = FALSE;
          return TRUE;
      }
      break;

  }
  return FALSE;
}

