/*
 * Implementation of the Microsoft Installer (msi.dll)
 *
 * Copyright 2005 Aric Stewart for 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 <stdarg.h>

#define COBJMACROS
#define NONAMELESSUNION

#include "windef.h"
#include "winbase.h"
#include "winreg.h"
#include "winnls.h"
#include "shlwapi.h"
#include "wine/debug.h"
#include "msi.h"
#include "msiquery.h"
#include "msipriv.h"
#include "wincrypt.h"
#include "winver.h"
#include "winuser.h"
#include "wine/unicode.h"
#include "sddl.h"

WINE_DEFAULT_DEBUG_CHANNEL(msi);

/*
 * These apis are defined in MSI 3.0
 */

typedef struct tagMediaInfo
{
    LPWSTR  path;
    WCHAR   szIndex[10];
    WCHAR   type;
} media_info;

static UINT OpenSourceKey(LPCWSTR szProduct, HKEY* key, BOOL user, BOOL create)
{
    HKEY rootkey = 0; 
    UINT rc; 
    static const WCHAR szSourceList[] = {'S','o','u','r','c','e','L','i','s','t',0};

    if (user)
        rc = MSIREG_OpenUserProductsKey(szProduct, &rootkey, create);
    else
        rc = MSIREG_OpenProductsKey(szProduct, &rootkey, create);

    if (rc)
        return rc;

    if (create)
        rc = RegCreateKeyW(rootkey, szSourceList, key);
    else
        rc = RegOpenKeyW(rootkey,szSourceList, key); 

    return rc;
}

static UINT OpenMediaSubkey(HKEY rootkey, HKEY *key, BOOL create)
{
    UINT rc;
    static const WCHAR media[] = {'M','e','d','i','a',0};

    if (create)
        rc = RegCreateKeyW(rootkey, media, key);
    else
        rc = RegOpenKeyW(rootkey,media, key); 

    return rc;
}

static UINT OpenNetworkSubkey(HKEY rootkey, HKEY *key, BOOL create)
{
    UINT rc;
    static const WCHAR net[] = {'N','e','t',0};

    if (create)
        rc = RegCreateKeyW(rootkey, net, key);
    else
        rc = RegOpenKeyW(rootkey, net, key); 

    return rc;
}

static UINT OpenURLSubkey(HKEY rootkey, HKEY *key, BOOL create)
{
    UINT rc;
    static const WCHAR URL[] = {'U','R','L',0};

    if (create)
        rc = RegCreateKeyW(rootkey, URL, key);
    else
        rc = RegOpenKeyW(rootkey, URL, key); 

    return rc;
}


static UINT find_given_source(HKEY key, LPCWSTR szSource, media_info *ss)
{
    DWORD index = 0;
    WCHAR szIndex[10];
    DWORD size;
    DWORD val_size;
    LPWSTR val;
    UINT rc = ERROR_SUCCESS;

    while (rc == ERROR_SUCCESS)
    {
        val = NULL;
        val_size = 0;
        size = sizeof(szIndex)/sizeof(szIndex[0]);
        rc = RegEnumValueW(key, index, szIndex, &size, NULL, NULL, NULL, &val_size);
        if (rc != ERROR_NO_MORE_ITEMS)
        {
            val = msi_alloc(val_size);
            RegEnumValueW(key, index, szIndex, &size, NULL, NULL, (LPBYTE)val, 
                &val_size);
            if (lstrcmpiW(szSource,val)==0)
            {
                ss->path = val;
                strcpyW(ss->szIndex,szIndex);
                break;
            }
            else
                strcpyW(ss->szIndex,szIndex);

            msi_free(val);
            index ++;
        }
    }
    return rc;
}

/******************************************************************
 *  MsiSourceListGetInfoW   (MSI.@)
 */
