// (c) 1991-2007 Electronic Arts Inc.

#include "whereis.h"

#if defined(_MSC_VER)
#pragma warning(push,1)

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif

#include <windows.h>
#include <vfw.h>
#include <malloc.h>
#pragma warning(pop)
#endif
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

/****************************************************************/
/*  Internal Functions                                          */
/****************************************************************/

static char *WHEREIS_getpath( char *newmatch,const char *old)
{
    char *d;
    char *x = NULL;

    strcpy(newmatch, old);

    d = newmatch;
    while (*d)                                  /* find last ':','/','\' */
    {
        if (*d==(char)':' || *d==(char)'/' || *d==(char)'\\')
            x = d;
        ++d;
    }

    if (x)                                     /* remove filename */
    {
        ++x;
        *x = '\0';
    }
    else
    {
        *newmatch = '\0';
    }

    return(newmatch);
}


static char *WHERE_getpath( char *newmatch,const char *old)
{
    char *d;
    char *x = NULL;

    strcpy(newmatch, old);

    d = newmatch;
    while (*d)                                  /* find last ':','/','\' */
    {
        if (*d==(char)':' || *d==(char)'/' || *d==(char)'\\')
            x = d;
        ++d;
    }

    if (x)                                     /* remove filename */
    {
        if (*x==':')
            ++x;
        *x = '\0';
    }
    else
    {
        *newmatch = '\0';
    }

    return(newmatch);
}


static char * PathToUNC(const char *filename)
{
    char fullbuf[512];
    char *justthefile;
    GetFullPathName(filename,sizeof(fullbuf),fullbuf,&justthefile);

    char uni[512];
    DWORD sizeof_uni = sizeof(uni);

    DWORD errorcode = WNetGetUniversalName(fullbuf,UNIVERSAL_NAME_INFO_LEVEL,(void *)uni,&sizeof_uni);
    char *msg=0;
    switch (errorcode)
    {
        case ERROR_BAD_DEVICE        : msg = "ERROR_BAD_DEVICE"; break;
        case ERROR_CONNECTION_UNAVAIL: msg = "ERROR_CONNECTION_UNAVAIL"; break;
        case ERROR_EXTENDED_ERROR    : msg = "ERROR_EXTENDED_ERROR"; break;
        case ERROR_MORE_DATA         : msg = "ERROR_MORE_DATA"; break;
        case ERROR_NOT_SUPPORTED     : msg = "ERROR_NOT_SUPPORTED"; break;
        case ERROR_NO_NET_OR_BAD_PATH: msg = "ERROR_NO_NET_OR_BAD_PATH"; break;
        case ERROR_NO_NETWORK        : msg = "ERROR_NO_NETWORK"; break;
        case ERROR_NOT_CONNECTED     : msg = "ERROR_NOT_CONNECTED"; break;
        default: break;
    }

    if (!errorcode)
    {
        char *unc = ((UNIVERSAL_NAME_INFO *) uni)->lpUniversalName;

        //  \\EAC-TOOLS\ ->  \\eac-tools.eac.ad.ea.com\ unc
        if (!strnicmp(unc, "\\\\EAC-TOOLS\\", strlen("\\\\EAC-TOOLS\\")))
        {
            static char envfullbuf[512];

            sprintf(envfullbuf,"\\\\eac-tools.eac.ad.ea.com\\%s",unc+strlen("\\\\EAC-TOOLS\\"));
            unc = envfullbuf;
        }

        //  \\eac-xfer ->  \\eac-xfer.eac.ad.ea.com unc
        if (!strnicmp(unc, "\\\\eac-xfer\\", strlen("\\\\eac-xfer\\")))
        {
            static char envfullbuf[512];

            sprintf(envfullbuf,"\\\\eac-xfer.eac.ad.ea.com\\%s",unc+strlen("\\\\eac-xfer\\"));
            unc = envfullbuf;
        }

        //  \\tlg-intranet ->  \\tlg-intranet.eac.ad.ea.com unc
        if (!strnicmp(unc, "\\\\tlg-intranet\\", strlen("\\\\tlg-intranet\\")))
        {
            static char envfullbuf[512];

            sprintf(envfullbuf,"\\\\tlg-intranet.eac.ad.ea.com\\%s",unc+strlen("\\\\tlg-intranet\\"));
            unc = envfullbuf;
        }

        return unc;
    }

    return NULL;
}





