/*
 * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include "jaccesswalker.h"
#include "AccessInfo.h"

HWND ourHwnd;
HWND topLevelWindow;
int depth = -1;
FILE *logfile;
HMENU popupMenu;

char theJaccesswalkerClassName[] = "JaccesswalkerWin";
char theAccessInfoClassName[] = "AccessInfoWin";

HWND theJaccesswalkerWindow;
HWND theTreeControlWindow;
HINSTANCE theInstance;
Jaccesswalker *theJaccesswalker;
AccessibleNode *theSelectedNode;
AccessibleNode *thePopupNode;
AccessibleContext theSelectedAccessibleContext;
HWND hwndTV;    // handle of tree-view control

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{

    if (logfile == null) {
        logfile = fopen(JACCESSWALKER_LOG, "w"); // overwrite existing log file
        logString(logfile, "Starting jaccesswalker.exe %s\n", getTimeAndDate());
    }

    theInstance = hInstance;

    // start Jaccesswalker
    theJaccesswalker = new Jaccesswalker(nCmdShow);

    return 0;
}

Jaccesswalker::Jaccesswalker(int nCmdShow) {

    HWND hwnd;
    static char szAppName[] = "jaccesswalker";
    static char szMenuName[] = "JACCESSWALKERMENU";
    MSG msg;
    WNDCLASSEX wc;

    // jaccesswalker window
    wc.cbSize = sizeof(wc);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WinProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = theInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDI_APPLICATION);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = szMenuName;
    wc.lpszClassName = szAppName;
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClassEx(&wc);

    // AccessInfo Window
    wc.cbSize = sizeof(WNDCLASSEX);

    wc.hInstance = theInstance;
    wc.lpszClassName = theAccessInfoClassName;
    wc.lpfnWndProc = (WNDPROC)AccessInfoWindowProc;
    wc.style = 0;

    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);

    wc.lpszMenuName = "";
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;

    wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);

    RegisterClassEx(&wc);

    // create the jaccesswalker window
    hwnd = CreateWindow(szAppName,
                        szAppName,
                        WS_OVERLAPPEDWINDOW,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        NULL,
                        NULL,
                        theInstance,
                        NULL);

    ourHwnd = hwnd;

    /* Initialize the common controls. */
    INITCOMMONCONTROLSEX cc;
    cc.dwSize = sizeof(INITCOMMONCONTROLSEX);
    cc.dwICC = ICC_TREEVIEW_CLASSES;
    InitCommonControlsEx(&cc);

    ShowWindow(hwnd, nCmdShow);

    UpdateWindow(hwnd);

    BOOL result = initializeAccessBridge();
    if (result != FALSE) {
        while (GetMessage(&msg, NULL, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        shutdownAccessBridge();
    }
}

/*
 * the jaccesswalker window proc
 */
LRESULT CALLBACK WinProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {

    int command;
    short width, height;

    switch(iMsg) {

    case WM_CREATE:
        // create the accessibility tree view
        theTreeControlWindow = CreateATreeView(hwnd);

        // load the popup menu
        popupMenu = LoadMenu(theInstance, "PopupMenu");
        popupMenu = GetSubMenu(popupMenu, 0);
        break;

    case WM_CLOSE:
        EndDialog(hwnd, TRUE);
        PostQuitMessage (0);
        break;

    case WM_SIZE:
        width = LOWORD(lParam);
        height = HIWORD(lParam);
        SetWindowPos(theTreeControlWindow, NULL, 0, 0, width, height, 0);
        return FALSE;  // let windows finish handling this

    case WM_COMMAND:
        command = LOWORD(wParam);
        switch(command) {

        case cExitMenuItem:
            EndDialog(hwnd, TRUE);
            PostQuitMessage (0);
            break;

        case cRefreshTreeItem:
            // update the accessibility tree
            theJaccesswalker->buildAccessibilityTree();
            break;

        case cAPIMenuItem:
            // open a new window with the Accessibility API in it for the
            // selected element in the tree
            if (theSelectedNode != (AccessibleNode *) 0) {
                theSelectedNode->displayAPIWindow();
            }
            break;

        case cAPIPopupItem:
            // open a new window with the Accessibility API in it for the
            // element in the tree adjacent to the popup menu
            if (thePopupNode != (AccessibleNode *) 0) {
                thePopupNode->displayAPIWindow();
            }
            break;

        }
        break;

    case WM_NOTIFY:  // receive tree messages

        NMTREEVIEW *nmptr = (LPNMTREEVIEW) lParam;
        switch (nmptr->hdr.code) {

        case TVN_SELCHANGED:
            // get the selected tree node
            theSelectedNode = (AccessibleNode *) nmptr->itemNew.lParam;
            break;

        case NM_RCLICK:

            // display a popup menu over the tree node
            POINT p;
            GetCursorPos(&p);
            TrackPopupMenu(popupMenu, 0, p.x, p.y, 0, hwnd, NULL);

            // get the tree node under the popup menu
            TVHITTESTINFO hitinfo;
            ScreenToClient(theTreeControlWindow, &p);
            hitinfo.pt = p;
            HTREEITEM node = TreeView_HitTest(theTreeControlWindow, &hitinfo);

            if (node != null) {
                TVITEMEX tvItem;
                tvItem.hItem = node;
                if (TreeView_GetItem(hwndTV, &tvItem) == TRUE) {
                    thePopupNode = (AccessibleNode *)tvItem.lParam;
                }
            }
            break;
        }
    }
    return DefWindowProc(hwnd, iMsg, wParam, lParam);
}

/*
 * Accessibility information window proc
 */
LRESULT CALLBACK AccessInfoWindowProc(HWND hWnd, UINT message,
                                    UINT wParam, LONG lParam) {
    short width, height;
    HWND dlgItem;

    switch (message) {
    case WM_CREATE:
        RECT rcClient;    // dimensions of client area
        HWND hwndEdit;    // handle of tree-view control

        // Get the dimensions of the parent window's client area,
        // and create the edit control.
        GetClientRect(hWnd, &rcClient);
        hwndEdit = CreateWindow("Edit",
                                "",
                                WS_VISIBLE | WS_TABSTOP | WS_CHILD |
                                ES_MULTILINE | ES_AUTOVSCROLL |
                                ES_READONLY | WS_VSCROLL,
                                0, 0, rcClient.right, rcClient.bottom,
                                hWnd,
                                (HMENU) cAccessInfoText,
                                theInstance,
                                NULL);
        break;

    case WM_CLOSE:
        DestroyWindow(hWnd);
        break;

    case WM_SIZE:
        width = LOWORD(lParam);
        height = HIWORD(lParam);
        dlgItem = GetDlgItem(hWnd, cAccessInfoText);
        SetWindowPos(dlgItem, NULL, 0, 0, width, height, 0);
        return FALSE;  // let windows finish handling this
        break;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }

    return 0;
}

/**
 * Build a tree (and the treeview control) of all accessible Java components
 *
 */
void Jaccesswalker::buildAccessibilityTree() {
    TreeView_DeleteAllItems (theTreeControlWindow);
    // have MS-Windows call EnumWndProc() with all of the top-level windows
    EnumWindows((WNDENUMPROC) EnumWndProc, NULL);
}

/**
 * Create (and display) the accessible component nodes of a parent AccessibleContext
 *
 */
BOOL CALLBACK EnumWndProc(HWND hwnd, LPARAM lParam) {
    if (IsJavaWindow(hwnd)) {
        long vmID;
        AccessibleContext ac;
        if (GetAccessibleContextFromHWND(hwnd, &vmID, &ac) == TRUE) {
            theJaccesswalker->addComponentNodes(vmID, ac, (AccessibleNode *) NULL,
                                         hwnd, TVI_ROOT, theTreeControlWindow);
        }
        topLevelWindow = hwnd;
    } else {
        char szClass [MAX_PATH] = {0};
        ::GetClassNameA(hwnd, szClass, sizeof(szClass) - 1);
        if ( ( 0 == ::strcmp(szClass, "IEFrame") )
             || ( 0 == ::strcmp(szClass, "MozillaUIWindowClass") ) ) {
            EnumChildWindows(hwnd, (WNDENUMPROC) EnumChildProc, NULL);
        }
    }
    return TRUE;
}

/*
Detects whether or not the specified Java window is one from which no useable
information can be obtained.

This function tests for various scenarios I have seen in Java applets where the
Java applet had no meaningful accessible information.  It does not detect all
scenarios, just the most common ones.
*/
BOOL IsInaccessibleJavaWindow(const HWND hwnd)
{
    BOOL ret_val ( FALSE );
    {
        BOOL bT ( FALSE );
        long vmIdWindow ( 0 );
        AccessibleContext acWindow ( 0 );
        bT = GetAccessibleContextFromHWND(hwnd, &vmIdWindow, &acWindow);
        if ( ( bT ) && ( 0 != vmIdWindow ) && ( 0 != acWindow ) ) {
            AccessibleContextInfo infoWindow = {0};
            bT = GetAccessibleContextInfo(vmIdWindow, acWindow, &infoWindow);
            if ( ( bT )
                 && ( 0 == infoWindow.name [0] )
                 && ( 0 == infoWindow.description [0] )
                 && ( 0 == ::wcscmp(infoWindow.role_en_US, L"frame") ) ) {
                if ( 0 == infoWindow.childrenCount ) {
                    ret_val = TRUE;
                } else if ( 1 == infoWindow.childrenCount ) {
                    AccessibleContext acChild ( 0 );
                    acChild =
                        GetAccessibleChildFromContext(vmIdWindow, acWindow, 0);
                    if ( NULL != acChild ) {
                        AccessibleContextInfo infoChild = {0};
                        bT = GetAccessibleContextInfo( vmIdWindow, acChild,
                                                       &infoChild );
                        if ( ( bT )
                             && ( 0 == infoChild.name [0] )
                             && ( 0 == infoChild.description [0] )
                             && ( 0 == ::wcscmp(infoChild.role_en_US, L"panel") )
                             && ( 1 == infoChild.childrenCount ) ) {
                            AccessibleContext acChild1 ( 0 );
                            acChild1 = GetAccessibleChildFromContext( vmIdWindow,
                                                                      acChild, 0);
                            if ( NULL != acChild1 ) {
                                AccessibleContextInfo infoChild1 = {0};
                                bT = GetAccessibleContextInfo( vmIdWindow,
                                                               acChild1, &infoChild1 );
                                if ( ( bT )
                                     && ( 0 == infoChild1.name [0] )
                                     && ( 0 == infoChild1.description [0] )
                                     && ( 0 == ::wcscmp(infoChild1.role_en_US, L"frame") )
                                     && ( 0 == infoChild1.childrenCount ) ) {
                                    ret_val = TRUE;
                                } else if ( ( bT )
                                            && ( 0 == infoChild1.name [0] )
                                            && ( 0 == infoChild1.description [0] )
                                            && ( 0 == ::wcscmp( infoChild1.role_en_US,
                                                                L"panel") )
                                            && ( 1 == infoChild1.childrenCount ) ) {
                                    AccessibleContext acChild2 ( 0 );
                                    acChild2 = GetAccessibleChildFromContext(
                                                    vmIdWindow, acChild1, 0 );
                                    if ( NULL != acChild2 ) {
                                        AccessibleContextInfo infoChild2 = {0};
                                        bT = GetAccessibleContextInfo(
                                                vmIdWindow, acChild2, &infoChild2 );
                                        if ( ( bT )
                                             && ( 0 == infoChild2.name [0] )
                                             && ( 0 == infoChild2.description [0] )
                                             && ( 0 == ::wcscmp( infoChild2.role_en_US,
                                                                 L"frame") )
                                             && ( 0 == infoChild2.childrenCount ) ) {
                                            ret_val = TRUE;
                                        }
                                    }
                                }
                            }
                        } else if ( ( bT )
                                    && ( 0 == infoChild.name [0] )
                                    && ( 0 == infoChild.description [0] )
                                    && ( 0 == ::wcscmp( infoChild.role_en_US,
                                                        L"canvas") )
                                    && ( 0 == infoChild.childrenCount ) ) {
                            ret_val = TRUE;
                        }
                    }
                }
            } else if ( ( bT )
                        && ( 0 == infoWindow.name [0] )
                        && ( 0 == infoWindow.description [0] )
                        && ( 0 == ::wcscmp(infoWindow.role_en_US, L"panel") ) ) {
                if ( 1 == infoWindow.childrenCount ) {
                    AccessibleContext acChild ( 0 );
                    acChild = GetAccessibleChildFromContext( vmIdWindow,
                                                             acWindow, 0 );
                    if ( NULL != acChild ) {
                        AccessibleContextInfo infoChild = {0};
                        bT = GetAccessibleContextInfo( vmIdWindow,
                                                       acChild, &infoChild );
                        if ( ( bT )
                             && ( 0 == infoChild.name [0] )
                             && ( 0 == infoChild.description [0] )
                             && ( 0 == ::wcscmp(infoChild.role_en_US, L"frame") )
                             && ( 0 == infoChild.childrenCount ) ) {
                                ret_val = TRUE;
                        } else if ( ( bT )
                                    && ( 0 == infoChild.name [0] )
                                    && ( 0 == infoChild.description [0] )
                                    && ( 0 == ::wcscmp( infoChild.role_en_US,
                                                        L"panel") )
                                    && ( 1 == infoChild.childrenCount ) ) {
                            AccessibleContext acChild1 ( 0 );
                            acChild1 = GetAccessibleChildFromContext( vmIdWindow,
                                                                      acChild, 0);
                            if ( NULL != acChild1 ) {
                                AccessibleContextInfo infoChild1 = {0};
                                bT = GetAccessibleContextInfo( vmIdWindow,
                                                               acChild1,
                                                               &infoChild1 );
                                if ( ( bT )
                                     && ( 0 == infoChild1.name [0] )
                                     && ( 0 == infoChild1.description [0] )
                                     && ( 0 == ::wcscmp( infoChild1.role_en_US,
                                                         L"frame") )
                                     && ( 0 == infoChild1.childrenCount ) ) {
                                    ret_val = TRUE;
                                }
                            }
                        }
                    }
                }
            }
        } else if ( FALSE == bT ) {
            ret_val = TRUE;
        }
    }
    return ret_val;
}

BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam)
{
    if ( ( IsJavaWindow(hwnd) )
         && ( FALSE == IsInaccessibleJavaWindow(hwnd) ) ) {
        long vmID ( 0 );
        AccessibleContext ac ( 0 );
        if ( TRUE == GetAccessibleContextFromHWND(hwnd, &vmID, &ac) ) {
            theJaccesswalker->addComponentNodes(
                vmID, ac, (AccessibleNode *) NULL,
                hwnd, TVI_ROOT, theTreeControlWindow);
        }
        topLevelWindow = hwnd;
    } else {
        EnumChildWindows(hwnd, (WNDENUMPROC) EnumChildProc, NULL);
    }
    return TRUE;
}

