/* (C) 2003-2007 Willem Jan Hengeveld <itsme@xs4all.nl>
 * Web: http://www.xs4all.nl/~itsme/
 *      http://wiki.xda-developers.com/
 *
 * $Id$
 *
 * this program allows you to list/modify databases, volumes, records
 * on wm2003 devices.
 *
 * on wm2005 the internal database api changed, and unfortunately the
 * external database api was not changed. leading to a not working api
 *
 */
#include <util/wintypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <util/rapitypes.h>
#include "debug.h"
#include "stringutils.h"
#include "args.h"

// todo:
//    - add itsutils interface for wm2005  edb databases
//        *DONE -> PoomInterface
//    - add delete record function
//    - add import database from text file function

//-----------------------------
void DumpObjectInfo(const CEGUID& volguid, CEOID oid, bool bRecurse);
void DumpDBRecord(const CEGUID& volguid, CEOID recid);
BOOL DumpDatabaseByOid(const CEGUID& volguid, CEOID oid);
void DumpDatabaseByName(const CEGUID& volguid, char *name);
void DumpDatabaseByHandle(HANDLE h);

void SearchDatabase(const CEGUID& volguid, HANDLE h, DWORD dwFieldId, char *seektype, char *szValue);
void SetFieldValue(HANDLE h, CEOID dwObjectId, DWORD dwFieldId, char *szValue);
bool DeleteRecord(HANDLE h, CEOID dwObjectId);

void EnumDatabases(const CEGUID& volguid, bool bRecurse);
void EnumVolumes(bool bDoListDatabases, bool bDoRecursiveDump);

void PrintFileInfo(const std::string& szFileName);
BOOL isOid(const CEGUID& volguid, CEOID oid);
std::string guidstring(const CEGUID& guid);
bool parseguid(const char *str, CEGUID& guid);

void usage()
{
    printf("(C) 2003-2008 Willem jan Hengeveld  itsme@xs4all.nl\n");
    printf("pdblist [options]\n"
            "   -d              : dump all databases\n"
            "   -D              : dump all databases of all volumes\n"
            "   -d {oid | name} : dump specific db\n"
            "   -o oid          : info about object [db, file, record]\n"
            "   -v              : list volumes\n"
            "   -v name         : operate on volume\n"
            "   -r              : recurse [dirs.files, db.rec.fields]\n"
            "   -f field        : operate on field [for assign or seek]\n"
            "   -e value        : operate on value [for assign or seek]\n"
            "   -a              : assign value to field\n"
            "   -R              : delete specified record\n"
            "   -s type         : seek value\n"
            "   -i ordering     : use ordering\n"
            );
}

