/*
 * Implementation of the Local Printmonitor User Interface
 *
 * Copyright 2007 Detlef Riekenberg
 *
 * 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 "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winreg.h"
#include "winuser.h"

#include "winspool.h"
#include "ddk/winsplp.h"

#include "wine/debug.h"
#include "localui.h"

WINE_DEFAULT_DEBUG_CHANNEL(localui);

/*****************************************************/

static HINSTANCE LOCALUI_hInstance;

/*****************************************************/

typedef struct tag_addportui_t {
    LPWSTR  portname;
    HANDLE  hXcv;
} addportui_t;

typedef struct tag_lptconfig_t {
    HANDLE  hXcv;
    DWORD   value;
} lptconfig_t;


static INT_PTR CALLBACK dlgproc_lptconfig(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);

/*****************************************************
 *   strdupWW [internal]
 */

static LPWSTR strdupWW(LPCWSTR pPrefix, LPCWSTR pSuffix)
{
    LPWSTR  ptr;
    DWORD   len;

    len = lstrlenW(pPrefix) + (pSuffix ? lstrlenW(pSuffix) : 0) + 1;
    ptr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
    if (ptr) {
        lstrcpyW(ptr, pPrefix);
        if (pSuffix) lstrcatW(ptr, pSuffix);
    }
    return ptr;
}

/*****************************************************
 *   dlg_configure_com [internal]
 *
 */

static BOOL dlg_configure_com(HANDLE hXcv, HWND hWnd, PCWSTR pPortName)
{
    COMMCONFIG cfg;
    LPWSTR shortname;
    DWORD status;
    DWORD dummy;
    DWORD len;
    BOOL  res;

    /* strip the colon (pPortName is never empty here) */
    len = lstrlenW(pPortName);
    shortname = HeapAlloc(GetProcessHeap(), 0, len  * sizeof(WCHAR));
    if (shortname) {
        memcpy(shortname, pPortName, (len -1) * sizeof(WCHAR));
        shortname[len-1] = '\0';

        /* get current settings */
        len = FIELD_OFFSET(COMMCONFIG, wcProviderData[1]);
        status = ERROR_SUCCESS;
        res = XcvDataW( hXcv, L"GetDefaultCommConfig",
                        (PBYTE) shortname,
                        (lstrlenW(shortname) +1) * sizeof(WCHAR),
                        (PBYTE) &cfg, len, &len, &status);

        if (res && (status == ERROR_SUCCESS)) {
            /* display the Dialog */
            res = CommConfigDialogW(pPortName, hWnd, &cfg);
            if (res) {
                status = ERROR_SUCCESS;
                /* set new settings */
                res = XcvDataW(hXcv, L"SetDefaultCommConfig",
                               (PBYTE) &cfg, len,
                               (PBYTE) &dummy, 0, &len, &status);
            }
        }
        HeapFree(GetProcessHeap(), 0, shortname);
        return res;
    }
    return FALSE;
}


/*****************************************************
 *   dlg_configure_lpt [internal]
 *
 */

static BOOL dlg_configure_lpt(HANDLE hXcv, HWND hWnd)
{
    lptconfig_t data;
    BOOL  res;


    data.hXcv = hXcv;

    res = DialogBoxParamW(LOCALUI_hInstance, MAKEINTRESOURCEW(LPTCONFIG_DIALOG), hWnd,
                               dlgproc_lptconfig, (LPARAM) &data);

    TRACE("got %u with %lu\n", res, GetLastError());

    if (!res) SetLastError(ERROR_CANCELLED);
    return res;
}

/******************************************************************
 *  dlg_port_already_exists [internal]
 */

static void dlg_port_already_exists(HWND hWnd, LPCWSTR portname)
{
    WCHAR res_PortW[IDS_LOCALPORT_MAXLEN];
    WCHAR res_PortExistsW[IDS_PORTEXISTS_MAXLEN];
    LPWSTR  message;
    DWORD   len;

    res_PortW[0] = '\0';
    res_PortExistsW[0] = '\0';
    LoadStringW(LOCALUI_hInstance, IDS_LOCALPORT, res_PortW, IDS_LOCALPORT_MAXLEN);
    LoadStringW(LOCALUI_hInstance, IDS_PORTEXISTS, res_PortExistsW, IDS_PORTEXISTS_MAXLEN);

    len = lstrlenW(portname) + IDS_PORTEXISTS_MAXLEN + 1;
    message = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
    if (message) {
        message[0] = '\0';
        swprintf(message, len, res_PortExistsW, portname);
        MessageBoxW(hWnd, message, res_PortW, MB_OK | MB_ICONERROR);
        HeapFree(GetProcessHeap(), 0, message);
    }
}

