/*
 * SHLWAPI thread and MT synchronisation functions
 *
 * Copyright 2002 Juergen Schmied
 * Copyright 2002 Jon Griffiths
 *
 * 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 <stdarg.h>
#include <string.h>

#define COBJMACROS

#include "windef.h"
#include "winbase.h"
#include "winnls.h"
#include "winuser.h"
#define NO_SHLWAPI_REG
#define NO_SHLWAPI_PATH
#define NO_SHLWAPI_GDI
#define NO_SHLWAPI_STREAM
#define NO_SHLWAPI_USER
#include "shlwapi.h"
#include "shlobj.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(shell);

extern DWORD SHLWAPI_ThreadRef_index;  /* Initialised in shlwapi_main.c */

INT WINAPI SHStringFromGUIDA(REFGUID,LPSTR,INT);

/**************************************************************************
 *      CreateAllAccessSecurityAttributes       [SHLWAPI.356]
 *
 * Initialise security attributes from a security descriptor.
 *
 * PARAMS
 *  lpAttr [O] Security attributes
 *  lpSec  [I] Security descriptor
 *
 * RETURNS
 *  Success: lpAttr, initialised using lpSec.
 *  Failure: NULL, if any parameters are invalid.
 *
 * NOTES
 *  This function always returns NULL if the underlying OS version
 *  Wine is impersonating does not use security descriptors (i.e. anything
 *  before Windows NT).
 */
LPSECURITY_ATTRIBUTES WINAPI CreateAllAccessSecurityAttributes(
	LPSECURITY_ATTRIBUTES lpAttr,
	PSECURITY_DESCRIPTOR lpSec,
        DWORD p3)
{
  /* This function is used within SHLWAPI only to create security attributes
   * for shell semaphores. */

  TRACE("(%p,%p,%08x)\n", lpAttr, lpSec, p3);

  if (!(GetVersion() & 0x80000000))  /* NT */
  {
    if (!lpSec || !lpAttr)
      return NULL;

    if (InitializeSecurityDescriptor(lpSec, 1))
    {
      if (SetSecurityDescriptorDacl(lpSec, TRUE, NULL, FALSE))
      {
         lpAttr->nLength = sizeof(SECURITY_ATTRIBUTES);
         lpAttr->lpSecurityDescriptor = lpSec;
         lpAttr->bInheritHandle = FALSE;
         return lpAttr;
      }
    }
  }
  return NULL;
}

/*************************************************************************
 *      _SHGetInstanceExplorer	[SHLWAPI.@]
 *
 * Get an interface to the shell explorer.
 *
 * PARAMS
 *  lppUnknown [O] Destination for explorers IUnknown interface.
 *
 * RETURNS
 *  Success: S_OK. lppUnknown contains the explorer interface.
 *  Failure: An HRESULT error code.
 */
HRESULT WINAPI _SHGetInstanceExplorer(IUnknown **lppUnknown)
{
  /* This function is used within SHLWAPI only to hold the IE reference
   * for threads created with the CTF_PROCESS_REF flag set. */
    return SHGetInstanceExplorer(lppUnknown);
}

/* Internal thread information structure */
typedef struct tagSHLWAPI_THREAD_INFO
{
  LPTHREAD_START_ROUTINE pfnThreadProc; /* Thread start */
  LPTHREAD_START_ROUTINE pfnCallback;   /* Thread initialisation */
  PVOID     pData;                      /* Application specific data */
  BOOL      bInitCom;                   /* Initialise COM for the thread? */
  HANDLE    hEvent;                     /* Signal for creator to continue */
  IUnknown *refThread;                  /* Reference to thread creator */
  IUnknown *refIE;                      /* Reference to the IE process */
} SHLWAPI_THREAD_INFO, *LPSHLWAPI_THREAD_INFO;

typedef struct
{
  const IUnknownVtbl* lpVtbl;
  LONG  *ref;
} threadref;

static HRESULT WINAPI threadref_QueryInterface(IUnknown *iface, REFIID riid, LPVOID *ppvObj)
{
  threadref * This = (threadref *)iface;

  TRACE("(%p, %s, %p)\n", This, debugstr_guid(riid), ppvObj);

  if (ppvObj == NULL)
    return E_POINTER;

  if (IsEqualGUID(&IID_IUnknown, riid)) {
    TRACE("(%p)->(IID_IUnknown %p)\n", This, ppvObj);
    *ppvObj = This;
    IUnknown_AddRef((IUnknown*)*ppvObj);
    return S_OK;
  }

  *ppvObj = NULL;
  FIXME("(%p, %s, %p) interface not supported\n", This, debugstr_guid(riid), ppvObj);
  return E_NOINTERFACE;
}

