/*
+-----------------------------------------------------------------------------------------------------------------------+
|                                                                                                                       |
|          Program Name: ExecuteAt                                                                                      |
|                Author: Michael Krämer                                                                                 |
|                        Michael@dk8pp.de               (private)                                                       |
|                        Michael.Kraemer@eu.necel.com   (business)                                                      |
|                  Date: 16-Aug-2006                                                                                    |
|              Language: Open Watcom C 1.7                                                                              |
|               Version: 0.3                                                                                            |
|        Program status: still testing                                                                                  |
|                                                                                                                       |
+-----------------------------------------------------------------------------------------------------------------------+
|                                                                                                                       |
|  Purpose: Start a program at a specific time and day of the week                                                      |
|                                                                                                                       |
+-----------------------------------------------------------------------------------------------------------------------+
*/

/*
+-----------------------------------------------------------------------------------------------------------------------+
|       Includes                                                                                                        |
+-----------------------------------------------------------------------------------------------------------------------+
*/
#include <stdio.h>
#include <windows.h>
#include <commctrl.h>
#include <time.h>
#include <stdint.h>
#include <stdbool.h>
#include "WinLib.h"
#include "resource.h"

/*
+-----------------------------------------------------------------------------------------------------------------------+
|       Defines                                                                                                         |
+-----------------------------------------------------------------------------------------------------------------------+
*/
#define STRINGSIZE 256          /* default size for strings */
#define PARMSIZE 20             /* maximum size of a string or positional command line parameter */
//#define SUSPENDBUTTON           /* define if a suspend button shall be supported */

#define MY_HKEY HKEY_CURRENT_USER /* handle of registry key */
#define MY_INI_SECT "ExecuteAt" /* Section name or registry key used by this program */
#define MY_REG_KEY MY_REG_KEY_ROOT MY_INI_SECT /* Registry key used by this program */
#define PROGRAMNAME "ExecuteAt"
#define PURPOSE "This program executes another program at a specified time and day.\r\r\n"\
                "\r\r\n"
#define PROGRAMVERSION 30       /* will be displayed as n.nn */

/* private messages */
#define USER_THREAD_ENDED 401   /* Thread has ended */

#define MAX_CMDLINE     256     /* maximum length of a commandline */
#define FLAGS_SIZE      16      /* size of flags */
#define HISTORY_SIZE    10      /* maximum number of entries in the history list */
#define REG_HISTORY_SIZE ((HISTORY_SIZE * MAX_CMDLINE) + HISTORY_SIZE + 1) /* Size of history in registry */
#define HISTORY_ITEMS   3       /* three items per entry: command name, flags, time */

#define SLEEPTIME       200     /* Sleep time in ms for timer thread */

#define TRIGGER_MARGIN  60      /* Margin for the trigger time. Program will be started up to TRIGGER_MARGIN
                                   seconds after the defined trigger time */

/*
+-----------------------------------------------------------------------------------------------------------------------+
|       structs and enums                                                                                               |
+-----------------------------------------------------------------------------------------------------------------------+
*/

/*
+-----------------------------------------------------------------------------------------------------------------------+
|       global variables                                                                                                |
+-----------------------------------------------------------------------------------------------------------------------+
*/
HINSTANCE       MyInstance ;
HWND            MainWindow ;
static char strng1[STRINGSIZE] ;
static char strng2[STRINGSIZE] ;
static char ini_filename[MAX_PATH+1] ;
/*
 Registry definitions
*/
static int32_t registry_x = 0 ;         /* left edge of dialog */
static int32_t registry_y = 0 ;         /* top edge of dialog */
static int32_t registry_width = 0 ;     /* width of dialog */
static int32_t registry_height = 0 ;    /* height of dialog */
static int32_t registry_version = PROGRAMVERSION ;      /* version of program which wrote the registry */
static char registry_history[REG_HISTORY_SIZE] ; /* history */
static INIVALUE inivalue[] = {{"Window", "PosX", REG_DWORD, REGFLAG_WRITEBACK, &registry_x, 1},
                       {"Window", "PosY", REG_DWORD, REGFLAG_WRITEBACK, &registry_y, 1},
                       {"Window", "Width", REG_DWORD, REGFLAG_WRITEBACK, &registry_width, 1},
                       {"Window", "Height", REG_DWORD, REGFLAG_WRITEBACK, &registry_height, 1},
                       {"Program", "Version", REG_DWORD, REGFLAG_WRITEBACK, &registry_version, 1},
                       {"Settings", "History", REG_MULTI_SZ, REGFLAG_WRITEBACK, &registry_history, REG_HISTORY_SIZE} } ;
