//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// This source code is licensed under Microsoft Shared Source License
// Version 1.0 for Windows CE.
// For a copy of the license visit http://go.microsoft.com/fwlink/?LinkId=3223.
//
#include <windows.h>
#include <commctrl.h>
#include <aygshell.h>

// from mysip.cpp
BOOL pfnSipShowIM( DWORD dwFlag );
BOOL pfnSipGetCurrentIM( CLSID* pClsid );
BOOL pfnSipSetCurrentIM( CLSID* pClsid );
BOOL pfnSipGetInfo( SIPINFO *pSipInfo );
BOOL pfnSipSetInfo( SIPINFO *pSipInfo );
//
// Magic number to send to SIP to get past no-app-change restriction.
//
#define MAGIC_BYPASS                0x06d00000


/*------------------------------------------------------------------------------
    Send Bubble Notification Message to App
------------------------------------------------------------------------------*/
LRESULT SendBubbleNotificationMessage( HWND hwnd, NMSHN *pNMSHN, TCHAR *pszTarget )
{
    DWORD dwProcessID;
    HPROCESS hProcess;
    LRESULT lRes = 0;

    ASSERT( hwnd );
    if ( !hwnd || !IsWindow( hwnd ) )
    {
        return lRes;
    }

    GetWindowThreadProcessId(hwnd, &dwProcessID);
    hProcess = OpenProcess(0, FALSE, dwProcessID);
    if (NULL != hProcess)
    {
        DWORD dwOldPerms = SetProcPermissions((DWORD)-1);
        __try
        {
            DWORD dwStrMem = 0;
            NMSHN *pnmshn = NULL;

            if ( pszTarget )
            {
                dwStrMem = sizeof(TCHAR) * ( _tcslen( pszTarget ) + 1 );
            }
            pnmshn = (NMSHN *)LocalAllocInProcess(0, sizeof(NMSHN) + dwStrMem, hProcess);

            if (NULL != pnmshn)
            {
                if ( pszTarget )
                {
                    LPTSTR pszMapped = NULL;
                    pszMapped = (LPTSTR)(pnmshn + 1);
                    _tcscpy(pszMapped, pszTarget);
                    pnmshn->hdr.hwndFrom = pNMSHN->hdr.hwndFrom;
                    pnmshn->hdr.idFrom = pNMSHN->hdr.idFrom;
                    pnmshn->hdr.code = pNMSHN->hdr.code;
                    if ( pszMapped )
                    {
                        pnmshn->pszLink = pszMapped;
                    }
                    pnmshn->lParam = pNMSHN->lParam;
                    pnmshn->dwReturn = pNMSHN->dwReturn;
                }
                else
                {
                    *pnmshn = *pNMSHN;
                }

                lRes = SendMessage( hwnd, WM_NOTIFY, (WPARAM)pNMSHN->hdr.idFrom, (LPARAM)pnmshn );
                
                LocalFreeInProcess(pnmshn, hProcess);
                pnmshn = NULL;
            }
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            // We can't let the shell crash in this case
            lRes = 0;
        }

        SetProcPermissions(dwOldPerms);
        CloseHandle(hProcess);
    }

    return lRes;
}

/*------------------------------------------------------------------------------
    FindWindowChildOfClass - needed for SHDoneButton
------------------------------------------------------------------------------*/
BOOL FindWindowChildOfClass(HWND hwndParent, LPCTSTR szChildWindowClass, HWND *phwndChild)
{
    HWND hwndChild = NULL;

    hwndChild = GetWindow(hwndParent, GW_CHILD);
    while(hwndChild != NULL)
    {
        // Is the child window of the proper type?
        TCHAR szWindowClassName[MAX_PATH];
        if(GetClassName(hwndChild, szWindowClassName, MAX_PATH))
        {
            if(!_tcsicmp(szWindowClassName, szChildWindowClass))
            {
                // Yes; return it
                *phwndChild = hwndChild;
                return TRUE;
            }
        }

        // No; keep searching
        hwndChild = GetWindow(hwndChild, GW_HWNDNEXT);
    }

    // Didn't find it
    *phwndChild = NULL;
    return FALSE;
}

