/*
 * MSCTF Server DLL
 *
 * Copyright 2008 Aric Stewart, CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"

#include <stdarg.h>
#include <stdio.h>

#define COBJMACROS

#include "wine/debug.h"
#include "wine/list.h"
#include "windef.h"
#include "winbase.h"
#include "winreg.h"
#include "shlwapi.h"
#include "shlguid.h"
#include "comcat.h"
#include "rpcproxy.h"
#include "msctf.h"
#include "inputscope.h"

#include "msctf_internal.h"

WINE_DEFAULT_DEBUG_CHANNEL(msctf);

static HINSTANCE MSCTF_hinstance;

typedef struct
{
    DWORD id;
    DWORD magic;
    LPVOID data;
} CookieInternal;

typedef struct {
    TF_LANGUAGEPROFILE      LanguageProfile;
    ITfTextInputProcessor   *pITfTextInputProcessor;
    ITfThreadMgr            *pITfThreadMgr;
    ITfKeyEventSink         *pITfKeyEventSink;
    TfClientId              tid;
} ActivatedTextService;

typedef struct
{
    struct list entry;
    ActivatedTextService *ats;
} AtsEntry;

static CookieInternal *cookies;
static UINT id_last;
static UINT array_size;

static struct list AtsList = LIST_INIT(AtsList);
static UINT activated = 0;

DWORD tlsIndex = 0;
TfClientId processId = 0;
ITfCompartmentMgr *globalCompartmentMgr = NULL;

const WCHAR szwSystemTIPKey[] = {'S','O','F','T','W','A','R','E','\\','M','i','c','r','o','s','o','f','t','\\','C','T','F','\\','T','I','P',0};
const WCHAR szwSystemCTFKey[] = {'S','O','F','T','W','A','R','E','\\','M','i','c','r','o','s','o','f','t','\\','C','T','F',0};

typedef HRESULT (*LPFNCONSTRUCTOR)(IUnknown *pUnkOuter, IUnknown **ppvOut);

static const struct {
    REFCLSID clsid;
    LPFNCONSTRUCTOR ctor;
} ClassesTable[] = {
    {&CLSID_TF_ThreadMgr, ThreadMgr_Constructor},
    {&CLSID_TF_InputProcessorProfiles, InputProcessorProfiles_Constructor},
    {&CLSID_TF_CategoryMgr, CategoryMgr_Constructor},
    {&CLSID_TF_LangBarMgr, LangBarMgr_Constructor},
    {&CLSID_TF_DisplayAttributeMgr, DisplayAttributeMgr_Constructor},
    {NULL, NULL}
};

typedef struct tagClassFactory
{
    IClassFactory IClassFactory_iface;
    LONG   ref;
    LPFNCONSTRUCTOR ctor;
} ClassFactory;

static inline ClassFactory *impl_from_IClassFactory(IClassFactory *iface)
{
    return CONTAINING_RECORD(iface, ClassFactory, IClassFactory_iface);
}

static void ClassFactory_Destructor(ClassFactory *This)
{
    TRACE("Destroying class factory %p\n", This);
    HeapFree(GetProcessHeap(),0,This);
}

static HRESULT WINAPI ClassFactory_QueryInterface(IClassFactory *iface, REFIID riid, LPVOID *ppvOut)
{
    *ppvOut = NULL;
    if (IsEqualIID(riid, &IID_IClassFactory) || IsEqualIID(riid, &IID_IUnknown)) {
        IClassFactory_AddRef(iface);
        *ppvOut = iface;
        return S_OK;
    }

    WARN("Unknown interface %s\n", debugstr_guid(riid));
    return E_NOINTERFACE;
}

static ULONG WINAPI ClassFactory_AddRef(IClassFactory *iface)
{
    ClassFactory *This = impl_from_IClassFactory(iface);
    return InterlockedIncrement(&This->ref);
}

static ULONG WINAPI ClassFactory_Release(IClassFactory *iface)
{
    ClassFactory *This = impl_from_IClassFactory(iface);
    ULONG ret = InterlockedDecrement(&This->ref);

    if (ret == 0)
        ClassFactory_Destructor(This);
    return ret;
}

static HRESULT WINAPI ClassFactory_CreateInstance(IClassFactory *iface, IUnknown *punkOuter, REFIID iid, LPVOID *ppvOut)
{
    ClassFactory *This = impl_from_IClassFactory(iface);
    HRESULT ret;
    IUnknown *obj;

    TRACE("(%p, %p, %s, %p)\n", iface, punkOuter, debugstr_guid(iid), ppvOut);
    ret = This->ctor(punkOuter, &obj);
    if (FAILED(ret))
        return ret;
    ret = IUnknown_QueryInterface(obj, iid, ppvOut);
    IUnknown_Release(obj);
    return ret;
}

static HRESULT WINAPI ClassFactory_LockServer(IClassFactory *iface, BOOL fLock)
{
    ClassFactory *This = impl_from_IClassFactory(iface);

    TRACE("(%p)->(%x)\n", This, fLock);

    return S_OK;
}

static const IClassFactoryVtbl ClassFactoryVtbl = {
    /* IUnknown */
    ClassFactory_QueryInterface,
    ClassFactory_AddRef,
    ClassFactory_Release,

    /* IClassFactory*/
    ClassFactory_CreateInstance,
    ClassFactory_LockServer
};

