/* (C) 2003-2007 Willem Jan Hengeveld <itsme@xs4all.nl>
 * Web: http://www.xs4all.nl/~itsme/
 *      http://wiki.xda-developers.com/
 *
 * $Id$
 *
 * this program prints directory listings from a windows ce device
 *
 * todo: (DONE) add disk space free/used info
 */
#include <stdio.h>
#include <string.h>
#include <util/wintypes.h>
#include <util/rapitypes.h>

#include "debug.h"
#include "args.h"

#include "vectorutils.h"
#include "stringutils.h"
#include "csidlpaths.h"
#include <map>
#include "util/timeconvert.h"

bool g_bRecurse= false;
bool g_bVerbose= false;

typedef std::map<std::string,std::string> StringStringMap;

#define AT_NONEXISTANT      1
#define AT_ISDIRECTORY      2
#define AT_ISFILE           3

int getCeAttributes(const std::string& name)
{
    DWORD dwAttr = CeGetFileAttributes( (WCHAR*)expand_csidl(ToWString(name)).c_str() );
    if (0xFFFFFFFF == dwAttr)
        return AT_NONEXISTANT;

    if (dwAttr & FILE_ATTRIBUTE_DIRECTORY)
        return AT_ISDIRECTORY;

    return AT_ISFILE;
}
bool isCeDirectory(const std::string& name)
{
    return getCeAttributes(name)==AT_ISDIRECTORY;
}
bool isCeFile(const std::string& name)
{
    return getCeAttributes(name)==AT_ISFILE;
}
bool isCeNonExistant(const std::string& name)
{
    return getCeAttributes(name)==AT_NONEXISTANT;
}
std::string concat_path(const std::string& p1, const std::string& p2)
{
    std::string result(p1);

    if (result.size() && result[result.size()-1]!='/' && result[result.size()-1]!='\\')
        result += '\\';

    result += p2;

    return result;
}

std::string GetBasePath(const std::string& name)
{
    size_t lastslash= name.find_last_of("\\/");
    if (lastslash==name.npos)
        return "";

    return name.substr(0, lastslash);
}