// REVIEW: STOLEN FROM AYGSHELL, TEMPORARY - used for FindMenuBar
#ifndef ARRAYSIZE
#define ARRAYSIZE(a)   (sizeof(a)/sizeof(a[0]))
#endif
/*------------------------------------------------------------------------------
    FindMenuBar - Used for SHDoneButton
------------------------------------------------------------------------------*/
HWND FindMenuBar(
    HWND hwnd       // In: Window whose menubar is to be found
)
{
    LPTSTR pszClass;
    HWND hwndSearch;
    HWND hwndMenuBar = NULL;

    pszClass = TEXT("menu_worker");

    // The menu bar is an owned window
    // Owned windows are always before their owner in the Z order
    // There may be other windows between the window and its owner (like the IME)
    // So we search for this window this way...

    hwndSearch = hwnd;

    while(NULL != (hwndSearch = GetWindow(hwndSearch, GW_HWNDPREV)))
    {
        if(GetWindow(hwndSearch, GW_OWNER) == hwnd)
        {
            TCHAR szBuff[100];
            
            if(GetClassName(hwndSearch, szBuff, ARRAYSIZE(szBuff)) &&
                (0 == lstrcmp(szBuff, pszClass)) &&
                IsWindowVisible(hwndSearch))
            {
                // we found the MenuBar!
                hwndMenuBar = hwndSearch;
                break;
            }
        }
    }

    return hwndMenuBar;
}


#define ADORNMENTS_SEPARATOR_ID 0xCCDEADCC // in cmdbar.cpp