UINT WINAPI MsiSourceListGetInfoW( LPCWSTR szProduct, LPCWSTR szUserSid,
                                   MSIINSTALLCONTEXT dwContext, DWORD dwOptions,
                                   LPCWSTR szProperty, LPWSTR szValue, 
                                   LPDWORD pcchValue) 
{
    HKEY sourcekey;
    UINT rc;

    TRACE("%s %s\n", debugstr_w(szProduct), debugstr_w(szProperty));

    if (!szProduct || lstrlenW(szProduct) > 39)
        return ERROR_INVALID_PARAMETER;

    if (szValue && !pcchValue)
        return ERROR_INVALID_PARAMETER;
    
    if (dwOptions == MSICODE_PATCH)
    {
        FIXME("Unhandled options MSICODE_PATCH\n");
        return ERROR_FUNCTION_FAILED;
    }
    
    if (szUserSid)
        FIXME("Unhandled UserSid %s\n",debugstr_w(szUserSid));

    if (dwContext == MSIINSTALLCONTEXT_USERUNMANAGED)
        FIXME("Unknown context MSIINSTALLCONTEXT_USERUNMANAGED\n");

    if (dwContext == MSIINSTALLCONTEXT_MACHINE)
        rc = OpenSourceKey(szProduct, &sourcekey, FALSE, FALSE);
    else
        rc = OpenSourceKey(szProduct, &sourcekey, TRUE, FALSE);

    if (rc != ERROR_SUCCESS)
        return ERROR_UNKNOWN_PRODUCT;

    if (strcmpW(szProperty, INSTALLPROPERTY_MEDIAPACKAGEPATHW) == 0)
    {
        HKEY key;
        rc = OpenMediaSubkey(sourcekey, &key, FALSE);
        if (rc == ERROR_SUCCESS)
            rc = RegQueryValueExW(key, INSTALLPROPERTY_MEDIAPACKAGEPATHW,
                    0, 0, (LPBYTE)szValue, pcchValue);
        if (rc != ERROR_SUCCESS && rc != ERROR_MORE_DATA)
            rc = ERROR_UNKNOWN_PROPERTY;
        RegCloseKey(key);
    }
    else if (strcmpW(szProperty, INSTALLPROPERTY_DISKPROMPTW) ==0)
    {
        HKEY key;
        rc = OpenMediaSubkey(sourcekey, &key, FALSE);
        if (rc == ERROR_SUCCESS)
            rc = RegQueryValueExW(key, INSTALLPROPERTY_DISKPROMPTW, 0, 0,
                    (LPBYTE)szValue, pcchValue);
        if (rc != ERROR_SUCCESS && rc != ERROR_MORE_DATA)
            rc = ERROR_UNKNOWN_PROPERTY;
        RegCloseKey(key);
    }
    else if (strcmpW(szProperty, INSTALLPROPERTY_LASTUSEDSOURCEW)==0)
    {
        LPWSTR buffer;
        DWORD size = 0;

        RegQueryValueExW(sourcekey, INSTALLPROPERTY_LASTUSEDSOURCEW, 0, 0,
                NULL, &size);
        if (size == 0)
            rc = ERROR_UNKNOWN_PROPERTY;
        else
        {
            LPWSTR ptr;
            buffer = msi_alloc(size);
            rc = RegQueryValueExW(sourcekey, INSTALLPROPERTY_LASTUSEDSOURCEW,
                    0, 0, (LPBYTE)buffer,&size); 
            ptr = strchrW(buffer,';');
            if (ptr) ptr = strchrW(ptr+1,';');
            if (!ptr)
                rc = ERROR_UNKNOWN_PROPERTY;
            else
            {
                ptr ++;
                lstrcpynW(szValue, ptr, *pcchValue);
                if (lstrlenW(ptr) > *pcchValue)
                {
                    *pcchValue = lstrlenW(ptr)+1;
                    rc = ERROR_MORE_DATA;
                }
                else
                    rc = ERROR_SUCCESS;
            }
            msi_free(buffer);
        }
    }
    else if (strcmpW(INSTALLPROPERTY_LASTUSEDTYPEW, szProperty)==0)
    {
        LPWSTR buffer;
        DWORD size = 0;

        RegQueryValueExW(sourcekey, INSTALLPROPERTY_LASTUSEDSOURCEW, 0, 0,
                NULL, &size);
        if (size == 0)
            rc = ERROR_UNKNOWN_PROPERTY;
        else
        {
            buffer = msi_alloc(size);
            rc = RegQueryValueExW(sourcekey, INSTALLPROPERTY_LASTUSEDSOURCEW,
                    0, 0, (LPBYTE)buffer,&size); 
            if (*pcchValue < 1)
            {
                rc = ERROR_MORE_DATA;
                *pcchValue = 1;
            }
            else
            {
                szValue[0] = buffer[0];
                rc = ERROR_SUCCESS;
            }
            msi_free(buffer);
        }
    }
    else if (strcmpW(INSTALLPROPERTY_PACKAGENAMEW, szProperty)==0)
    {
        rc = RegQueryValueExW(sourcekey, INSTALLPROPERTY_PACKAGENAMEW, 0, 0, 
                (LPBYTE)szValue, pcchValue);
        if (rc != ERROR_SUCCESS && rc != ERROR_MORE_DATA)
            rc = ERROR_UNKNOWN_PROPERTY;
    }
    else
    {
        FIXME("Unknown property %s\n",debugstr_w(szProperty));
        rc = ERROR_UNKNOWN_PROPERTY;
    }

    RegCloseKey(sourcekey);
    return rc;
}