static MYINI myini = {MY_HKEY, MY_REG_KEY, MY_INI_SECT ".ini", &inivalue[0], INI_NO, sizeof(inivalue)/sizeof(inivalue[0])} ;

/*
 Command line parameter definitions
*/

/* These are indexes into the CLO table below. Keep them in sync */
typedef enum {PARM_RC=0,
              PARM_RD,
              PARM_RR,
              PARM_RW,
              PARM_IF,
              PARM_ID,
              PARM_IR,
              PARM_IW,
              PARM_AS,
              PARM_PP,
              PARM_PT,
              PARM_LAST} parm_enum ;

CLO clo[] = {{"rc", PARM_SIMPLE, NULL, 0, false, "clear registry entries for this program"},
             {"rd", PARM_SIMPLE, NULL, 0, false, "disable use of the registry for this program\r\r\n"
                                            "     (will use .ini file instead unless -id specified"},
             {"rr", PARM_SIMPLE, NULL, 0, false, "read initialization values from registry"},
             {"rw", PARM_SIMPLE, NULL, 0, false, "write initialization values back to registry"},
             {"if", PARM_STRING, ini_filename, MAX_PATH, false, "specify ini filename"},
             {"id", PARM_SIMPLE, NULL, 0, false, "disable use of ini file for this program"},
             {"ir", PARM_SIMPLE, NULL, 0, false, "read initialization values from ini file"},
             {"iw", PARM_SIMPLE, NULL, 0, false, "write initialization values back to ini file\r\r\n"
                                            "     NOTE: avoid using registry and ini-file at the same time\r\r\n"
                                            "     Exception: -rr -iw to export registry to ini-file and\r\r\n"
                                            "                -ir -rw to export ini-file to registry"},
             {"as", PARM_SIMPLE, NULL, 0, false, "start automatically, i.e. no need to klick start button"},
             {"pp", PARM_INT, NULL, 0, false, "set process priority for dialog\r\r\n"
                                       "       (0:idle, 1:normal, 2:high, 3:realtime)"},
             {"pt", PARM_INT, NULL, 0, false, "set priority for processing thread\r\r\n"
                                       "       (0:idle, 1:lowest, 2:below normal, 3:normal,\r\r\n"
                                "        4:above normal, 5:highest, 6:time critical)"} } ;
uint32_t clo_size = PARM_LAST ;

static char errorparm[MAXPARMLENGTH] ;

/*
 Dialog coordinates definitions
*/
COORDS coordinates[] = {{0, 0, 0, 0, 0, 0},  /* first entry is reserved for main dialog */
#ifdef SUSPENDBUTTON
                        {BUT_SUSPEND, 0, 0, 0, 0, MOVE_X|MOVE_Y},
#endif
                        {DLG_STATUS, 0, 0, 0, 0, RESIZE_X|RESIZE_Y|MOVE_Y},
                        {DLG_EXECUTEFILE, 0, 0, 0, 0, RESIZE_X},
                        {DLG_AT, 0, 0, 0, 0, RESIZE_X},
                        {BUT_INFO, 0, 0, 0, 0, 0},
                        {BUT_START, 0, 0, 0, 0, MOVE_X},
                        {BUT_CANCEL, 0, 0, 0, 0, MOVE_X},
                        {BUT_EXIT, 0, 0, 0, 0, MOVE_X}
                       } ;
COORDS *sorted_coord[sizeof(coordinates)/sizeof(coordinates[0])-1] ;
uint32_t coord_size = sizeof(coordinates)/sizeof(coordinates[0]) ;
static bool validreg = false ;        /* valid data from registry retrieved */
static bool abort_thread = false ; /* set true to abort the thread */
char purpose[] = PURPOSE ;

static char command[MAX_CMDLINE] ;
static uint32_t volatile start_time ;     // start time in seconds since midnight
static char *phistory[HISTORY_SIZE] ;
static char history[HISTORY_SIZE][MAX_CMDLINE] ;
static char flags[FLAGS_SIZE] ;
static uint32_t oldindex ;

/*
+-----------------------------------------------------------------------------------------------------------------------+
|       Function prototypes                                                                                             |
+-----------------------------------------------------------------------------------------------------------------------+
*/
static BOOL CALLBACK ExecuteAt(HWND window_handle, unsigned msg, WPARAM wparam, LPARAM lparam) ;
void display_info(char *msg) ;
static DWORD WINAPI WaitExecute(LPVOID lpParam) ;
static void get_history(char **phistory, uint32_t size) ;
static void put_history(char **phistory, uint32_t size, char *newcommand, char *time, char *flags) ;
static void init_combobox(void) ;
static void init_dialog(char *strng) ;
static void end_program(void) ;