int main( int argc, char *argv[])
{
    DebugStdOut();

    CEOID dwObjectId=0;
    char *szDatabaseName=NULL ;
    char *szVolumeName=NULL ;
    bool bDoListDatabases=false;
    bool bDoRecursiveDump=false;
    bool bDoListVolumes=false;
    bool bDoDeleteRecord= false;
    DWORD dwFieldId= 0;
    char *szValue= NULL;
    bool bDoSet= false;
    char *szSeekType= NULL;
    char *szFileName= NULL;
    DWORD dwOrdering= 0;

    int argsfound=0;
    for (int i=1 ; i<argc ; i++)
    {
        if (argv[i][0]=='-') switch(argv[i][1])
        {
            case 'd': if (!HANDLESTROPTION(szDatabaseName)) bDoListDatabases=true; break;
            case 'D': bDoListVolumes= true; bDoListDatabases=true; break;
            case 'o': HANDLEULOPTION(dwObjectId, CEOID); break;
            case 'v': if (!HANDLESTROPTION(szVolumeName)) bDoListVolumes= true; break;
            case 'r': bDoRecursiveDump= true; break;
            case 'f': HANDLEULOPTION(dwFieldId, DWORD); break;
            case 'e': HANDLESTROPTION(szValue); break;
            case 'a': bDoSet= true; break;
            case 's': HANDLESTROPTION(szSeekType); break;
            case 'l': HANDLESTROPTION(szFileName); break;
            case 'i': HANDLEULOPTION(dwOrdering, DWORD); break;
            case 'R': bDoDeleteRecord= true; break;
//!!! i and f always have to be equal for certain seektypes
            default:
                usage();
                return 1;
        }
        else 
        {
            argsfound++;
        }
    }
    if (argsfound>0)
    {
        usage();
        return 1;
    }


    if (FAILED(CeRapiInit()))
    {
        error("CeRapiInit");
        return 1;
    }

    CEGUID volguid;
    if (szVolumeName && szVolumeName[0]=='{') {
        parseguid(szVolumeName, volguid);
    }
    else if (szVolumeName) {
        if (!CeMountDBVol(&volguid, const_cast<WCHAR*>(ToWString(szVolumeName).c_str()), 0x80000000|OPEN_EXISTING)) {
            ceerror("CeMountDBVol(%hs)\n", szVolumeName);
            CeRapiUninit();
            return 1;
        }
        debug("mounted %hs - %hs\n", guidstring(volguid).c_str(), szVolumeName);
    }
    else if (bDoListDatabases) {
        CREATE_SYSTEMGUID(&volguid);
    }
    else {
        CREATE_INVALIDGUID(&volguid);
    }

    // open database when name given.
    CEOID dbid=0;
    HANDLE h= 0;
    if (szDatabaseName) {
        h= CeOpenDatabaseEx(const_cast<CEGUID*>(&volguid), &dbid, const_cast<WCHAR*>(ToWString(szDatabaseName).c_str()), dwOrdering, CEDB_AUTOINCREMENT, 0);
        if (h==INVALID_HANDLE_VALUE) {
            ceerror("CeOpenDatabaseEx");
        }
    }
    // todo: restore feature where a volid + recid implies the database


    if (szFileName) 
        PrintFileInfo(szFileName);
    else if (bDoListVolumes)
        EnumVolumes(bDoListDatabases, bDoRecursiveDump);
    else if (bDoListDatabases)
        EnumDatabases(volguid, bDoRecursiveDump);
    else if (bDoDeleteRecord)
        DeleteRecord(h, dwObjectId);
    else if (bDoSet)
        SetFieldValue(h, dwObjectId, dwFieldId, szValue);
    else if (szDatabaseName) {
        if (dwFieldId)
        {
            if (szSeekType==NULL)
            {
                error("need seektype when searching database\n");
                return 1;
            }
            SearchDatabase(volguid, h, dwFieldId, szSeekType, szValue);
        }
        else {
            DumpDatabaseByHandle(h);
        }
    }
    else
        DumpObjectInfo(volguid, dwObjectId, bDoRecursiveDump);

    if (szVolumeName) {
        if (!CeUnmountDBVol(&volguid)) {
            ceerror("CeUnmountDBVol(%hs)\n", szVolumeName);
        }
    }
    CeRapiUninit();

    return 0;
}
void EnumDatabases(const CEGUID& volguid, bool bRecurse)
{
    CEGUID volid= volguid;
    HANDLE hEnumDb= CeFindFirstDatabaseEx(&volid, 0);   // 0=alltypes
    if (hEnumDb==INVALID_HANDLE_VALUE)
    {
        ceerror("CeFindFirstDatabase");
        return;
    }

    CEOID dbid;
    while (0!=(dbid=CeFindNextDatabaseEx(hEnumDb, &volid)))
    {
        DumpObjectInfo(volguid, dbid, false);
        if (bRecurse)
            DumpDatabaseByOid(volguid, dbid);
    }
    CeCloseHandle(hEnumDb);
}

std::string guidstring(const CEGUID& guid)
{
    return stringformat("{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
            guid.Data1, guid.Data2&0xffff, guid.Data2>>16,
            guid.Data3&0xff, (guid.Data3>>8)&0xff,
            (guid.Data3>>16)&0xff, (guid.Data3>>24)&0xff,
            guid.Data4&0xff, (guid.Data4>>8)&0xff,
            (guid.Data4>>16)&0xff, (guid.Data4>>24)&0xff);
}
bool parseguid(const char *str, CEGUID& guid)
{
    unsigned long fields[11];
    sscanf(str, "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
fields+0, fields+1, fields+2, fields+3, fields+4, fields+5, fields+6, fields+7, fields+8, fields+9, fields+10);

    guid.Data1= fields[0];
    guid.Data2= fields[1]|(fields[2]<<16);
    guid.Data3= fields[3]|(fields[4]<<8)|(fields[5]<<16)|(fields[6]<<24);
    guid.Data4= fields[7]|(fields[8]<<8)|(fields[9]<<16)|(fields[10]<<24);
    return true;
}

