//
// snoop.cpp
//
// CSnoopWnd implementation.
//

#include "globals.h"
#include "snoop.h"
#include "case.h"
#include "editsess.h"

class CUpdateTextEditSession : public CEditSessionBase
{
public:
    CUpdateTextEditSession(ITfContext *pContext, ITfRange *pRange, CSnoopWnd *pSnoopWnd) : CEditSessionBase(pContext)
    {
        _pSnoopWnd = pSnoopWnd;
        _pRange = pRange;
        if (_pRange != NULL)
        {
            _pRange->AddRef();
        }
    }
    ~CUpdateTextEditSession()
    {
        SafeRelease(_pRange);
    }

    // ITfEditSession
    STDMETHODIMP DoEditSession(TfEditCookie ec);

private:
    CSnoopWnd *_pSnoopWnd;
    ITfRange *_pRange;
};


#define SNOOP_X_POS     0
#define SNOOP_Y_POS     0

#define SNOOP_WIDTH     300
#define SNOOP_HEIGHT    (SNOOP_WIDTH / 3)

ATOM CSnoopWnd::_atomWndClass = 0;

//+---------------------------------------------------------------------------
//
// ctor
//
//----------------------------------------------------------------------------

CSnoopWnd::CSnoopWnd(CCaseTextService *pCase)
{
    _pCase = pCase; // no AddRef because CSnoopWnd is contained in the
                    // pCase lifetime
    _hWnd = NULL;
    _cchText = 0;
}

//+---------------------------------------------------------------------------
//
// _InitClass
//
//----------------------------------------------------------------------------

/* static */
BOOL CSnoopWnd::_InitClass()
{
    WNDCLASS wc;

    wc.style = 0;
    wc.lpfnWndProc = CSnoopWnd::_WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = g_hInst;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName = NULL;
    wc.lpszClassName = TEXT("SnoopWndClass");

    _atomWndClass = RegisterClass(&wc);

    return (_atomWndClass != 0);
}

//+---------------------------------------------------------------------------
//
// _UninitClass
//
//----------------------------------------------------------------------------

/* static */
void CSnoopWnd::_UninitClass()
{
    if (_atomWndClass != 0)
    {
        UnregisterClass((LPCTSTR)_atomWndClass, g_hInst);
    }
}


//+---------------------------------------------------------------------------
//
// _Init
//
//----------------------------------------------------------------------------

BOOL CSnoopWnd::_Init()
{
    // nb: on windows 2000, you can use WS_EX_NOACTIVATE to prevent windows
    // from taking the foreground.  We don't use that here for compatibility.
    // Instead, we use WS_DISABLED, which can be burdensome for more complex
    // ui.

    _hWnd = CreateWindowEx(WS_EX_TOPMOST | WS_EX_TOOLWINDOW,
                           (LPCTSTR)_atomWndClass,
                           TEXT("Snoop Window"),
                           WS_BORDER | WS_DISABLED | WS_POPUP,
                           SNOOP_X_POS, SNOOP_Y_POS,
                           SNOOP_WIDTH, SNOOP_HEIGHT,
                           NULL,
                           NULL,
                           g_hInst,
                           this);

    return (_hWnd != NULL);
}

//+---------------------------------------------------------------------------
//
// _Uninit
//
//----------------------------------------------------------------------------

void CSnoopWnd::_Uninit()
{
    if (_hWnd != NULL)
    {
        DestroyWindow(_hWnd);
        _hWnd = NULL;
    }
}

//+---------------------------------------------------------------------------
//
// _Show
//
//----------------------------------------------------------------------------

void CSnoopWnd::_Show()
{
    ShowWindow(_hWnd, SW_SHOWNA);
}

//+---------------------------------------------------------------------------
//
// _Hide
//
//----------------------------------------------------------------------------

void CSnoopWnd::_Hide()
{
    ShowWindow(_hWnd, SW_HIDE);
}

