/* 
 * Setupapi cabinet routines
 *
 * Copyright 2003 Gregory M. Turner
 *
 * 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>
#include <stdlib.h>
#include <fcntl.h>
#include <share.h>

#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
#include "winreg.h"
#include "setupapi.h"
#include "setupapi_private.h"
#include "fdi.h"
#include "wine/debug.h"

OSVERSIONINFOW OsVersionInfo;

HINSTANCE SETUPAPI_hInstance = 0;

typedef struct
{
    PSP_FILE_CALLBACK_A msghandler;
    void *context;
    char cab_path[MAX_PATH];
    char last_cab[MAX_PATH];
    char most_recent_target[MAX_PATH];
} SC_HSC_A, *PSC_HSC_A;

WINE_DEFAULT_DEBUG_CHANNEL(setupapi);

static void * CDECL sc_cb_alloc(ULONG cb)
{
  return HeapAlloc(GetProcessHeap(), 0, cb);
}

static void CDECL sc_cb_free(void *pv)
{
  HeapFree(GetProcessHeap(), 0, pv);
}

static INT_PTR CDECL sc_cb_open(char *pszFile, int oflag, int pmode)
{
  DWORD creation = 0, sharing = 0;
  int ioflag = 0;
  SECURITY_ATTRIBUTES sa;

  switch(oflag & (_O_RDONLY | _O_WRONLY | _O_RDWR)) {
  case _O_RDONLY:
    ioflag |= GENERIC_READ;
    break;
  case _O_WRONLY:
    ioflag |= GENERIC_WRITE;
    break;
  case _O_RDWR:
    ioflag |= GENERIC_READ | GENERIC_WRITE;
    break;
  }

  if (oflag & _O_CREAT) {
    if (oflag & _O_EXCL)
      creation = CREATE_NEW;
    else if (oflag & _O_TRUNC)
      creation = CREATE_ALWAYS;
    else
      creation = OPEN_ALWAYS;
  } else {
    if (oflag & _O_TRUNC)
      creation = TRUNCATE_EXISTING;
    else
      creation = OPEN_EXISTING;
  }

  switch( pmode & 0x70 ) {
    case _SH_DENYRW:
      sharing = 0L;
      break;
    case _SH_DENYWR:
      sharing = FILE_SHARE_READ;
      break;
    case _SH_DENYRD:
      sharing = FILE_SHARE_WRITE;
      break;
    case _SH_COMPAT:
    case _SH_DENYNO:
      sharing = FILE_SHARE_READ | FILE_SHARE_WRITE;
      break;
  }

  sa.nLength              = sizeof( SECURITY_ATTRIBUTES );
  sa.lpSecurityDescriptor = NULL;
  sa.bInheritHandle       = !(ioflag & _O_NOINHERIT);

  return (INT_PTR) CreateFileA(pszFile, ioflag, sharing, &sa, creation, FILE_ATTRIBUTE_NORMAL, NULL);
}

static UINT CDECL sc_cb_read(INT_PTR hf, void *pv, UINT cb)
{
    DWORD num_read;

    if (!ReadFile((HANDLE)hf, pv, cb, &num_read, NULL))
        return -1;
    return num_read;
}

static UINT CDECL sc_cb_write(INT_PTR hf, void *pv, UINT cb)
{
    DWORD num_written;

    if (!WriteFile((HANDLE)hf, pv, cb, &num_written, NULL))
        return -1;
    return num_written;
}

static int CDECL sc_cb_close(INT_PTR hf)
{
    if (!CloseHandle((HANDLE)hf))
        return -1;
    return 0;
}

static LONG CDECL sc_cb_lseek(INT_PTR hf, LONG dist, int seektype)
{
    DWORD ret;

    if (seektype < 0 || seektype > 2)
        return -1;

    if (((ret = SetFilePointer((HANDLE)hf, dist, NULL, seektype)) == INVALID_SET_FILE_POINTER) && GetLastError())
        return -1;
    return ret;
}

#define SIZEOF_MYSTERIO (MAX_PATH*3)

static INT_PTR CDECL sc_FNNOTIFY_A(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin)
{
  FILE_IN_CABINET_INFO_A fici;
  PSC_HSC_A phsc = pfdin->pv;
  CABINET_INFO_A ci;
  FILEPATHS_A fp;
  UINT err;

  CHAR mysterio[SIZEOF_MYSTERIO]; /* how big? undocumented! probably 256... */

  memset(mysterio, 0, SIZEOF_MYSTERIO);

  switch (fdint) {
  case fdintCABINET_INFO:
    TRACE("New cabinet, path %s, set %u, number %u, next disk %s, next cabinet %s.\n",
            debugstr_a(pfdin->psz3), pfdin->setID, pfdin->iCabinet, debugstr_a(pfdin->psz2), debugstr_a(pfdin->psz1));
    ci.CabinetFile = pfdin->psz1;
    ci.CabinetPath = pfdin->psz3;
    ci.DiskName = pfdin->psz2;
    ci.SetId = pfdin->setID;
    ci.CabinetNumber = pfdin->iCabinet;
    phsc->msghandler(phsc->context, SPFILENOTIFY_CABINETINFO, (UINT_PTR) &ci, 0);
    return 0;
  case fdintPARTIAL_FILE:
    return 0;
  case fdintCOPY_FILE:
    TRACE("Copy file %s, length %d, date %#x, time %#x, attributes %#x.\n",
            debugstr_a(pfdin->psz1), pfdin->cb, pfdin->date, pfdin->time, pfdin->attribs);
    fici.NameInCabinet = pfdin->psz1;
    fici.FileSize = pfdin->cb;
    fici.Win32Error = 0;
    fici.DosDate = pfdin->date;
    fici.DosTime = pfdin->time;
    fici.DosAttribs = pfdin->attribs;
    memset(fici.FullTargetName, 0, MAX_PATH);
    err = phsc->msghandler(phsc->context, SPFILENOTIFY_FILEINCABINET,
                           (UINT_PTR)&fici, (UINT_PTR)phsc->last_cab);
    if (err == FILEOP_DOIT) {
      TRACE("Callback specified filename: %s\n", debugstr_a(fici.FullTargetName));
      if (!fici.FullTargetName[0]) {
        WARN("Empty return string causing abort.\n");
        SetLastError(ERROR_PATH_NOT_FOUND);
        return -1;
      }
      strcpy( phsc->most_recent_target, fici.FullTargetName );
      return sc_cb_open(fici.FullTargetName, _O_BINARY | _O_CREAT | _O_WRONLY,  _S_IREAD | _S_IWRITE);
    } else {
      TRACE("Callback skipped file.\n");
      return 0;
    }
  case fdintCLOSE_FILE_INFO:
    TRACE("File extracted.\n");
    fp.Source = phsc->last_cab;
    fp.Target = phsc->most_recent_target;
    fp.Win32Error = 0;
    fp.Flags = 0;
    /* FIXME: set file time and attributes */
    sc_cb_close(pfdin->hf);
    err = phsc->msghandler(phsc->context, SPFILENOTIFY_FILEEXTRACTED, (UINT_PTR)&fp, 0);
    if (err) {
      SetLastError(err);
      return FALSE;
    } else
      return TRUE;
  case fdintNEXT_CABINET:
    TRACE("Need new cabinet, path %s, file %s, disk %s, set %u, number %u.\n",
            debugstr_a(pfdin->psz3), debugstr_a(pfdin->psz1),
            debugstr_a(pfdin->psz2), pfdin->setID, pfdin->iCabinet);
    ci.CabinetFile = pfdin->psz1;
    ci.CabinetPath = pfdin->psz3;
    ci.DiskName = pfdin->psz2;
    ci.SetId = pfdin->setID;
    ci.CabinetNumber = pfdin->iCabinet;
    sprintf(phsc->last_cab, "%s%s", phsc->cab_path, ci.CabinetFile);
    err = phsc->msghandler(phsc->context, SPFILENOTIFY_NEEDNEWCABINET, (UINT_PTR)&ci, (UINT_PTR)mysterio);
    if (err) {
      SetLastError(err);
      return -1;
    } else {
      if (mysterio[0]) {
        /* some easy paranoia.  no such carefulness exists on the wide API IIRC */
        lstrcpynA(pfdin->psz3, mysterio, SIZEOF_MYSTERIO);
      }
      return 0;
    }
  default:
    FIXME("Unknown notification type %d.\n", fdint);
    return 0;
  }
}