void EnumVolumes(bool bDoListDatabases, bool bDoRecursiveDump)
{
    CEGUID dbvol;
    CREATE_INVALIDGUID(&dbvol);

    int bufferSize= 1024;
    WCHAR *volumeName= (WCHAR*)malloc(bufferSize*sizeof(WCHAR));
    while(true)
    {
        if (!CeEnumDBVolumes(&dbvol, volumeName, bufferSize))
        {
            int err= CeGetLastError();
            if (err==ERROR_NO_MORE_ITEMS)
                break;
            else if (err==ERROR_INSUFFICIENT_BUFFER)
            {
                bufferSize *= 2;
                volumeName= (WCHAR*)realloc(volumeName, bufferSize*sizeof(WCHAR));
            }
            else
            {
                error(err, "CeEnumDbVolumes");
                break;
            }
        }
        else {
            printf("volume %s %ls\n", guidstring(dbvol).c_str(), volumeName);
            if (bDoListDatabases)
                EnumDatabases(dbvol, bDoRecursiveDump);
        }
    }
    free(volumeName);
}

CEOID FindDatabaseForRecord(const CEGUID& volguid, CEOID recid)
{
    CEOIDINFO info; memset(&info, 0, sizeof(info));

    if (!CeOidGetInfoEx(const_cast<CEGUID*>(&volguid), recid, &info))
    {
        ceerror("finddb-CeOidGetInfoEx(%08lx)", recid);
        return 0;
    }
    if (info.wObjType==OBJTYPE_DATABASE)
        return recid;

    if (info.wObjType!=OBJTYPE_RECORD)
    {
        printf("ERROR - expected record object id\n");
        return 0;
    }

    return info.infRecord.oidParent;
}

int StrAndBlobSizes(CEPROPVAL *props, WORD nProps);
void DumpProperty(CEPROPVAL *property);
void DumpRecord(CEPROPVAL *props, WORD nProps);
void DumpDBRecord(const CEGUID& volguid, CEOID recid)
{
    CEOID dbid= FindDatabaseForRecord(volguid, recid);
    if (dbid==recid)
        recid=0;

    //debug("dumprec: open(%hs, %08lx)\n", guidstring(volguid).c_str(), dbid);
    HANDLE h= CeOpenDatabaseEx(const_cast<CEGUID*>(&volguid), &dbid, NULL, 0, 0, 0);
    if (h==INVALID_HANDLE_VALUE)
    {
        ceerror("CeOpenDatabaseEx");
        return;
    }
    DWORD dwIndex;
    if (0==CeSeekDatabase(h, CEDB_SEEK_CEOID, recid, &dwIndex))
    {
        ceerror("CeSeekDatabase");
        CeCloseHandle(h);
        return;
    }

    CEPROPVAL *props=NULL;
    DWORD bufsize=0;
    WORD nProps;
    if (!CeReadRecordProps(h, CEDB_ALLOWREALLOC, &nProps, NULL, (BYTE**)&props, &bufsize))
    {
        ceerror("CeReadRecordProps");
        CeCloseHandle(h);
        return;
    }
    DumpRecord(props, nProps);
    LocalFree(props);
    CeCloseHandle(h);
}

void DumpDatabaseByName(const CEGUID& volguid, char *name)
{
    CEOID dbid=0;
    //debug("dumpdb: open(%hs, '%ls')\n", guidstring(volguid).c_str(), wname);
    HANDLE h= CeOpenDatabaseEx(const_cast<CEGUID*>(&volguid), &dbid, const_cast<WCHAR*>(ToWString(name).c_str()), 0, CEDB_AUTOINCREMENT, 0);
    if (h==INVALID_HANDLE_VALUE)
    {
        ceerror("CeOpenDatabaseEx");
        return;
    }
    DumpDatabaseByHandle(h);
    CeCloseHandle(h);
}