//+---------------------------------------------------------------------------
//
// _UpdateText
//
//----------------------------------------------------------------------------

void CSnoopWnd::_UpdateText(ITfRange *pRange)
{
    ITfDocumentMgr *pdmFocus;
    ITfContext *pContext;
    CUpdateTextEditSession *pEditSession;
    HRESULT hr;

    if (pRange == NULL)
    {
        // caller wants us to just use the selection in the focus doc
        if (_pCase->_GetThreadMgr()->GetFocus(&pdmFocus) != S_OK)
            return;

        hr = pdmFocus->GetTop(&pContext);

        pdmFocus->Release();

        if (hr != S_OK)
            return;
    }
    else if (pRange->GetContext(&pContext) != S_OK)
        return;

    if (pEditSession = new CUpdateTextEditSession(pContext, pRange, this))
    {
        // we need a document read lock to scan text
        // the CUpdateTextEditSession will do all the work when the
        // CUpdateTextEditSession::DoEditSession method is called by the context
        pContext->RequestEditSession(_pCase->_GetClientId(), pEditSession, TF_ES_READ | TF_ES_ASYNCDONTCARE, &hr);

        pEditSession->Release();
    }

    pContext->Release();
}

//+---------------------------------------------------------------------------
//
// DoEditSession
//
//----------------------------------------------------------------------------

STDAPI CUpdateTextEditSession::DoEditSession(TfEditCookie ec)
{
    _pSnoopWnd->_UpdateText(ec, _pContext, _pRange);
    return S_OK;
}

//+---------------------------------------------------------------------------
//
// _UpdateText
//
//----------------------------------------------------------------------------

void CSnoopWnd::_UpdateText(TfEditCookie ec, ITfContext *pContext, ITfRange *pRange)
{
    LONG cchBefore;
    LONG cchAfter;
    TF_SELECTION tfSelection;
    ULONG cFetched;
    BOOL fReleaseRange = FALSE;

    if (pRange == NULL)
    {
        // caller wants us to use the selection
        if (pContext->GetSelection(ec, TS_DEFAULT_SELECTION, 1, &tfSelection, &cFetched) != S_OK ||
            cFetched != 1)
        {
            return;
        }

        pRange = tfSelection.range; // no AddRef, take ownership of the pointer
        fReleaseRange = TRUE;
    }

    // arbitrarily grab some text before and after the range start anchor

    pRange->Collapse(ec, TF_ANCHOR_START);

    pRange->ShiftStart(ec, -MAX_SNOOP_TEXT / 2, &cchBefore, NULL);

    cchBefore = -cchBefore; // we shifted backwards, so make count a positive number

    pRange->GetText(ec, 0, _achText, cchBefore, (ULONG *)&cchBefore);

    pRange->Collapse(ec, TF_ANCHOR_END);

    pRange->ShiftEnd(ec, MAX_SNOOP_TEXT - cchBefore, &cchAfter, NULL);

    pRange->GetText(ec, 0, _achText + cchBefore, cchAfter, (ULONG *)&cchAfter);

    _cchText = cchBefore + cchAfter;

    // force a repaint

    InvalidateRect(_hWnd, NULL, TRUE);

    if (fReleaseRange)
    {
        pRange->Release();
    }
}

//+---------------------------------------------------------------------------
//
// _WndProc
//
// Snoop window proc.
//----------------------------------------------------------------------------

/* static */
LRESULT CALLBACK CSnoopWnd::_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;

    switch (uMsg)
    {
        case WM_CREATE:
            _SetThis(hWnd, lParam);
            return 0;

        case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);
            _GetThis(hWnd)->_OnPaint(hWnd, hdc);
            EndPaint(hWnd, &ps);
            return 0;
    }

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

//+---------------------------------------------------------------------------
//
// _OnPaint
//
// WM_PAINT handler for CSnoopWnd.
//----------------------------------------------------------------------------