static ULONG WINAPI threadref_AddRef(IUnknown *iface)
{
  threadref * This = (threadref *)iface;

  TRACE("(%p)\n", This);
  return InterlockedIncrement(This->ref);
}

static ULONG WINAPI threadref_Release(IUnknown *iface)
{
  LONG refcount;
  threadref * This = (threadref *)iface;

  TRACE("(%p)\n", This);

  refcount = InterlockedDecrement(This->ref);
  if (!refcount)
      HeapFree(GetProcessHeap(), 0, This);

  return refcount;
}

/* VTable */
static const IUnknownVtbl threadref_vt =
{
  threadref_QueryInterface,
  threadref_AddRef,
  threadref_Release,
};

/*************************************************************************
 * SHCreateThreadRef [SHLWAPI.@]
 *
 * Create a per-thread IUnknown object
 *
 * PARAMS
 *   lprefcount [I] Pointer to a LONG to be used as refcount
 *   lppUnknown [O] Destination to receive the created object reference
 *
 * RETURNS
 *   Success: S_OK. lppUnknown is set to the object reference.
 *   Failure: E_INVALIDARG, if a parameter is NULL
 */
HRESULT WINAPI SHCreateThreadRef(LONG *lprefcount, IUnknown **lppUnknown)
{
  threadref * This;
  TRACE("(%p, %p)\n", lprefcount, lppUnknown);

  if (!lprefcount || !lppUnknown)
    return E_INVALIDARG;

  This = HeapAlloc(GetProcessHeap(), 0, sizeof(threadref));
  This->lpVtbl = &threadref_vt;
  This->ref = lprefcount;

  *lprefcount = 1;
  *lppUnknown = (IUnknown *) This;
  TRACE("=> returning S_OK with %p\n", This);
  return S_OK;
}

/*************************************************************************
 * SHGetThreadRef	[SHLWAPI.@]
 *
 * Get a per-thread object reference set by SHSetThreadRef().
 *
 * PARAMS
 *   lppUnknown [O] Destination to receive object reference
 *
 * RETURNS
 *   Success: S_OK. lppUnknown is set to the object reference.
 *   Failure: E_NOINTERFACE, if an error occurs or no object is set
 */
HRESULT WINAPI SHGetThreadRef(IUnknown **lppUnknown)
{
  TRACE("(%p)\n", lppUnknown);

  if (SHLWAPI_ThreadRef_index == TLS_OUT_OF_INDEXES)
    return E_NOINTERFACE;

  *lppUnknown = TlsGetValue(SHLWAPI_ThreadRef_index);
  if (!*lppUnknown)
    return E_NOINTERFACE;

  /* Add a reference. Caller will Release() us when finished */
  IUnknown_AddRef(*lppUnknown);
  return S_OK;
}

/*************************************************************************
 * SHSetThreadRef	[SHLWAPI.@]
 *
 * Store a per-thread object reference.
 *
 * PARAMS
 *   lpUnknown [I] Object reference to store
 *
 * RETURNS
 *   Success: S_OK. lpUnknown is stored and can be retrieved by SHGetThreadRef()
 *   Failure: E_NOINTERFACE, if an error occurs
 */
HRESULT WINAPI SHSetThreadRef(IUnknown *lpUnknown)
{
  TRACE("(%p)\n", lpUnknown);

  if (SHLWAPI_ThreadRef_index == TLS_OUT_OF_INDEXES)
    return E_NOINTERFACE;

  TlsSetValue(SHLWAPI_ThreadRef_index, lpUnknown);
  return S_OK;
}

/*************************************************************************
 * SHReleaseThreadRef	[SHLWAPI.@]
 *
 * Release a per-thread object reference.
 *
 * PARAMS
 *  None.
 *
 * RETURNS
 *   Success: S_OK. The threads object reference is released.
 *   Failure: An HRESULT error code.
 */
HRESULT WINAPI SHReleaseThreadRef(void)
{
  FIXME("() - stub!\n");
  return S_OK;
}

/*************************************************************************
 * SHLWAPI_ThreadWrapper
 *
 * Internal wrapper for executing user thread functions from SHCreateThread.
 */