static HRESULT ClassFactory_Constructor(LPFNCONSTRUCTOR ctor, LPVOID *ppvOut)
{
    ClassFactory *This = HeapAlloc(GetProcessHeap(),0,sizeof(ClassFactory));
    This->IClassFactory_iface.lpVtbl = &ClassFactoryVtbl;
    This->ref = 1;
    This->ctor = ctor;
    *ppvOut = This;
    TRACE("Created class factory %p\n", This);
    return S_OK;
}

/*************************************************************************
 * DWORD Cookie Management
 */
DWORD generate_Cookie(DWORD magic, LPVOID data)
{
    UINT i;

    /* try to reuse IDs if possible */
    for (i = 0; i < id_last; i++)
        if (cookies[i].id == 0) break;

    if (i == array_size)
    {
        if (!array_size)
        {
            cookies = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(CookieInternal) * 10);
            if (!cookies)
            {
                ERR("Out of memory, Unable to alloc cookies array\n");
                return 0;
            }
            array_size = 10;
        }
        else
        {
            CookieInternal *new_cookies = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cookies,
                                                      sizeof(CookieInternal) * (array_size * 2));
            if (!new_cookies)
            {
                ERR("Out of memory, Unable to realloc cookies array\n");
                return 0;
            }
            cookies = new_cookies;
            array_size *= 2;
        }
    }

    cookies[i].id = i + 1; /* a return of 0 is used for failure */
    cookies[i].magic = magic;
    cookies[i].data = data;

    if (i == id_last)
        id_last++;

    return cookies[i].id;
}

DWORD get_Cookie_magic(DWORD id)
{
    UINT index = id - 1;

    if (index >= id_last)
        return 0;

    if (cookies[index].id == 0)
        return 0;

    return cookies[index].magic;
}

LPVOID get_Cookie_data(DWORD id)
{
    UINT index = id - 1;

    if (index >= id_last)
        return NULL;

    if (cookies[index].id == 0)
        return NULL;

    return cookies[index].data;
}

LPVOID remove_Cookie(DWORD id)
{
    UINT index = id - 1;

    if (index >= id_last)
        return NULL;

    if (cookies[index].id == 0)
        return NULL;

    cookies[index].id = 0;
    return cookies[index].data;
}

DWORD enumerate_Cookie(DWORD magic, DWORD *index)
{
    unsigned int i;
    for (i = *index; i < id_last; i++)
        if (cookies[i].id != 0 && cookies[i].magic == magic)
        {
            *index = (i+1);
            return cookies[i].id;
        }
    return 0x0;
}

/*****************************************************************************
 * Active Text Service Management
 *****************************************************************************/