/****************************************************************/
/*  Registry Functions                                          */
/****************************************************************/

static int SearchRegEXT(const char *name,char *whereisbuf, int whereisbuflen=512)
{
    int len=0;

#if defined(_MSC_VER)
    int err;
    unsigned long valuelen;
    HKEY phkeyresult;
    char *subkey=(char *)alloca(whereisbuflen);
//    char subkey[512];
    HKEY key=HKEY_LOCAL_MACHINE;

    if (!strncmp(name,"HKEY_",strlen("HKEY_")))
    {
        struct
        {
            char *name;
            HKEY hkey;
        } hkeys[]=
        {
            {   "HKEY_CLASSES_ROOT",    HKEY_CLASSES_ROOT },
            {   "HKEY_CURRENT_USER",    HKEY_CURRENT_USER },
            {   "HKEY_LOCAL_MACHINE",   HKEY_LOCAL_MACHINE },
            {   "HKEY_USERS",           HKEY_USERS },
            {   "HKEY_CURRENT_CONFIG",  HKEY_CURRENT_CONFIG },
            {   NULL, 0 }
        };

        for (int i=0; hkeys[i].name; ++i)
        {
            if (!strncmp(name,hkeys[i].name,strlen(hkeys[i].name)))
            {
                key = hkeys[i].hkey;
                name += strlen(hkeys[i].name);
                if (*name=='/' || *name=='\\')
                    ++name;
            }
        }
    }

    /* Try to open the subkey */

    sprintf(subkey,"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s",name);
    err = RegOpenKey(key,subkey,&phkeyresult);
    if (err)
    {
        sprintf(subkey,"SOFTWARE\\Microsoft\\%s",name);
        err = RegOpenKey(key,subkey,&phkeyresult);
    }
    if (err)
    {
        sprintf(subkey,"SOFTWARE\\Electronic Arts\\%s",name);
        err = RegOpenKey(key,subkey,&phkeyresult);
    }
    if (err)
    {
        sprintf(subkey,"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s.exe",name);
        err = RegOpenKey(key,subkey,&phkeyresult);
    }
    if (err)
    {
        sprintf(subkey,"SOFTWARE\\Apple Computer, Inc.\\%s",name);
        err = RegOpenKey(key,subkey,&phkeyresult);
    }

    // allow user to fully specify registry subkey
    // ie SOFTWARE\Electronic Arts\Framework-0.6.2
    if (err)
    {
        sprintf(subkey,"%s",name);
        err = RegOpenKey(key,subkey,&phkeyresult);
    }
    // allow user to specify registry subkey with forward slashes (easier to pass)
    // ie SOFTWARE/Electronic Arts/Framework-0.6.2
    if (err)
    {
        sprintf(subkey,"%s",name);
        for (int i=0; subkey[i]; ++i)
            if (subkey[i]=='/') subkey[i]='\\';
        err = RegOpenKey(key,subkey,&phkeyresult);
    }

    if (!err)
    {
        /* subkey exists so read in value */

        err = 1;
        if (strstr(subkey,".exe"))
        {
            valuelen=whereisbuflen;
            memset(whereisbuf,0,whereisbuflen);

            err = RegQueryValueEx(phkeyresult,
                        NULL,
                        NULL,
                        NULL,
                        (unsigned char *)whereisbuf,
                        &valuelen);
        }

        if (err)
        {
            valuelen=whereisbuflen;
            memset(whereisbuf,0,whereisbuflen);

            // first try Path value
            err = RegQueryValueEx(phkeyresult,
                        TEXT("Path"),  // most apps use path - ie framework
                        NULL,
                        NULL,
                        (unsigned char *)whereisbuf,
                        &valuelen);
        }

        if (err)
        {
            valuelen=whereisbuflen;
            memset(whereisbuf,0,whereisbuflen);

            err = RegQueryValueEx(phkeyresult,
                        TEXT("InstallPath"), // xbox uses this one
                        NULL,
                        NULL,
                        (unsigned char *)whereisbuf,
                        &valuelen);
        }

        if (err)
        {
            valuelen=whereisbuflen;
            memset(whereisbuf,0,whereisbuflen);

            err = RegQueryValueEx(phkeyresult,
                        TEXT("Install Dir"), // platform sdk uses this one HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MicrosoftSDK\Directories
                        NULL,
                        NULL,
                        (unsigned char *)whereisbuf,
                        &valuelen);
        }

        if (err)
        {
            valuelen=whereisbuflen;
            memset(whereisbuf,0,whereisbuflen);

            err = RegQueryValueEx(phkeyresult,
                        TEXT("InstallDir"), // quicktime uses this
                        NULL,
                        NULL,
                        (unsigned char *)whereisbuf,
                        &valuelen);
        }

        if (err)
        {
            valuelen=whereisbuflen;
            memset(whereisbuf,0,whereisbuflen);

            err = RegQueryValueEx(phkeyresult,
                        NULL,
                        NULL,
                        NULL,
                        (unsigned char *)whereisbuf,
                        &valuelen);
        }


    }


    if (!err)
        len = (int)strlen(whereisbuf);

#endif

    return(len);
}

