/*
  Name:             kmtip
  Copyright:        Copyright (C) SIL International.
  Documentation:    
  Description:      
  Create Date:      7 Sep 2009

  Modified Date:    28 Mar 2016
  Authors:          mcdurdin
  Related Files:    
  Dependencies:     

  Bugs:             
  Todo:             
  Notes:            
  History:          07 Sep 2009 - mcdurdin - I2095 - TSF addin is not threadsafe
                    20 Nov 2012 - mcdurdin - I3582 - V9.0 - Remove language bar integration
                    20 Nov 2012 - mcdurdin - I3581 - V9.0 - KMTip needs to pass activated profile guid through to Keyman32 to switch keyboards
                    28 Nov 2012 - mcdurdin - I3590 - V9.0 - Initialise keystroke sink after we know which keyboard to activate
                    28 Nov 2012 - mcdurdin - I3588 - V9.0 - Use preserved keys in TSF to handle key combinations in Keyman
                    01 Jan 2013 - mcdurdin - I3714 - V9.0 - Applications hang when switching kmtip off if Keyman not running
                    01 May 2014 - mcdurdin - I4216 - V9.0 - Keyman TIP should use ITfTextInputProcessorEx
                    28 May 2014 - mcdurdin - I3706 - V9.0 - If kmtip CKMTipTextService::Activate fails, should we release pThreadMgr?
                    16 Jun 2014 - mcdurdin - I4274 - V9.0 - kmtip does not work if already active before KM starts
*/
//
// kmtip.cpp
//
// IUnknown, ITfTextInputProcessor implementation.
//

#include "pch.h"
#include "kmtip.h"
#include "registryw.h"

//+---------------------------------------------------------------------------
//
// CreateInstance
//
//----------------------------------------------------------------------------

/* static */
HRESULT CKMTipTextService::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObj)
{
    CKMTipTextService *pKMTip;
    HRESULT hr;

    if (ppvObj == NULL)
        return E_INVALIDARG;

    *ppvObj = NULL;

    if (NULL != pUnkOuter)
        return CLASS_E_NOAGGREGATION;

    if ((pKMTip = new CKMTipTextService) == NULL)
        return E_OUTOFMEMORY;

    hr = pKMTip->QueryInterface(riid, ppvObj);

    pKMTip->Release(); // caller still holds ref if hr == S_OK

    return hr;
}

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

__declspec(thread) CKMTipTextService *CKMTipTextService::ThreadThis;

CKMTipTextService::CKMTipTextService()
{
    DllAddRef();

    _pThreadMgr = NULL;
    _tfClientId = TF_CLIENTID_NULL;
   // I3582
    _dwThreadMgrEventSinkCookie = TF_INVALID_COOKIE;

    _keystrokeSinkInitialized = FALSE;
    _dwActiveLanguageProfileNotifySinkCookie = 0;
    _PreservedKeys = NULL;
    _cPreservedKeyCount = 0;
    _dwDeepIntegration = 0;

    RegistryReadOnlyW reg(HKEY_CURRENT_USER);
    _tryAndStartKeyman = reg.OpenKeyReadOnly(REGSZ_KeymanEngineDebug_CU) && reg.ValueExists(REGSZ_Flag_UseAutoStartTask) && reg.ReadInteger(REGSZ_Flag_UseAutoStartTask) != 0;

    _cRef = 1;
    ThreadThis = this;
}

//+---------------------------------------------------------------------------
//
// dtor
//
//----------------------------------------------------------------------------

CKMTipTextService::~CKMTipTextService()
{
  ThreadThis = NULL;
  DllRelease();
}

//+---------------------------------------------------------------------------
//
// QueryInterface
//
//----------------------------------------------------------------------------

