//
//  MAILMRGE.C
//  Pegasus Mail for Windows extension providing a simple
//  "mail merge" capability.
//
//  Copyright (c) 1994-95, David Harris, All Rights Reserved.
//
//  The author grants explicit permission for this source code to be
//  used or modified as required, subject only to the conditions that
//  the copyright notices above are preserved and that by using this
//  code you agree that the code is provided without warranty of any
//  kind, either explicit or implied, and you use it at your own
//  risk.
//

//  If you are using Borland C v4.0 or earlier, uncomment the next
//  line to prevent the Win32 porting layer code from being used.
//  #define NOPORTLAYER

#define STRICT
#include <windows.h>

#ifndef NOPORTLAYER
#include <windowsx.h>
#endif

#include <bwcc.h>
#include <stdio.h>
#include <string.h>
#include <io.h>               //  for "access()"
#include "..\wpmforms.h"
#include "mailmrge.h"

int atoi (const char *buf);

#ifndef MAXFPATH
#define MAXFPATH 128
#endif

char helpname [MAXFPATH];
char help_used = 0;

#define MAXFIELDS 32    //  Maximum number of fields per record

char szFormDlgClassName [] = "bordlg_mrg";
char do_logging = 1;
HFONT hLogFont;
HWND last_focus;

int register_form_classes (void);

HINSTANCE  hLibInstance;            // set in LibMain, used throughout the DLL


#pragma warn -par


BOOL FAR PASCAL _export generic_proc (HWND hDialog, UINT wMsg, 
   WPARAM wParam, LPARAM lParam)
   {
   //  General purpose dialog procedure - simply exits as soon
   //  as any button is pressed returning the ID of the button.

   BOOL fProcessed = TRUE;

   switch (wMsg)
      {
      case WM_INITDIALOG :
//         centre_window (hDialog);
         break;

      case WM_COMMAND :
#ifdef NOPORTLAYER
         if (HIWORD (lParam) != BN_CLICKED) break;
         EndDialog (hDialog, wParam);
#else
         if (GET_WM_COMMAND_CMD(wParam, lParam) != BN_CLICKED) break;
         EndDialog (hDialog, GET_WM_COMMAND_ID(wParam, lParam));
#endif
         break;

      default:
         fProcessed = FALSE;
         break;
      }

   return fProcessed;
   }


static void report_error (char *dlg_name)
   {
   FARPROC iproc;

   iproc = MakeProcInstance ((FARPROC) generic_proc, hLibInstance);
   DialogBox (hLibInstance, dlg_name, NULL, (DLGPROC) iproc);
   FreeProcInstance (iproc);
   }


WORD FAR PASCAL _export FORMINIT (WORD version, int variant, HWND hParent,
   char *data, HWND *hDialog, char *callback_name)
   {
   //  First, check to see if the version of the form manager is
   //  one we can work with. This check is pretty arbitrary - you
   //  should probably assume that you may not work with any version
   //  where the major version number is higher than the original
   //  version you targeted.

   if (version < 0x102)
      report_error ("VER");

   //  Now check the variant number; for this extension, we only
   //  provide a COMPOSER format.

   if (variant != 0) return 0;

   (*hDialog) = CreateDialog (hLibInstance, (LPCSTR) "MERGE", hParent, NULL);
   if ((*hDialog) == NULL) return 0;
   return 1;
   }


void trim_newline (char *str)
   {
   /*  Remove the terminating newline from a string
   **  if it's present. */

   int i;

   i = strlen (str) - 1;
   while ((i >= 0) && ((str [i] == '\n') || (str [i] == '\r'))) 
      str [i -- ] = '\0';
   }