/***********************************************************************
 *		SetupIterateCabinetA (SETUPAPI.@)
 */
BOOL WINAPI SetupIterateCabinetA(const char *file, DWORD reserved,
                                 PSP_FILE_CALLBACK_A callback, void *context)
{

    SC_HSC_A my_hsc;
    ERF erf;
    CHAR pszCabinet[MAX_PATH], pszCabPath[MAX_PATH], *filepart = NULL;
    size_t path_size = 0;
    const char *p;
    DWORD fpnsize;
    HFDI hfdi;
    BOOL ret;

    TRACE("file %s, reserved %#x, callback %p, context %p.\n",
            debugstr_a(file), reserved, callback, context);

    if (!file)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    if (strlen(file) >= MAX_PATH)
    {
        SetLastError(ERROR_BAD_PATHNAME);
        return FALSE;
    }

    fpnsize = GetFullPathNameA(file, MAX_PATH, pszCabPath, &filepart);
    if (fpnsize > MAX_PATH)
    {
        SetLastError(ERROR_BAD_PATHNAME);
        return FALSE;
    }

    if (filepart)
    {
        strcpy(pszCabinet, filepart);
        *filepart = '\0';
    }
    else
    {
        strcpy(pszCabinet, file);
        pszCabPath[0] = '\0';
    }

    for (p = file; *p; ++p)
    {
        if (*p == '/' || *p == '\\')
            path_size = p - file;
    }
    memcpy(my_hsc.cab_path, file, path_size);
    my_hsc.cab_path[path_size] = 0;

    TRACE("path: %s, cabfile: %s\n", debugstr_a(pszCabPath), debugstr_a(pszCabinet));

    strcpy(my_hsc.last_cab, file);

    my_hsc.msghandler = callback;
    my_hsc.context = context;
    hfdi = FDICreate(sc_cb_alloc, sc_cb_free, sc_cb_open, sc_cb_read,
            sc_cb_write, sc_cb_close, sc_cb_lseek, cpuUNKNOWN, &erf);

    if (!hfdi) return FALSE;

    ret = FDICopy(hfdi, pszCabinet, pszCabPath, 0, sc_FNNOTIFY_A, NULL, &my_hsc);

    FDIDestroy(hfdi);
    return ret;
}