/******************************************************************
 *  MsiSourceListSetInfoW   (MSI.@)
 */
UINT WINAPI MsiSourceListSetInfoW( LPCWSTR szProduct, LPCWSTR szUserSid,
                                   MSIINSTALLCONTEXT dwContext, DWORD dwOptions,
                                   LPCWSTR szProperty, LPCWSTR szValue)
{
    HKEY sourcekey;
    UINT rc;

    TRACE("%s %s %x %x %s %s\n", debugstr_w(szProduct), debugstr_w(szUserSid),
            dwContext, dwOptions, debugstr_w(szProperty), debugstr_w(szValue));

    if (!szProduct || lstrlenW(szProduct) > 39)
        return ERROR_INVALID_PARAMETER;

    if (dwOptions & MSICODE_PATCH)
    {
        FIXME("Unhandled options MSICODE_PATCH\n");
        return ERROR_FUNCTION_FAILED;
    }
    
    if (szUserSid)
        FIXME("Unhandled UserSid %s\n",debugstr_w(szUserSid));

    if (dwContext == MSIINSTALLCONTEXT_USERUNMANAGED)
        FIXME("Unknown context MSIINSTALLCONTEXT_USERUNMANAGED\n");

    if (dwContext == MSIINSTALLCONTEXT_MACHINE)
        rc = OpenSourceKey(szProduct, &sourcekey, FALSE, TRUE);
    else
        rc = OpenSourceKey(szProduct, &sourcekey, TRUE, TRUE);

    if (rc != ERROR_SUCCESS)
        return ERROR_UNKNOWN_PRODUCT;


    if (strcmpW(szProperty, INSTALLPROPERTY_MEDIAPACKAGEPATHW) == 0)
    {
        HKEY key;
        DWORD size = lstrlenW(szValue)*sizeof(WCHAR);
        rc = OpenMediaSubkey(sourcekey, &key, FALSE);
        if (rc == ERROR_SUCCESS)
            rc = RegSetValueExW(key, INSTALLPROPERTY_MEDIAPACKAGEPATHW, 0,
                    REG_SZ, (const BYTE *)szValue, size);
        if (rc != ERROR_SUCCESS)
            rc = ERROR_UNKNOWN_PROPERTY;
        RegCloseKey(key);
    }
    else if (strcmpW(szProperty, INSTALLPROPERTY_DISKPROMPTW) == 0)
    {
        HKEY key;
        DWORD size = lstrlenW(szValue)*sizeof(WCHAR);
        rc = OpenMediaSubkey(sourcekey, &key, FALSE);
        if (rc == ERROR_SUCCESS)
            rc = RegSetValueExW(key, INSTALLPROPERTY_DISKPROMPTW, 0,
                    REG_SZ, (const BYTE *)szValue, size);
        if (rc != ERROR_SUCCESS)
            rc = ERROR_UNKNOWN_PROPERTY;
        RegCloseKey(key);
    }
    else if (strcmpW(szProperty, INSTALLPROPERTY_LASTUSEDSOURCEW)==0)
    {
        LPWSTR buffer = NULL;
        DWORD size;
        WCHAR typechar = 'n';
        static const WCHAR LastUsedSource_Fmt[] = {'%','c',';','%','i',';','%','s',0};

        /* make sure the source is registered */
        MsiSourceListAddSourceExW(szProduct, szUserSid, dwContext, 
                dwOptions, szValue, 0); 

        if (dwOptions & MSISOURCETYPE_NETWORK)
            typechar = 'n';
        else if (dwOptions & MSISOURCETYPE_URL)
            typechar = 'u';
        else if (dwOptions & MSISOURCETYPE_MEDIA)
            typechar = 'm';
        else
            ERR("Unknown source type! %x\n", dwOptions);

        size = (lstrlenW(szValue)+5)*sizeof(WCHAR);
        buffer = msi_alloc(size);
        sprintfW(buffer, LastUsedSource_Fmt, typechar, 1, szValue);
        rc = RegSetValueExW(sourcekey, INSTALLPROPERTY_LASTUSEDSOURCEW, 0, 
                REG_EXPAND_SZ, (LPBYTE)buffer, size);
        if (rc != ERROR_SUCCESS)
            rc = ERROR_UNKNOWN_PROPERTY;
        msi_free( buffer );
    }
    else if (strcmpW(INSTALLPROPERTY_PACKAGENAMEW, szProperty)==0)
    {
        DWORD size = lstrlenW(szValue)*sizeof(WCHAR);
        rc = RegSetValueExW(sourcekey, INSTALLPROPERTY_PACKAGENAMEW, 0,
                REG_SZ, (const BYTE *)szValue, size);
        if (rc != ERROR_SUCCESS)
            rc = ERROR_UNKNOWN_PROPERTY;
    }
    else
    {
        FIXME("Unknown property %s\n",debugstr_w(szProperty));
        rc = ERROR_UNKNOWN_PROPERTY;
    }

    RegCloseKey(sourcekey);
    return rc;

}