/******************************************************************
 *  dlg_invalid_portname [internal]
 */

static void dlg_invalid_portname(HWND hWnd, LPCWSTR portname)
{
    WCHAR res_PortW[IDS_LOCALPORT_MAXLEN];
    WCHAR res_InvalidNameW[IDS_INVALIDNAME_MAXLEN];
    LPWSTR  message;
    DWORD   len;

    res_PortW[0] = '\0';
    res_InvalidNameW[0] = '\0';
    LoadStringW(LOCALUI_hInstance, IDS_LOCALPORT, res_PortW, IDS_LOCALPORT_MAXLEN);
    LoadStringW(LOCALUI_hInstance, IDS_INVALIDNAME, res_InvalidNameW, IDS_INVALIDNAME_MAXLEN);

    len = lstrlenW(portname) + IDS_INVALIDNAME_MAXLEN;
    message = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
    if (message) {
        message[0] = '\0';
        swprintf(message, len, res_InvalidNameW, portname);
        MessageBoxW(hWnd, message, res_PortW, MB_OK | MB_ICONERROR);
        HeapFree(GetProcessHeap(), 0, message);
    }
}

/******************************************************************
 * display the Dialog "Nothing to configure"
 *
 */

static void dlg_nothingtoconfig(HWND hWnd)
{
    WCHAR res_PortW[IDS_LOCALPORT_MAXLEN];
    WCHAR res_nothingW[IDS_NOTHINGTOCONFIG_MAXLEN];

    res_PortW[0] = '\0';
    res_nothingW[0] = '\0';
    LoadStringW(LOCALUI_hInstance, IDS_LOCALPORT, res_PortW, IDS_LOCALPORT_MAXLEN);
    LoadStringW(LOCALUI_hInstance, IDS_NOTHINGTOCONFIG, res_nothingW, IDS_NOTHINGTOCONFIG_MAXLEN);

    MessageBoxW(hWnd, res_nothingW, res_PortW, MB_OK | MB_ICONINFORMATION);
}

/******************************************************************
 *  dlg_win32error [internal]
 */

static void dlg_win32error(HWND hWnd, DWORD lasterror)
{
    WCHAR res_PortW[IDS_LOCALPORT_MAXLEN];
    LPWSTR  message = NULL;
    DWORD   res;

    res_PortW[0] = '\0';
    LoadStringW(LOCALUI_hInstance, IDS_LOCALPORT, res_PortW, IDS_LOCALPORT_MAXLEN);


    res = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                        NULL, lasterror, 0, (LPWSTR) &message, 0, NULL);

    if (res > 0) {
        MessageBoxW(hWnd, message, res_PortW, MB_OK | MB_ICONERROR);
        LocalFree(message);
    }
}

/*****************************************************************************
 *
 */

static INT_PTR CALLBACK dlgproc_addport(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    addportui_t * data;
    DWORD   status;
    DWORD   dummy;
    DWORD   len;
    DWORD   res;

    switch(msg)
    {
    case WM_INITDIALOG:
        SetWindowLongPtrW(hwnd, DWLP_USER, lparam);
        return TRUE;

    case WM_COMMAND:
        if (wparam == MAKEWPARAM(IDOK, BN_CLICKED))
        {
            data = (addportui_t *) GetWindowLongPtrW(hwnd, DWLP_USER);
            /* length in WCHAR, without the '\0' */
            len = SendDlgItemMessageW(hwnd, ADDPORT_EDIT, WM_GETTEXTLENGTH, 0, 0);
            data->portname = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR));

            if (!data->portname) {
                EndDialog(hwnd, FALSE);
                return TRUE;
            }
            /* length is in WCHAR, including the '\0' */
            GetDlgItemTextW(hwnd, ADDPORT_EDIT, data->portname, len + 1);
            status = ERROR_SUCCESS;
            res = XcvDataW( data->hXcv, L"PortIsValid", (BYTE *) data->portname,
                            (lstrlenW(data->portname) + 1) * sizeof(WCHAR),
                            (PBYTE) &dummy, 0, &len, &status);

            TRACE("got %lu with status %lu\n", res, status);
            if (res && (status == ERROR_SUCCESS)) {
                /* The caller must free data->portname */
                EndDialog(hwnd, TRUE);
                return TRUE;
            }

            if (res && (status == ERROR_INVALID_NAME)) {
                dlg_invalid_portname(hwnd, data->portname);
                HeapFree(GetProcessHeap(), 0, data->portname);
                data->portname = NULL;
                return TRUE;
            }

            dlg_win32error(hwnd, status);
            HeapFree(GetProcessHeap(), 0, data->portname);
            data->portname = NULL;
            return TRUE;
        }

        if (wparam == MAKEWPARAM(IDCANCEL, BN_CLICKED))
        {
            EndDialog(hwnd, FALSE);
            return TRUE;
        }
        return FALSE;
    }
    return FALSE;
}