/*
+-----------------------------------------------------------------------------------------------------------------------+
|       WinMain                                                                                                         |
+-----------------------------------------------------------------------------------------------------------------------+
|                                                                                                                       |
| MAIN program.                                                                                                         |
|                                                                                                                       |
+-----------------------------------------------------------------------------------------------------------------------+
*/
int WINAPI WinMain(HINSTANCE this_inst, HINSTANCE prev_inst, LPSTR cmdline, int cmdshow)
{
uint32_t static volatile rv ;
bool startup ;
uint32_t i ;
   startup = true ;
   errorparm[0] = '\0' ;
   getparms(cmdline, errorparm) ;
   if (clo[PARM_RC].found) clear_registry(&myini) ;
   myini.initype = INI_NO ;
   if (!clo[PARM_RD].found && !clo[PARM_RR].found && !clo[PARM_RW].found && !clo[PARM_IR].found && !clo[PARM_IW].found)
   { /* enable registry read/write if none of the previous parameters was found (as default and for compatibility) */
      myini.initype |= (INI_REGREAD + INI_REGWRITE) ;
   }
   if (clo[PARM_RD].found && !clo[PARM_RR].found && !clo[PARM_RW].found && !clo[PARM_IR].found && !clo[PARM_IW].found && !clo[PARM_ID].found)
   { /* enable ini file read/write if registry is disabled and none of the previous parameters was found
        use "-rd -id" to use neither registry nor ini file */
      myini.initype |= (INI_FILEREAD + INI_FILEWRITE) ;
   }
   if (clo[PARM_RR].found) myini.initype |= INI_REGREAD ;
   if (clo[PARM_RW].found) myini.initype |= INI_REGWRITE ;
   if (clo[PARM_IR].found) myini.initype |= INI_FILEREAD ;
   if (clo[PARM_IW].found) myini.initype |= INI_FILEWRITE ;
   if (clo[PARM_RD].found) myini.initype &= ~(INI_REGREAD + INI_REGWRITE) ;
   if (clo[PARM_ID].found) myini.initype &= ~(INI_FILEREAD + INI_FILEWRITE) ;
   if (clo[PARM_IF].found) myini.inifilename = ini_filename ;

   for (i=0; i<HISTORY_SIZE; i++)
   {
      phistory[i] = history[i] ;
      history[i][0] = '\0' ;
      history[i][1] = '\0' ;
   }
   oldindex = -1 ;
   if (startup)
   { /* start windowed version */
      if (clo[PARM_PP].found) set_process_prio(clo[PARM_PP].value) ;
      MyInstance = this_inst ;
      InitCommonControls() ;
      rv = DialogBox(this_inst, "DLG_EXECUTEAT", 0, (DLGPROC)ExecuteAt) ;
   }
   return(0);
}