/******************************************************************
 *  MsiSourceListAddSourceW (MSI.@)
 */
UINT WINAPI MsiSourceListAddSourceW( LPCWSTR szProduct, LPCWSTR szUserName,
        DWORD dwReserved, LPCWSTR szSource)
{
    INT ret;
    LPWSTR sidstr = NULL;
    DWORD sidsize = 0;
    DWORD domsize = 0;

    TRACE("%s %s %s\n", debugstr_w(szProduct), debugstr_w(szUserName), debugstr_w(szSource));

    if (LookupAccountNameW(NULL, szUserName, NULL, &sidsize, NULL, &domsize, NULL))
    {
        PSID psid = msi_alloc(sidsize);

        if (LookupAccountNameW(NULL, szUserName, psid, &sidsize, NULL, &domsize, NULL))
            ConvertSidToStringSidW(psid, &sidstr);

        msi_free(psid);
    }

    ret = MsiSourceListAddSourceExW(szProduct, sidstr, 
        MSIINSTALLCONTEXT_USERMANAGED, MSISOURCETYPE_NETWORK, szSource, 0);

    if (sidstr)
        LocalFree(sidstr);

    return ret;
}

/******************************************************************
 *  MsiSourceListAddSourceA (MSI.@)
 */
UINT WINAPI MsiSourceListAddSourceA( LPCSTR szProduct, LPCSTR szUserName,
        DWORD dwReserved, LPCSTR szSource)
{
    INT ret;
    LPWSTR szwproduct;
    LPWSTR szwusername;
    LPWSTR szwsource;

    szwproduct = strdupAtoW( szProduct );
    szwusername = strdupAtoW( szUserName );
    szwsource = strdupAtoW( szSource );

    ret = MsiSourceListAddSourceW(szwproduct, szwusername, 0, szwsource);

    msi_free(szwproduct);
    msi_free(szwusername);
    msi_free(szwsource);

    return ret;
}