static HRESULT activate_given_ts(ActivatedTextService *actsvr, ITfThreadMgr* tm)
{
    HRESULT hr;

    /* Already Active? */
    if (actsvr->pITfTextInputProcessor)
        return S_OK;

    hr = CoCreateInstance (&actsvr->LanguageProfile.clsid, NULL, CLSCTX_INPROC_SERVER,
        &IID_ITfTextInputProcessor, (void**)&actsvr->pITfTextInputProcessor);
    if (FAILED(hr)) return hr;

    hr = ITfTextInputProcessor_Activate(actsvr->pITfTextInputProcessor, tm, actsvr->tid);
    if (FAILED(hr))
    {
        ITfTextInputProcessor_Release(actsvr->pITfTextInputProcessor);
        actsvr->pITfTextInputProcessor = NULL;
        return hr;
    }

    actsvr->pITfThreadMgr = tm;
    ITfThreadMgr_AddRef(tm);
    return hr;
}

static HRESULT deactivate_given_ts(ActivatedTextService *actsvr)
{
    HRESULT hr = S_OK;

    if (actsvr->pITfTextInputProcessor)
    {
        hr = ITfTextInputProcessor_Deactivate(actsvr->pITfTextInputProcessor);
        ITfTextInputProcessor_Release(actsvr->pITfTextInputProcessor);
        ITfThreadMgr_Release(actsvr->pITfThreadMgr);
        actsvr->pITfTextInputProcessor = NULL;
        actsvr->pITfThreadMgr = NULL;
    }

    return hr;
}

static void deactivate_remove_conflicting_ts(REFCLSID catid)
{
    AtsEntry *ats, *cursor2;

    LIST_FOR_EACH_ENTRY_SAFE(ats, cursor2, &AtsList, AtsEntry, entry)
    {
        if (IsEqualCLSID(catid,&ats->ats->LanguageProfile.catid))
        {
            deactivate_given_ts(ats->ats);
            list_remove(&ats->entry);
            HeapFree(GetProcessHeap(),0,ats->ats);
            HeapFree(GetProcessHeap(),0,ats);
            /* we are guarenteeing there is only 1 */
            break;
        }
    }
}

HRESULT add_active_textservice(TF_LANGUAGEPROFILE *lp)
{
    ActivatedTextService *actsvr;
    ITfCategoryMgr *catmgr;
    AtsEntry *entry;
    ITfThreadMgr *tm = TlsGetValue(tlsIndex);
    ITfClientId *clientid;

    if (!tm) return E_UNEXPECTED;

    actsvr = HeapAlloc(GetProcessHeap(),0,sizeof(ActivatedTextService));
    if (!actsvr) return E_OUTOFMEMORY;

    ITfThreadMgr_QueryInterface(tm,&IID_ITfClientId,(LPVOID)&clientid);
    ITfClientId_GetClientId(clientid, &lp->clsid, &actsvr->tid);
    ITfClientId_Release(clientid);

    if (!actsvr->tid)
    {
        HeapFree(GetProcessHeap(),0,actsvr);
        return E_OUTOFMEMORY;
    }

    actsvr->pITfTextInputProcessor = NULL;
    actsvr->LanguageProfile = *lp;
    actsvr->pITfKeyEventSink = NULL;

    /* get TIP category */
    if (SUCCEEDED(CategoryMgr_Constructor(NULL,(IUnknown**)&catmgr)))
    {
        static const GUID *list[3] = {&GUID_TFCAT_TIP_SPEECH, &GUID_TFCAT_TIP_KEYBOARD, &GUID_TFCAT_TIP_HANDWRITING};

        ITfCategoryMgr_FindClosestCategory(catmgr,
                &actsvr->LanguageProfile.clsid, &actsvr->LanguageProfile.catid,
                list, 3);

        ITfCategoryMgr_Release(catmgr);
    }
    else
    {
        ERR("CategoryMgr construction failed\n");
        actsvr->LanguageProfile.catid = GUID_NULL;
    }

    if (!IsEqualGUID(&actsvr->LanguageProfile.catid,&GUID_NULL))
        deactivate_remove_conflicting_ts(&actsvr->LanguageProfile.catid);

    if (activated > 0)
        activate_given_ts(actsvr, tm);

    entry = HeapAlloc(GetProcessHeap(),0,sizeof(AtsEntry));

    if (!entry)
    {
        HeapFree(GetProcessHeap(),0,actsvr);
        return E_OUTOFMEMORY;
    }

    entry->ats = actsvr;
    list_add_head(&AtsList, &entry->entry);

    return S_OK;
}