/*------------------------------------------------------------------------------
    SHDoneButton
------------------------------------------------------------------------------*/
extern "C" BOOL SHDoneButton(HWND hwndRequester, DWORD dwState)
{
    __try
    {
        DWORD dwStyle;
        BOOL bReturnVal = TRUE;
        HWND hwndChild = NULL;
        HWND hwndCB = NULL;

        if ( !hwndRequester || !IsWindow( hwndRequester ) || ( ( dwState != SHDB_SHOW ) && ( dwState != SHDB_HIDE ) ) )
        {
            return FALSE;
        }

        // check for WS_NONAVDONEBUTTON
        // from the docs
        // "If you need to create a top-level window without the Smart Minimize button in the upper right of the Navigation
        // Bar, do not use the WS_CAPTION style, and add the WS_NONAVDONEBUTTON to the styles when calling CreateWindow"
        dwStyle = GetWindowLong( hwndRequester, GWL_STYLE );
        if ( ( dwStyle & WS_CAPTION ) != WS_CAPTION )
        {
            // changing the rules a bit for adding OK button to the caption bar
            DWORD dwExStyle = GetWindowLong( hwndRequester, GWL_EXSTYLE );
            if ( ( dwExStyle & WS_NONAVDONEBUTTON ) == WS_NONAVDONEBUTTON )
            {
                return TRUE;
            }
        }
  
        // check for menu bar
        if ( bReturnVal )
        {
            // This function will only work if the window requesting the done button owns
            // a popup command bar.
            hwndChild = FindMenuBar( hwndRequester );
            bReturnVal = ( hwndChild != NULL );
        }
        if ( bReturnVal )
        {
            bReturnVal = FindWindowChildOfClass(hwndChild, TOOLBARCLASSNAME, &hwndCB);
        }
        if ( bReturnVal )
        {
            TBBUTTON tbbutton;
            int cButtons,n;

            cButtons = SendMessage(hwndCB, TB_BUTTONCOUNT, 0, 0);

            // Got the command bar; add the appropriate adornments.
            if(dwState == SHDB_SHOW)
            {
                BOOL bFound = FALSE;
                int iCloseButtonIndex = -1;
                int iSepIndex = -1;
                // check to see if the adornments are there, but hidden
                for (n = cButtons - 1; (n >= 0 && n >= cButtons - 3) & !bFound; n--)
                {
                    if (SendMessage(hwndCB, TB_GETBUTTON, (WPARAM)n, (LPARAM)&tbbutton))
                    {
                        if ( tbbutton.idCommand == IDOK ) 
                        {
                            // show the ok button, hide the close button
                            bReturnVal = SendMessage( hwndCB, TB_HIDEBUTTON, (WPARAM)tbbutton.idCommand, (LPARAM)MAKELONG(FALSE,0) );
                            SendMessage( hwndCB, TB_HIDEBUTTON, (WPARAM)WM_CLOSE, (LPARAM)MAKELONG(TRUE,0) );
                            bFound = TRUE;
                        }
                        else if ( tbbutton.idCommand == WM_CLOSE )
                        {
                            iCloseButtonIndex = n;
                        }
                        else if ( ( tbbutton.fsStyle & TBSTYLE_SEP ) && ( tbbutton.idCommand == ADORNMENTS_SEPARATOR_ID ) )
                        {
                            iSepIndex = n;
                        }

                    }
                }

                if ( !bFound )
                {
                    // delete the close button so we can re-add it
                    if ( iCloseButtonIndex > -1 )
                    {
                        SendMessage( hwndCB, TB_DELETEBUTTON, (WPARAM)iCloseButtonIndex, (LPARAM)0 );
                    }

                    if ( iSepIndex > -1 )
                    {
                        SendMessage( hwndCB, TB_DELETEBUTTON, (WPARAM)iSepIndex, (LPARAM)0 );
                    }

                    // add the ok button
                    bReturnVal = CommandBar_AddAdornments(hwndCB, CMDBAR_OK, 0);
                    // hide the close button
                    SendMessage( hwndCB, TB_HIDEBUTTON, (WPARAM)WM_CLOSE, (LPARAM)MAKELONG(TRUE,0) );
                }
            }
            else
            {
                // hide the OK button, show the close button
                // Start from the right most button and move left a max of 3
                for (n = cButtons - 1; n >= 0 && n >= cButtons - 3; n--)
                {
                    if ( SendMessage(hwndCB, TB_GETBUTTON, (WPARAM)n, (LPARAM)&tbbutton) )
                    {
                        if ( ( tbbutton.idCommand == IDOK ) ||
                             ( ( ( tbbutton.fsStyle & TBSTYLE_SEP ) == TBSTYLE_SEP ) &&
                               ( tbbutton.idCommand != ADORNMENTS_SEPARATOR_ID ) ) )
                        {
                            bReturnVal = SendMessage( hwndCB, TB_HIDEBUTTON, (WPARAM)tbbutton.idCommand, (LPARAM)MAKELONG(TRUE,0) );
                        }
                        else if ( tbbutton.idCommand == WM_CLOSE )
                        {
                            SendMessage( hwndCB, TB_HIDEBUTTON, (WPARAM)tbbutton.idCommand, (LPARAM)MAKELONG(FALSE,0) );
                        }
                    }
                }
            }
        }

        //dwStyle = GetWindowLong( hwndRequester, GWL_STYLE );
        // didn't find a menu bar, probably a dialog
        if ( !bReturnVal && ( hwndRequester != GetDesktopWindow() ) && ( ( dwStyle & WS_CAPTION ) == WS_CAPTION ) )
        {
            // changing the rules a bit for adding OK button to the caption bar
            DWORD dwExStyle = GetWindowLong( hwndRequester, GWL_EXSTYLE );

            if ( dwState == SHDB_SHOW )
            {
                // need to add the OK button to the caption
                SetWindowLong( hwndRequester, GWL_EXSTYLE, WS_EX_CAPTIONOKBTN | dwExStyle );
                // need to remove the sysmenu
                // REVIEW: #54220, no longer removing the System menu, so that dialogs can have a close
                // button in addition to a ok button
                //SetWindowLong( hwndRequester, GWL_STYLE, ~WS_SYSMENU & dwStyle );
            }
            else
            {
                // need to remove the OK button from the caption
                SetWindowLong( hwndRequester, GWL_EXSTYLE, ~WS_EX_CAPTIONOKBTN & dwExStyle );
                // add back the sysmenu
                // REVIEW: #54220, no longer removing the System menu, so that dialogs can have a close
                // button in addition to a ok button
                //SetWindowLong( hwndRequester, GWL_STYLE, WS_SYSMENU | dwStyle );
            }

            bReturnVal = TRUE;
        }

        return bReturnVal;
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        ASSERT(FALSE);
        return FALSE;
    }
}

extern BOOL Taskbar_ChangeItemText( HWND hwndApp, LPCTSTR pszText );

extern "C" BOOL WINAPI SHSetNavBarText( HWND hwnd, LPCTSTR pszText )
{
    __try
    {
        if ( !hwnd || !IsWindow( hwnd ) )
        {
            RETAILMSG(1, (TEXT("SHELL32: SHSetNavBarText bogus hwnd %x passed in!.\r\n"),hwnd));
            return FALSE;
        }

        return Taskbar_ChangeItemText( hwnd, pszText );
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        ASSERT(FALSE);
        return FALSE;
    }
}

/* Support code for SHSipPreference */
// thread proc
HANDLE  g_hEvent = NULL;
HANDLE  g_hSipWaitThread = NULL;
BOOL    g_bSipGoingUp = FALSE;
#define SIP_WAIT_TIME 1000