/******************************************************************
 *  MsiSourceListAddSourceExW (MSI.@)
 */
UINT WINAPI MsiSourceListAddSourceExW( LPCWSTR szProduct, LPCWSTR szUserSid,
        MSIINSTALLCONTEXT dwContext, DWORD dwOptions, LPCWSTR szSource, 
        DWORD dwIndex)
{
    HKEY sourcekey;
    HKEY typekey;
    UINT rc;
    media_info source_struct;

    TRACE("%s %s %x %x %s %i\n", debugstr_w(szProduct), debugstr_w(szUserSid),
          dwContext, dwOptions, debugstr_w(szSource), dwIndex);

    if (!szProduct)
        return ERROR_INVALID_PARAMETER;

    if (!szSource)
        return ERROR_INVALID_PARAMETER;

    if (dwOptions & MSICODE_PATCH)
    {
        FIXME("Unhandled options MSICODE_PATCH\n");
        return ERROR_FUNCTION_FAILED;
    }

    if (szUserSid)
        FIXME("Unhandled UserSid %s\n",debugstr_w(szUserSid));

    if (dwContext == MSIINSTALLCONTEXT_USERUNMANAGED)
        FIXME("Unknown context MSIINSTALLCONTEXT_USERUNMANAGED\n");

    if (dwContext == MSIINSTALLCONTEXT_MACHINE)
        rc = OpenSourceKey(szProduct, &sourcekey, FALSE, TRUE);
    else
        rc = OpenSourceKey(szProduct, &sourcekey, TRUE, TRUE);

    if (rc != ERROR_SUCCESS)
        return ERROR_UNKNOWN_PRODUCT;

    if (dwOptions & MSISOURCETYPE_NETWORK)
        rc = OpenNetworkSubkey(sourcekey, &typekey, TRUE);
    else if (dwOptions & MSISOURCETYPE_URL)
        rc = OpenURLSubkey(sourcekey, &typekey, TRUE);
    else if (dwOptions & MSISOURCETYPE_MEDIA)
        rc = OpenMediaSubkey(sourcekey, &typekey, TRUE);
    else
    {
        ERR("unknown media type: %08x\n", dwOptions);
        RegCloseKey(sourcekey);
        return ERROR_FUNCTION_FAILED;
    }

    source_struct.szIndex[0] = 0;
    if (find_given_source(typekey, szSource, &source_struct)==ERROR_SUCCESS)
    {
        DWORD current_index = atoiW(source_struct.szIndex);
        /* found the source */
        if (dwIndex > 0 && current_index != dwIndex)
            FIXME("Need to reorder the sources!\n");
        msi_free( source_struct.path );
    }
    else
    {
        DWORD current_index = 0;
        static const WCHAR fmt[] = {'%','i',0};
        DWORD size = lstrlenW(szSource)*sizeof(WCHAR);

        if (source_struct.szIndex[0])
            current_index = atoiW(source_struct.szIndex);
        /* new source */
        if (dwIndex > 0 && dwIndex < current_index)
            FIXME("Need to reorder the sources!\n");

        current_index ++;
        sprintfW(source_struct.szIndex,fmt,current_index);
        rc = RegSetValueExW(typekey, source_struct.szIndex, 0, REG_EXPAND_SZ, 
                (const BYTE *)szSource, size);
    }

    RegCloseKey(typekey);
    RegCloseKey(sourcekey);
    return rc;
}