/*
+-----------------------------------------------------------------------------------------------------------------------+
|       ExecuteAt                                                                                                       |
+-----------------------------------------------------------------------------------------------------------------------+
|                                                                                                                       |
| Processes messages for the DLG_EXECUTEAT dialog                                                                       |
|                                                                                                                       |
+-----------------------------------------------------------------------------------------------------------------------+
*/
BOOL CALLBACK ExecuteAt(HWND window_handle, unsigned msg, WPARAM wparam, LPARAM lparam)
{
OPENFILENAME execfilestruct ;

//FILE *outfile ;
char static exefilename[256] = "" ;
char static exefilename_no_path[256] ;
char static def_out_ext[] = "Executable files\0*.exe;*.bat;*.cmd\0All files\0*.*\0\0" ;
DWORD static dwThreadId, dwThrdParam = 1;
HANDLE static hThread = NULL ;
BOOL retcd ;
BOOL static terminate_program ;
RECT static rect ;
HFONT hFont ;
#define LOCALSTRSIZE 16
char str1[LOCALSTRSIZE], str2[LOCALSTRSIZE], str3[LOCALSTRSIZE] ;
uint32_t len, len1 ;
uint32_t i ;
uint32_t start_hour ;
uint32_t start_minute ;
uint32_t start_second ;

   retcd = 0 ; /* return 0 if we don't process the message */

   switch(msg)
   {
   case WM_INITDIALOG:
      retcd = 1 ;
      MainWindow = window_handle ;
// Make sure to define the below mentioned icon!
      SendMessage(MainWindow, WM_SETICON, ICON_BIG, (LPARAM) LoadIcon(MyInstance, "MyWinIcon")) ;
//    ShowError(DLG_STATUS) ; /* display last error message */

// save original coordinates of dialog
      SaveCoordinates() ;

// Select font for controls (sample code)
      hFont = CreateFont(
          15,                  // logical height of font
          0,                   // logical average character width
          0,                   // angle of escapement
          0,                   // base-line orientation angle
          FW_NORMAL,           // font weight
          false,               // italic attribute flag
          false,               // underline attribute flag
          false,               // strikeout attribute flag
          ANSI_CHARSET,        // character set identifier
          OUT_DEFAULT_PRECIS,  // output precision
          CLIP_DEFAULT_PRECIS, // clipping precision
          DEFAULT_QUALITY,     // output quality
          DEFAULT_PITCH,       // pitch and family
          "Courier New"        // pointer to typeface name string
      ) ;

      SendMessage(GetDlgItem(MainWindow, DLG_EXECUTEFILE), WM_SETFONT, (WPARAM) hFont, MAKELPARAM(false, 0));

      validreg = get_registry(&myini) ; // get registry content

      if (validreg)
      {
         MoveWindow(
         MainWindow,            // handle of window
         registry_x,            // horizontal position
         registry_y,            // vertical position
         registry_width,        // width
         registry_height,       // height
         true);                 // repaint flag
// Initialize history list
         init_combobox() ;
      }

      /* display any illegal parameter */
      if (strlen(errorparm) != 0)
      {
         LoadString(MyInstance, STR_INVALIDPARM, strng1, STRINGSIZE);
         sprintf_s(strng2, STRINGSIZE, strng1, errorparm) ;
         SetDlgItemText(MainWindow, DLG_STATUS, strng2) ;
      }
#ifdef SUSPENDBUTTON
      EnableWindow(GetDlgItem(MainWindow, BUT_SUSPEND), false) ;
#endif
      terminate_program = false ;

      sprintf_s(strng2, STRINGSIZE, PROGRAMNAME " - Version %u.%2.2u", PROGRAMVERSION/100, PROGRAMVERSION%100) ;
      SendMessage(MainWindow, WM_SETTEXT, 0, (LPARAM) strng2);
      if (clo[PARM_AS].found)
      { /* Autostart minimized */
         SendMessage(MainWindow, WM_COMMAND, BUT_START, 0) ;
         ShowWindow(MainWindow, SW_MINIMIZE) ;
      }
   break ;

   case WM_USER:
      retcd = 1 ;
      switch(LOWORD(wparam))
      {
      case USER_THREAD_ENDED:
         LoadString(MyInstance, STR_THREADENDED, strng1, sizeof(strng1));
         SetDlgItemText(MainWindow, DLG_STATUS, strng1) ;
         CloseHandle(hThread);
         hThread = NULL ;
//         if (terminate_program) end_program() ;
         EnableWindow(GetDlgItem(MainWindow, BUT_EXECUTE), true) ;
         EnableWindow(GetDlgItem(MainWindow, BUT_START), true) ;
         EnableWindow(GetDlgItem(MainWindow, BUT_AT), true) ;
         EnableWindow(GetDlgItem(MainWindow, DLG_EXECUTEFILE), true) ;
         EnableWindow(GetDlgItem(MainWindow, DLG_AT), true) ;
         EnableWindow(GetDlgItem(MainWindow, CB_SU), true) ;
         EnableWindow(GetDlgItem(MainWindow, CB_MO), true) ;
         EnableWindow(GetDlgItem(MainWindow, CB_TU), true) ;
         EnableWindow(GetDlgItem(MainWindow, CB_WE), true) ;
         EnableWindow(GetDlgItem(MainWindow, CB_TH), true) ;
         EnableWindow(GetDlgItem(MainWindow, CB_FR), true) ;
         EnableWindow(GetDlgItem(MainWindow, CB_SA), true) ;
      break ;
      }
   break ;

   case WM_COMMAND:
      retcd = 1 ;
      switch(LOWORD(wparam))
      {
      case BUT_EXECUTE:
         execfilestruct.lStructSize = sizeof(OPENFILENAME) ;
         execfilestruct.hwndOwner = MainWindow ;
         execfilestruct.hInstance = 0 ;
         execfilestruct.lpstrFilter = def_out_ext ;
         execfilestruct.lpstrCustomFilter = NULL ;
         execfilestruct.nMaxCustFilter = 0 ;
         execfilestruct.nFilterIndex = 1 ;
         execfilestruct.lpstrFile = exefilename ;
         execfilestruct.nMaxFile = sizeof(exefilename) ;
         execfilestruct.lpstrFileTitle = exefilename_no_path ;
         execfilestruct.nMaxFileTitle = sizeof(exefilename_no_path) ;
         execfilestruct.lpstrInitialDir = NULL ;
         execfilestruct.lpstrTitle = "Sample data output file" ;
         execfilestruct.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT ;
         execfilestruct.nFileOffset = 0 ;
         execfilestruct.nFileExtension = 0 ;
         execfilestruct.lpstrDefExt = NULL ;
         execfilestruct.lCustData = 0 ;
         execfilestruct.lpfnHook = NULL ;
         execfilestruct.lpTemplateName = NULL ;
         if (GetOpenFileName(&execfilestruct) != 0)
         {
            SetDlgItemText(MainWindow, DLG_EXECUTEFILE, execfilestruct.lpstrFile) ;
         }
      break ;

      case DLG_EXECUTEFILE:
         GetDlgItemText(MainWindow, DLG_EXECUTEFILE, exefilename, sizeof(exefilename)) ;
         i = SendMessage(GetDlgItem(MainWindow, DLG_EXECUTEFILE), CB_GETCURSEL, 0, 0) ; // get selected entry
         if (i != CB_ERR)
         {
            if ((oldindex == -1) || (oldindex != i)) init_dialog(phistory[i]) ;
            oldindex = i ;
         }
      break ;

      case BUT_INFO:
         sprintf_s(strng1, STRINGSIZE, "%s - Version %u.%2.2u", PROGRAMNAME, PROGRAMVERSION/100, PROGRAMVERSION%100) ;
         PrintProgramInfo(DLG_HELPLIST, strng1, __DATE__, __TIME__) ;
      break ;

      case BUT_AT:
         MessageBox(MainWindow,
                    "Enter time as HH:MM:SS\r"
                    "   Example 14:53:27\r",
                    "Help", MB_OK) ;
      break ;

      case IDOK:
      case BUT_START:
/* Start */
/* Clear status display */
         abort_thread = false ;
         LoadString(MyInstance, STR_EMPTY, strng1, sizeof(strng1));
         SetDlgItemText(MainWindow, DLG_STATUS, strng1) ;

         GetDlgItemText(MainWindow, DLG_EXECUTEFILE, exefilename, sizeof(exefilename)) ;

         strcpy_s(command, MAX_CMDLINE, exefilename) ;

         GetDlgItemText(MainWindow, DLG_AT, strng1, sizeof(strng1)) ;
         start_hour = start_minute = start_second = 0 ;
         str1[0]='\0' ; str2[0]='\0' ; str3[0]='\0' ;
         len = sscanf_s(strng1, "%[^:.,;\n]%*1s%[^:.,;\n]%*1s%[^:.,;\n]", &str1, LOCALSTRSIZE, &str2, LOCALSTRSIZE, &str3, LOCALSTRSIZE) ;
         len = 0 ;
         len1 = sscanf_s(str1, "%d", &start_hour) ;
         if (len1 != EOF) len = len1 ;
         len1 = sscanf_s(str2, "%d", &start_minute) ;
         if (len1 != EOF) len += len1 ;
         len1 = sscanf_s(str3, "%d", &start_second) ;
         if (len1 != EOF) len += len1 ;
         start_time = start_hour * 3600 + start_minute * 60 + start_second ;

         if ((start_hour >= 24) || (start_minute >= 60) || (start_second >= 60) || (len == 0))
         {
            LoadString(MyInstance, STR_TIMEERROR, strng1, sizeof(strng1));
         }
         else
         {
            sprintf_s(strng1, STRINGSIZE, "%2.2i:%2.2i:%2.2i", start_hour, start_minute, start_second) ;
            SetDlgItemText(MainWindow, DLG_AT, strng1) ;
            flags[0] = (IsDlgButtonChecked(MainWindow, CB_SU) == BST_CHECKED) ? '1' : '0' ;
            flags[1] = (IsDlgButtonChecked(MainWindow, CB_MO) == BST_CHECKED) ? '1' : '0' ;
            flags[2] = (IsDlgButtonChecked(MainWindow, CB_TU) == BST_CHECKED) ? '1' : '0' ;
            flags[3] = (IsDlgButtonChecked(MainWindow, CB_WE) == BST_CHECKED) ? '1' : '0' ;
            flags[4] = (IsDlgButtonChecked(MainWindow, CB_TH) == BST_CHECKED) ? '1' : '0' ;
            flags[5] = (IsDlgButtonChecked(MainWindow, CB_FR) == BST_CHECKED) ? '1' : '0' ;
            flags[6] = (IsDlgButtonChecked(MainWindow, CB_SA) == BST_CHECKED) ? '1' : '0' ;
            flags[7] = '\0' ;
            flags[8] = '\0' ;
            put_history(&phistory[0], HISTORY_SIZE, exefilename, strng1, flags) ;
            init_combobox() ;
            EnableWindow(GetDlgItem(MainWindow, BUT_EXECUTE), false) ;
            EnableWindow(GetDlgItem(MainWindow, BUT_START), false) ;
            EnableWindow(GetDlgItem(MainWindow, BUT_AT), false) ;
            EnableWindow(GetDlgItem(MainWindow, DLG_EXECUTEFILE), false) ;
            EnableWindow(GetDlgItem(MainWindow, DLG_AT), false) ;
            EnableWindow(GetDlgItem(MainWindow, CB_SU), false) ;
            EnableWindow(GetDlgItem(MainWindow, CB_MO), false) ;
            EnableWindow(GetDlgItem(MainWindow, CB_TU), false) ;
            EnableWindow(GetDlgItem(MainWindow, CB_WE), false) ;
            EnableWindow(GetDlgItem(MainWindow, CB_TH), false) ;
            EnableWindow(GetDlgItem(MainWindow, CB_FR), false) ;
            EnableWindow(GetDlgItem(MainWindow, CB_SA), false) ;
            if (hThread == NULL)
            {
               hThread = CreateThread(
                   NULL,                        // no security attributes
                   0,                           // use default stack size
                   WaitExecute,                 // thread function
                   &dwThrdParam,                // argument to thread function
                   0,                           // use default creation flags
                   &dwThreadId);                // returns the thread identifier
            }

           // Check the return value for success.
            if (hThread == NULL)
               LoadString(MyInstance, STR_THREADSTARTERROR, strng1, sizeof(strng1));
            else
            {
               LoadString(MyInstance, STR_WORKING, strng2, sizeof(strng2));
               sprintf_s(strng1, STRINGSIZE, strng2, start_hour, start_minute, start_second) ;
            }
         }
         SetDlgItemText(MainWindow, DLG_STATUS, strng1) ;
      break ;

      case BUT_CANCEL:
         abort_thread = true ;
      break ;

      case IDABORT:
      case IDCANCEL:
      case BUT_EXIT:
         GetWindowRect(MainWindow, &rect) ; /* get current dialog coordinates */
         registry_x = rect.left ;
         registry_y = rect.top ;
         registry_width = rect.right - rect.left ;
         registry_height = rect.bottom - rect.top ;
         if (registry_x < 0) registry_x = 0 ;
         if (registry_y < 0) registry_y = 0 ;
         registry_version = PROGRAMVERSION ;    /* version of program which wrote the registry */
         put_registry(&myini) ;                 /* store the dialog properties in registry */
         EndDialog(MainWindow, true) ;          /* exits the dialog box */
      break ;

      default:
         retcd = 0 ; /* we have not processed this command, so return zero */
      break ;
      }
   break ;

   default:
      retcd = WinLibMessage(msg, wparam, lparam) ; /* WinLib processes some messages */
   break ;
   }

   return(retcd) ;
} /* ExecuteAt */

