/*
 *  FILECOMP.C - handles filename completion.
 *
 *
 *  Comments:
 *
 *    30-Jul-1998 (John P Price <linux-guru@gcfl.net>)
 *       moved from command.c file
 *       made second TAB display list of filename matches
 *       made filename be lower case if last character typed is lower case
 *
 *    25-Jan-1999 (Eric Kohl)
 *       Cleanup. Unicode safe!
 *
 *    30-Apr-2004 (Filip Navara <xnavara@volny.cz>)
 *       Make the file listing readable when there is a lot of long names.
 *

 *    05-Jul-2004 (Jens Collin <jens.collin@lakhei.com>)
 *       Now expands lfn even when trailing " is omitted.
 */

#include "precomp.h"

#ifdef FEATURE_UNIX_FILENAME_COMPLETION

VOID CompleteFilename (LPTSTR str, UINT charcount)
{
    WIN32_FIND_DATA file;
    HANDLE hFile;
    INT   curplace = 0;
    INT   start;
    INT   count;
    INT step;
    INT c = 0;
    BOOL  found_dot = FALSE;
    BOOL  perfectmatch = TRUE;
    TCHAR path[MAX_PATH];
    TCHAR fname[MAX_PATH];
    TCHAR maxmatch[MAX_PATH] = _T("");
    TCHAR directory[MAX_PATH];
    LPCOMMAND cmds_ptr;

    /* expand current file name */
    count = charcount - 1;
    if (count < 0)
        count = 0;

    /* find how many '"'s there is typed already. */
    step = count;
    while (step > 0)
    {
        if (str[step] == _T('"'))
            c++;
        step--;
    }
    /* if c is odd, then user typed " before name, else not. */

    /* find front of word */
    if (str[count] == _T('"') || (c % 2))
    {
        count--;
        while (count > 0 && str[count] != _T('"'))
            count--;
    }
    else
    {
        while (count > 0 && str[count] != _T(' '))
            count--;
    }

    /* if not at beginning, go forward 1 */
    if (str[count] == _T(' '))
        count++;

    start = count;

    if (str[count] == _T('"'))
        count++;	/* don't increment start */

    /* extract directory from word */
    _tcscpy (directory, &str[count]);
    curplace = _tcslen (directory) - 1;

    if (curplace >= 0 && directory[curplace] == _T('"'))
        directory[curplace--] = _T('\0');

    _tcscpy (path, directory);

    while (curplace >= 0 && directory[curplace] != _T('\\') &&
                   directory[curplace] != _T('/') &&
           directory[curplace] != _T(':'))
    {
        directory[curplace] = 0;
        curplace--;
    }

    /* look for a '.' in the filename */
    for (count = _tcslen (directory); path[count] != _T('\0'); count++)
    {
        if (path[count] == _T('.'))
        {
            found_dot = TRUE;
            break;
        }
    }

    if (found_dot)
        _tcscat (path, _T("*"));
    else
        _tcscat (path, _T("*.*"));

    /* current fname */
    curplace = 0;

    hFile = FindFirstFile (path, &file);
    if (hFile != INVALID_HANDLE_VALUE)
    {
        /* find anything */
        do
        {
            /* ignore "." and ".." */
            if (!_tcscmp (file.cFileName, _T(".")) ||
                !_tcscmp (file.cFileName, _T("..")))
                continue;

            _tcscpy (fname, file.cFileName);

            if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                _tcscat (fname, _T("\\"));

            if (!maxmatch[0] && perfectmatch)
            {
                _tcscpy(maxmatch, fname);
            }
            else
            {
                for (count = 0; maxmatch[count] && fname[count]; count++)
                {
                    if (tolower(maxmatch[count]) != tolower(fname[count]))
                    {
                        perfectmatch = FALSE;
                        maxmatch[count] = 0;
                        break;
                    }
                }

                if (maxmatch[count] == _T('\0') &&
                    fname[count] != _T('\0'))
                    perfectmatch = FALSE;
            }
        }
        while (FindNextFile (hFile, &file));

        FindClose (hFile);

        /* only quote if the filename contains spaces */
        if (_tcschr(directory, _T(' ')) ||
            _tcschr(maxmatch, _T(' ')))
        {
            str[start] = _T('\"');
            _tcscpy (&str[start+1], directory);
            _tcscat (&str[start], maxmatch);
            _tcscat (&str[start], _T("\"") );
        }
        else
        {
            _tcscpy (&str[start], directory);
            _tcscat (&str[start], maxmatch);
        }

        if (!perfectmatch)
        {
            MessageBeep (-1);
        }
    }
    else
    {
        /* no match found - search for internal command */
        for (cmds_ptr = cmds; cmds_ptr->name; cmds_ptr++)
        {
            if (!_tcsnicmp (&str[start], cmds_ptr->name,
                _tcslen (&str[start])))
            {
                /* return the mach only if it is unique */
                if (_tcsnicmp (&str[start], (cmds_ptr+1)->name, _tcslen (&str[start])))
                    _tcscpy (&str[start], cmds_ptr->name);
                break;
            }
        }

        MessageBeep (-1);
    }
}