static void unquote(char *d,const char *s)
{
    while (*s)
    {
        if (*s!='"')
            *d = *s;
        ++s;
        ++d;
    }
    *d = 0;
}

/****************************************************************/
/*  SearchPath Functions                                        */
/****************************************************************/

static int SearchPathEXT(const char *path,const char *searchstr,char *whereisbuf)
{
    char searchbuf[512];
    int len=0;

    sprintf(searchbuf,"%s",searchstr);
    len = SearchPath(path,searchbuf,NULL,512,whereisbuf,NULL);

    if (!len)
    {
        char unquotedbuf[512];

        unquote(unquotedbuf,searchstr);
        searchstr = (const char *) unquotedbuf;

        if (!len)
        {
            sprintf(searchbuf,"%s.com",searchstr);
            len = SearchPath(path,searchbuf,NULL,512,whereisbuf,NULL);
        }
        if (!len)
        {
            sprintf(searchbuf,"%s.exe",searchstr);
            len = SearchPath(path,searchbuf,NULL,512,whereisbuf,NULL);
        }
        if (!len)
        {
            sprintf(searchbuf,"%s.bat",searchstr);
            len = SearchPath(path,searchbuf,NULL,512,whereisbuf,NULL);
        }
        if (!len)
        {
            sprintf(searchbuf,"%s.dll",searchstr);
            len = SearchPath(path,searchbuf,NULL,512,whereisbuf,NULL);
        }
        if (!len)
        {
            sprintf(searchbuf,"%s.pl",searchstr);
            len = SearchPath(path,searchbuf,NULL,512,whereisbuf,NULL);
        }
        if (!len)
        {
            sprintf(searchbuf,"%s.lib",searchstr);
            len = SearchPath(path,searchbuf,NULL,512,whereisbuf,NULL);
        }
        if (!len)
        {
            sprintf(searchbuf,"%s.8bi",searchstr);
            len = SearchPath(path,searchbuf,NULL,512,whereisbuf,NULL);
        }
        if (!len)
        {
            sprintf(searchbuf,"%s.apl",searchstr);
            len = SearchPath(path,searchbuf,NULL,512,whereisbuf,NULL);
        }
        if (!len)
        {
            sprintf(searchbuf,"%s.ttf",searchstr);
            len = SearchPath(path,searchbuf,NULL,512,whereisbuf,NULL);
        }
        if (!len)
        {
            sprintf(searchbuf,"%s.ttc",searchstr);
            len = SearchPath(path,searchbuf,NULL,512,whereisbuf,NULL);
        }
        if (!len)
        {
            sprintf(searchbuf,"pc/%s",searchstr);
            len = SearchPath(path,searchbuf,NULL,512,whereisbuf,NULL);
        }
        if (!len)
        {
            sprintf(searchbuf,"cmn/%s",searchstr);
            len = SearchPath(path,searchbuf,NULL,512,whereisbuf,NULL);
        }
    }

    return(len);
}

static int SearchAppEXT(const char *name,const char *subfolderstr, const char *searchstr, char *whereisbuf)
{
    char searchbuf[512];

    int len = SearchRegEXT(name,searchbuf,sizeof(searchbuf));
    if (len)
    {
        WHEREIS_getpath(searchbuf,searchbuf);
        strcat(searchbuf,subfolderstr);
        len = SearchPathEXT(searchbuf,searchstr,whereisbuf);
    }
    return(len);
}