struct iterate_wtoa_ctx
{
    PSP_FILE_CALLBACK_A orig_cb;
    void *orig_ctx;
};

static UINT WINAPI iterate_wtoa_cb(void *pctx, UINT message, UINT_PTR param1, UINT_PTR param2)
{
    struct iterate_wtoa_ctx *ctx = pctx;

    switch (message)
    {
    case SPFILENOTIFY_CABINETINFO:
    case SPFILENOTIFY_NEEDNEWCABINET:
    {
        const CABINET_INFO_A *infoA = (const CABINET_INFO_A *)param1;
        WCHAR pathW[MAX_PATH], fileW[MAX_PATH], diskW[MAX_PATH];
        CABINET_INFO_W infoW =
        {
            .CabinetPath = pathW,
            .CabinetFile = fileW,
            .DiskName = diskW,
            .SetId = infoA->SetId,
            .CabinetNumber = infoA->CabinetNumber,
        };

        MultiByteToWideChar(CP_ACP, 0, infoA->CabinetPath, -1, pathW, ARRAY_SIZE(pathW));
        MultiByteToWideChar(CP_ACP, 0, infoA->CabinetFile, -1, fileW, ARRAY_SIZE(fileW));
        MultiByteToWideChar(CP_ACP, 0, infoA->DiskName, -1, diskW, ARRAY_SIZE(diskW));

        if (message == SPFILENOTIFY_CABINETINFO)
            return ctx->orig_cb(ctx->orig_ctx, message, (UINT_PTR)&infoW, 0);
        else
        {
            char *newpathA = (char *)param2;
            WCHAR newpathW[MAX_PATH] = {0};
            BOOL ret = ctx->orig_cb(ctx->orig_ctx, message, (UINT_PTR)&infoW, (UINT_PTR)newpathW);

            WideCharToMultiByte(CP_ACP, 0, newpathW, -1, newpathA, MAX_PATH, NULL, NULL);
            return ret;
        }
    }
    case SPFILENOTIFY_FILEINCABINET:
    {
        FILE_IN_CABINET_INFO_A *infoA = (FILE_IN_CABINET_INFO_A *)param1;
        const char *cabA = (const char *)param2;
        WCHAR cabW[MAX_PATH], fileW[MAX_PATH];
        FILE_IN_CABINET_INFO_W infoW =
        {
            .NameInCabinet = fileW,
            .FileSize = infoA->FileSize,
            .Win32Error = infoA->Win32Error,
            .DosDate = infoA->DosDate,
            .DosTime = infoA->DosTime,
            .DosAttribs = infoA->DosAttribs,
        };
        BOOL ret;

        MultiByteToWideChar(CP_ACP, 0, infoA->NameInCabinet, -1, fileW, ARRAY_SIZE(fileW));
        MultiByteToWideChar(CP_ACP, 0, cabA, -1, cabW, ARRAY_SIZE(cabW));

        ret = ctx->orig_cb(ctx->orig_ctx, message, (UINT_PTR)&infoW, (UINT_PTR)cabW);

        WideCharToMultiByte(CP_ACP, 0, infoW.FullTargetName, -1, infoA->FullTargetName,
                ARRAY_SIZE(infoA->FullTargetName), NULL, NULL);

        return ret;
    }
    case SPFILENOTIFY_FILEEXTRACTED:
    {
        const FILEPATHS_A *pathsA = (const FILEPATHS_A *)param1;
        WCHAR targetW[MAX_PATH], sourceW[MAX_PATH];
        FILEPATHS_W pathsW =
        {
            .Target = targetW,
            .Source = sourceW,
            .Win32Error = pathsA->Win32Error,
            .Flags = pathsA->Flags,
        };

        MultiByteToWideChar(CP_ACP, 0, pathsA->Target, -1, targetW, ARRAY_SIZE(targetW));
        MultiByteToWideChar(CP_ACP, 0, pathsA->Source, -1, sourceW, ARRAY_SIZE(sourceW));

        return ctx->orig_cb(ctx->orig_ctx, message, (UINT_PTR)&pathsW, 0);
    }
    default:
        FIXME("Unexpected callback %#x.\n", message);
        return FALSE;
    }
}