/*
 * returns 1 if at least one match, else returns 0
 */
BOOL ShowCompletionMatches (LPTSTR str, INT charcount)
{
    WIN32_FIND_DATA file;
    HANDLE hFile;
    BOOL  found_dot = FALSE;
    INT   curplace = 0;
    INT   count;
    TCHAR path[MAX_PATH];
    TCHAR fname[MAX_PATH];
    TCHAR directory[MAX_PATH];
    SHORT screenwidth;

    /* expand current file name */
    count = charcount - 1;
    if (count < 0)
        count = 0;

    /* find front of word */
    if (str[count] == _T('"'))
    {
        count--;
        while (count > 0 && str[count] != _T('"'))
            count--;
    }
    else
    {
        while (count > 0 && str[count] != _T(' '))
            count--;
    }

    /* if not at beginning, go forward 1 */
    if (str[count] == _T(' '))
        count++;

    if (str[count] == _T('"'))
        count++;

    /* extract directory from word */
    _tcscpy (directory, &str[count]);
    curplace = _tcslen (directory) - 1;

    if (curplace >= 0 && directory[curplace] == _T('"'))
        directory[curplace--] = _T('\0');

    _tcscpy (path, directory);

    while (curplace >= 0 &&
           directory[curplace] != _T('\\') &&
           directory[curplace] != _T(':'))
    {
        directory[curplace] = 0;
        curplace--;
    }

    /* look for a . in the filename */
    for (count = _tcslen (directory); path[count] != _T('\0'); count++)
    {
        if (path[count] == _T('.'))
        {
            found_dot = TRUE;
            break;
        }
    }

    if (found_dot)
        _tcscat (path, _T("*"));
    else
        _tcscat (path, _T("*.*"));

    /* current fname */
    curplace = 0;

    hFile = FindFirstFile (path, &file);
    if (hFile != INVALID_HANDLE_VALUE)
    {
        UINT longestfname = 0;
        /* Get the size of longest filename first. */
        do
        {
            if (_tcslen(file.cFileName) > longestfname)
            {
                longestfname = _tcslen(file.cFileName);
                /* Directories get extra brackets around them. */
                if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                    longestfname += 2;
            }
        }
        while (FindNextFile (hFile, &file));
        FindClose (hFile);

        hFile = FindFirstFile (path, &file);

        /* Count the highest number of columns */
        GetScreenSize(&screenwidth, NULL);

        /* For counting columns of output */
        count = 0;

        /* Increase by the number of spaces behind file name */
        longestfname += 3;

        /* find anything */
        ConOutChar(_T('\n'));
        do
        {
            /* ignore . and .. */
            if (!_tcscmp (file.cFileName, _T(".")) ||
                !_tcscmp (file.cFileName, _T("..")))
                continue;

            if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                _stprintf (fname, _T("[%s]"), file.cFileName);
            else
                _tcscpy (fname, file.cFileName);

            ConOutPrintf (_T("%*s"), - longestfname, fname);
            count++;
            /* output as much columns as fits on the screen */
            if (count >= (screenwidth / longestfname))
            {
                /* print the new line only if we aren't on the
                 * last column, in this case it wraps anyway */
                if (count * longestfname != (UINT)screenwidth)
                    ConOutChar(_T('\n'));
                count = 0;
            }
        }
        while (FindNextFile (hFile, &file));

        FindClose (hFile);

        if (count)
            ConOutChar(_T('\n'));
    }
    else
    {
        /* no match found */
        MessageBeep (-1);
        return FALSE;
    }

    return TRUE;
}
#endif

#ifdef FEATURE_4NT_FILENAME_COMPLETION

typedef struct _FileName
{
    TCHAR Name[MAX_PATH];
} FileName;