/*
+-----------------------------------------------------------------------------------------------------------------------+
|       init_dialog                                                                                                     |
+-----------------------------------------------------------------------------------------------------------------------+
|                                                                                                                       |
| Initialize dialog elements from history string (time and flags).                                                      |
|                                                                                                                       |
+-----------------------------------------------------------------------------------------------------------------------+
*/
void init_dialog(char *strng)
{
uint32_t i ;
char static mystrng[STRINGSIZE] ;
   i = strlen(strng) + 1 ;
   strcpy_s(mystrng, STRINGSIZE, &strng[i]) ;
   SetDlgItemText(MainWindow, DLG_AT, mystrng) ;
   i = i + strlen(&strng[i]) + 1 ;
   strcpy_s(mystrng, STRINGSIZE, &strng[i]) ;
   CheckDlgButton(MainWindow, CB_SU,       (mystrng[0] == '1') ? BST_CHECKED : BST_UNCHECKED) ;
   CheckDlgButton(MainWindow, CB_MO,       (mystrng[1] == '1') ? BST_CHECKED : BST_UNCHECKED) ;
   CheckDlgButton(MainWindow, CB_TU,       (mystrng[2] == '1') ? BST_CHECKED : BST_UNCHECKED) ;
   CheckDlgButton(MainWindow, CB_WE,       (mystrng[3] == '1') ? BST_CHECKED : BST_UNCHECKED) ;
   CheckDlgButton(MainWindow, CB_TH,       (mystrng[4] == '1') ? BST_CHECKED : BST_UNCHECKED) ;
   CheckDlgButton(MainWindow, CB_FR,       (mystrng[5] == '1') ? BST_CHECKED : BST_UNCHECKED) ;
   CheckDlgButton(MainWindow, CB_SA,       (mystrng[6] == '1') ? BST_CHECKED : BST_UNCHECKED) ;
   return ;
}