STDAPI CKMTipTextService::QueryInterface(REFIID riid, void **ppvObj)
{
    if (ppvObj == NULL)
        return E_INVALIDARG;

    *ppvObj = NULL;

    if (IsEqualIID(riid, IID_IUnknown) ||
        IsEqualIID(riid, IID_ITfTextInputProcessor))
    {
        *ppvObj = (ITfTextInputProcessor *)this;
    }
    else if (IsEqualIID(riid, IID_ITfThreadMgrEventSink))
    {
        *ppvObj = (ITfThreadMgrEventSink *)this;
    }
    else if (IsEqualIID(riid, IID_ITfActiveLanguageProfileNotifySink))   // I3581
    {
      *ppvObj = (ITfActiveLanguageProfileNotifySink *)this;
    }
    else if (IsEqualIID(riid, IID_ITfThreadFocusSink))
    {
        *ppvObj = (ITfThreadFocusSink *)this;
    }
    else if (IsEqualIID(riid, IID_ITfTextEditSink))
    {
        *ppvObj = (ITfTextEditSink *)this;
    }
    else if (IsEqualIID(riid, IID_ITfKeyEventSink))
    {
        *ppvObj = (ITfKeyEventSink *)this;
    }

    if (*ppvObj)
    {
        AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}


//+---------------------------------------------------------------------------
//
// AddRef
//
//----------------------------------------------------------------------------

STDAPI_(ULONG) CKMTipTextService::AddRef()
{
    return ++_cRef;
}

//+---------------------------------------------------------------------------
//
// Release
//
//----------------------------------------------------------------------------

STDAPI_(ULONG) CKMTipTextService::Release()
{
    LONG cr = --_cRef;

    assert(_cRef >= 0);

    if (_cRef == 0)
    {
      Log(L"CKMTipTextService::Release --> deleting");
      delete this;
    }

    return cr;
}

//+---------------------------------------------------------------------------
//
// Activate
//
//----------------------------------------------------------------------------

STDAPI CKMTipTextService::ActivateEx(ITfThreadMgr *ptim, TfClientId tid, DWORD dwFlags) {   // I4216
  LogEnter();
  ITfDocumentMgr *pFocusDoc;

  _cPreservedKeyCount = 0;   // I3588   // I3714
  _PreservedKeys = NULL;   // I3588   // I3714

  _pThreadMgr = ptim;
  _pThreadMgr->AddRef();

  _tfClientId = tid;

  if(!_InitThreadMgrSink()) {
		Log(L"Could not initialise thread manager sink");
    goto ExitError;
	}

  if(!_InitKeyman()) {
		Log(L"Could not initialise Keyman");
    goto ExitError;
  }

  // start tracking the focus doc
  if(_pThreadMgr->GetFocus(&pFocusDoc) == S_OK) {
    // The system will call OnSetFocus only for focus events after Activate
    // is called.
    OnSetFocus(pFocusDoc, NULL);
    pFocusDoc->Release();
  }

  return S_OK;

ExitError:
  Deactivate(); // cleanup any half-finished init   // I3706
  return E_FAIL;
}

STDAPI CKMTipTextService::Activate(ITfThreadMgr *pThreadMgr, TfClientId tfClientId)
{
  LogEnter();
  ITfDocumentMgr *pFocusDoc;

  _cPreservedKeyCount = 0;   // I3588   // I3714
  _PreservedKeys = NULL;   // I3588   // I3714

  _pThreadMgr = pThreadMgr;
  _pThreadMgr->AddRef();

  _tfClientId = tfClientId;

  if (!_InitThreadMgrSink())
	{
		Log(L"Could not initialise thread manager sink");
    goto ExitError;
	}

  if(!_InitKeyman())	{
    _TryAndStartKeyman();
		Log(L"Could not initialise Keyman");
    //goto ExitError;
	}

  // start tracking the focus doc
  if (_pThreadMgr->GetFocus(&pFocusDoc) == S_OK)
  {
    // The system will call OnSetFocus only for focus events after Activate
    // is called.
    OnSetFocus(pFocusDoc, NULL);
    pFocusDoc->Release();
  }

  return S_OK;

ExitError:
  Deactivate(); // cleanup any half-finished init   // I3706
  return E_FAIL;
}

//+---------------------------------------------------------------------------
//
// Deactivate
//
//----------------------------------------------------------------------------

STDAPI CKMTipTextService::Deactivate()
{
  LogEnter();

  _UninitThreadMgrSink();
  _UninitKeystrokeSink();
  _UninitKeyman();

  // we MUST release all refs to _pThreadMgr in Deactivate
  SafeReleaseClear(_pThreadMgr);

  _tfClientId = TF_CLIENTID_NULL;

  return S_OK;
}