VOID FindPrefixAndSuffix(LPTSTR strIN, LPTSTR szPrefix, LPTSTR szSuffix)
{
    /* String that is to be examined */
    TCHAR str[MAX_PATH];
    /* temp pointers to used to find needed parts */
    TCHAR * szSearch;
    TCHAR * szSearch1;
    TCHAR * szSearch2;
    TCHAR * szSearch3;
    /* number of quotes in the string */
    INT nQuotes = 0;
    /* used in for loops */
    UINT i;
    /* Char number to break the string at */
    INT PBreak = 0;
    INT SBreak = 0;
    /* when phrasing a string, this tells weather
       you are inside quotes ot not. */
    BOOL bInside = FALSE;

    szPrefix[0] = _T('\0');
    szSuffix[0] = _T('\0');

    /* Copy over the string to later be edited */
    _tcscpy(str,strIN);

    /* Count number of " */
    for(i = 0; i < _tcslen(str); i++)
    {
        if (str[i] == _T('\"'))
            nQuotes++;
    }

    /* Find the prefix and suffix */
    if (nQuotes % 2 && nQuotes >= 1)
    {
        /* Odd number of quotes.  Just start from the last " */
        /* THis is the way MS does it, and is an easy way out */
        szSearch = _tcsrchr(str, _T('\"'));
        /* Move to the next char past the " */
        szSearch++;
        _tcscpy(szSuffix,szSearch);
        /* Find the one closest to end */
        szSearch1 = _tcsrchr(str, _T('\"'));
        szSearch2 = _tcsrchr(str, _T('\\'));
        szSearch3 = _tcsrchr(str, _T('/'));
        if ((szSearch2 != NULL) && (szSearch1 < szSearch2))
            szSearch = szSearch2;
        else if ((szSearch3 != NULL) && (szSearch1 < szSearch3))
            szSearch = szSearch3;
        else
            szSearch = szSearch1;
        /* Move one char past */
        szSearch++;
        szSearch[0] = _T('\0');
        _tcscpy(szPrefix,str);
        return;

    }

    if (!_tcschr(str, _T(' ')))
    {
        /* No spaces, everything goes to Suffix */
        _tcscpy(szSuffix,str);
        /* look for a slash just in case */
        szSearch = _tcsrchr(str, _T('\\'));
        if (szSearch)
        {
            szSearch++;
            szSearch[0] = _T('\0');
            _tcscpy(szPrefix,str);
        }
        else
        {
            szPrefix[0] = _T('\0');
        }
        return;
    }

    if (!nQuotes)
    {
        /* No quotes, and there is a space*/
        /* Take it after the last space */
        szSearch = _tcsrchr(str, _T(' '));
        szSearch++;
        _tcscpy(szSuffix,szSearch);
        /* Find the closest to the end space or \ */
        _tcscpy(str,strIN);
        szSearch1 = _tcsrchr(str, _T(' '));
        szSearch2 = _tcsrchr(str, _T('\\'));
        szSearch3 = _tcsrchr(str, _T('/'));
        if ((szSearch2 != NULL) && (szSearch1 < szSearch2))
            szSearch = szSearch2;
        else if ((szSearch3 != NULL) && (szSearch1 < szSearch3))
            szSearch = szSearch3;
        else
            szSearch = szSearch1;
        szSearch++;
        szSearch[0] = _T('\0');
        _tcscpy(szPrefix,str);
        return;
    }

    /* All else fails and there is a lot of quotes, spaces and |
       Then we search through and find the last space or \ that is
        not inside a quotes */
    for(i = 0; i < _tcslen(str); i++)
    {
        if (str[i] == _T('\"'))
            bInside = !bInside;
        if (str[i] == _T(' ') && !bInside)
            SBreak = i;
        if ((str[i] == _T(' ') || str[i] == _T('\\')) && !bInside)
            PBreak = i;
    }
    SBreak++;
    PBreak++;
    _tcscpy(szSuffix,&strIN[SBreak]);
    strIN[PBreak] = _T('\0');
    _tcscpy(szPrefix,strIN);
    if (szPrefix[_tcslen(szPrefix) - 2] == _T('\"') &&
        szPrefix[_tcslen(szPrefix) - 1] != _T(' '))
    {
        /* need to remove the " right before a \ at the end to
           allow the next stuff to stay inside one set of quotes
            otherwise you would have multiple sets of quotes*/
        _tcscpy(&szPrefix[_tcslen(szPrefix) - 2],_T("\\"));
    }
}

int __cdecl compare(const void *arg1,const void *arg2)
{
    FileName * File1;
    FileName * File2;
    INT ret;

    File1 = cmd_alloc(sizeof(FileName));
    if (!File1)
        return 0;

    File2 = cmd_alloc(sizeof(FileName));
    if (!File2)
    {
        cmd_free(File1);
        return 0;
    }

    memcpy(File1,arg1,sizeof(FileName));
    memcpy(File2,arg2,sizeof(FileName));

     /* ret = _tcsicmp(File1->Name, File2->Name); */
     ret = lstrcmpi(File1->Name, File2->Name);

    cmd_free(File1);
    cmd_free(File2);
    return ret;
}