// input dialogs
// using an array is better than the MDPG implementation
// but could maybe smarter than this
#define MAX_INPUT_DIALOGS   32
HWND    g_arrhwndInputDialogs[ MAX_INPUT_DIALOGS ];
BOOL    g_bInitializedInputDialogs = FALSE; 

//
// from wpcpriv.h
// Private SHSipPreference flags.  These are continuations of the SIPSTATE enum.
// that's defined in the public AYGSHELL.H file.  Careful here.
//
#define  SIP_INPUTDIALOGINIT ((SIPSTATE)5)
#define  SIP_DOWN_NOVALIDATE ((SIPSTATE)6)


static DWORD HandleSipWait( LPVOID lpParameter )
{
    ASSERT( g_hEvent );
    if ( !g_hEvent )
    {
        return 1;
    }

    DWORD dwWaitCode = WaitForSingleObject( g_hEvent, SIP_WAIT_TIME );

    if ( dwWaitCode == WAIT_TIMEOUT )
    {
        SIPINFO sipinfo = {0};
        sipinfo.cbSize = sizeof( SIPINFO );
        if ( g_bSipGoingUp )
        {
            if( pfnSipGetInfo( &sipinfo ) )
            {
                sipinfo.fdwFlags &= ~SIPF_OFF;
                sipinfo.fdwFlags |= SIPF_ON | MAGIC_BYPASS;
                pfnSipSetInfo( &sipinfo );
            }
        }
        else
        {
            if( pfnSipGetInfo( &sipinfo ) )
            {
                sipinfo.fdwFlags &= ~SIPF_ON;
                sipinfo.fdwFlags |= SIPF_OFF | MAGIC_BYPASS;
                pfnSipSetInfo( &sipinfo );
            }
        }
    }

    // release the event handle
    HANDLE localEvent = g_hEvent;
    g_hEvent = NULL;
    CloseHandle( localEvent );

    return 0;
}

extern "C" BOOL SHSipPreference(HWND hwnd, SIPSTATE st)
{
    __try
    {
        BOOL fRet = TRUE;
        BOOL bCreateThread = FALSE;
        BOOL bKillThread = FALSE;
        BOOL bNewValue = FALSE;
        int  i;


        // validate SIPSTATE
        if ( ( st < SIP_UP ) || ( st > SIP_DOWN_NOVALIDATE ) || !hwnd )
        {
            SetLastError( ERROR_INVALID_PARAMETER );
            return FALSE;
        }

        if ( !g_bInitializedInputDialogs )
        {
            for ( i = 0; i < MAX_INPUT_DIALOGS; i++ )
            {
                g_arrhwndInputDialogs[ i ] = NULL;
            }

            g_bInitializedInputDialogs = TRUE;
        }

        // get rid of any bogus input dialogs, check to see if
        // hwnd is a child of any of them
        for ( i = 0; i < MAX_INPUT_DIALOGS; i++ )
        {
            if ( g_arrhwndInputDialogs[ i ] )
            {
                if ( !IsWindow( g_arrhwndInputDialogs[ i ] ) )
                {
                    g_arrhwndInputDialogs[ i ] = NULL;
                }
                else
                {
                    if ( IsChild( g_arrhwndInputDialogs[ i ], hwnd ) )
                    {
                        return FALSE;
                    }
                }
            }
        }

        switch ( st )
        {
            case SIP_UP:
                if ( g_bSipGoingUp && g_hEvent )
                {
                    fRet = true;
                }
                else
                {
                    bNewValue = TRUE;
                    bCreateThread = TRUE;
                }
                break;
            case SIP_DOWN_NOVALIDATE: // private
            case SIP_DOWN:
                if ( !g_bSipGoingUp && g_hEvent )
                {
                    fRet = TRUE;
                }
                else
                {
                    bNewValue = FALSE;
                    bCreateThread = TRUE;
                }
                break;
            case SIP_FORCEDOWN:
                fRet = pfnSipShowIM( SIPF_OFF );
                bKillThread = TRUE;
                break;
            case SIP_UNCHANGED:
                // kill the thread (if it exists)
                bKillThread = TRUE;
                break;
            case SIP_INPUTDIALOGINIT:
                if ( g_bSipGoingUp && g_hEvent )
                {
                    fRet = true;
                }
                else
                {
                    bNewValue = TRUE;
                    bCreateThread = TRUE;
                }
                // fall through
            case SIP_INPUTDIALOG:
                // find a space in the input dialog array and add this
                for ( i = 0; i < MAX_INPUT_DIALOGS; i++ )
                {
                    if ( !g_arrhwndInputDialogs[ i ] )
                    {
                        g_arrhwndInputDialogs[ i ] = hwnd;
                        break;
                    }
                }
                break;
            default:
                fRet = FALSE;
                break;
        }

        if ((bKillThread || bCreateThread) && g_hEvent)
        {
            DWORD dwExitCode;
            SetEvent( g_hEvent );
            while( GetExitCodeThread( g_hSipWaitThread, &dwExitCode ) && ( dwExitCode == STILL_ACTIVE ) )
            {
                Sleep(0);
            }
            // release the thread handle
            CloseHandle( g_hSipWaitThread );
            g_hSipWaitThread = NULL;
        }

        if ( bCreateThread )
        {
            g_bSipGoingUp = bNewValue;
            g_hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
            if ( g_hEvent )
            {
                g_hSipWaitThread = CreateThread( NULL, 0, HandleSipWait, NULL, 0, NULL );
                if ( !g_hSipWaitThread )
                {
                    fRet = FALSE;
                }
            }
            else
            {
                fRet = FALSE;
            }
        }

        return fRet;
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        ASSERT(FALSE);
        return FALSE;
    }
}


