/******************************************************************************
* Copyright (C) 2006 Nick Gammon.  All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* The software is provided "as is", without warranty of any kind,
* express or implied, including but not limited to the warranties of
* merchantability, fitness for a particular purpose and noninfringement.
* In no event shall the authors or copyright holders be liable for any
* claim, damages or other liability, whether in an action of contract,
* tort or otherwise, arising from, out of or in connection with the
* software or the use or other dealings in the software.
******************************************************************************/


/*

  windows_utils - a couple of utilities for use on Windows with Lua

  Author:   Nick Gammon
  Date:     23 May 2006
  Version:  1.0

*/

#include <windows.h>
#include <string.h>

#pragma comment( lib, "lua50.lib" )

#define LUA_API __declspec(dllexport)

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"


char buf [1024];
int found = 0;

/* callback function for EnumWindows */

BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam)
 {
 const char * name = (const char *) lParam;  /* wanted title */

 /* find title of this window */

 if (GetWindowText (hwnd, buf, sizeof buf) == 0)
   return TRUE;  /* keep going */
 
 /* if title shorter than our string, can't match */
 if (strlen (buf) < strlen (name))
   return TRUE;  /* keep going */

 /* see if leading part of title matches */
 if (memcmp (buf, name, strlen (name)) == 0)
   {
   SetForegroundWindow (hwnd);  /* bring to foreground */
   found = 1;
   return FALSE;  
   }  /* found match */
 
 return TRUE;
   
 } /* end of EnumWindowsProc */

/* sends the nominated window to the front, based on the 
   leading characters in its name (first match) 

Example:

  windows_utils.send_to_front ("Crimson Editor")

*/

static int send_to_front (lua_State *L)
  {
  const char * name = luaL_checkstring (L, 1);
  EnumWindows (&EnumWindowsProc, (int) name);
  lua_pushboolean  (L, found);    /* found flag */
  return 1;   /* one result */
  } /* end of send_to_front */

/* Execute an operating-system command:

  windows_utils.shell_execute (filename, params, defdir, operation, show_command)

  Only the first argument (filename) is required, the rest are optional.

  filename:  The file to open or print or the folder to open or explore. 
             The function can open an executable file or a document file. 
             The function can print a document file. 

  params:    A string that specifies the parameters to be passed to the application. 
             If filename specifies a document file, params should be nil or "".

  defdir:    A string that specifies the default directory.
             
  operation: The following operation strings are valid: 
             "open":  The function opens the file specified by the filename parameter. 
                      The file can be an executable file or a document file. It can also be a folder. 
             "print": The function prints the file specified by filename. 
                      The file should be a document file. 
                      If the file is an executable file, the function opens the file, 
                      as if "open" had been specified. 
             "explore":  The function explores the folder specified by filename.  

              This parameter can be nil or "". 
              In that case, the function opens the file specified by filename. 

  show_command: If filename specifies an executable file, show_command specifies how the 
                application is to be shown when it is opened. 

                This parameter can be nil in which case it defaults to 1 - the recommended default.

                This parameter can be one of the following values: 
  
                 0:  Hides the window and activates another window. 
                 1:  Activates and displays a window. 
                     If the window is minimized or maximized, Windows restores it to 
                     its original size and position. 
                     An application should specify this flag when displaying the window for the first time. 
                 2:  Activates the window and displays it as a minimized window. 
                 3:  Activates the window and displays it as a maximized window. 
                 4:  Displays a window in its most recent size and position. 
                     The active window remains active. 
                 5:  Activates the window and displays it in its current size and position.  
                 6:  Minimizes the specified window and activates the next top-level window in the z-order. 
                 7:  Displays the window as a minimized window. 
                     The active window remains active. 
                 8:  Displays the window in its current state. 
                     The active window remains active. 
                 9:  Activates and displays the window. 
                     If the window is minimized or maximized, Windows restores it to its original 
                     size and position. 
                     An application should specify this flag when restoring a minimized window. 
                10:  Sets the show state based on the SW_ flag specified in the STARTUPINFO structure 
                     passed to theCreateProcess function by the program that started the application. 
                     An application should call ShowWindow with this flag to set the initial show 
                     state of its main window. 


  If sucessful, the function returns true. 
  If not, it returns nil followed by an error message. You could use "assert" to test for failure.

  Examples:

  assert (windows_utils.shell_execute ("c:\\mushclient\\worlds\\SMAUG.MCL"))  -- document
                                                                                                            
  assert (windows_utils.shell_execute ("http://www.gammon.com.au/"))  -- web page

  assert (windows_utils.shell_execute ("mailto:someone@somewhere.com"))  -- open mail client

  assert (windows_utils.shell_execute ("c:\\", nil, nil, "explore"))  -- explore disk

  assert (windows_utils.shell_execute ("c:\\readme.txt", nil, nil, "print"))  -- print a file

*/

