/*
 * Copyright (c) 2002 Trent Mick
 * 
 * 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.
 */

/* Console launch executable.  
 * 
 * This program exists solely to launch:
 * 		python <installdir>/<exename>.py <argv>
 * on Windows. "<exename>.py" must be in the same directory.
 *
 * Rationale:
 *    - On some Windows flavours .py *can* be put on the PATHEXT to be
 *      able to find "<exename>.py" if it is on the PATH. This is fine
 *      until you need shell redirection to work. It does NOT for
 *      extensions to PATHEXT.  Redirection *does* work for "python
 *      <script>.py" so we will try to do that.
 */

#ifdef WIN32
    #include <windows.h>
    #include <process.h>
    #include <direct.h>
    #include <shlwapi.h>
#else /* linux */
    #include <unistd.h>
#endif /* WIN32 */
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

//---- constants

#define BUF_LENGTH 2048
#define MAX_PYTHON_ARGS 50
#define MAX_FILES 50
#define MAXPATHLEN 1024
#ifdef WIN32
    #define SEP '\\'
    #define ALTSEP '/'
    // path list element separator
    #define DELIM ';'
#else /* linux */
    #define SEP '/'
    // path list element separator
    #define DELIM ':'
#endif

#ifdef WIN32
    #define spawnvp _spawnvp
    #define snprintf _snprintf
    #define vsnprintf _vsnprintf
    //NOTE: this is for the stat *call* and the stat *struct*
    #define stat _stat
#endif


//---- globals

char* programName = NULL;
char* programPath = NULL;
#ifndef WIN32 /* i.e. linux */
    extern char **environ;   // the user environment
#endif /* linux */

//---- error logging functions

void _LogError(const char* format ...)
{
    va_list ap;
    va_start(ap, format);
#if defined(WIN32) && defined(_WINDOWS)
    // put up a MessageBox
    char caption[BUF_LENGTH+1];
    snprintf(caption, BUF_LENGTH, "Error in %s", programName);
    char msg[BUF_LENGTH+1];
    vsnprintf(msg, BUF_LENGTH, format, ap);
    va_end(ap);
    MessageBox(NULL, msg, caption, MB_OK | MB_ICONEXCLAMATION);
#else
    fprintf(stderr, "%s: error: ", programName);
    vfprintf(stderr, format, ap);
    va_end(ap);
#endif /* WIN32 && _WINDOWS */
}


void _LogWarning(const char* format ...)
{
    va_list ap;
    va_start(ap, format);
#if defined(WIN32) && defined(_WINDOWS)
    // put up a MessageBox
    char caption[BUF_LENGTH+1];
    snprintf(caption, BUF_LENGTH, "Warning in %s", programName);
    char msg[BUF_LENGTH+1];
    vsnprintf(msg, BUF_LENGTH, format, ap);
    va_end(ap);
    MessageBox(NULL, msg, caption, MB_OK | MB_ICONWARNING);
#else
    fprintf(stderr, "%s: warning: ", programName);
    vfprintf(stderr, format, ap);
    va_end(ap);
#endif /* WIN32 && _WINDOWS */
}



//---- utilities functions

/* _IsDir: Is the given dirname an existing directory */
static int _IsDir(char *dirname)
{
#ifdef WIN32
    DWORD dwAttrib;
    dwAttrib = GetFileAttributes(dirname);
    if (dwAttrib == -1) {
        return 0;
    }
    if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) {
        return 1;
    }
    return 0;
#else /* i.e. linux */
    struct stat buf;
    if (stat(dirname, &buf) != 0)
        return 0;
    if (!S_ISDIR(buf.st_mode))
        return 0;
    return 1;
#endif
}


/* _IsLink: Is the given filename a symbolic link */
static int _IsLink(char *filename)
{
#ifdef WIN32
    return 0;
#else /* i.e. linux */
    struct stat buf;
    if (lstat(filename, &buf) != 0)
        return 0;
    if (!S_ISLNK(buf.st_mode))
        return 0;
    return 1;
#endif
}


/* Is executable file
 * On Linux: check 'x' permission. On Windows: just check existence.
 */
static int _IsExecutableFile(char *filename)
{
#ifdef WIN32
    return (int)PathFileExists(filename);
#else /* i.e. linux */
    struct stat buf;
    if (stat(filename, &buf) != 0)
        return 0;
    if (!S_ISREG(buf.st_mode))
        return 0;
    if ((buf.st_mode & 0111) == 0)
        return 0;
    return 1;
#endif /* WIN32 */
}


/* _GetProgramPath: Determine the absolute path to the given program name.
 *
 *      Takes into account the current working directory, etc.
 *      The implementations require the global 'programName' to be set.
 */
#ifdef WIN32
    static char* _GetProgramPath(void)
    {
        //XXX this is ugly but I didn't want to use malloc, no reason
        static char progPath[MAXPATHLEN+1];
        // get absolute path to module
        if (!GetModuleFileName(NULL, progPath, MAXPATHLEN)) {
            _LogError("could not get absolute program name from "\
                "GetModuleFileName\n");
            exit(1);
        }
        // just need dirname
        for (char* p = progPath+strlen(progPath);
             *p != SEP && *p != ALTSEP;
             --p)
        {
            *p = '\0';
        }
        *p = '\0';  // remove the trailing SEP as well

        return progPath;
    }