// REVIEW: we have no g_sipinfo, what to do?  Can it be ignored?
extern "C" BOOL WINAPI NotSystemParametersInfo( UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni )
{
    __try
    {
        BOOL fRes = FALSE, fZeroPtr = FALSE;
        SIPINFO si, *psi;

        switch( uiAction ) {

        case SPI_SETCOMPLETIONINFO:
            fRes = TRUE;
            break;

        case SPI_GETWORKAREA:
            fRes = SystemParametersInfo( SPI_GETWORKAREA, 0, pvParam, 0 );
            break;

        case SPI_SETSIPINFO:
        {
            BOOL fNotifyCompIME = FALSE;
            si = *(SIPINFO *)pvParam;   

            if( si.cbSize == sizeof( SIPINFO ) &&
                si.dwImDataSize && si.pvImData &&
                ZeroPtr( si.pvImData ) == (DWORD)si.pvImData )
            {
                si.pvImData = MapCallerPtr(si.pvImData, si.dwImDataSize);
            }

            fRes = pfnSipSetInfo( &si );

            break;
        }

        case SPI_GETSIPINFO:
        {
            psi = (SIPINFO *)pvParam;
            if (psi)
            {
                if( 
                    psi->cbSize == sizeof( SIPINFO ) &&
                    psi->dwImDataSize && psi->pvImData &&
                    ZeroPtr( psi->pvImData ) == (DWORD)psi->pvImData )
                {
                    psi->pvImData = MapCallerPtr(psi->pvImData, psi->dwImDataSize);
                    fZeroPtr = TRUE;
                }

                fRes = pfnSipGetInfo( (SIPINFO *)pvParam );

                // since this is a special flag of our own, and not one the OS knows about, we need to fill this bit in ourselves;
                // REVIEW: don't have auto complete yet
    //            psi->fdwFlags = (g_sipinfo.fdwFlags & SIPF_DISABLECOMPLETION) | (psi->fdwFlags & ~SIPF_DISABLECOMPLETION);

                if (!fRes)
                {
                    // trouble in toon town.  Let's lie a little...
                    RETAILMSG(1, (TEXT("SHELL32: XNotSystemParametersInfo, SPI_GETSIPINFO, SipGetInfo failed!  Defaulting...\r\n")));
                    SystemParametersInfo(SPI_GETWORKAREA, 0, &(((SIPINFO *)pvParam)->rcVisibleDesktop), FALSE);
                    ((SIPINFO *)pvParam)->fdwFlags = SIPF_DOCKED;
                }

                if( fZeroPtr ) {
                    psi->pvImData = (void *)ZeroPtr( psi->pvImData );
                }
            }
            break;
        }

        case SPI_SETCURRENTIM:
            fRes = pfnSipSetCurrentIM( (CLSID *)pvParam );
            break; 

        case SPI_GETCURRENTIM:
            fRes = pfnSipGetCurrentIM( (CLSID *)pvParam );
            break;

        default:
            SetLastError( ERROR_INVALID_PARAMETER );
            break;

        } // switch( uiAction )

        return fRes;
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        ASSERT(FALSE);
        return FALSE;
    }

    UNREFERENCED_PARAMETER( fWinIni );
}
