#include <windows.h>
#include "stringutils.h"
#include "debug.h"

#include <string>

struct propinfo {
    DWORD dwLevel;
    DWORD dwIndex;
};

std::string stringofname(LPSTR lpszString)
{
    if (!IsBadReadPtr(lpszString, 4))
        return stringformat("'%s'", lpszString);
    else
        return stringformat("%08lx", lpszString);
}
bool isasciistring(BYTE *p)
{
    while (1)
    {
        if (IsBadReadPtr(p,1))
            return false;
        if (p[0]==0)
            return true;
        if (!isprint(p[0]))
            return false;
        p+=1;
    }
}
bool isunicodestring(BYTE *p)
{
    while (1)
    {
        if (IsBadReadPtr(p,2))
            return false;
        if (p[1])
            return false;

        if (p[0]==0)
            return true;
        if (!isprint(p[0]))
            return false;
        p+=2;
    }
}
DWORD FindDataLen(BYTE *p, DWORD dwMax)
{
    for (DWORD dwI= 0 ; dwI < dwMax ; dwI++)
    {
        if (IsBadReadPtr(p+dwI, 1))
            return dwI;
    }
    return dwMax;
}
std::string stringofdata(HANDLE hData)
{
    if (!IsBadReadPtr(hData, 4)) {
        if (isunicodestring((BYTE*)hData))
            return stringformat("L'%ls'", hData);
        else if (isasciistring((BYTE*)hData))
            return stringformat("'%s'", hData);
        else {
            BYTE *p= (BYTE*)hData;
            DWORD dwLen= FindDataLen(p, 16);
            return stringformat("%08lx: ", hData) + ascdump(ByteVector(p, p+dwLen), "\r\n\t\"'");
        }
    }
    else
        return stringformat("%08lx", hData);
}

BOOL CALLBACK enumprop(HWND hWnd, LPSTR lpszString, HANDLE hData, ULONG_PTR dwData)
{
    struct propinfo *pi= (struct propinfo *)dwData;

    if (pi->dwIndex==0 && pi->dwLevel>0)
        debug("%*sproperties of wnd%08lx {\n", (pi->dwLevel-1)*3, "", hWnd);
    debug("%*sproperty%02d %s=%s\n", pi->dwLevel*3, "", pi->dwIndex, stringofname(lpszString).c_str(), stringofdata(hData).c_str());

    pi->dwIndex++;
    return TRUE;
}

//----------------------------------------------------------------