static int shell_execute (lua_State *L)
  {
  const char * filename = luaL_checkstring (L, 1);
  const char * params = luaL_optstring (L, 2, "");
  const char * defdir = luaL_optstring (L, 3, "");
  const char * operation = luaL_optstring (L, 4, "open");
  const int  nShowCmd = (int) luaL_optnumber (L, 5, SW_SHOWNORMAL);
  const char * err = NULL;

  int result = (int) ShellExecute (NULL, 
                                  operation, 
                                  filename, 
                                  params [0] ? params : NULL ,
                                  defdir [0] ? defdir : NULL ,
                                  nShowCmd);

  if (result > 32)
    {
    lua_pushboolean  (L, 1);    /* OK flag */
    return 1;   /* one result */
    }

  lua_pushnil (L);    /* bad result flag */

  switch (result)
    {
    case 0:                     
      err = "The operating system is out of memory or resources."; 
      break;
    case ERROR_FILE_NOT_FOUND : 
      err = "The specified file was not found."; 
      break;
    case ERROR_PATH_NOT_FOUND : 
      err = "The specified path was not found."; 
      break;
    case ERROR_BAD_FORMAT : 
      err = "The .exe file is invalid (non-Win32® .exe or error in .exe image)."; 
      break;
    case SE_ERR_ACCESSDENIED : 
      err = "The operating system denied access to the specified file."; 
      break;
    case SE_ERR_ASSOCINCOMPLETE : 
      err = "The file name association is incomplete or invalid."; 
      break;
    case SE_ERR_DDEBUSY : 
      err = "The DDE transaction could not be completed because other DDE transactions were being processed."; 
      break;
    case SE_ERR_DDEFAIL : 
      err = "The DDE transaction failed."; 
      break;
    case SE_ERR_DDETIMEOUT : 
      err = "The DDE transaction could not be completed because the request timed out."; 
      break;
    case SE_ERR_DLLNOTFOUND : 
      err = "The specified dynamic-link library was not found."; 
      break;
    case SE_ERR_NOASSOC : 
      err = "There is no application associated with the given file name extension."; 
      break;
    case SE_ERR_OOM : 
      err = "There was not enough memory to complete the operation."; 
      break;
    case SE_ERR_SHARE : 
      err = "A sharing violation occurred."; 
      break;
    default:
      sprintf (buf, "Unknown error code: %i returned.", result);
      break;

    } /* end of switch on result */

  if (err == NULL) 
    err = buf;

  lua_pushstring (L, err);

  return 2; /* returning nil and error message  */

  } // end of shell_execute	


static const luaL_reg windows_utils_lib[] = 
{
  {"send_to_front", send_to_front},
  {"shell_execute", shell_execute},
  {NULL, NULL}
};


/*
** Open test library
*/
LUALIB_API int luaopen_windows_utils (lua_State *L)
 {
  luaL_openlib(L, "windows_utils", windows_utils_lib, 0);
  return 1;
 }