static int dump_the_data (FILE *dest, FILE *src, char **fields, int nfields)
   {
   //  Write the formatted data into the temporary file we've created
   //  for the message. Fields are inserted into the message by using
   //  placeholders in the message file: a placeholder consists of a 
   //  tilde character (~), followed by a string of characters, ending
   //  with another tilde. The following placeholders are recognized:
   //
   //  "1" .. "32"  - replaced by the matching field from the data record
   //  "~"          - replaced by a single tilde

   int c, state, field;

   rewind (src);
   state = 0;
   while ((c = fgetc (src)) != EOF)
      {
      switch (state)
         {
         case 0 :       //  Normal state
            if (c == '~')
               state = 1;
            else
               fputc (c, dest);
            break;

         case 1 :       //  Checking a placeholder
            if (c == '~')
               {
               fputc ('~', dest);
               state = 0;
               }
            else
               {
               if (strchr ("01234567890", c) != NULL)
                  {
                  field = c - '0';
                  state = 2;
                  }
               else 
                  {
                  fputc ('~', dest);
                  fputc (c, dest);
                  state = 0;
                  }
               }
            break;

         case 2 :
            if (c == '~')
               {
               if ((field > 0) && (field <= nfields))
                  fprintf (dest, "%s", fields [field - 1]);
               state = 0;
               }
            else if (strchr ("01234567890", c) != NULL)
               {
               field *= 10;
               field += (c - '0');
               }
            else 
               {
               fputc ('~', dest);
               fputc (c, dest);
               state = 0;
               }
            break;
         }
      }

   return 1;
   }


static int mailmerge (HWND hWnd)
   {
   //  Perform the actual mail merge:
   //
   //  * Retrieve and validate the parameters in the dialog
   //  * Open the data and format files
   //  * Read the data one line at a time and parse it
   //  * Generate a message per record using the format file.
   //
   //  Returns:  1 on success
   //            0 on failure

   char buffer [1024], dfname [MAXFPATH], 
      ffname [MAXFPATH], mfname [MAXFPATH], seps [80];
   char *fields [MAXFIELDS], *s;
   FILE *dfil, *ffil, *mfil;
   HWND hParent = GetParent (hWnd);
   int email_field, i;

   //  First, check to see that the user has actually defined
   //  a field separator of some kind.

   if (IsDlgButtonChecked (hWnd, IDC_TAB))
      {
      seps [0] = '\t';
      seps [1] = '\0';
      }
   else
      GetDlgItemText (hWnd, IDC_OTHEREDIT, seps, sizeof (seps));

   if (seps [0] == '\0')
      {
      report_error ("SEPS");
      return 0;
      }

   //  Now get the data filename and the format filename,
   //  verify that they exist, then open them.

   GetDlgItemText (hWnd, IDC_DATAFILE, dfname, sizeof (dfname));
   if (access (dfname, 0) != 0)
      {
      report_error ("NSDF");
      return 0;
      }

   GetDlgItemText (hWnd, IDC_FORMATFILE, ffname, sizeof (ffname));
   if (access (ffname, 0) != 0)
      {
      report_error ("NSFF");
      return 0;
      }

   if ((dfil = fopen (dfname, "rt")) == NULL)
      {
      report_error ("NSDF");
      return 0;
      }

   if ((ffil = fopen (ffname, "rt")) == NULL)
      {
      fclose (dfil);
      report_error ("NSFF");
      return 0;
      }

   //  Now tell the Extension Manager to create a message for us...

   if (SendMessage (hParent, WM_F_NEWMESSAGE, 0, 0) == 0)
      {
      report_error ("MSGF");
      return 0;
      }

   // ... and fill out the basic fields in the message.
   
   GetDlgItemText (hWnd, IDC_SUBJECT, buffer, sizeof (buffer));

   SendMessage (hParent, WM_F_SUBJECT, 0, (LPARAM) buffer);
   if (IsDlgButtonChecked (hWnd, IDC_COPYSELF))
      SendMessage (hParent, WM_F_COPYSELF, 1, 0);
   if (IsDlgButtonChecked (hWnd, IDC_URGENT))
      SendMessage (hParent, WM_F_URGENT, 1, 0);
   if (IsDlgButtonChecked (hWnd, IDC_CONFIRMREADING))
      SendMessage (hParent, WM_F_CONFIRMREADING, 1, 0);
   if (IsDlgButtonChecked (hWnd, IDC_USEMIME))
      SendMessage (hParent, WM_F_MIME, 1, 0);

   GetDlgItemText (hWnd, IDC_EMAIL, buffer, sizeof (buffer));
   email_field = atoi (buffer);
   if (email_field < 1) email_field = 1;
   -- email_field;

   //  Now we're down to business: we parse the data file one
   //  line at a time, creating a temporary file for each message
   //  we create, then send it.

   while (fgets (buffer, sizeof (buffer), dfil) != NULL)
      {
      trim_newline (buffer);
      if (buffer [0] == '\0') continue;      //  Blank line
      i = 1;
      s = buffer;
      fields [0] = buffer;
      while (*s)
         {
         if (strchr (seps, *s) != NULL)
            {
            *s = '\0';
            if (i < MAXFIELDS)
               fields [i ++] = s + 1;
            }
         ++ s;
         }

      fields [i] = NULL;

      SendMessage (hParent, WM_F_TEMPFILE, sizeof (mfname), (LPARAM) mfname);
      if ((mfil = fopen (mfname, "wt")) == NULL)
         {
         fclose (dfil);
         fclose (ffil);
         report_error ("TMPF");
         return 0;
         }

      dump_the_data (mfil, ffil, fields, i);
      fclose (mfil);
      SendMessage (hParent, WM_F_TO, 0, (LPARAM) (fields [email_field]));
      SendMessage (hParent, WM_F_MESSAGEFILE, 0, (LPARAM) mfname);
      SendMessage (hParent, WM_F_SENDMESSAGE, 0, 0);
      }

   fclose (dfil);
   fclose (ffil);
   return 1;
   }