/*
+-----------------------------------------------------------------------------------------------------------------------+
|       init_combobox                                                                                                   |
+-----------------------------------------------------------------------------------------------------------------------+
|                                                                                                                       |
| Get the command line history and initialize the combo box                                                             |
|                                                                                                                       |
+-----------------------------------------------------------------------------------------------------------------------+
*/
void init_combobox(void)
{
uint32_t i ;
HWND item ;

   item = GetDlgItem(MainWindow, DLG_EXECUTEFILE) ;

// Decode history from registry_history
   get_history(&phistory[0], HISTORY_SIZE) ;

// Clear combo box
   for (i=0; i<HISTORY_SIZE; i++) SendMessage(item, CB_DELETESTRING, 0, 0);

// Initialize combo box
   for (i=0; i<HISTORY_SIZE; i++)
   {
      if (strlen(phistory[i]) != 0)
         SendMessage(item, CB_ADDSTRING, 0, (LPARAM) (LPCTSTR) phistory[i]);
   }
   SendMessage(item, CB_SETCURSEL, 0, 0) ; // default is the first entry
   init_dialog(phistory[0]) ;
//   SendMessage(item, EM_SETSEL, 0, 0); /* position to top left */
}

/*
+-----------------------------------------------------------------------------------------------------------------------+
|       get_history                                                                                                     |
+-----------------------------------------------------------------------------------------------------------------------+
|                                                                                                                       |
| Get the command line history from registry_history                                                                    |
|                                                                                                                       |
+-----------------------------------------------------------------------------------------------------------------------+
*/
void get_history(char **phistory, uint32_t size)
{
uint32_t i, j, k, l ;
BOOL done ;
uint32_t itemcntr ;

   j = 0 ;
   l = 0 ;
   done = false ;
   for (i=0; ((i<size) && !done); i++)
   {
      done = false ;
      k=0 ;
      itemcntr = 0 ;
      do
      {
         phistory[j][k] = registry_history[l] ;
         if (registry_history[l] == '\0') itemcntr++ ;
         k++ ;
         l++ ;
      }
      while (itemcntr < HISTORY_ITEMS) ;
      phistory[j][k] = '\0' ;
      done = (registry_history[l] == '\0') ;
      j++ ;
   }
   while (j < size) *phistory[j++] = '\0' ;
}