static int SearchAssociations(const char *name,char *whereisbuf)
{
    int len=0;

#if defined(_MSC_VER)
    int err;
    unsigned long valuelen;
    HKEY key=HKEY_CURRENT_USER;
    int whereisbuflen=512;
    char *subkey=(char *)alloca(whereisbuflen);
    HKEY phkeyresult;
    sprintf(subkey,"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.%s\\OpenWithList",name);

    err = RegOpenKey(key,subkey,&phkeyresult);
    if (!err)
    {
            valuelen=whereisbuflen;
            memset(whereisbuf,0,whereisbuflen);

            err = RegQueryValueEx(phkeyresult,
                        TEXT("a"),
                        NULL,
                        NULL,
                        (unsigned char *)whereisbuf,
                        &valuelen);
    }
    if (!err)
        len = (int)strlen(whereisbuf);


#endif
    return(len);
}



/****************************************************************/
/*  Package Functions                                           */
/****************************************************************/

static int PACKAGE_charmap(char c)
{
    int i=(int)(unsigned char)c;
    if (i>='A' && i<'Z')
        i += 'a'-'A';
    if (i=='\\')
        i = '/';    // map slashes to the same

    return i;
}

// PACKAGE_stricmp compares alphanumerically
// case sensitive
// digits compare as greater than non-digits
// zero padding ignored
// numbers limited to 2^31

static int PACKAGE_stricmp(const char *s0, const char *s1)
{
    int diff=0;
    while ((*s0 || *s1) && !diff)
    {
        diff = PACKAGE_charmap(*s0) - PACKAGE_charmap(*s1);
        if (diff && (isdigit(*s0) || isdigit(*s1)))
        {
            int n0=-1;
            if (isdigit(*s0))
            {
                char digits[100];
                char *d=digits;
                while (isdigit(*s0))
                {
                    if (d<digits+sizeof(digits)-1)
                        *d++ = *s0;
                    ++s0;
                }
                *d = '\0';
                n0 = atoi(digits);
            }

            int n1=-1;
            if (isdigit(*s1))
            {
                char digits[100];
                char *d=digits;
                while (isdigit(*s1))
                {
                    if (d<digits+sizeof(digits)-1)
                        *d++ = *s1;
                    ++s1;
                }
                *d = '\0';
                n1 = atoi(digits);
            }
            diff = n0-n1;
        }
        else
        {
            ++s0;
            ++s1;
        }
    }

    return diff;
}