static DWORD WINAPI SHLWAPI_ThreadWrapper(PVOID pTi)
{
  SHLWAPI_THREAD_INFO ti;
  HRESULT hCom = E_FAIL;
  DWORD dwRet;

  TRACE("(%p)\n", pTi);

  /* We are now executing in the context of the newly created thread.
   * So we copy the data passed to us (it is on the stack of the function
   * that called us, which is waiting for us to signal an event before
   * returning). */
  memcpy(&ti, pTi, sizeof(SHLWAPI_THREAD_INFO));

  /* Initialise COM for the thread, if desired */
  if (ti.bInitCom)
  {
    hCom = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED|COINIT_DISABLE_OLE1DDE);

    if (FAILED(hCom))
      hCom = CoInitializeEx(NULL, COINIT_DISABLE_OLE1DDE);
  }

  /* Execute the callback function before returning */
  if (ti.pfnCallback)
    ti.pfnCallback(ti.pData);

  /* Signal the thread that created us; it can return now */
  SetEvent(ti.hEvent);

  /* Execute the callers start code */
  dwRet = ti.pfnThreadProc(ti.pData);

  /* Release references to the caller and IE process, if held */
  if (ti.refThread)
    IUnknown_Release(ti.refThread);

  if (ti.refIE)
    IUnknown_Release(ti.refIE);

  if (SUCCEEDED(hCom))
    CoUninitialize();

  /* Return the users thread return value */
  return dwRet;
}

/*************************************************************************
 *      SHCreateThread	[SHLWAPI.16]
 *
 * Create a new thread.
 *
 * PARAMS
 *   pfnThreadProc [I] Function to execute in new thread
 *   pData         [I] Application specific data passed to pfnThreadProc
 *   dwFlags       [I] CTF_ flags from "shlwapi.h"
 *   pfnCallback   [I] Function to execute before pfnThreadProc
 *
 * RETURNS
 *   Success: TRUE. pfnThreadProc was executed.
 *   Failure: FALSE. pfnThreadProc was not executed.
 *
 * NOTES
 *   If the thread cannot be created, pfnCallback is NULL, and dwFlags
 *   has bit CTF_INSIST set, pfnThreadProc will be executed synchronously.
 */
BOOL WINAPI SHCreateThread(LPTHREAD_START_ROUTINE pfnThreadProc, VOID *pData,
                           DWORD dwFlags, LPTHREAD_START_ROUTINE pfnCallback)
{
  SHLWAPI_THREAD_INFO ti;
  BOOL bCalled = FALSE;

  TRACE("(%p,%p,0x%X,%p)\n", pfnThreadProc, pData, dwFlags, pfnCallback);

  /* Set up data to pass to the new thread (On our stack) */
  ti.pfnThreadProc = pfnThreadProc;
  ti.pfnCallback = pfnCallback;
  ti.pData = pData;
  ti.bInitCom = dwFlags & CTF_COINIT ? TRUE : FALSE;
  ti.hEvent = CreateEventW(NULL,FALSE,FALSE,NULL);

  /* Hold references to the current thread and IE process, if desired */
  if(dwFlags & CTF_THREAD_REF)
    SHGetThreadRef(&ti.refThread);
  else
    ti.refThread = NULL;

  if(dwFlags & CTF_PROCESS_REF)
    _SHGetInstanceExplorer(&ti.refIE);
  else
    ti.refIE = NULL;

  /* Create the thread */
  if(ti.hEvent)
  {
    DWORD dwRetVal;
    HANDLE hThread;

    hThread = CreateThread(NULL, 0, SHLWAPI_ThreadWrapper, &ti, 0, &dwRetVal);

    if(hThread)
    {
      /* Wait for the thread to signal us to continue */
      WaitForSingleObject(ti.hEvent, INFINITE);
      CloseHandle(hThread);
      bCalled = TRUE;
    }
    CloseHandle(ti.hEvent);
  }

  if (!bCalled)
  {
    if (!ti.pfnCallback && dwFlags & CTF_INSIST)
    {
      /* Couldn't call, call synchronously */
      pfnThreadProc(pData);
      bCalled = TRUE;
    }
    else
    {
      /* Free references, since thread hasn't run to do so */
      if(ti.refThread)
        IUnknown_Release(ti.refThread);

      if(ti.refIE)
        IUnknown_Release(ti.refIE);
    }
  }
  return bCalled;
}

/*************************************************************************
 *      SHGlobalCounterGetValue	[SHLWAPI.223]
 *
 * Get the current count of a semaphore.
 *
 * PARAMS
 *  hSem [I] Semaphore handle
 *
 * RETURNS
 *  The current count of the semaphore.
 */