/*
0x00000001 FILE_ATTRIBUTE_READONLY
0x00000002 FILE_ATTRIBUTE_HIDDEN
0x00000004 FILE_ATTRIBUTE_SYSTEM
0x00000010 FILE_ATTRIBUTE_DIRECTORY
0x00000020 FILE_ATTRIBUTE_ARCHIVE
0x00000040 FILE_ATTRIBUTE_INROM
0x00000080 FILE_ATTRIBUTE_NORMAL
0x00000100 FILE_ATTRIBUTE_TEMPORARY
0x00000200 FILE_ATTRIBUTE_SPARSE_FILE
0x00000400 FILE_ATTRIBUTE_REPARSE_POINT
0x00000800 FILE_ATTRIBUTE_COMPRESSED
0x00001000 FILE_ATTRIBUTE_OFFLINE
0x00001000 FILE_ATTRIBUTE_ROMSTATICREF
0x00002000 FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
0x00002000 FILE_ATTRIBUTE_ROMMODULE
0x00004000 FILE_ATTRIBUTE_ENCRYPTED
0x00010000 FILE_ATTRIBUTE_HAS_CHILDREN
0x00020000 FILE_ATTRIBUTE_SHORTCUT
*/
std::string fileflags(DWORD dwAttr)
{
    std::string attr;
    attr += (dwAttr&FILE_ATTRIBUTE_DIRECTORY) ? 'd' : '-';
    attr += (dwAttr&FILE_ATTRIBUTE_ARCHIVE)   ? 'a' : '-';
    attr += (dwAttr&FILE_ATTRIBUTE_READONLY)  ? 'r' : '-';
    attr += (dwAttr&FILE_ATTRIBUTE_HIDDEN)    ? 'h' : '-';
    attr += (dwAttr&FILE_ATTRIBUTE_SYSTEM)    ? 's' : '-';
    attr += (dwAttr&FILE_ATTRIBUTE_NORMAL)    ? 'n' : '-';
    attr += (dwAttr&FILE_ATTRIBUTE_INROM)     ? 'R' : '-';
    attr += (dwAttr&FILE_ATTRIBUTE_COMPRESSED)     ? 'c' : '-';
    attr += (dwAttr&FILE_ATTRIBUTE_ROMMODULE)     ? 'm' : '-';
    attr += (dwAttr&FILE_ATTRIBUTE_SPARSE_FILE)    ? 'S' : '-';
    attr += (dwAttr&FILE_ATTRIBUTE_REPARSE_POINT)  ? 'p' : '-';
    DWORD dwAll=FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_INROM|FILE_ATTRIBUTE_COMPRESSED|FILE_ATTRIBUTE_ROMMODULE|FILE_ATTRIBUTE_SPARSE_FILE|FILE_ATTRIBUTE_REPARSE_POINT;
    if (dwAttr&~dwAll) {
        attr += stringformat("[%08lx]", dwAttr&~dwAll);
    }
    return attr;
}
std::string MakeFileLine(const CE_FIND_DATA& wfd, const std::string& name)
{
    std::string line;
    if (g_bVerbose)
        line= fileflags(wfd.dwFileAttributes)+" ";
#ifdef _WIN32
    SYSTEMTIME systime;
    FILETIME lfiletime;
    FileTimeToLocalFileTime(&wfd.ftLastWriteTime, &lfiletime);
    FileTimeToSystemTime(&lfiletime, &systime);
    line += stringformat("%04d-%02d-%02d %02d:%02d:%02d.%03d",
            systime.wYear, systime.wMonth, systime.wDay,
            systime.wHour, systime.wMinute, systime.wSecond,systime.wMilliseconds);
#else
    time_t t= filetimetounix(&wfd.ftLastWriteTime);
    struct tm *tm=localtime(&t);
    line += stringformat("%04d-%02d-%02d %02d:%02d:%02d.%03d",
            tm->tm_year+1900, tm->tm_mon+1,  tm->tm_mday,
            tm->tm_hour, tm->tm_min, tm->tm_sec, 0);
#endif
    if(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        line += stringformat(" <DIR>   %s\\", name.c_str());
    else {
// ignoring the possibility of 4G+ files on portable devices for now.
        line += stringformat(" %7d %s", wfd.nFileSizeLow, name.c_str());
    }

    return line;
}

bool CeFileGlob(const std::string& name, StringStringMap& filelist, StringStringMap& dirlist)
{
    CE_FIND_DATA wfd;
std::Wstring wname= ToWString(name);
std::Wstring ewname= expand_csidl(ToWString(name));
    HANDLE hFind = CeFindFirstFile( (WCHAR*)expand_csidl(ToWString(name)).c_str(), &wfd);
    if (INVALID_HANDLE_VALUE == hFind)
        return false;

    std::string rootpath= GetBasePath(name);
    do {
        std::string fullpath= concat_path(rootpath, ToString(wfd.cFileName));
        if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
            dirlist[fullpath]= MakeFileLine(wfd, fullpath);
        else
            filelist[fullpath]= MakeFileLine(wfd, fullpath);
    } while (CeFindNextFile(hFind, &wfd));

    CeFindClose(hFind);

    return true;
}

bool ProcessDirectory(const std::string& name, int level= 0)
{
    StringStringMap files;
    StringStringMap subdirs;

    if (!CeFileGlob(concat_path(name,"*"), files, subdirs))
        return false;

    for (StringStringMap::iterator i= files.begin() ; i!=files.end() ; ++i)
        printf("%s\n", (*i).second.c_str());

    bool bOk= true;

    for (StringStringMap::iterator i= subdirs.begin() ; i!=subdirs.end() ; ++i) {
        printf("%s\n", (*i).second.c_str());
        if (g_bRecurse) {
            // problem: CeFileGlob means both invalid path, and zero files
            /* bOk = bOk && */ ProcessDirectory((*i).first, level+1);
        }
    }
    return bOk;
}
bool ProcessFile(const std::string& name)
{
    CE_FIND_DATA wfd;
    HANDLE hFind = CeFindFirstFile( (WCHAR*)expand_csidl(ToWString(name)).c_str(), &wfd);
    if (INVALID_HANDLE_VALUE == hFind)
        return false;

    printf("%s\n", MakeFileLine(wfd, name).c_str());

    return true;
}
bool hasWildCards(const std::string& name)
{
    return name.find_first_of("*?")!=name.npos;
}

bool ProcessName(const std::string& name)
{

    int type= getCeAttributes(name);
    if (type==AT_ISDIRECTORY)
        return ProcessDirectory(name);
    else if (type==AT_ISFILE)
        return ProcessFile(name);
    else {
        printf("%s not found\n", name.c_str());
        return false;
    }
}

void usage()
{
    printf("(C) 2003-2008 Willem jan Hengeveld  itsme@xs4all.nl\n");
    printf("Usage: pdir [-r] dir ...\n");
    printf("       -l     to list special paths\n");
    printf("       -r     recurse into directories\n");
    printf("       -v     verbose listing: with file attributes\n");
}
int main( int argc, char *argv[])
{
    DebugStdOut();

    StringList dirlist;
    bool do_list_csidl_paths= false;

    for (int i=1 ; i<argc ; i++)
    {
        if (argv[i][0]=='-') switch(argv[i][1])
        {
            case 'r': g_bRecurse= true; break;
            case 'v': g_bVerbose= true; break;
            case 'l': do_list_csidl_paths= true; break;
            default:
                usage();
                return 1;
        }
        else
        {
            dirlist.push_back(argv[i]);
        }
    }

    HRESULT hr= CeRapiInit();
    if (FAILED(hr))
    {
        printf( "Failed - %08lx\n", hr);
        return 1;
    }

    if (do_list_csidl_paths)  {
        if (dirlist.empty())
            list_csidl_paths();
        else {
            print_csidl_path(dirlist.front());
            CeRapiUninit();
            return 0;
        }
    }
    else if (dirlist.empty())
        dirlist.push_back("");      // empty string for root.
    bool bOk= true;

    for (StringList::iterator i= dirlist.begin() ; i!=dirlist.end(); ++i)
    {
        if (hasWildCards(*i)) {
            StringStringMap files;
            StringStringMap subdirs;
            if (!CeFileGlob(*i, files, subdirs))
            {
                debug("error matching wildcard %s\n", (*i).c_str());
                bOk= false;
                continue;
            }
            for (StringStringMap::iterator i= files.begin() ; i!=files.end() ; ++i)
                printf("%s\n", (*i).second.c_str());
            for (StringStringMap::iterator i= subdirs.begin() ; i!=subdirs.end() ; ++i)
                printf("%s\n", (*i).second.c_str());
        }
        else {
            bOk = ProcessName(*i) && bOk;
        }
    }
    STORE_INFORMATION si;
    if (!CeGetStoreInformation(&si))
        error("CeGetStoreInformation");
    else {
        printf("%10d used,  %10d free,  %10d total\n", si.dwStoreSize-si.dwFreeSize, si.dwFreeSize, si.dwStoreSize);
    }

    CeRapiUninit();

    return bOk ? 0 : 1;
}