/*****************************************************************************
 *   dlgproc_lptconfig  [internal]
 *
 * Our message-proc is simple, as the range-check is done only during the
 * command "OK" and the dialog is set to the start-value at "out of range".
 *
 * Native localui.dll does the check during keyboard-input and set the dialog
 * to the previous value.
 *
 */

static INT_PTR CALLBACK dlgproc_lptconfig(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    lptconfig_t * data;
    WCHAR   bufferW[16];
    DWORD   status;
    DWORD   dummy;
    DWORD   len;
    DWORD   res;


    switch(msg)
    {
    case WM_INITDIALOG:
        SetWindowLongPtrW(hwnd, DWLP_USER, lparam);
        data = (lptconfig_t *) lparam;

        /* Get current setting */
        data->value = 45;
        status = ERROR_SUCCESS;
        res = XcvDataW( data->hXcv, L"GetTransmissionRetryTimeout",
                        (PBYTE) &dummy, 0,
                        (PBYTE) &data->value, sizeof(data->value), &len, &status);

        TRACE("got %lu with status %lu\n", res, status);

        /* Set current setting as the initial value in the Dialog */
        SetDlgItemInt(hwnd, LPTCONFIG_EDIT, data->value, FALSE);
        return TRUE;

    case WM_COMMAND:
        if (wparam == MAKEWPARAM(IDOK, BN_CLICKED))
        {
            data = (lptconfig_t *) GetWindowLongPtrW(hwnd, DWLP_USER);

            status = FALSE;
            res = GetDlgItemInt(hwnd, LPTCONFIG_EDIT, (BOOL *) &status, FALSE);
            /* length is in WCHAR, including the '\0' */
            GetDlgItemTextW(hwnd, LPTCONFIG_EDIT, bufferW, ARRAY_SIZE(bufferW));
            TRACE("got %s and %lu (translated: %lu)\n", debugstr_w(bufferW), res, status);

            /* native localui.dll use the same limits */
            if ((res > 0) && (res < 1000000) && status) {
                swprintf(bufferW, ARRAY_SIZE(bufferW), L"%u", res);
                res = XcvDataW( data->hXcv, L"ConfigureLPTPortCommandOK",
                        (PBYTE) bufferW,
                        (lstrlenW(bufferW) +1) * sizeof(WCHAR),
                        (PBYTE) &dummy, 0, &len, &status);

                TRACE("got %lu with status %lu\n", res, status);
                EndDialog(hwnd, TRUE);
                return TRUE;
            }

            /* Set initial value and rerun the Dialog */
            SetDlgItemInt(hwnd, LPTCONFIG_EDIT, data->value, FALSE);
            return TRUE;
        }

        if (wparam == MAKEWPARAM(IDCANCEL, BN_CLICKED))
        {
            EndDialog(hwnd, FALSE);
            return TRUE;
        }
        return FALSE;
    }
    return FALSE;
}


/*****************************************************
 * get_type_from_name (internal)
 *
 */