static int GetPackageDirectory(const char *szPackage, char *whereisbuf)//, const char *szVersion)
{
    int len = SearchRegEXT("framework", whereisbuf, 512);
    if (len)
    {
        char slashbuf[512];
        strcpy(slashbuf,szPackage);
        for (int i=0; slashbuf[i]; ++i)
            if (slashbuf[i]=='/')
                slashbuf[i] = '\\';

        bool FindVersion=false;
        if (strchr(slashbuf,'\\'))  // version specified
        {
            sprintf(whereisbuf,"%s\\%s",whereisbuf,slashbuf);
            if (whereisbuf[strlen(whereisbuf)-1]=='\\')
                strcat(whereisbuf,"*");
        }
        else
        {
            sprintf(whereisbuf,"%s\\%s\\*",whereisbuf,slashbuf);
            FindVersion = true;
        }

        WIN32_FIND_DATA findfiledata;


        HANDLE hFind = FindFirstFileEx(
                            whereisbuf,
                            (FINDEX_INFO_LEVELS) FindExInfoStandard,
                            &findfiledata,
                            FindExSearchLimitToDirectories,
                            NULL, 0);

        if (hFind == INVALID_HANDLE_VALUE)
        {
            return 0;
        }

        char whereisbufLatest[512];
        whereisbufLatest[0] = '\0';
        bool Found=false;
        do
        {
            if ((findfiledata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                && (PACKAGE_stricmp(findfiledata.cFileName,whereisbufLatest) > 0))
            {
                strcpy(whereisbufLatest, findfiledata.cFileName);
                Found = true;
            }
        }
        while(FindNextFile(hFind, &findfiledata));
        if (!Found)
            return 0;

        char *ext = strrchr(whereisbuf,'\\');
        if (ext)
        {
            strcpy(ext+1,whereisbufLatest); // replace * with what we found
        }

        len = (int)strlen(whereisbuf);
    }

    return len;
}



/****************************************************************/
/*  Video For Windows Functions                                 */
/****************************************************************/


static int ConvertFourCC(const char * str)
{
    int value = 0;
    int l = (int)strlen(str);
    value = 0;
    if (l == 4)
    {
        for(int i = 3; i >= 0; i--)
        {
            value |= ((int)(str[i])<<(i*8));
        }
        return value;
    }
    else
    {
        printf("ERROR: 4CC must be exactly 4 characters\n");
        return 0;
    }
}

static void WHEREIS_strcpy(char *dst, const wchar_t *src)
{
    while (*src)
    {
        *dst++ = (char)(unsigned char)*src;
        ++src;
    }
    *dst++ = '\0';
}


static int SearchVFW(const char *fourcc, char *whereisbuf, int verbose)
{
    if (strlen(fourcc)!=4)
        return 0;

    ICINFO icinfo;
    HIC      hic;
    int fcc = 0;
    if (fourcc)
        fcc = ConvertFourCC(fourcc);
    memset(&icinfo,0,sizeof(ICINFO));
    whereisbuf[0] = '\0';
    for (int i = 0; ICInfo(0,i,&icinfo);i++)
    {
        int old4cc = 0;
        hic = ICOpen(icinfo.fccType,icinfo.fccHandler,ICMODE_QUERY);
        if (hic && (fcc == 0 || fcc == icinfo.fccHandler))
        {
            old4cc = icinfo.fccHandler;
            ICGetInfo(hic,&icinfo,sizeof(icinfo));
            if (verbose)
            {
                printf("------------------------------------\n");
                printf("Initial 4CC:\t%c%c%c%c\n", (old4cc)&0xff,(old4cc>>8)&0xff,(old4cc>>16)&0xff,(old4cc>>24)&0xff);
                printf("FCC:\t\t%c%c%c%c\n",(icinfo.fccHandler)&0xff,(icinfo.fccHandler>>8)&0xff,(icinfo.fccHandler>>16)&0xff,(icinfo.fccHandler>>24)&0xff);
                wprintf(L"Name:\t\t%s\n",icinfo.szName);
                wprintf(L"Description:\t%s\n",icinfo.szDescription);
                printf("Version:\t%d\n",icinfo.dwVersion);
                wprintf(L"Driver:\t\t%s\n",icinfo.szDriver);
                printf("FLAGS:\t\t%s%s%s\n\t\t%s%s\n\t\t%s%s\n",(icinfo.dwFlags&VIDCF_COMPRESSFRAMES)?"Compress All Frames,":"",
                                                                                       (icinfo.dwFlags&VIDCF_CRUNCH)?"Compress To Frame Size,":"",
                                                                                       (icinfo.dwFlags&VIDCF_DRAW)?"Can Draw,":"",
                                                                                       (icinfo.dwFlags&VIDCF_FASTTEMPORALC)?"Temporal Compression,":"",
                                                                                       (icinfo.dwFlags&VIDCF_FASTTEMPORALD)?"Temporal Decompression,":"",
                                                                                       (icinfo.dwFlags&VIDCF_QUALITY)?"Quality Value Support,":"",
                                                                                       (icinfo.dwFlags&VIDCF_TEMPORAL)?"InterFrame Compression,":"");
            }
            WHEREIS_strcpy(whereisbuf,icinfo.szDriver);
        }
        memset(&icinfo,0,sizeof(ICINFO));
        ICClose(hic);
    }

    return (int)strlen(whereisbuf);
}



/****************************************************************/
/*  Public Entry Point                                          */
/****************************************************************/



// PUBLIC interface


char * WhereIs_Find(const char *whatToFind)
{
    static char whereisbuf[512];
    int len=0;

    if (!whatToFind || whatToFind[0]=='\0')
    {
        len = GetCurrentDirectory(sizeof(whereisbuf)-1, whereisbuf);
        goto returnWhere;
    }

    // first look right here

    if (!len)
        len = SearchPathEXT(".",whatToFind,whereisbuf);

    if (!len)
    {
        char *envpath = getenv("path");
        if (envpath)
        {
            len = SearchPathEXT(envpath,whatToFind,whereisbuf);
        }
    }

    // windows system folders

    if (!len)
        len = SearchPathEXT(NULL,whatToFind,whereisbuf);


    // packages.  To find framework, but preceed registry search
    if(!len)
        len = GetPackageDirectory(whatToFind,whereisbuf);

    // see if it is a registered app
    if (!len)
        len = SearchRegEXT(whatToFind,whereisbuf,sizeof(whereisbuf));

    // search known environment variable locations
    if (!len)
    {
        const char *envnames[]=
        {
            "lib",
            "include",
            "ps2_cpath",
            "MWCIncludes",
            "MWLibraries",

            // EA technology roots
            "GIMEX","REALGRAPH","REALCORE","MVCODEC","GX","UTIL","P2","FONTWIZ","NXN","GY","ACDSEE","PHOTOSHOP","RCMP","ARTTOOLKIT","SND","EAGL","THRASH",
            NULL
        };
        const char **nextname=envnames;
        while (*nextname && !len)
        {
            char *envpath = getenv(*nextname);
            if (envpath)
            {
                len = SearchPathEXT(envpath,whatToFind,whereisbuf);
            }
            ++nextname;
        }
    }

    // search sony libs and includes
    if (!len)
    {
        char *scehome = getenv("SCEHOME");
        if (!scehome)
            scehome="\\usr\\local\\sce";

        char libbuf[512];
        sprintf(libbuf,"%s\\ee\\lib",scehome);
        len = SearchPathEXT(libbuf,whatToFind,whereisbuf);
        if (!len)
        {
            sprintf(libbuf,"%s\\ee\\include",scehome);
            len = SearchPathEXT(libbuf,whatToFind,whereisbuf);
        }
    }

    // windows fonts
    if (!len)
    {
        char winbuf[512];
        GetWindowsDirectory(winbuf,sizeof(winbuf));
        strcat(winbuf,"\\fonts");
        len = SearchPathEXT(winbuf,whatToFind,whereisbuf);
    }

    // Video For Windows codec
    if (!len)
        len = SearchVFW(whatToFind,whereisbuf,0);


    // extension associations
    if (!len)
        len = SearchAssociations(whatToFind,whereisbuf);

    // Plugins

    if (!len)
        len = SearchAppEXT("acdsee","..\\plugins",whatToFind,whereisbuf);
    if (!len)
        len = SearchAppEXT("photoshp","Plug-Ins\\File Formats",whatToFind,whereisbuf);
    if (!len)
        len = SearchAppEXT("p2",".",whatToFind,whereisbuf);



    // find sn ps2 tools
    if (!len)
    {
        char winbuf[512];
        int tlen = SearchRegEXT("ps2dbg",winbuf,sizeof(winbuf));
        if (tlen)
        {
            char *p = strrchr(winbuf,'\\');
            if (p)
            {
                *p = 0;
                len = SearchPathEXT(winbuf,whatToFind,whereisbuf);
            }
        }
    }

    // find sn gamecube tools
    if (!len)
    {
        char *envpath = getenv("path");
        if (envpath)
        {
            char winbuf[512];
            int tlen = SearchPathEXT(envpath,"ngccc.exe",winbuf);
            if (tlen)
            {
                char *p = strrchr(winbuf,'\\');
                if (p)
                {
                    *p = 0;
                    p = strrchr(winbuf,'\\');
                    if (p)
                    {
                        *p = 0;
                        len = SearchPathEXT(winbuf,whatToFind,whereisbuf);
                    }
                }
            }
        }
    }

returnWhere:
    whereisbuf[len] = 0;
    if (!len)
        return NULL;
    return whereisbuf;
}





/*
 * abs2rel: convert an absolute path name into relative.
 *
 *  i)  pathname    absolute path
 *  i)  folder      base directory (must be absolute path)
 *  o)  result      result buffer
 *  i)  size        size of result buffer
 *  r)      != NULL: relative path
 *          == NULL: error
 */

#if defined(_MSC_VER)
static const char Slash = '\\';
#else
static const char Slash = '/';
#endif

static inline bool isseperator(char c)
{
    return c=='/' || c=='\\';
}

// compare two characters slash and case independent
// true if different, false if same

static inline bool chrcmp(char c1, char c2)
{
    if (tolower(c1)-tolower(c2))
        if (isseperator(c1)!=isseperator(c2))
            return true;
    return false;
}

static char *abs2rel(const char *pathname, const char *folder, char *result, const size_t size)
{
    const char *p=pathname;
    const char *f=folder;

    // find common folder
    const char *branch = f-1;   // +1 added later
    for (;*p && !chrcmp(*p,*f); ++p, ++f)
        if (isseperator(*p))
            branch = f;
    if ((!*p && isseperator(*f)) || (!*f && isseperator(*p)))
        branch = f;

    p = pathname+(branch-folder)+1; // remember source branch point as well

    // go up tree with ../ to branch point

    char *r = result;
    if (branch>=folder)
    {
        for (;*branch; ++branch)
        {
            if (isseperator(*branch) && branch[1])
            {
                if (r>=(result+size-3)) // out of string space
                    return NULL;
                *r++ = '.';
                *r++ = '.';
                *r++ = Slash;

            }
        }
    }
    *r = '\0';
    if (r>=(result+size-strlen(p))) // out of string space
        return NULL;
    strcpy(r,p);
    return result;
}





char * WhereIs_Format(const char *whereis, int OPTquote, int OPTdir, int OPTshortname,int OPTlongname, int OPTunc, int OPTslash, int OPTrelative)
{
    static char wherebuf[512];

    if (!whereis)
        return NULL;

    strcpy(wherebuf,whereis);

    for (int i=0; i<OPTdir; ++i)
        WHERE_getpath(wherebuf,wherebuf);

    if (OPTshortname)
    {
        char shortbuf[512];
        GetShortPathName(wherebuf,shortbuf,sizeof(shortbuf));
        strcpy(wherebuf,shortbuf);
    }
    if (OPTlongname)
    {
        char longbuf[512];
        GetLongPathName(wherebuf,longbuf,sizeof(longbuf));
        strcpy(wherebuf,longbuf);
    }

    if (OPTunc)
    {
        char *unc = PathToUNC(wherebuf);
        if (unc)
            strcpy(wherebuf,unc);
    }

    if (OPTquote==1 || (OPTquote==-1 && !OPTdir && wherebuf[0]!='"' && strchr(wherebuf,' ')))
    {
        char tempbuf[512];
        strcpy(tempbuf,wherebuf);
        sprintf(wherebuf,"\"%s\"",tempbuf);
    }

#if defined(_MSC_VER)
    if (OPTrelative)
    {
        char CurrentDirectory[512];
        GetCurrentDirectory((DWORD)sizeof(CurrentDirectory), CurrentDirectory);

        if (OPTshortname)
        {
            char shortbuf[512];
            GetShortPathName(CurrentDirectory,shortbuf,sizeof(shortbuf));
            strcpy(CurrentDirectory,shortbuf);
        }

        char RelativeName[512];

        if (abs2rel(wherebuf,CurrentDirectory,RelativeName,sizeof(RelativeName)))
            strcpy(wherebuf,RelativeName);
    }
#endif

    if (OPTslash==1 || OPTslash==3)
    {
        for (int i=0; wherebuf[i]; ++i)
            if (wherebuf[i]=='\\')
                wherebuf[i] = '/';
    }


    if (OPTslash==3)
    {
        if (wherebuf[1]==':')
        {
            wherebuf[1] = toupper(wherebuf[0]);
            wherebuf[0] = '/';
        }
    }

    if (OPTslash==2)
    {
        for (int i=0; wherebuf[i]; ++i)
            if (wherebuf[i]=='/')
                wherebuf[i] = '\\';
    }

    return wherebuf;
}

#ifdef _MANAGED

    System::String *WhereIs::Find(System::String *whatToFind)
    {
        char *what = (char *)System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(whatToFind).ToPointer();

        char *found = ::WhereIs_Find(what);
        System::String *res = NULL;
        if (found)
            res = new System::String(found);

        System::Runtime::InteropServices::Marshal::FreeHGlobal(what);
        return res;
    }

#endif