/***********************************************************************
 *              SetupIterateCabinetW (SETUPAPI.@)
 */
BOOL WINAPI SetupIterateCabinetW(const WCHAR *fileW, DWORD reserved,
        PSP_FILE_CALLBACK_W handler, void *context)
{
    struct iterate_wtoa_ctx ctx = {handler, context};
    char fileA[MAX_PATH];

    if (!WideCharToMultiByte(CP_ACP, 0, fileW, -1, fileA, ARRAY_SIZE(fileA), NULL, NULL))
        return FALSE;

    return SetupIterateCabinetA(fileA, reserved, iterate_wtoa_cb, &ctx);
}

/***********************************************************************
 * DllMain
 *
 * PARAMS
 *     hinstDLL    [I] handle to the DLL's instance
 *     fdwReason   [I]
 *     lpvReserved [I] reserved, must be NULL
 *
 * RETURNS
 *     Success: TRUE
 *     Failure: FALSE
 */

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason) {
    case DLL_PROCESS_ATTACH:
        DisableThreadLibraryCalls(hinstDLL);
        OsVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW);
        if (!GetVersionExW(&OsVersionInfo))
            return FALSE;
        SETUPAPI_hInstance = hinstDLL;
        break;
    case DLL_PROCESS_DETACH:
        if (lpvReserved) break;
        SetupCloseLog();
        break;
    }

    return TRUE;
}