//
// ic.cpp
//

#include "private.h"
#include "common.h"
#include "korimx.h"
#include "icpriv.h"
#include "ipointcic.h"
#include "cleanup.h"
#include "helpers.h"

//+---------------------------------------------------------------------------
//
// OnStartCleanupContext
//
//----------------------------------------------------------------------------

HRESULT CKorIMX::OnStartCleanupContext()
{
    // nb: a real tip, for performace, should skip input contexts it knows
    // it doesn't need a lock and callback on.  For instance, kimx only
    // cares about ic's with ongoing compositions.  We could remember which ic's
    // have compositions, then return FALSE for all but the ic's with compositions.
    // It is really bad perf to have the library make a lock request for every
    // ic!
    m_fPendingCleanup = fTrue;
    return S_OK;
}

//+---------------------------------------------------------------------------
//
// OnEndCleanupContext
//
// Called after all ic's with cleanup sinks have been called.
//----------------------------------------------------------------------------

HRESULT CKorIMX::OnEndCleanupContext()
{
    // our profile just changed or we are about to be deactivated
    // in either case we don't have to worry about anything interrupting ic cleanup
    // callbacks anymore
    m_fPendingCleanup = fFalse;
    return S_OK;
}

//+---------------------------------------------------------------------------
//
// OnCleanupContext
//
// This method is a callback for the library helper CleanupAllContexts.
// We have to be very careful here because we may be called _after_ this tip
// has been deactivated, if the app couldn't grant a lock right away.
//----------------------------------------------------------------------------

HRESULT CKorIMX::OnCleanupContext(TfEditCookie ecWrite, ITfContext *pic)
{
    // all kimx cares about is finalizing compositions
    CleanupAllCompositions(ecWrite, pic, CLSID_KorIMX, _CleanupCompositionsCallback, this);

    return S_OK;
}

//+---------------------------------------------------------------------------
//
// ITfActiveLanguageProfileNotifySink::OnActivated
//
//----------------------------------------------------------------------------
STDAPI CKorIMX::OnActivated(REFCLSID clsid, REFGUID guidProfile, BOOL bActivated)
{
    // our profile just changed or we are about to be deactivated
    // in either case we don't have to worry about anything interrupting ic cleanup
    // callbacks anymore
    m_fPendingCleanup = fFalse;

    //if (IsSoftKbdEnabled())
    //    OnActivatedSoftKbd(bActivated);
        
    return S_OK;
}

//+---------------------------------------------------------------------------
//
// _CleanupCompositionsCallback
//
//----------------------------------------------------------------------------

/* static */
void CKorIMX::_CleanupCompositionsCallback(TfEditCookie ecWrite, ITfRange *rangeComposition, void *pvPrivate)
{
    CKorIMX* pKorTip = (CKorIMX*)pvPrivate;
    ITfContext *pic;

    if (rangeComposition->GetContext(&pic) != S_OK)
        return;
        
    if (pKorTip)
        pKorTip->MakeResultString(ecWrite, pic, rangeComposition);
    // _FinalizeRange(ecWrite, pic, rangeComposition);

    pic->Release();
}