void CSnoopWnd::_OnPaint(HWND hWnd, HDC hdc)
{
    RECT rc;

    // background
    GetClientRect(hWnd, &rc);
    FillRect(hdc, &rc, (HBRUSH)GetStockObject(LTGRAY_BRUSH));

    // text
    TextOutW(hdc, 0, 0, _achText, _cchText);
}

//+---------------------------------------------------------------------------
//
// _InitSnoopWnd
//
// Create and init the snoop window.
//----------------------------------------------------------------------------

BOOL CCaseTextService::_InitSnoopWnd()
{
    BOOL fThreadfocus;
    ITfSource *pSource = NULL;

    // create a snoop window

    if ((_pSnoopWnd = new CSnoopWnd(this)) == NULL)
        return FALSE;

    if (!_pSnoopWnd->_Init())
        goto ExitError;

    // we also need a thread focus sink

    if (_pThreadMgr->QueryInterface(IID_ITfSource, (void **)&pSource) != S_OK)
    {
        pSource = NULL;
        goto ExitError;
    }

    if (pSource->AdviseSink(IID_ITfThreadFocusSink, (ITfThreadFocusSink *)this, &_dwThreadFocusSinkCookie) != S_OK)
    {
        // make sure we don't try to Unadvise _dwThreadFocusSinkCookie later
        _dwThreadFocusSinkCookie = TF_INVALID_COOKIE;
        goto ExitError;
    }

    pSource->Release();

    // we may need to display the snoop window right now
    // our thread focus sink won't be called until something changes,
    // so we need to check the current state.

    if (_pThreadMgr->IsThreadFocus(&fThreadfocus) == S_OK && fThreadfocus)
    {
        OnSetThreadFocus();
    }

    return TRUE;

ExitError:
    SafeRelease(pSource);
    _UninitSnoopWnd();
    return FALSE;
}

//+---------------------------------------------------------------------------
//
// _UninitSnoopWnd
//
// Uninit and free the snoop window, unadvise the thread focus sink.
//----------------------------------------------------------------------------

void CCaseTextService::_UninitSnoopWnd()
{
    ITfSource *pSource;

    if (_pSnoopWnd != NULL)
    {
        _pSnoopWnd->_Uninit();
        delete _pSnoopWnd;
    }

    if (_dwThreadFocusSinkCookie != TF_INVALID_COOKIE)
    {
        if (_pThreadMgr->QueryInterface(IID_ITfSource, (void **)&pSource) == S_OK)
        {
            pSource->UnadviseSink(_dwThreadFocusSinkCookie);
            pSource->Release();
        }

        _dwThreadFocusSinkCookie = TF_INVALID_COOKIE;
    }
}

//+---------------------------------------------------------------------------
//
// _Menu_ShowSnoopWnd
//
// Show or hide the snoop window.
//----------------------------------------------------------------------------

void CCaseTextService::_Menu_ShowSnoopWnd(CCaseTextService *_this)
{
    _this->_fShowSnoop = !_this->_fShowSnoop;

    if (_this->_fShowSnoop)
    {
        _this->_pSnoopWnd->_Show();
    }
    else
    {
        _this->_pSnoopWnd->_Hide();
    }
}

//+---------------------------------------------------------------------------
//
// OnSetThreadFocus
//
// Called by the system when the thread/appartment of this text service gains
// the ui focus.
//----------------------------------------------------------------------------

STDAPI CCaseTextService::OnSetThreadFocus()
{
    if (_fShowSnoop)
    {
        _pSnoopWnd->_Show();
    }

    return S_OK;
}

//+---------------------------------------------------------------------------
//
// OnKillThreadFocus
//
// Called by the system when the thread/appartment of this text service loses
// the ui focus.
//----------------------------------------------------------------------------

STDAPI CCaseTextService::OnKillThreadFocus()
{
    // only show our snoop window when our thread has the focus.
    if (_fShowSnoop)
    {
        _pSnoopWnd->_Hide();
    }

    return S_OK;
}