#pragma warn -use

LONG FAR PASCAL _export MrgProc (HWND hWnd, UINT wMsg, 
   WPARAM wParam, LPARAM lParam)
   {
   //  Service routine for the form's enclosed dialog. This is a
   //  standard windows modeless WndProc.

   DWORD dwResult = 0;
   BOOL fCallDefProc = TRUE;
   char buffer [256], *s, *to, *subj;
   RECT r;

   switch (wMsg)
      {
      case WM_FM_INIT :
         EnableWindow (GetDlgItem (hWnd, IDC_OTHEREDIT), FALSE);
         CheckRadioButton (hWnd, IDC_TAB, IDC_OTHER, IDC_TAB);

         //  Now construct our help file name
         GetModuleFileName (hLibInstance, buffer, sizeof (buffer));
         s = strrchr (buffer, '.');
         if (s != NULL)
            {
            strcpy (s, ".HLP");
            strcpy (helpname, buffer);
            }
         break;

      case WM_DESTROY :
         //  A good place to store some preferences, perhaps.
         if (help_used) WinHelp (hWnd, helpname, HELP_QUIT, 0);
         break;

      case WM_FM_INITFOCUS :
         SetFocus (GetDlgItem (hWnd, 101));
         break;

      case WM_FM_RESTOREFOCUS :
         //  There's a glitch in the way the Windows MDI manager
         //  seems to manage focus which means that we can have
         //  focus taken away and not returned when the user does
         //  anything outside our window. WinPMail has some quite
         //  complex logic to deal with this case and will send
         //  this message whenever focus should be restored in
         //  our window. We set focus to the last active control
         //  (which we know from trapping EN_SETFOCUS messages).

         if (last_focus) SetFocus (last_focus);
         break;

      case WM_COMMAND :
         fCallDefProc = FALSE;

#ifdef NOPORTLAYER
         if (HIWORD(lParam) == EN_SETFOCUS)
#else
         if (GET_WM_COMMAND_CMD(wParam, lParam) == EN_SETFOCUS)
#endif
            {
            //  We have to trap EN_SETFOCUS messages so we know which
            //  control was active last. When a menu selection is made
            //  our current control will lose focus and because of a
            //  curiosity in Windows' MDI management, we won't get it
            //  back. Instead, WinPMail generates a WM_FM_RESTOREFOCUS
            //  message which signals to us that we should set focus to
            //  whatever the last active control was.

#ifdef NOPORTLAYER
            last_focus = (HWND) LOWORD (lParam);
#else
            last_focus = GET_WM_COMMAND_HWND(wParam, lParam);
#endif
            break;
            }

#ifdef NOPORTLAYER
         switch (wParam)
#else
         switch (GET_WM_COMMAND_ID(wParam, lParam))
#endif
            {
            case IDC_TAB :
               EnableWindow (GetDlgItem (hWnd, IDC_OTHEREDIT), FALSE);
               CheckRadioButton (hWnd, IDC_TAB, IDC_OTHER, IDC_TAB);
               break;

            case IDC_OTHER :
               EnableWindow (GetDlgItem (hWnd, IDC_OTHEREDIT), TRUE);
               CheckRadioButton (hWnd, IDC_TAB, IDC_OTHER, IDC_OTHER);
               break;

            case IDC_BROWSEDATAFILE :
               if (SendMessage (GetParent (hWnd), WM_F_BROWSEFILE, 1, (LPARAM) buffer))
                  {
                  SendDlgItemMessage (hWnd, IDC_DATAFILE, EM_SETSEL, 
                     0, (LPARAM) 0x7FFF0000L);
                  SendDlgItemMessage (hWnd, IDC_DATAFILE, EM_REPLACESEL,
                     0, (LPARAM) buffer);
                  }
               break;

            case IDC_BROWSEFORMATFILE :
               if (SendMessage (GetParent (hWnd), WM_F_BROWSEFILE, 1, (LPARAM) buffer))
                  {
                  SendDlgItemMessage (hWnd, IDC_FORMATFILE, EM_SETSEL, 
                     0, (LPARAM) 0x7FFF0000L);
                  SendDlgItemMessage (hWnd, IDC_FORMATFILE, EM_REPLACESEL,
                     0, (LPARAM) buffer);
                  }
               break;

            case IDC_HELP :
               if (helpname [0])
                  {
                  help_used = 1;
                  WinHelp (hWnd, helpname, HELP_CONTEXT, 1);
                  }
               else
                  MessageBeep (0);
               break;

            case IDC_SEND :
               if (! mailmerge (hWnd)) break;
               //  Drop through and close up shop...

            case IDC_CANCEL :
               PostMessage (GetParent (hWnd), WM_CLOSE, 0, 0);
               break;
            }
         break;
      }

   if (fCallDefProc)
      dwResult = BWCCDefDlgProc (hWnd, wMsg, wParam, lParam);

   return dwResult;
   }