/*---------------------------------------------------------------------------
    CKorIMX::_InitICPriv

    Init IC private data
---------------------------------------------------------------------------*/
HRESULT CKorIMX::_InitICPriv(ITfContext *pic)
{
    CICPriv *picp;
    CCompartmentEventSink* pCompartmentSink;
    ITfSourceSingle *pSourceSingle;
    TF_STATUS dcs;

    // Check pic
    if (pic == NULL)
        return E_FAIL;
    //
    // check enable/disable (Candidate stack)
    //
    if (IsDisabledIC(pic) || IsEmptyIC(pic))
        return S_OK;

    // Initialize Private data members
    if ((picp = GetInputContextPriv(pic)) == NULL)
        {
        IUnknown *punk;

        if ((picp = new CICPriv) == NULL)
               return E_OUTOFMEMORY;

        // IC
        picp->RegisterIC(pic);
        // IMX
        picp->RegisterIMX(this);

    	if (picp->IsInitializedIPoint() == FALSE)
    	    {
    		//struct _GUID RefID={0}; // dummy id
    		IImeIPoint1 *pIP;
            LPCIPointCic pCIPointCic = NULL;

            //////////////////////////////////////////////////////////////////////
            // Create IImeIPoint1 instance
            //////////////////////////////////////////////////////////////////////
            if ((pCIPointCic = new CIPointCic(this)) == NULL)
                {
                return E_OUTOFMEMORY;
                }

            // This increments the reference count
            if (FAILED(pCIPointCic->QueryInterface(IID_IImeIPoint1, (VOID **)&pIP)))
                {
                delete pCIPointCic;
                return E_OUTOFMEMORY;
                }

    		// initialize kernel
    		pCIPointCic->Initialize(pic);

    		// register ic depended objects.
    		picp->RegisterIPoint(pIP);
    		picp->InitializedIPoint(fTrue);
    	    }
    	
        //
        // text edit sink/edit transaction sink
        //
        ITfSource *pSource;
        DWORD dwCookieForTextEditSink = 0;
        //DWORD dwCookieForTransactionSink = 0;
        if (pic->QueryInterface(IID_ITfSource, (void **)&pSource ) == S_OK)
            {
            pSource->AdviseSink(IID_ITfTextEditSink, (ITfTextEditSink *)this, &dwCookieForTextEditSink);
            //pSource->AdviseSink(IID_ITfEditTransactionSink, (ITfEditTransactionSink *)this, &dwCookieForTransactionSink);

            pSource->Release();

            picp->RegisterCookieForTextEditSink(dwCookieForTextEditSink);
            //picp->RegisterCookieForTransactionSink(dwCookieForTransactionSink);
            }

        // compartment event sink
        if ((pCompartmentSink = new CCompartmentEventSink(_CompEventSinkCallback, picp)) != NULL )
            {
            picp->RegisterCompartmentEventSink(pCompartmentSink);

            // On/Off - compartment
            pCompartmentSink->_Advise(GetTIM(), GUID_COMPARTMENT_KEYBOARD_OPENCLOSE, FALSE);
            
            // Conversion mode - compartment
            pCompartmentSink->_Advise(GetTIM(), GUID_COMPARTMENT_KORIMX_CONVMODE, FALSE);

            // SoftKeyboard Open/Close
            pCompartmentSink->_Advise(GetTIM(), GUID_COMPARTMENT_KOR_SOFTKBD_OPENCLOSE, FALSE);

            // Soft Keyboard layout change
            pCompartmentSink->_Advise(GetTIM(), GUID_COMPARTMENT_SOFTKBD_KBDLAYOUT, FALSE);
            }

        Assert(pCompartmentSink != NULL);

        if (pic->QueryInterface(IID_ITfSourceSingle, (void **)&pSourceSingle) == S_OK)
            {
            // setup a cleanup callback
            // nb: a real tip doesn't need to be this aggressive, for instance
                // kimx probably only needs this sink on the focus ic.
            pSourceSingle->AdviseSingleSink(GetTID(), IID_ITfCleanupContextSink, (ITfCleanupContextSink *)this);
            pSourceSingle->Release();
            }

        // Initialized kernel
        picp->Initialized(fTrue);

        // Set to compartment GUID
        GetCompartmentUnknown(pic, GUID_IC_PRIVATE, &punk);
        if (!punk)
            {
            SetCompartmentUnknown(GetTID(), pic, GUID_IC_PRIVATE, picp);
            picp->Release();
            }
        else
            {
            // Praive data already exist.
            punk->Release();
            return E_FAIL;
            }

        }

        // Set AIMM1.2
        picp->SetAIMM(fFalse);
        pic->GetStatus(&dcs);

        if (dcs.dwStaticFlags & TF_SS_TRANSITORY)
            picp->SetAIMM(fTrue);

    return S_OK;
}