/*
+-----------------------------------------------------------------------------------------------------------------------+
|       put_history                                                                                                     |
+-----------------------------------------------------------------------------------------------------------------------+
|                                                                                                                       |
| Put the command line history including the latest command back into the registry_history                              |
|                                                                                                                       |
+-----------------------------------------------------------------------------------------------------------------------+
*/
void put_history(char **phistory, uint32_t size, char *newcommand, char *time, char *flags)
{
uint32_t i, j, k, l, m ;
char *data ;
char static cmd[MAX_CMDLINE] ;

// Get fresh copy of history first
   get_history(phistory, size) ;

   j = 0 ;
   m = 0 ;
   for (i=0; i<size+1; i++)
   {
      if (i == 0)
      {
         k = 0 ; l = 0 ;
         do {cmd[k++] = newcommand[l] ;} while (newcommand[l++] != '\0') ;
         l = 0 ;
         do {cmd[k++] = time[l] ;} while (time[l++] != '\0') ;
         l = 0 ;
         do {cmd[k++] = flags[l] ;} while (flags[l++] != '\0') ;
         cmd[k] = '\0' ;
         data = cmd ;
      }
      else
      {
         data = phistory[i-1] ;
         if (strcmp(data, newcommand) == 0) data = NULL ; // ignore if identical to current command
      }
      if ((data != NULL) && (j < size))
      {
         k = 0 ;
         do
         {
            registry_history[m] = data[k] ;
            m++ ; k++ ;
         } while ((data[k-1] != '\0') || (data[k] != '\0')) ;
      }
   }
   registry_history[m] = '\0' ;
// Get fresh copy of history once again
   get_history(phistory, size) ;
}