BOOL DumpDatabaseByOid(const CEGUID& volguid, CEOID oid)
{
    //debug("dumpdb: open(%hs, %08lx)\n", guidstring(volguid).c_str(), oid);
    HANDLE h= CeOpenDatabaseEx(const_cast<CEGUID*>(&volguid), &oid, NULL, 0, CEDB_AUTOINCREMENT, 0);
    if (h==INVALID_HANDLE_VALUE)
    {
        ceerror("CeOpenDatabaseEx");
        return FALSE;
    }
    DumpDatabaseByHandle(h);
    CeCloseHandle(h);

    return TRUE;
}

void DumpDatabaseByHandle(HANDLE h)
{
    CEPROPVAL *props=NULL;
    DWORD bufsize=0;

    CEOID rid;
    WORD nProps;

    while (0!=(rid= CeReadRecordProps(h, CEDB_ALLOWREALLOC, &nProps, NULL, (BYTE**)&props, &bufsize)))
    {
        printf("%08lx (%5d %2d  %5d)\n", rid, bufsize, nProps,
            bufsize-nProps*sizeof(CEPROPVAL)-StrAndBlobSizes(props, nProps));
        DumpRecord(props, nProps);
        //printf("    "); hexdump((BYTE*)&props[nProps], bufsize-nProps*sizeof(CEPROPVAL));
        printf("\n");
    }
    LocalFree(props);

}
int StrAndBlobSizes(CEPROPVAL *props, WORD nProps)
{
    int totalsize=0;

    for (int i=0 ; i<nProps ; i++) {
        switch(props[i].propid&0xffff)
        {
        case CEVT_LPWSTR:
            totalsize += (wcslen(props[i].val.lpwstr)+1)*2;
            break;
        case CEVT_BLOB:
            totalsize += props[i].val.blob.dwCount;
            break;
        }
    }
    return totalsize;
}

void DumpRecord(CEPROPVAL *props, WORD nProps)
{
    for (int i=0 ; i<nProps ; i++) {
        DumpProperty(&props[i]);
    }
}
void DumpProperty(CEPROPVAL *p)
{
    printf("        %04x T%02x L%04x F%04x ", p->propid>>16, p->propid&0xffff, p->wLenData, p->wFlags);
    switch(p->propid&0xffff)
    {
    case CEVT_I2:       printf("I2  %d", p->val.iVal); break;
    case CEVT_UI2:      printf("UI2 %u", p->val.uiVal); break;
    case CEVT_I4:       printf("I4  %ld", p->val.lVal); break;
    case CEVT_UI4:      printf("UI4 %lu", p->val.ulVal); break;
    case CEVT_FILETIME:
        SYSTEMTIME systime;
        FileTimeToSystemTime(&p->val.filetime, &systime);
        printf("FT  %04d-%02d-%02d %02d:%02d:%02d.%03d",
                systime.wYear, systime.wMonth, systime.wDay,
                systime.wHour, systime.wMinute, systime.wSecond,systime.wMilliseconds);
        break;
    case CEVT_LPWSTR:
        printf("STR [%08lx]", p->val.lpwstr);
        if (!IsBadReadPtr(p->val.lpwstr, 1))
            printf("(%2d) '%ls'", wcslen(p->val.lpwstr), p->val.lpwstr);
        else
            printf("  ..... invalid ptr");
        break;
    case CEVT_BLOB:
        printf("BIN [%08lx] (%2d): ", p->val.blob.lpb, p->val.blob.dwCount);
        if (!IsBadReadPtr(p->val.blob.lpb, p->val.blob.dwCount))
            debug("%hs\n", hexdump(p->val.blob.lpb, p->val.blob.dwCount).c_str());
        else
            printf("  ..... invalid ptr");
        break;
    case CEVT_BOOL:     printf("BOO "); printf("%s", p->val.boolVal ? "TRUE" : "FALSE"); break;
    case CEVT_R8:       printf("DBL "); printf("%lg", p->val.dblVal); break;
    default:
        printf("??? %hs\n", hexdump((BYTE*)&p->val, sizeof(p->val)).c_str()); break;
    }
    printf("\n");
}