/*---------------------------------------------------------------------------
    CKorIMX::_DeleteICPriv

    Delete IC private data
---------------------------------------------------------------------------*/
HRESULT CKorIMX::_DeleteICPriv(ITfContext *pic)
{
    CICPriv        *picp;
    IUnknown        *punk;
    CCompartmentEventSink* pCompartmentSink;
    ITfSource         *pSource;
    ITfSourceSingle *pSourceSingle;
    
    if (pic == NULL)
        return E_FAIL;

    picp = GetInputContextPriv(pic);

#ifdef DBG
    Assert(IsDisabledIC(pic) || picp != NULL );
#endif
    
    if (picp == NULL)
         return S_FALSE;

    //
    // Compartment event sink
    //
    pCompartmentSink = picp->GetCompartmentEventSink();
    if (pCompartmentSink)
        {
        pCompartmentSink->_Unadvise();
        pCompartmentSink->Release();
        }

    //
    // text edit sink
    //
    if (pic->QueryInterface( IID_ITfSource, (void **)&pSource) == S_OK)
        {
        pSource->UnadviseSink(picp->GetCookieForTextEditSink());
        //pSource->UnadviseSink(picp->GetCookieForTransactionSink());
        pSource->Release();
        }
    picp->RegisterCookieForTextEditSink(0);

    // Clear ITfCleanupContextSink
    if (pic->QueryInterface(IID_ITfSourceSingle, (void **)&pSourceSingle) == S_OK)
        {
        pSourceSingle->UnadviseSingleSink(GetTID(), IID_ITfCleanupContextSink);
        pSourceSingle->Release();
        }

	// UnInitialize IPoint
	IImeIPoint1 *pIP = GetIPoint(pic);
	// IImeIPoint
	if (pIP)
	    {
		pIP->Release();
	    }
	picp->RegisterIPoint(NULL);
	picp->InitializedIPoint(fFalse);	// reset
	
    // Reset init flag
    picp->Initialized(fFalse);

    // We MUST clear out the private data before cicero is free 
    // to release the ic
    GetCompartmentUnknown(pic, GUID_IC_PRIVATE, &punk);
    if (punk)
        punk->Release();
    ClearCompartment(GetTID(), pic, GUID_IC_PRIVATE, fFalse);

    return S_OK;
}

/*---------------------------------------------------------------------------
    CKorIMX::GetInputContextPriv

    Get IC private data
---------------------------------------------------------------------------*/
CICPriv *CKorIMX::GetInputContextPriv(ITfContext *pic)
{
    IUnknown *punk;

    if (pic == NULL)
        return NULL;
        
    GetCompartmentUnknown(pic, GUID_IC_PRIVATE, &punk);

    if (punk)
        punk->Release();

    return (CICPriv *)punk;
}


/*---------------------------------------------------------------------------
    CKorIMX::OnICChange
---------------------------------------------------------------------------*/
void CKorIMX::OnFocusChange(ITfContext *pic, BOOL fActivate)
{
    BOOL fReleaseIC     = fFalse;
    BOOL fDisabledIC     = IsDisabledIC(pic);
    BOOL fEmptyIC         = IsEmptyIC(pic);
    BOOL fCandidateIC     = IsCandidateIC(pic);

    BOOL fInEditSession;
    HRESULT hr;

    if (fEmptyIC)
        {
        if (m_pToolBar)
            m_pToolBar->SetCurrentIC(NULL);

        if (IsSoftKbdEnabled())
            SoftKbdOnThreadFocusChange(fFalse);
        return;    // do nothing
        }
        
    if (fDisabledIC == fTrue && fCandidateIC == fFalse )
        {
        if (m_pToolBar)
            m_pToolBar->SetCurrentIC(NULL);

        if (IsSoftKbdEnabled())
            SoftKbdOnThreadFocusChange(fFalse);
        return;    // do nothing
        }

    // O10 #278261: Restore Soft Keyboard winfow after switched from Empty Context to normal IC.
    if (IsSoftKbdEnabled())
        SoftKbdOnThreadFocusChange(fActivate);

    // Notify focus change to IME Pad svr
	if (m_pPadCore)
	    {
		m_pPadCore->SetFocus(fActivate);
	    }

    // Terminate
    if (fActivate == fFalse)
        {
        if (!fDisabledIC && pic && GetIPComposition(pic))
            {
            if (SUCCEEDED(pic->InWriteSession(GetTID(), &fInEditSession)) && !fInEditSession)
                {
                CEditSession2 *pes;
                ESSTRUCT ess;

                ESStructInit(&ess, ESCB_COMPLETE);
                
                if ((pes = new CEditSession2(pic, this, &ess, _EditSessionCallback2)))
                       {
                    pes->Invoke(ES2_READWRITE | ES2_SYNC, &hr);
                    pes->Release();
                    }
                }
            }

        // Close cand UI if opened.
        if (m_fCandUIOpen)
            CloseCandidateUIProc();
            
        return;
        }

    // fActivate == TRUE
    if (fDisabledIC)
        {
        pic = GetRootIC();
        fReleaseIC = fTrue;
        }

    if (m_pToolBar)
        m_pToolBar->SetCurrentIC(pic);

	if (m_pPadCore)
	    {
		IImeIPoint1* pIP = GetIPoint(pic);
		m_pPadCore->SetIPoint(pIP);
	    }

    if (pic && !fDisabledIC)
        {
        CICPriv *picp;

        // Sync GUID_COMPARTMENT_KEYBOARD_OPENCLOSE with GUID_COMPARTMENT_KORIMX_CONVMODE
        // This for Word now but looks not good since we don't sync On/Off status with conv mode.
        // In future Apps should set GUID_MODEBIAS_HANGUL on boot and should be Korean specific code. 
        if (GetConvMode(pic) == TIP_NULL_CONV_MODE) // if this is first boot.
            {
            if (IsOn(pic))
                SetCompartmentDWORD(GetTID(), GetTIM(), GUID_COMPARTMENT_KORIMX_CONVMODE, TIP_HANGUL_MODE, fFalse);
            else
                SetCompartmentDWORD(GetTID(), GetTIM(), GUID_COMPARTMENT_KORIMX_CONVMODE, TIP_ALPHANUMERIC_MODE, fFalse);
            }
        else
            {
            // Reset ModeBias
            picp = GetInputContextPriv(pic);
            if (picp)
                picp->SetModeBias(NULL);
            }

        // Modebias check here
        CheckModeBias(pic);
        }

    if (fReleaseIC)
        SafeRelease(pic);
}