LONG WINAPI SHGlobalCounterGetValue(HANDLE hSem)
{
  LONG dwOldCount = 0;

  TRACE("(%p)\n", hSem);
  ReleaseSemaphore(hSem, 1, &dwOldCount); /* +1 */
  WaitForSingleObject(hSem, 0);           /* -1 */
  return dwOldCount;
}

/*************************************************************************
 *      SHGlobalCounterIncrement	[SHLWAPI.224]
 *
 * Claim a semaphore.
 *
 * PARAMS
 *  hSem [I] Semaphore handle
 *
 * RETURNS
 *  The new count of the semaphore.
 */
LONG WINAPI SHGlobalCounterIncrement(HANDLE hSem)
{
  LONG dwOldCount = 0;

  TRACE("(%p)\n", hSem);
  ReleaseSemaphore(hSem, 1, &dwOldCount);
  return dwOldCount + 1;
}

/*************************************************************************
 *      SHGlobalCounterDecrement	[SHLWAPI.424]
 *
 * Release a semaphore.
 *
 * PARAMS
 *  hSem [I] Semaphore handle
 *
 * RETURNS
 *  The new count of the semaphore.
 */
DWORD WINAPI SHGlobalCounterDecrement(HANDLE hSem)
{
  DWORD dwOldCount = 0;

  TRACE("(%p)\n", hSem);

  dwOldCount = SHGlobalCounterGetValue(hSem);
  WaitForSingleObject(hSem, 0);
  return dwOldCount - 1;
}

/*************************************************************************
 *      SHGlobalCounterCreateNamedW	[SHLWAPI.423]
 *
 * Unicode version of SHGlobalCounterCreateNamedA.
 */
HANDLE WINAPI SHGlobalCounterCreateNamedW(LPCWSTR lpszName, DWORD iInitial)
{
  static const WCHAR szPrefix[] = { 's', 'h', 'e', 'l', 'l', '.', '\0' };
  const int iPrefixLen = 6;
  WCHAR szBuff[MAX_PATH];
  const int iBuffLen = sizeof(szBuff)/sizeof(WCHAR);
  SECURITY_DESCRIPTOR sd;
  SECURITY_ATTRIBUTES sAttr, *pSecAttr;
  HANDLE hRet;

  TRACE("(%s,%d)\n", debugstr_w(lpszName), iInitial);

  /* Create Semaphore name */
  memcpy(szBuff, szPrefix, (iPrefixLen + 1) * sizeof(WCHAR));
  if (lpszName)
    StrCpyNW(szBuff + iPrefixLen, lpszName, iBuffLen - iPrefixLen);

  /* Initialise security attributes */
  pSecAttr = CreateAllAccessSecurityAttributes(&sAttr, &sd, 0);

  if (!(hRet = CreateSemaphoreW(pSecAttr , iInitial, MAXLONG, szBuff)))
    hRet = OpenSemaphoreW(SYNCHRONIZE|SEMAPHORE_MODIFY_STATE, 0, szBuff);
  return hRet;
}

/*************************************************************************
 *      SHGlobalCounterCreateNamedA	[SHLWAPI.422]
 *
 * Create a semaphore.
 *
 * PARAMS
 *  lpszName [I] Name of semaphore
 *  iInitial [I] Initial count for semaphore
 *
 * RETURNS
 *  A new semaphore handle.
 */
HANDLE WINAPI SHGlobalCounterCreateNamedA(LPCSTR lpszName, DWORD iInitial)
{
  WCHAR szBuff[MAX_PATH];

  TRACE("(%s,%d)\n", debugstr_a(lpszName), iInitial);

  if (lpszName)
    MultiByteToWideChar(0, 0, lpszName, -1, szBuff, MAX_PATH);
  return SHGlobalCounterCreateNamedW(lpszName ? szBuff : NULL, iInitial);
}

/*************************************************************************
 *      SHGlobalCounterCreate	[SHLWAPI.222]
 *
 * Create a semaphore using the name of a GUID.
 *
 * PARAMS
 *  guid [I] GUID to use as semaphore name
 *
 * RETURNS
 *  A handle to the semaphore.
 *
 * NOTES
 *  The initial count of the semaphore is set to 0.
 */
HANDLE WINAPI SHGlobalCounterCreate (REFGUID guid)
{
  char szName[40];

  TRACE("(%s)\n", debugstr_guid(guid));

  /* Create a named semaphore using the GUID string */
  SHStringFromGUIDA(guid, szName, sizeof(szName) - 1);
  return SHGlobalCounterCreateNamedA(szName, 0);
}