// CreateATreeView - creates a tree-view control.
// Returns the handle of the new control if successful or NULL
//     otherwise.
// hwndParent - handle of the control's parent window
HWND CreateATreeView(HWND hwndParent) {
    RECT rcClient;  // dimensions of client area

    // Get the dimensions of the parent window's client area, and create
    // the tree-view control.
    GetClientRect(hwndParent, &rcClient);
    hwndTV = CreateWindow(WC_TREEVIEW,
                          "",
                          WS_VISIBLE | WS_TABSTOP | WS_CHILD |
                          TVS_HASLINES | TVS_HASBUTTONS |
                          TVS_LINESATROOT,
                          0, 0, rcClient.right, rcClient.bottom,
                          hwndParent,
                          (HMENU) cTreeControl,
                          theInstance,
                          NULL);

    return hwndTV;
}

/**
 * Create (and display) the accessible component nodes of a parent AccessibleContext
 *
 */
void Jaccesswalker::addComponentNodes(long vmID, AccessibleContext context,
                                    AccessibleNode *parent, HWND hwnd,
                                    HTREEITEM treeNodeParent, HWND treeWnd) {

    AccessibleNode *newNode = new AccessibleNode( vmID, context, parent, hwnd,
                                                  treeNodeParent );

    AccessibleContextInfo info;
    if (GetAccessibleContextInfo(vmID, context, &info) != FALSE) {
        char s[LINE_BUFSIZE];

        wsprintf(s, "%ls", info.name);
        newNode->setAccessibleName(s);
        wsprintf(s, "%ls", info.role);
        newNode->setAccessibleRole(s);

        wsprintf(s, "%ls [%ls]", info.name, info.role);

        TVITEM tvi;
        tvi.mask = TVIF_PARAM | TVIF_TEXT;
        tvi.pszText = (char *) s; // Accessible name and role
        tvi.cchTextMax = (int)strlen(s);
        tvi.lParam = (LPARAM) newNode; // Accessibility information

        TVINSERTSTRUCT tvis;
        tvis.hParent = treeNodeParent;
        tvis.hInsertAfter = TVI_LAST;
        tvis.item = tvi;

        HTREEITEM treeNodeItem = TreeView_InsertItem(treeWnd, &tvis);

        for (int i = 0; i < info.childrenCount; i++) {
            addComponentNodes(vmID, GetAccessibleChildFromContext(vmID, context, i),
                              newNode, hwnd, treeNodeItem, treeWnd);
        }
    } else {
        char s[LINE_BUFSIZE];
        snprintf( s, sizeof(s),
            "ERROR calling GetAccessibleContextInfo; vmID = %lX, context = %p",
            vmID, (void*)context );

        TVITEM tvi;
        tvi.mask = TVIF_PARAM | TVIF_TEXT;  // text and lParam are only valid parts
        tvi.pszText = (char *) s;
        tvi.cchTextMax = (int)strlen(s);
        tvi.lParam = (LPARAM) newNode;

        TVINSERTSTRUCT tvis;
        tvis.hParent = treeNodeParent;
        tvis.hInsertAfter = TVI_LAST;  // make tree in order given
        tvis.item = tvi;

        HTREEITEM treeNodeItem = TreeView_InsertItem(treeWnd, &tvis);
    }
}

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