/******************************************************************
 *  MsiSourceListAddMediaDisk(MSI.@)
 */
UINT WINAPI MsiSourceListAddMediaDiskW(LPCWSTR szProduct, LPCWSTR szUserSid, 
        MSIINSTALLCONTEXT dwContext, DWORD dwOptions, DWORD dwDiskId, 
        LPCWSTR szVolumeLabel, LPCWSTR szDiskPrompt)
{
    HKEY sourcekey;
    HKEY mediakey;
    UINT rc;
    WCHAR szIndex[10];
    static const WCHAR fmt[] = {'%','i',0};
    static const WCHAR disk_fmt[] = {'%','s',';','%','s',0};
    static const WCHAR empty[1] = {0};
    LPCWSTR pt1,pt2;
    LPWSTR buffer;
    DWORD size;

    TRACE("%s %s %x %x %i %s %s\n", debugstr_w(szProduct),
            debugstr_w(szUserSid), dwContext, dwOptions, dwDiskId,
            debugstr_w(szVolumeLabel), debugstr_w(szDiskPrompt));

    if (!szProduct || lstrlenW(szProduct) > 39)
        return ERROR_INVALID_PARAMETER;

    if (dwOptions & MSICODE_PATCH)
    {
        FIXME("Unhandled options MSICODE_PATCH\n");
        return ERROR_FUNCTION_FAILED;
    }
    
    if (szUserSid)
        FIXME("Unhandled UserSid %s\n",debugstr_w(szUserSid));

    if (dwContext == MSIINSTALLCONTEXT_USERUNMANAGED)
        FIXME("Unknown context MSIINSTALLCONTEXT_USERUNMANAGED\n");

    if (dwContext == MSIINSTALLCONTEXT_MACHINE)
        rc = OpenSourceKey(szProduct, &sourcekey, FALSE, TRUE);
    else
        rc = OpenSourceKey(szProduct, &sourcekey, TRUE, TRUE);

    if (rc != ERROR_SUCCESS)
        return ERROR_UNKNOWN_PRODUCT;

    OpenMediaSubkey(sourcekey,&mediakey,TRUE);

    sprintfW(szIndex,fmt,dwDiskId);

    size = 2;
    if (szVolumeLabel)
    {
        size +=lstrlenW(szVolumeLabel);
        pt1 = szVolumeLabel;
    }
    else
        pt1 = empty;
    if (szDiskPrompt)
    {
        size +=lstrlenW(szDiskPrompt);
        pt2 = szDiskPrompt;
    }
    else
        pt2 = empty;

    size *=sizeof(WCHAR);

    buffer = msi_alloc(size);
    sprintfW(buffer,disk_fmt,pt1,pt2);

    RegSetValueExW(mediakey, szIndex, 0, REG_SZ, (LPBYTE)buffer, size);
    msi_free( buffer );

    RegCloseKey(sourcekey);
    RegCloseKey(mediakey);

    return ERROR_SUCCESS;
}

/******************************************************************
 *  MsiSourceListAddSourceExA (MSI.@)
 */
UINT WINAPI MsiSourceListClearAllA( LPCSTR szProduct, LPCSTR szUserName, DWORD dwReserved )
{
    FIXME("(%s %s %d)\n", debugstr_a(szProduct), debugstr_a(szUserName), dwReserved);
    return ERROR_SUCCESS;
}

/******************************************************************
 *  MsiSourceListAddSourceExW (MSI.@)
 */
UINT WINAPI MsiSourceListClearAllW( LPCWSTR szProduct, LPCWSTR szUserName, DWORD dwReserved )
{
    FIXME("(%s %s %d)\n", debugstr_w(szProduct), debugstr_w(szUserName), dwReserved);
    return ERROR_SUCCESS;
}