// REVIEW::
// tmp solution
ITfContext* CKorIMX::GetRootIC(ITfDocumentMgr* pDim)
{
    if (pDim == NULL)
        {
        pDim = m_pCurrentDim;
        if( pDim == NULL )
            return NULL;
        }

    IEnumTfContexts *pEnumIc = NULL;
    if (SUCCEEDED(pDim->EnumContexts(&pEnumIc)))
        {
        ITfContext *pic = NULL;
        while (pEnumIc->Next(1, &pic, NULL) == S_OK)
            break;
        pEnumIc->Release();

        return pic;
        }
        
    return NULL;    // error case
}

IImeIPoint1* CKorIMX::GetIPoint(ITfContext *pic)
{
    CICPriv *picp;
    
    if (pic == NULL)
        {
        return NULL;
        }
    
    picp = GetInputContextPriv(pic);

    if (picp)
        {
        return picp->GetIPoint();
        }
    
    return NULL;
}
BOOL CKorIMX::IsDisabledIC(ITfContext *pic)
{
    DWORD dwFlag;

    if (pic == NULL)
        return fFalse;
           
    GetCompartmentDWORD(pic, GUID_COMPARTMENT_KEYBOARD_DISABLED, &dwFlag, fFalse);

    if (dwFlag)
        return fTrue;    // do not create any kernel related info into ic.
    else
        return fFalse;
}

/*   I S  E M P T Y   I  C   */
BOOL CKorIMX::IsEmptyIC(ITfContext *pic)
{
    DWORD dwFlag;
    
    if (pic == NULL)
        return fFalse;
    
    GetCompartmentDWORD(pic, GUID_COMPARTMENT_EMPTYCONTEXT, &dwFlag, fFalse);

    if (dwFlag)
        return fTrue;    // do not create any kernel related info into ic.

    return fFalse;
}

/*   I S  C A N D I D A T E  I  C   */
/*------------------------------------------------------------------------------

    Check if the input context is one of candidate UI

------------------------------------------------------------------------------*/
BOOL CKorIMX::IsCandidateIC(ITfContext *pic)
{
    DWORD dwFlag;
    
    if (pic == NULL) 
        return fFalse;
    
    GetCompartmentDWORD( pic, GUID_COMPARTMENT_KEYBOARD_DISABLED, &dwFlag, fFalse);

    if (dwFlag)
        return fTrue;    // do not create any kernel related info into ic.

    return fFalse;
}


HWND CKorIMX::GetAppWnd(ITfContext *pic)
{
    ITfContextView* pView;
    HWND hwndApp = 0;

    if (pic == NULL)
        return 0;

    pic->GetActiveView(&pView);
    if (pView == NULL)
        return 0;

    pView->GetWnd(&hwndApp);
    pView->Release();
    
    return hwndApp;

}