/**
 * Create an AccessibleNode
 *
 */
AccessibleNode::AccessibleNode(long JavaVMID, AccessibleContext context,
                               AccessibleNode *parent, HWND hwnd,
                               HTREEITEM parentTreeNodeItem) {
    vmID = JavaVMID;
    ac = context;
    parentNode = parent;
    baseHWND = hwnd;
    treeNodeParent = parentTreeNodeItem;

    // setting accessibleName and accessibleRole not done here,
    // in order to minimize calls to the AccessBridge
    // (since such a call is needed to enumerate children)
}

/**
 * Destroy an AccessibleNode
 *
 */
AccessibleNode::~AccessibleNode() {
    ReleaseJavaObject(vmID, ac);
}

/**
 * Set the accessibleName string
 *
 */
void AccessibleNode::setAccessibleName(char *name) {
    strncpy(accessibleName, name, MAX_STRING_SIZE);
}

/**
 * Set the accessibleRole string
 *
 */
void AccessibleNode::setAccessibleRole(char *role) {
    strncpy(accessibleRole, role, SHORT_STRING_SIZE);
}







/**
 * Create an API window to show off the info for this AccessibleContext
 */
BOOL AccessibleNode::displayAPIWindow() {

    HWND apiWindow = CreateWindow(theAccessInfoClassName,
                                  "Java Accessibility API view",
                                  WS_OVERLAPPEDWINDOW,
                                  CW_USEDEFAULT,
                                  CW_USEDEFAULT,
                                  600,
                                  750,
                                  HWND_DESKTOP,
                                  NULL,
                                  theInstance,
                                  (void *) NULL);

    if (!apiWindow) {
        printError("cannot create API window");
        return FALSE;
    }

    char buffer[HUGE_BUFSIZE];
    buffer[0] = '\0';
    getAccessibleInfo(vmID, ac, buffer, sizeof(buffer));
    displayAndLog(apiWindow, cAccessInfoText, logfile, buffer);

    ShowWindow(apiWindow, SW_SHOWNORMAL);
    UpdateWindow(apiWindow);

    return TRUE;
}