BOOL
FileNameContainsSpecialCharacters(LPTSTR pszFileName)
{
    TCHAR chr;

    while ((chr = *pszFileName++) != _T('\0'))
    {
        if ((chr == _T(' ')) ||
            (chr == _T('!')) ||
            (chr == _T('%')) ||
            (chr == _T('&')) ||
            (chr == _T('(')) ||
            (chr == _T(')')) ||
            (chr == _T('{')) ||
            (chr == _T('}')) ||
            (chr == _T('[')) ||
            (chr == _T(']')) ||
            (chr == _T('=')) ||
            (chr == _T('\'')) ||
            (chr == _T('`')) ||
            (chr == _T(',')) ||
            (chr == _T(';')) ||
            (chr == _T('^')) ||
            (chr == _T('~')) ||
            (chr == _T('+')) ||
            (chr == 0xB4)) // 'ยด'
        {
            return TRUE;
        }
    }

    return FALSE;
}


VOID CompleteFilename (LPTSTR strIN, BOOL bNext, LPTSTR strOut, UINT cusor)
{
    /* Length of string before we complete it */
    INT_PTR StartLength;
    /* Length of string after completed */
    //INT EndLength;
    /* The number of chars added too it */
    //static INT DiffLength = 0;
    /* Used to find and assemble the string that is returned */
    TCHAR szBaseWord[MAX_PATH];
    TCHAR szPrefix[MAX_PATH];
    TCHAR szOriginal[MAX_PATH];
    TCHAR szSearchPath[MAX_PATH];
    /* Save the strings used last time, so if they hit tab again */
    static TCHAR LastReturned[MAX_PATH];
    static TCHAR LastSearch[MAX_PATH];
    static TCHAR LastPrefix[MAX_PATH];
    /* Used to search for files */
    HANDLE hFile;
    WIN32_FIND_DATA file;
    /* List of all the files */
    FileName * FileList = NULL;
    /* Number of files */
    INT FileListSize = 0;
    /* Used for loops */
    UINT i;
    /* Editable string of what was passed in */
    TCHAR str[MAX_PATH];
    /* Keeps track of what element was last selected */
    static INT Sel;
    BOOL NeededQuote = FALSE;
    BOOL ShowAll = TRUE;
    TCHAR * line = strIN;

    strOut[0] = _T('\0');

    while (_istspace (*line))
        line++;
    if (!_tcsnicmp (line, _T("rd "), 3) || !_tcsnicmp (line, _T("cd "), 3))
        ShowAll = FALSE;

    /* Copy the string, str can be edited and original should not be */
    _tcscpy(str,strIN);
    _tcscpy(szOriginal,strIN);

    /* Look to see if the cusor is not at the end of the string */
    if ((cusor + 1) < _tcslen(str))
        str[cusor] = _T('\0');

    /* Look to see if they hit tab again, if so cut off the diff length */
    if (_tcscmp(str,LastReturned) || !_tcslen(str))
    {
        /* We need to know how many chars we added from the start */
        StartLength = _tcslen(str);

        /* no string, we need all files in that directory */
        if (!StartLength)
        {
            _tcscat(str,_T("*"));
        }

        /* Zero it out first */
        szBaseWord[0] = _T('\0');
        szPrefix[0] = _T('\0');

        /*What comes out of this needs to be:
            szBaseWord =  path no quotes to the object
            szPrefix = what leads up to the filename
            no quote at the END of the full name */
        FindPrefixAndSuffix(str,szPrefix,szBaseWord);
        /* Strip quotes */
        for(i = 0; i < _tcslen(szBaseWord); )
        {
            if (szBaseWord[i] == _T('\"'))
                memmove(&szBaseWord[i],&szBaseWord[i + 1], _tcslen(&szBaseWord[i]) * sizeof(TCHAR));
            else
                i++;
        }

        /* clear it out */
        memset(szSearchPath, 0, sizeof(szSearchPath));

        /* Start the search for all the files */
        GetFullPathName(szBaseWord, MAX_PATH, szSearchPath, NULL);

        /* Got a device path? Fallback to the the current dir plus the short path */
        if (szSearchPath[0] == _T('\\') && szSearchPath[1] == _T('\\') &&
            szSearchPath[2] == _T('.') && szSearchPath[3] == _T('\\'))
        {
            GetCurrentDirectory(MAX_PATH, szSearchPath);
            _tcscat(szSearchPath, _T("\\"));
            _tcscat(szSearchPath, szBaseWord);
        }

        if (StartLength > 0)
        {
            _tcscat(szSearchPath,_T("*"));
        }
        _tcscpy(LastSearch,szSearchPath);
        _tcscpy(LastPrefix,szPrefix);
    }
    else
    {
        _tcscpy(szSearchPath, LastSearch);
        _tcscpy(szPrefix, LastPrefix);
        StartLength = 0;
    }
    /* search for the files it might be */
    hFile = FindFirstFile (szSearchPath, &file);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        /* Assemble the original string and return */
        _tcscpy(strOut,szOriginal);
        return;
    }

    /* assemble a list of all files names */
    do
    {
        FileName * oldFileList = FileList;

        if (!_tcscmp (file.cFileName, _T(".")) ||
            !_tcscmp (file.cFileName, _T("..")))
            continue;

        /* Don't show files when they are doing 'cd' or 'rd' */
        if (!ShowAll &&
            file.dwFileAttributes != INVALID_FILE_ATTRIBUTES &&
            !(file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
        {
                continue;
        }

        /* Add the file to the list of files */
        FileList = cmd_realloc(FileList, ++FileListSize * sizeof(FileName));

        if (FileList == NULL)
        {
            /* Don't leak old buffer */
            cmd_free(oldFileList);
            /* Assemble the original string and return */
            _tcscpy(strOut,szOriginal);
            FindClose(hFile);
            ConOutFormatMessage (GetLastError());
            return;
        }
        /* Copies the file name into the struct */
        _tcscpy(FileList[FileListSize-1].Name,file.cFileName);

    } while(FindNextFile(hFile,&file));

    FindClose(hFile);

    /* Check the size of the list to see if we found any matches */
    if (FileListSize == 0)
    {
        _tcscpy(strOut,szOriginal);
        if (FileList != NULL)
            cmd_free(FileList);
        return;

    }
    /* Sort the files */
    qsort(FileList,FileListSize,sizeof(FileName), compare);

    /* Find the next/previous */
    if (_tcslen(szOriginal) && !_tcscmp(szOriginal,LastReturned))
    {
        if (bNext)
        {
            if (FileListSize - 1 == Sel)
                Sel = 0;
            else
                Sel++;
        }
        else
        {
            if (!Sel)
                Sel = FileListSize - 1;
            else
                Sel--;
        }
    }
    else
    {
        Sel = 0;
    }

    /* nothing found that matched last time so return the first thing in the list */
    strOut[0] = _T('\0');

    /* Special character in the name */
    if (FileNameContainsSpecialCharacters(FileList[Sel].Name))
    {
        INT LastSpace;
        BOOL bInside;
        /* It needs a " at the end */
        NeededQuote = TRUE;
        LastSpace = -1;
        bInside = FALSE;
        /* Find the place to put the " at the start */
        for(i = 0; i < _tcslen(szPrefix); i++)
        {
            if (szPrefix[i] == _T('\"'))
                bInside = !bInside;
            if (szPrefix[i] == _T(' ') && !bInside)
                LastSpace = i;
        }

        /* insert the quotation and move things around */
        if (szPrefix[LastSpace + 1] != _T('\"') && LastSpace != -1)
        {
            memmove ( &szPrefix[LastSpace+1], &szPrefix[LastSpace], (_tcslen(szPrefix)-LastSpace+1) * sizeof(TCHAR) );

            if ((UINT)(LastSpace + 1) == _tcslen(szPrefix))
            {
                _tcscat(szPrefix,_T("\""));
            }
            szPrefix[LastSpace + 1] = _T('\"');
        }
        else if (LastSpace == -1)
        {
            /* Add quotation only if none exists already */
            if (szPrefix[0] != _T('\"'))
            {
                _tcscpy(szBaseWord,_T("\""));
                _tcscat(szBaseWord,szPrefix);
                _tcscpy(szPrefix,szBaseWord);
            }
        }
    }

    _tcscpy(strOut,szPrefix);
    _tcscat(strOut,FileList[Sel].Name);

    /* check for odd number of quotes means we need to close them */
    if (!NeededQuote)
    {
        for(i = 0; i < _tcslen(strOut); i++)
        {
            if (strOut[i] == _T('\"'))
                NeededQuote = !NeededQuote;
        }
    }

    if (NeededQuote || (_tcslen(szPrefix) && szPrefix[_tcslen(szPrefix) - 1] == _T('\"')))
        _tcscat(strOut,_T("\""));

    _tcscpy(LastReturned,strOut);
    //EndLength = _tcslen(strOut);
    //DiffLength = EndLength - StartLength;
    if (FileList != NULL)
        cmd_free(FileList);
}
#endif