/*
+-----------------------------------------------------------------------------------------------------------------------+
|       DWORD WINAPI WaitExecute(LPVOID lpParam)                                                                        |
+-----------------------------------------------------------------------------------------------------------------------+
|                                                                                                                       |
| Wait for time and execute command                                                                                     |
| The user can abort this thread by setting the global variable abort_thread to true.                                   |
|                                                                                                                       |
+-----------------------------------------------------------------------------------------------------------------------+
*/
DWORD WINAPI WaitExecute(LPVOID lpParam)
{
SYSTEMTIME systime ;
PROCESS_INFORMATION pi;
STARTUPINFO si;
uint32_t current_time ;
uint32_t maxtime ;
bool trigger ;
bool day_ok ;
uint32_t dayofweek ;
char static my_command[MAX_CMDLINE] ;
uint32_t i, j ;

   maxtime = start_time + TRIGGER_MARGIN ;
   trigger = true ;
   while (!abort_thread)
   {
      GetLocalTime(&systime) ;
      dayofweek = systime.wDayOfWeek ;
      current_time = systime.wHour * 3600 + systime.wMinute * 60 + systime.wSecond ;
// Handle wrapping at midnight.
      if (maxtime >= (current_time + 24*3600))
      { // start time was before midnight, but midnight has already passed
         current_time += 24*3600 ;
         if (dayofweek == 0) dayofweek = 6 ;
         else dayofweek -= 1 ; // correct the weekday
      }
      day_ok = (((dayofweek == 0) && (flags[0] == '1'))
             || ((dayofweek == 1) && (flags[1] == '1'))
             || ((dayofweek == 2) && (flags[2] == '1'))
             || ((dayofweek == 3) && (flags[3] == '1'))
             || ((dayofweek == 4) && (flags[4] == '1'))
             || ((dayofweek == 5) && (flags[5] == '1'))
             || ((dayofweek == 6) && (flags[6] == '1')) ) ;
      if ((current_time < start_time) || (current_time > maxtime))
         trigger = true ;
      else
      {
         if (trigger && day_ok)
         {
            trigger = false ;
            si.cb = sizeof(si) ;
            si.lpDesktop = NULL ;
            si.lpTitle = NULL ;
            si.dwFlags = STARTF_USESHOWWINDOW ;
            si.wShowWindow = SW_MINIMIZE ;
            si.cbReserved2 = 0 ;
            si.lpReserved2 = NULL ;
/* copy string and replace parameters */
/* supported parameters:
   &&yyyy: year
   &&mo:   month
   &&dd:   day
   &&hh:   hour
   &&mi:   minute
   &&ss:   second
*/
            i = 0 ;
            j = 0 ;
            while ((command[i] != '\0') && (i < MAX_CMDLINE))
            {
               if ((command[i] == '&') && (command[i+1] == '&'))
               { /* symbolic parameter detected */
                  i = i + 2 ;
                  if ((command[i] == 'y') && (command[i+1] == 'y') && (command[i+2] == 'y') && (command[i+3] == 'y'))
                  { /* year */
                     i = i + 4 ;
                     j += sprintf_s(&my_command[j], MAX_CMDLINE-j, "%4.4u", systime.wYear) ;
                  }
                  if ((command[i] == 'm') && (command[i+1] == 'o'))
                  { /* month */
                     i = i + 2 ;
                     j += sprintf_s(&my_command[j], MAX_CMDLINE-j, "%2.2u", systime.wMonth) ;
                  }
                  if ((command[i] == 'd') && (command[i+1] == 'd'))
                  { /* day */
                     i = i + 2 ;
                     j += sprintf_s(&my_command[j], MAX_CMDLINE-j, "%2.2u", systime.wDay) ;
                  }
                  if ((command[i] == 'h') && (command[i+1] == 'h'))
                  { /* hour */
                     i = i + 2 ;
                     j += sprintf_s(&my_command[j], MAX_CMDLINE-j, "%2.2u", systime.wHour) ;
                  }
                  if ((command[i] == 'm') && (command[i+1] == 'i'))
                  { /* minute */
                     i = i + 2 ;
                     j += sprintf_s(&my_command[j], MAX_CMDLINE-j, "%2.2u", systime.wMinute) ;
                  }
                  if ((command[i] == 's') && (command[i+1] == 's'))
                  { /* second */
                     i = i + 2 ;
                     j += sprintf_s(&my_command[j], MAX_CMDLINE-j, "%2.2u", systime.wSecond) ;
                  }
               }
               else
               {
                  my_command[j] = command[i] ;
                  i++ ;
                  j++ ;
               }
            }
            CreateProcess(
                          NULL,                    // pointer to name of executable module
                          my_command,              // pointer to command line string
                          NULL,                    // pointer to process security attributes
                          NULL,                    // pointer to thread security attributes
                          true,                    // handle inheritance flag
                          NORMAL_PRIORITY_CLASS,   // creation flags
                          NULL,                    // pointer to new environment block
                          NULL,                    // pointer to current directory name
                          &si,                     // pointer to STARTUPINFO
                          &pi);                    // pointer to PROCESS_INFORMATION

// No need to wait (???)
//            WaitForSingleObject(pi.hProcess,INFINITE) ;
         }
      }
      Sleep(SLEEPTIME) ;
   }

// Notify main window that the thread is finished
    SendMessage(MainWindow, WM_USER, USER_THREAD_ENDED, 0) ;

    return 0;
}