void DumpFileInfo(CEOID oid, CEFILEINFO *info);
void DumpDirectoryInfo(CEOID oid, CEDIRINFO *info);
void DumpDatabaseInfo(CEOID oid, CEDBASEINFO *info);
void DumpRecordInfo(CEOID oid, CERECORDINFO *info);

BOOL isOid(const CEGUID& volguid, CEOID oid)
{
    CEOIDINFO info;
    return CeOidGetInfoEx(const_cast<CEGUID*>(&volguid), oid, &info);
}

void DumpObjectInfo(const CEGUID& volguid, CEOID oid, bool bRecurse)
{
    CEOIDINFO info; memset(&info, 0, sizeof(info));
    if (!CeOidGetInfoEx(const_cast<CEGUID*>(&volguid), oid, &info))
    {
        ceerror("doi-CeOidGetInfoEx(%s, %08lx)", guidstring(volguid).c_str(), oid);
        return;
    }
    switch(info.wObjType)
    {
        case OBJTYPE_INVALID:
            printf("oid%08lx is not a valid object\n", oid);
            break;
        case OBJTYPE_FILE:
            DumpFileInfo(oid, &info.infFile);
            break;
        case OBJTYPE_DIRECTORY:
            DumpDirectoryInfo(oid, &info.infDirectory);
            break;
        case OBJTYPE_DATABASE:
            DumpDatabaseInfo(oid, &info.infDatabase);
            if (bRecurse) {
                DumpDatabaseByOid(volguid, oid);
            }
            break;
        case OBJTYPE_RECORD:
            DumpRecordInfo(oid, &info.infRecord);
            if (bRecurse) {
                DumpDBRecord(volguid, oid);
            }
            break;
        default:
            printf("oid%08lx: unknown obj type %d\n", info.wObjType);
    }
}


void DumpFileInfo(CEOID oid, CEFILEINFO *info)
{
    printf("oid%08lx: file A%08lx P%08lx %7d ... '%ls'\n",
            oid, info->dwAttributes, info->oidParent, info->dwLength,
            /* info->ftLastChanged */
            info->szFileName);
}

void DumpDirectoryInfo(CEOID oid, CEDIRINFO *info)
{
    printf("oid%08lx: dir A%08lx P%08lx '%ls'\n",
            oid, info->dwAttributes, info->oidParent, info->szDirName);
}

void DumpDatabaseInfo(CEOID oid, CEDBASEINFO *info)
{
    printf("oid%08lx: dbase F%08lx T%08lx %4d %6d ... '%ls'\n",
            oid, info->dwFlags, info->dwDbaseType,
            info->wNumRecords, info->dwSize,
            /* ftLastModified, wNumSortOrder, rgSortSpecs */
            info->szDbaseName);
    if (info->wNumSortOrder)
    {
        printf("   ORDERING:");
        for (int i=0 ; i<info->wNumSortOrder ; i++)
        {
            printf(" %08lx:%08lx", info->rgSortSpecs[i].propid, info->rgSortSpecs[i].dwFlags);
        }
        printf("\n");
    }
}
void DumpRecordInfo(CEOID oid, CERECORDINFO *info)
{
    printf("oid%08lx: rec P%08lx\n", oid, info->oidParent);
}
void ValueToWString(WCHAR **pwstr, char *str)
{
    std::wstring wstr= ToWString(str);
    *pwstr= (WCHAR*)malloc((wstr.size()+1)*sizeof(WCHAR));
    memcpy(*pwstr, wstr.c_str(), (wstr.size()+1)*sizeof(WCHAR));
}