BOOL get_active_textservice(REFCLSID rclsid, TF_LANGUAGEPROFILE *profile)
{
    AtsEntry *ats;

    LIST_FOR_EACH_ENTRY(ats, &AtsList, AtsEntry, entry)
    {
        if (IsEqualCLSID(rclsid,&ats->ats->LanguageProfile.clsid))
        {
            if (profile)
                *profile = ats->ats->LanguageProfile;
            return TRUE;
        }
    }
    return FALSE;
}

HRESULT activate_textservices(ITfThreadMgr *tm)
{
    HRESULT hr = S_OK;
    AtsEntry *ats;

    activated ++;
    if (activated > 1)
        return S_OK;

    LIST_FOR_EACH_ENTRY(ats, &AtsList, AtsEntry, entry)
    {
        hr = activate_given_ts(ats->ats, tm);
        if (FAILED(hr))
            FIXME("Failed to activate text service\n");
    }
    return hr;
}

HRESULT deactivate_textservices(void)
{
    AtsEntry *ats;

    if (activated > 0)
        activated --;

    if (activated == 0)
        LIST_FOR_EACH_ENTRY(ats, &AtsList, AtsEntry, entry)
            deactivate_given_ts(ats->ats);

    return S_OK;
}

CLSID get_textservice_clsid(TfClientId tid)
{
    AtsEntry *ats;

    LIST_FOR_EACH_ENTRY(ats, &AtsList, AtsEntry, entry)
        if (ats->ats->tid == tid)
            return ats->ats->LanguageProfile.clsid;
    return GUID_NULL;
}

HRESULT get_textservice_sink(TfClientId tid, REFCLSID iid, IUnknown **sink)
{
    AtsEntry *ats;

    if (!IsEqualCLSID(iid,&IID_ITfKeyEventSink))
        return E_NOINTERFACE;

    LIST_FOR_EACH_ENTRY(ats, &AtsList, AtsEntry, entry)
        if (ats->ats->tid == tid)
        {
            *sink = (IUnknown*)ats->ats->pITfKeyEventSink;
            return S_OK;
        }

    return E_FAIL;
}

HRESULT set_textservice_sink(TfClientId tid, REFCLSID iid, IUnknown* sink)
{
    AtsEntry *ats;

    if (!IsEqualCLSID(iid,&IID_ITfKeyEventSink))
        return E_NOINTERFACE;

    LIST_FOR_EACH_ENTRY(ats, &AtsList, AtsEntry, entry)
        if (ats->ats->tid == tid)
        {
            ats->ats->pITfKeyEventSink = (ITfKeyEventSink*)sink;
            return S_OK;
        }

    return E_FAIL;
}

/*************************************************************************
 * MSCTF DllMain
 */
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD fdwReason, LPVOID fImpLoad)
{
    TRACE("%p 0x%x %p\n", hinst, fdwReason, fImpLoad);
    switch (fdwReason)
    {
        case DLL_WINE_PREATTACH:
            return FALSE;   /* prefer native version */
        case DLL_PROCESS_ATTACH:
            MSCTF_hinstance = hinst;
            tlsIndex = TlsAlloc();
            break;
        case DLL_PROCESS_DETACH:
            if (fImpLoad) break;
            TlsFree(tlsIndex);
            break;
    }
    return TRUE;
}

/*************************************************************************
 *              DllCanUnloadNow (MSCTF.@)
 */
HRESULT WINAPI DllCanUnloadNow(void)
{
    return S_FALSE;
}

/***********************************************************************
 *              DllGetClassObject (MSCTF.@)
 */