static DWORD get_type_from_name(LPCWSTR name)
{
    HANDLE  hfile;

    if (!wcsnicmp(name, L"LPT", ARRAY_SIZE(L"LPT") -1))
        return PORT_IS_LPT;

    if (!wcsnicmp(name, L"COM", ARRAY_SIZE(L"COM") -1))
        return PORT_IS_COM;

    if (!wcsicmp(name, L"FILE:"))
        return PORT_IS_FILE;

    if (name[0] == '/')
        return PORT_IS_UNIXNAME;

    if (name[0] == '|')
        return PORT_IS_PIPE;

    if (!wcsncmp(name, L"CUPS:", ARRAY_SIZE(L"CUPS:") -1))
        return PORT_IS_CUPS;

    if (!wcsncmp(name, L"LPR:", ARRAY_SIZE(L"LPR:") -1))
        return PORT_IS_LPR;

    /* Must be a file or a directory. Does the file exist ? */
    hfile = CreateFileW(name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    TRACE("%p for OPEN_EXISTING on %s\n", hfile, debugstr_w(name));
    if (hfile == INVALID_HANDLE_VALUE) {
        /* Can we create the file? */
        hfile = CreateFileW(name, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, NULL);
        TRACE("%p for OPEN_ALWAYS\n", hfile);
    }
    if (hfile != INVALID_HANDLE_VALUE) {
        CloseHandle(hfile);
        return PORT_IS_FILENAME;
    }
    /* We can't use the name. use GetLastError() for the reason */
    return PORT_IS_UNKNOWN;
}

/*****************************************************
 *   open_monitor_by_name [internal]
 *
 */
static BOOL open_monitor_by_name(LPCWSTR pPrefix, LPCWSTR pPort, HANDLE * phandle)
{
    PRINTER_DEFAULTSW pd;
    LPWSTR  fullname;
    BOOL    res;

    * phandle = 0;
    TRACE("(%s,%s)\n", debugstr_w(pPrefix),debugstr_w(pPort) );

    fullname = strdupWW(pPrefix, pPort);
    pd.pDatatype = NULL;
    pd.pDevMode  = NULL;
    pd.DesiredAccess = SERVER_ACCESS_ADMINISTER;

    res = OpenPrinterW(fullname, phandle, &pd);
    HeapFree(GetProcessHeap(), 0, fullname);
    return res;
}

/*****************************************************
 *   localui_AddPortUI [exported through MONITORUI]
 *
 * Display a Dialog to add a local Port
 *
 * PARAMS
 *  pName       [I] Servername or NULL (local Computer)
 *  hWnd        [I] Handle to parent Window for the Dialog-Box or NULL
 *  pMonitorName[I] Name of the Monitor, that should be used to add a Port or NULL
 *  ppPortName  [O] PTR to PTR of a buffer, that receive the Name of the new Port or NULL
 *
 * RETURNS
 *  Success: TRUE
 *  Failure: FALSE
 *
 * NOTES
 * The caller must free the buffer (returned in ppPortName) with GlobalFree().
 * Native localui.dll failed with ERROR_INVALID_PARAMETER, when the user tried
 * to add a Port, that start with "COM" or "LPT".
 *
 */
static BOOL WINAPI localui_AddPortUI(PCWSTR pName, HWND hWnd, PCWSTR pMonitorName, PWSTR *ppPortName)
{
    addportui_t data;
    HANDLE  hXcv;
    DWORD   needed;
    DWORD   dummy;
    DWORD   status;
    DWORD   res = FALSE;

    TRACE(  "(%s, %p, %s, %p) (*ppPortName: %p)\n", debugstr_w(pName), hWnd,
            debugstr_w(pMonitorName), ppPortName, ppPortName ? *ppPortName : NULL);

    if (open_monitor_by_name(L",XcvMonitor ", pMonitorName, &hXcv)) {

        ZeroMemory(&data, sizeof(addportui_t));
        data.hXcv = hXcv;
        res = DialogBoxParamW(LOCALUI_hInstance, MAKEINTRESOURCEW(ADDPORT_DIALOG), hWnd,
                               dlgproc_addport, (LPARAM) &data);

        TRACE("got %lu with %lu for %s\n", res, GetLastError(), debugstr_w(data.portname));

        if (ppPortName) *ppPortName = NULL;

        if (res) {
            res = XcvDataW(hXcv, L"AddPort", (BYTE *) data.portname,
                            (lstrlenW(data.portname)+1) * sizeof(WCHAR),
                            (PBYTE) &dummy, 0, &needed, &status);

            TRACE("got %lu with status %lu\n", res, status);
            if (res && (status == ERROR_SUCCESS) && ppPortName) {
                /* Native localui uses GlobalAlloc also.
                   The caller must GlobalFree the buffer */
                *ppPortName = GlobalAlloc(GPTR, (lstrlenW(data.portname)+1) * sizeof(WCHAR));
                if (*ppPortName) lstrcpyW(*ppPortName, data.portname);
            }

            if (res && (status == ERROR_ALREADY_EXISTS)) {
                dlg_port_already_exists(hWnd, data.portname);
                /* Native localui also return "TRUE" from AddPortUI in this case */
            }

            HeapFree(GetProcessHeap(), 0, data.portname);
        }
        else
        {
            SetLastError(ERROR_CANCELLED);
        }
        ClosePrinter(hXcv);
    }

    TRACE("=> %lu with %lu\n", res, GetLastError());
    return res;
}


/*****************************************************
 *   localui_ConfigurePortUI [exported through MONITORUI]
 *
 * Display the Configuration-Dialog for a specific Port
 *
 * PARAMS
 *  pName     [I] Servername or NULL (local Computer)
 *  hWnd      [I] Handle to parent Window for the Dialog-Box or NULL
 *  pPortName [I] Name of the Port, that should be configured
 *
 * RETURNS
 *  Success: TRUE
 *  Failure: FALSE
 *
 */
static BOOL WINAPI localui_ConfigurePortUI(PCWSTR pName, HWND hWnd, PCWSTR pPortName)
{
    HANDLE  hXcv;
    DWORD   res;

    TRACE("(%s, %p, %s)\n", debugstr_w(pName), hWnd, debugstr_w(pPortName));
    if (open_monitor_by_name(L",XcvPort ", pPortName, &hXcv)) {

        res = get_type_from_name(pPortName);
        switch(res)
        {

        case PORT_IS_COM:
            res = dlg_configure_com(hXcv, hWnd, pPortName);
            break;

        case PORT_IS_LPT:
            res = dlg_configure_lpt(hXcv, hWnd);
            break;

        default:
            dlg_nothingtoconfig(hWnd);
            SetLastError(ERROR_CANCELLED);
            res = FALSE;
        }

        ClosePrinter(hXcv);
        return res;
    }
    return FALSE;

}

/*****************************************************
 *   localui_DeletePortUI [exported through MONITORUI]
 *
 * Delete a specific Port
 *
 * PARAMS
 *  pName     [I] Servername or NULL (local Computer)
 *  hWnd      [I] Handle to parent Window
 *  pPortName [I] Name of the Port, that should be deleted
 *
 * RETURNS
 *  Success: TRUE
 *  Failure: FALSE
 *
 * NOTES
 *  Native localui does not allow deleting a COM/LPT port (ERROR_NOT_SUPPORTED)
 *
 */
static BOOL WINAPI localui_DeletePortUI(PCWSTR pName, HWND hWnd, PCWSTR pPortName)
{
    HANDLE  hXcv;
    DWORD   dummy;
    DWORD   needed;
    DWORD   status;

    TRACE("(%s, %p, %s)\n", debugstr_w(pName), hWnd, debugstr_w(pPortName));

    if ((!pPortName) || (!pPortName[0])) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    if (open_monitor_by_name(L",XcvPort ", pPortName, &hXcv)) {
        /* native localui tests here for LPT / COM - Ports and failed with
           ERROR_NOT_SUPPORTED. */
        if (XcvDataW(hXcv, L"DeletePort", (BYTE *) pPortName,
            (lstrlenW(pPortName)+1) * sizeof(WCHAR), (LPBYTE) &dummy, 0, &needed, &status)) {

            ClosePrinter(hXcv);
            if (status != ERROR_SUCCESS) SetLastError(status);
            return (status == ERROR_SUCCESS);
        }
        ClosePrinter(hXcv);
        return FALSE;
    }
    SetLastError(ERROR_UNKNOWN_PORT);
    return FALSE;
}

/*****************************************************
 *      InitializePrintMonitorUI  (LOCALUI.@)
 *
 * Initialize the User-Interface for the Local Ports
 *
 * RETURNS
 *  Success: Pointer to a MONITORUI Structure
 *  Failure: NULL
 *
 */

PMONITORUI WINAPI InitializePrintMonitorUI(void)
{
    static MONITORUI mymonitorui =
    {
        sizeof(MONITORUI),
        localui_AddPortUI,
        localui_ConfigurePortUI,
        localui_DeletePortUI
    };

    TRACE("=> %p\n", &mymonitorui);
    return &mymonitorui;
}

/*****************************************************
 *      DllMain
 */
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    TRACE("(%p, %ld, %p)\n",hinstDLL, fdwReason, lpvReserved);

    switch(fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            DisableThreadLibraryCalls( hinstDLL );
            LOCALUI_hInstance = hinstDLL;
            break;
    }
    return TRUE;
}