DWORD MakePropVal(CEPROPVAL *pv, DWORD dwFieldId, char *szValue)
{
    pv->propid= dwFieldId;
    pv->wFlags= 0;
    pv->wLenData= 0;

    switch(dwFieldId&0xffff)
    {
        case CEVT_I2: pv->val.iVal= (short)strtol(szValue, 0, 0); break;
        case CEVT_UI2: pv->val.uiVal= (unsigned short)strtoul(szValue, 0, 0); break;
        case CEVT_I4:  pv->val.lVal= strtol(szValue, 0, 0); break;
        case CEVT_UI4: pv->val.ulVal= strtoul(szValue, 0, 0); break;
        case CEVT_BOOL: pv->val.boolVal= strtoul(szValue, 0, 0); break;
        //case CEVT_FILETIME: ValueToFiletime(&pv->val.filetime, szValue); break;
        case CEVT_LPWSTR:   ValueToWString(&pv->val.lpwstr, szValue); break;
        //case CEVT_BLOB: ValueToBlob(&pv->val.blob, szValue); break;
        case CEVT_R8:   pv->val.dblVal= strtod(szValue, 0); break;
        default:
            return 0;
    }
    DumpProperty(pv);
    return (DWORD)pv;
}

void SearchDatabase(const CEGUID& volguid, HANDLE h, DWORD dwFieldId, char *seektype, char *szValue)
{
    DWORD dwValue;
    DWORD dwSeekType;
    CEPROPVAL pv;
    bool usePv= false;

    if (strcmp(seektype, "<")==0) {
        usePv= true;
        dwSeekType= CEDB_SEEK_VALUESMALLER;
    }
    else if (strcmp(seektype, "=")==0) {
        usePv= true;
        dwSeekType=CEDB_SEEK_VALUEFIRSTEQUAL;
    }
    else if (strcmp(seektype, "=+")==0) {
        usePv= true;
        dwSeekType=CEDB_SEEK_VALUENEXTEQUAL;
    }
    else if (strcmp(seektype, ">")==0) {
        usePv= true;
        dwSeekType=CEDB_SEEK_VALUEGREATER;
    }
    else if (strcmp(seektype, "++")==0) {
        dwSeekType=CEDB_SEEK_CURRENT;
    }
    else if (strcmp(seektype, "--")==0) {
        dwSeekType=CEDB_SEEK_CURRENT;
    }
    else if (strcmp(seektype, "[")==0) {
        dwSeekType=CEDB_SEEK_BEGINNING;
    }
    else if (strcmp(seektype, "]")==0) {
        dwSeekType=CEDB_SEEK_END;
    }
    else {
        debug("invalid seektype: %hs\n", seektype);
        return;
    }
    if (usePv)
    {
        dwValue= MakePropVal(&pv, dwFieldId, szValue);
        if (dwValue==0)
        {
            debug("invalid field type\n");
            return;
        }
    }
    else {
        dwValue= strtoul(szValue, 0, 0);
    }

    DWORD dwIndex;
    CEOID oid= CeSeekDatabase(h, dwSeekType, dwValue, &dwIndex);
    debug("CeSeekDatabase(%08lx, %08lx, %08lx) = %08lx   i=%08lx\n", h, dwSeekType, dwValue, oid, dwIndex);

    if (oid==0)
    {
        ceerror("CeSeekDatabase");
        CeCloseHandle(h);
        return;
    }
    printf("found match:\n");
    DumpObjectInfo(volguid, oid, true);
}

bool DeleteRecord(HANDLE h, CEOID dwObjectId)
{
    if (!CeDeleteRecord(h, dwObjectId)) {
        ceerror("CeDeleteRecord");
        return false;
    }
    return true;
}
void SetFieldValue(HANDLE h, CEOID dwObjectId, DWORD dwFieldId, char *szValue)
{
    CEPROPVAL pv;

    if (!MakePropVal(&pv, dwFieldId, szValue))
    {
        debug("error making propvalue\n");
        return;
    }

    CEOID oid= CeWriteRecordProps(h, dwObjectId, 1, &pv);
    if (oid==0)
        ceerror("CeWriteRecordProps");

    debug("set field %08lx in record %08lx to %s\n", dwFieldId, oid, szValue);
}

void PrintFileInfo(const std::string& szFileName)
{
    CE_FIND_DATA wfd;

    HANDLE hFind= CeFindFirstFile(ToWString(szFileName).c_str(), &wfd);

    if (hFind==NULL || hFind==INVALID_HANDLE_VALUE)
    {
        debug("could not find %s\n", szFileName.c_str());
        return;
    }
    CloseHandle(hFind);
    debug("oid=%08lx\n", wfd.dwOID);
}