#else

    /* _JoinPath requires that any buffer argument passed to it has at
       least MAXPATHLEN + 1 bytes allocated.  If this requirement is met,
       it guarantees that it will never overflow the buffer.  If stuff
       is too long, buffer will contain a truncated copy of stuff.
    */
    static void
    _JoinPath(char *buffer, char *stuff)
    {
        size_t n, k;
        if (stuff[0] == SEP)
            n = 0;
        else {
            n = strlen(buffer);
            if (n > 0 && buffer[n-1] != SEP && n < MAXPATHLEN)
                buffer[n++] = SEP;
        }
        k = strlen(stuff);
        if (n + k > MAXPATHLEN)
            k = MAXPATHLEN - n;
        strncpy(buffer+n, stuff, k);
        buffer[n+k] = '\0';
    }


    static char*
    _GetProgramPath(void)
    {
        /* XXX this routine does *no* error checking */
        char* path = getenv("PATH");
        static char progPath[MAXPATHLEN+1];

        /* If there is no slash in the argv0 path, then we have to
         * assume the program is on the user's $PATH, since there's no
         * other way to find a directory to start the search from.  If
         * $PATH isn't exported, you lose.
         */
        if (strchr(programName, SEP)) {
            strncpy(progPath, programName, MAXPATHLEN);
        }
        else if (path) {
            int bufspace = MAXPATHLEN;
            while (1) {
                char *delim = strchr(path, DELIM);

                if (delim) {
                    size_t len = delim - path;
                    if (len > bufspace) {
                        len = bufspace;
                    }
                    strncpy(progPath, path, len);
                    *(progPath + len) = '\0';
                    bufspace -= len;
                }
                else {
                    strncpy(progPath, path, bufspace);
                }

                _JoinPath(progPath, programName);
                if (_IsExecutableFile(progPath)) {
                    break;
                }

                if (!delim) {
                    progPath[0] = '\0';
                    break;
                }
                path = delim + 1;
            }
        }
        else {
            progPath[0] = '\0';
        }

        // now we have to resolve a string of possible symlinks
        //   - we'll just handle the simple case of a single level of
        //     indirection
        //
        // XXX note this does not handle multiple levels of symlinks
        //     here is pseudo-code for that (please implement it :):
        // while 1:
        //     if islink(progPath):
        //         linkText = readlink(progPath)
        //         if isabsolute(linkText):
        //             progPath = os.path.join(dirname(progPath), linkText)
        //         else:
        //             progPath = linkText
        //     else:
        //         break
        if (_IsLink(progPath)) {
            char newProgPath[MAXPATHLEN+1];
            readlink(progPath, newProgPath, MAXPATHLEN);
            strncpy(progPath, newProgPath, MAXPATHLEN);
        }

        
        // prefix with the current working directory if the path is
        // relative to conform with the Windows version of this
        if (strlen(progPath) != 0 && progPath[0] != SEP) {
            char cwd[MAXPATHLEN+1];
            char tmp[MAXPATHLEN+1];
            //XXX should check for failure retvals
            getcwd(cwd, MAXPATHLEN);
            snprintf(tmp, MAXPATHLEN, "%s%c%s", cwd, SEP, progPath);
            strncpy(progPath, tmp, MAXPATHLEN);
        }
        
        // 'progPath' now contains the full path to the program *and* the program
        // name. The latter is not desire.
        char* pLetter = progPath + strlen(progPath);
        for (;pLetter != progPath && *pLetter != SEP; --pLetter) {
            /* do nothing */
        }
        *pLetter = '\0';

        return progPath;
    }
#endif  /* WIN32 */


//---- mainline

int main(int argc, char** argv)
{
    programName = argv[0];
    programPath = _GetProgramPath();

    // Determine the extension-less program basename.
    // XXX Will not always handle app names with '.' in them (other than
    //     the '.' for the extension.
    char programNameNoExt[MAXPATHLEN+1];
    char *pStart, *pEnd;
    pStart = pEnd = programName + strlen(programName) - 1;
    while (pStart != programName && *(pStart-1) != SEP) {
	pStart--;
    }
    while (1) {
	if (pEnd == pStart) {
            pEnd = programName + strlen(programName) - 1;
	    break;
	}
	pEnd--;
	if (*(pEnd+1) == '.') {
	    break;
	}
    }
    strncpy(programNameNoExt, pStart, pEnd-pStart+1);
    *(programNameNoExt+(pEnd-pStart+1)) = '\0';

    // determine the full path to "<exename>.py"
    char pyFile[MAXPATHLEN+1];
    snprintf(pyFile, MAXPATHLEN, "%s%c%s.py", programPath, SEP,
	     programNameNoExt);
    
    // Build the argument array for launching.
    char* pythonArgs[MAX_PYTHON_ARGS+1];
    int nPythonArgs = 0;
    pythonArgs[nPythonArgs++] = "python";
    pythonArgs[nPythonArgs++] = "-tt";
    pythonArgs[nPythonArgs++] = pyFile;
    for (int i = 1; i < argc; ++i) {
        pythonArgs[nPythonArgs++] = argv[i];
    }
    pythonArgs[nPythonArgs++] = NULL;

    return _spawnvp(_P_WAIT, pythonArgs[0], pythonArgs);
}


//---- mainline for win32 subsystem:windows app
#ifdef WIN32
    int WINAPI WinMain(
        HINSTANCE hInstance,      /* handle to current instance */
        HINSTANCE hPrevInstance,  /* handle to previous instance */
        LPSTR lpCmdLine,          /* pointer to command line */
        int nCmdShow              /* show state of window */
    )
    {
        return main(__argc, __argv);
    }
#endif