struct childwininfo {
    DWORD dwIndex;
    DWORD dwLevel;
    HWND hParentWnd;
};
std::string stringofwtext(HWND hWnd)
{
    TCHAR text[1024];
    if (GetWindowText(hWnd, text, 1024))
        return stringformat("'%s'", text);
    else
        return "-";
}
// todo: use these functions.
// get instance id from somewhere.
//
// use psapi, to get process information
std::string stringofwclass(HWND hWnd)
{
    DWORD tid, pid;
    tid= GetWindowThreadProcessId(hWnd, &pid);

    debug("hw=%08lx: pid=%08lx tid=%08lx\n", hWnd, tid, pid);

    ByteVector bv_wi;  bv_wi.resize(sizeof(WINDOWINFO));
    WINDOWINFO *wi= (WINDOWINFO*)vectorptr(bv_wi);
    wi->cbSize= bv_wi.size();
    if (!GetWindowInfo(hWnd, wi)) {
        error("GetWindowInfo(wnd%08lx)", hWnd);
        return "-nowindowinfo-";
    }
    debug("wi: %s\n", hexdump(bv_wi, 4).c_str());

    ByteVector bv_wc;  bv_wc.resize(sizeof(WNDCLASSEX));
    WNDCLASSEX *wc= (WNDCLASSEX *)vectorptr(bv_wc);
    wc->cbSize= bv_wc.size();
    if (!GetClassInfoEx(NULL, (LPCSTR)wi->atomWindowType, wc)) {
        error("GetClassInfoEx(%04x)", wi->atomWindowType);
        return "-noclassinfo-";
    }
    debug("wc: %s\n", hexdump(bv_wc, 4).c_str());

    if (wc->cbWndExtra) {
        bv_wi.resize(sizeof(WINDOWINFO)+wc->cbWndExtra);
        wi= (WINDOWINFO*)vectorptr(bv_wi);
        wi->cbSize= bv_wi.size();
        if (!GetWindowInfo(hWnd, wi)) {
            error("GetWindowInfo#2(wnd%08lx)", hWnd);
            return "-nowindowinfo2-";
        }
        debug("wnd extra=%04x %s\n", wc->cbWndExtra, hexdump((BYTE*)(wi+1), wc->cbWndExtra).c_str());
    }

    if (wc->cbClsExtra) {
        bv_wc.resize(sizeof(WNDCLASSEX)+wc->cbClsExtra);
        wc= (WNDCLASSEX *)vectorptr(bv_wc);
        wc->cbSize= bv_wc.size();
        if (!GetClassInfoEx(NULL, (LPCSTR)wi->atomWindowType, wc)) {
            error("GetClassInfoEx#2(%04x)", wi->atomWindowType);
            return "-noclassinfo2-";
        }
        debug("cls extra=%04x %s\n", wc->cbClsExtra, hexdump((BYTE*)(wc+1), wc->cbClsExtra).c_str());
    }


    if ( (((DWORD)wc->lpszClassName)&~0xffff)==0 ) {
        TCHAR name[1024];
        if (!GlobalGetAtomName((ATOM)wc->lpszClassName, name, 1024))
            return stringformat("%04x: %04x", wi->atomWindowType, wc->lpszClassName);
        else
            return stringformat("%04x: %04x: '%s'", wi->atomWindowType, wc->lpszClassName, name);
    }
    else {
        return stringformat("%04x: '%s'", wi->atomWindowType, wc->lpszClassName);
    }
}
std::string stringofwmodule(HWND hWnd)
{
    TCHAR text[1024];
    if (!GetWindowModuleFileName(hWnd, text, 1024))
        return "-nomoduleinfo-";
    else
        return stringformat("'%s'", text);
}
BOOL CALLBACK EnumChildWinProc(HWND hWnd, LPARAM lParam)
{
    struct childwininfo *wi= (struct childwininfo *)lParam;
    if (wi->dwIndex==0 && wi->dwLevel>0)
        debug("%*schildren of wnd%08lx {\n", (wi->dwLevel-1)*3, "", wi->hParentWnd);
    DWORD pid;
    DWORD tid= GetWindowThreadProcessId(hWnd, &pid);
    debug("%*swin%02d  wnd%08lx : tid=%08lx pid=%08lx %s\n", wi->dwLevel*3, "", wi->dwIndex, hWnd, tid, pid, stringofwtext(hWnd).c_str());
    debug("%*swin%02d  wnd%08lx : class %s\n", wi->dwLevel*3, "", wi->dwIndex, hWnd, stringofwclass(hWnd).c_str());
    debug("%*swin%02d  wnd%08lx : module %s\n", wi->dwLevel*3, "", wi->dwIndex, hWnd, stringofwmodule(hWnd).c_str());

    struct propinfo pi;
    pi.dwIndex= 0;
    pi.dwLevel= wi->dwLevel+1;
    if (EnumPropsEx(hWnd, &enumprop, (LPARAM)&pi)==-1)
        debug("%*s- no properties found\n", wi->dwLevel*3, "", hWnd);
    if (pi.dwIndex)
        debug("%*s}\n", wi->dwLevel*3, "");

    struct childwininfo cwi;
    cwi.dwIndex= 0;
    cwi.dwLevel= wi->dwLevel+1;
    cwi.hParentWnd= hWnd;
    if (!EnumChildWindows(hWnd, &EnumChildWinProc, (LPARAM)&cwi))
        error("%*sEnumChildWindows(%08lx)\n", wi->dwLevel*3, "", hWnd);
    if (cwi.dwIndex)
        debug("%*s}\n", wi->dwLevel*3, "");

    wi->dwIndex++;
    return TRUE;
}

//----------------------------------------------------------------

int main(int , char **)
{
    DebugStdOut();

    struct childwininfo cwi;
    cwi.dwIndex= 0;
    cwi.dwLevel= 0;
    cwi.hParentWnd= 0;
    if (!EnumWindows(&EnumChildWinProc, (LPARAM)&cwi))
        error("EnumWindows");

    debug("\n\n------------------------local atoms:\n\n");

    char name[1024];
    for (int atom= 0 ; atom<65536 ; atom++) {
        if (GetAtomName(atom, name, 1024)) {
            if (atom<1 || atom>=0xc000 || stringformat("#%d", atom)!=name)
                debug("atom %04x : %s\n", atom, name);
        }
    }
    debug("\n\n------------------------global atoms:\n\n");
    for (int atom= 0 ; atom<65536 ; atom++) {
        if (GlobalGetAtomName(atom, name, 1024))
            if (atom<1 || atom>=0xc000 || stringformat("#%d", atom)!=name)
                debug("globalatom %04x : %s\n", atom, name);
    }
}