HRESULT WINAPI DllGetClassObject(REFCLSID clsid, REFIID iid, LPVOID *ppvOut)
{
    int i;

    *ppvOut = NULL;
    if (!IsEqualIID(iid, &IID_IUnknown) && !IsEqualIID(iid, &IID_IClassFactory))
        return E_NOINTERFACE;

    for (i = 0; ClassesTable[i].clsid != NULL; i++)
        if (IsEqualCLSID(ClassesTable[i].clsid, clsid)) {
            return ClassFactory_Constructor(ClassesTable[i].ctor, ppvOut);
        }
    FIXME("CLSID %s not supported\n", debugstr_guid(clsid));
    return CLASS_E_CLASSNOTAVAILABLE;
}

/***********************************************************************
 *		DllRegisterServer (MSCTF.@)
 */
HRESULT WINAPI DllRegisterServer(void)
{
    return __wine_register_resources( MSCTF_hinstance );
}

/***********************************************************************
 *		DllUnregisterServer (MSCTF.@)
 */
HRESULT WINAPI DllUnregisterServer(void)
{
    return __wine_unregister_resources( MSCTF_hinstance );
}

/***********************************************************************
 *              TF_CreateThreadMgr (MSCTF.@)
 */
HRESULT WINAPI TF_CreateThreadMgr(ITfThreadMgr **pptim)
{
    TRACE("\n");
    return ThreadMgr_Constructor(NULL,(IUnknown**)pptim);
}

/***********************************************************************
 *              TF_GetThreadMgr (MSCTF.@)
 */
HRESULT WINAPI TF_GetThreadMgr(ITfThreadMgr **pptim)
{
    TRACE("\n");
    *pptim = TlsGetValue(tlsIndex);

    if (*pptim)
        ITfThreadMgr_AddRef(*pptim);

    return S_OK;
}

/***********************************************************************
 *              SetInputScope(MSCTF.@)
 */
HRESULT WINAPI SetInputScope(HWND hwnd, InputScope inputscope)
{
    FIXME("STUB: %p %i\n",hwnd,inputscope);
    return S_OK;
}

/***********************************************************************
 *              SetInputScopes(MSCTF.@)
 */
HRESULT WINAPI SetInputScopes(HWND hwnd, const InputScope *pInputScopes,
                              UINT cInputScopes, WCHAR **ppszPhraseList,
                              UINT cPhrases, WCHAR *pszRegExp, WCHAR *pszSRGS)
{
    UINT i;
    FIXME("STUB: %p ... %s %s\n",hwnd, debugstr_w(pszRegExp), debugstr_w(pszSRGS));
    for (i = 0; i < cInputScopes; i++)
        TRACE("\tScope[%u] = %i\n",i,pInputScopes[i]);
    for (i = 0; i < cPhrases; i++)
        TRACE("\tPhrase[%u] = %s\n",i,debugstr_w(ppszPhraseList[i]));

    return S_OK;
}

/***********************************************************************
 *              TF_CreateInputProcessorProfiles(MSCTF.@)
 */
HRESULT WINAPI TF_CreateInputProcessorProfiles(
                        ITfInputProcessorProfiles **ppipr)
{
    return InputProcessorProfiles_Constructor(NULL,(IUnknown**)ppipr);
}

/***********************************************************************
 *              TF_InvalidAssemblyListCacheIfExist(MSCTF.@)
 */
HRESULT WINAPI TF_InvalidAssemblyListCacheIfExist(void)
{
    FIXME("Stub\n");
    return S_OK;
}

/***********************************************************************
 *              TF_CreateLangBarMgr (MSCTF.@)
 */
HRESULT WINAPI TF_CreateLangBarMgr(ITfLangBarMgr **pppbm)
{
    TRACE("\n");
    return LangBarMgr_Constructor(NULL,(IUnknown**)pppbm);
}

HRESULT WINAPI TF_CreateLangBarItemMgr(ITfLangBarItemMgr **pplbim)
{
    FIXME("stub %p\n", pplbim);
    *pplbim = NULL;

    return E_NOTIMPL;
}

/***********************************************************************
 *              TF_InitMlngInfo (MSCTF.@)
 */
HRESULT WINAPI TF_InitMlngInfo(void)
{
    FIXME("stub\n");
    return S_OK;
}