#pragma warn -sus

void unregister_form_classes (void)
   {
   //  Remove any classes associated with the form; we have the
   //  same problem here as we do with registering the classes
   //  for the DLL - we only want to deregister the classes on
   //  the last time we're unloaded.

   if (GetModuleUsage (hLibInstance) > 1) return;      //  Not a problem
   UnregisterClass (szFormDlgClassName, hLibInstance);
   }


BOOL FAR PASCAL LibMain (HINSTANCE hInst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine)
   {
   WNDCLASS wc;

   if (! hLibInstance)
      {
      hLibInstance = hInst;
      BWCCGetVersion ();      //  Forces BWCC to be dynamically loaded.

      //  Register any window classes used by the form. Forms will usually
      //  register either one or occasionally two classes which define
      //  the composition and reader dialogs created by the DLL.
      //
      //  There's a gotcha here, of course (aren't there always, in
      //  Windows?)... You can't register a window class more than once,
      //  so if the DLL has already been loaded and the user asks to
      //  create a second instance of the form, we have to be careful
      //  not to re-register the class. We do this by checking the
      //  instance usage counter of the DLL - if it's greater than 1,
      //  then we DON'T register the classes.

#pragma warn -sig
      wc.style          = WS_CHILD;
#pragma warn .sig
      wc.lpfnWndProc    = MrgProc;
      wc.cbClsExtra     = 0;
      wc.cbWndExtra     = DLGWINDOWEXTRA;
	   wc.hInstance      = hLibInstance;
	   wc.hIcon          = NULL;
      wc.hCursor        = LoadCursor (NULL, IDC_ARROW);
      wc.hbrBackground  = (HBRUSH) (COLOR_WINDOW + 1);
      wc.lpszMenuName   = NULL;
      wc.lpszClassName  = szFormDlgClassName;
      if (! RegisterClass (&wc))
         MessageBeep (0);
      }

   return (TRUE);             // Initialization went OK
   }

