/*
 * PROJECT:     ReactOS Sound Volume Control
 * LICENSE:     LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
 * PURPOSE:     Main volume adjustment dialog
 * COPYRIGHT:   Copyright 2004-2005 Thomas Weidenmueller <w3seek@reactos.com>
 *              Copyright 2011 Johannes Anderwald <johannes.anderwald@reactos.org>
 *              Copyright 2018-2019 Eric Kohl <eric.kohl@reactos.org>
 */

#include "sndvol32.h"

#include <shellapi.h>

HINSTANCE hAppInstance;
ATOM MainWindowClass;
HWND hMainWnd;
HANDLE hAppHeap;
LPTSTR lpAppTitle;
PREFERENCES_CONTEXT Preferences;

#define GetDialogData(hwndDlg, type) \
    ( P##type )GetWindowLongPtr((hwndDlg), DWLP_USER)
#define GetWindowData(hwnd, type) \
    ( P##type )GetWindowLongPtr((hwnd), GWL_USERDATA)

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



typedef struct _PREFERENCES_FILL_DEVICES
{
    PPREFERENCES_CONTEXT PrefContext;
    HWND hComboBox;
    UINT Selected;
} PREFERENCES_FILL_DEVICES, *PPREFERENCES_FILL_DEVICES;

static BOOL CALLBACK
FillDeviceComboBox(PSND_MIXER Mixer,
                   UINT Id,
                   LPCTSTR ProductName,
                   PVOID Context)
{
    LRESULT lres;
    PPREFERENCES_FILL_DEVICES FillContext = (PPREFERENCES_FILL_DEVICES)Context;

    UNREFERENCED_PARAMETER(Mixer);

    lres = SendMessage(FillContext->hComboBox,
                       CB_ADDSTRING,
                       0,
                       (LPARAM)ProductName);
    if (lres != CB_ERR)
    {
        /* save the index so we don't screw stuff when the combobox is sorted... */
        SendMessage(FillContext->hComboBox,
                    CB_SETITEMDATA,
                    (WPARAM)lres,
                    Id);

        if (Id == FillContext->Selected)
        {
            SendMessage(FillContext->hComboBox,
                        CB_SETCURSEL,
                        (WPARAM)lres,
                        0);
        }
    }

    return TRUE;
}

static BOOL CALLBACK
PrefDlgAddLine(PSND_MIXER Mixer,
               LPMIXERLINE Line,
               UINT DisplayControls,
               PVOID Context)
{
    PPREFERENCES_CONTEXT PrefContext = (PPREFERENCES_CONTEXT)Context;

    UNREFERENCED_PARAMETER(Mixer);
    UNREFERENCED_PARAMETER(DisplayControls);

    switch (Line->dwComponentType)
    {
        case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
            if (PrefContext->PlaybackID == (DWORD)-1)
            {
                PrefContext->PlaybackID = Line->dwLineID;

                if (PrefContext->SelectedLine == (DWORD)-1)
                {
                    PrefContext->SelectedLine = Line->dwLineID;
                }
            }
            else
                goto AddToOthersLines;

            break;

        case MIXERLINE_COMPONENTTYPE_DST_WAVEIN:
            if (PrefContext->RecordingID == (DWORD)-1)
            {
                PrefContext->RecordingID = Line->dwLineID;

                if (PrefContext->SelectedLine == (DWORD)-1)
                {
                    PrefContext->SelectedLine = Line->dwLineID;
                }
            }
            else
                goto AddToOthersLines;

            break;

        default:
        {
            LRESULT lres;
            HWND hwndCbOthers;

            if (PrefContext->SelectedLine == (DWORD)-1)
            {
                PrefContext->SelectedLine = Line->dwLineID;
            }

AddToOthersLines:
            hwndCbOthers = GetDlgItem(PrefContext->hwndDlg,
                                      IDC_LINE);

            lres = SendMessage(hwndCbOthers,
                               CB_ADDSTRING,
                               0,
                               (LPARAM)Line->szName);
            if (lres != CB_ERR)
            {
                SendMessage(hwndCbOthers,
                            CB_SETITEMDATA,
                            (WPARAM)lres,
                            Line->dwLineID);

                PrefContext->OtherLines++;
            }
            break;
        }
    }

    return TRUE;
}

static BOOL CALLBACK
PrefDlgAddConnection(PSND_MIXER Mixer,
                     DWORD LineID,
                     LPMIXERLINE Line,
                     PVOID Context)
{
    PPREFERENCES_CONTEXT PrefContext = (PPREFERENCES_CONTEXT)Context;
    HWND hwndControls;
    LVITEM lvi;
    UINT i;

    UNREFERENCED_PARAMETER(Mixer);
    UNREFERENCED_PARAMETER(LineID);

    if (Line->cControls != 0)
    {
        hwndControls = GetDlgItem(PrefContext->hwndDlg,
                                  IDC_CONTROLS);

        lvi.mask = LVIF_TEXT | LVIF_PARAM;
        lvi.iItem = PrefContext->tmp++;
        lvi.iSubItem = 0;
        lvi.pszText = Line->szName;
        lvi.lParam = (LPARAM)Line->dwSource;

        i = SendMessage(hwndControls,
                        LVM_INSERTITEM,
                        0,
                        (LPARAM)&lvi);
        if (i != (UINT)-1)
        {
            TCHAR LineName[MIXER_LONG_NAME_CHARS];
            DWORD Flags;
            BOOL SelLine = FALSE;

            if (SndMixerGetLineName(PrefContext->Mixer,
                                    PrefContext->SelectedLine,
                                    LineName,
                                    MIXER_LONG_NAME_CHARS,
                                    TRUE) == -1)
            {
                LineName[0] = TEXT('\0');
            }

            if (ReadLineConfig(PrefContext->DeviceName,
                               LineName,
                               Line->szName,
                               &Flags))
            {
                if (Flags != 0x4)
                {
                    SelLine = TRUE;
                }
            }

            ListView_SetCheckState(hwndControls,
                                   i,
                                   SelLine);
        }
    }

    return TRUE;
}

static VOID
UpdatePrefDlgControls(PPREFERENCES_CONTEXT Context,
                      DWORD LineID)
{
    INT OldID, MixerID = 0;
    INT DeviceCbIndex;

    /* select the mixer */
    DeviceCbIndex = SendDlgItemMessage(Context->hwndDlg,
                                       IDC_MIXERDEVICE,
                                       CB_GETCURSEL,
                                       0,
                                       0);
    if (DeviceCbIndex != CB_ERR)
    {
        MixerID = SendDlgItemMessage(Context->hwndDlg,
                                     IDC_MIXERDEVICE,
                                     CB_GETITEMDATA,
                                     DeviceCbIndex,
                                     0);
        if (MixerID == CB_ERR)
        {
            MixerID = 0;
        }
    }

    OldID = Context->Selected;
    if (MixerID != OldID &&
        SndMixerSelect(Context->Mixer,
                       MixerID))
    {
        Context->Selected = SndMixerGetSelection(Context->Mixer);

        /* update the controls */
        Context->PlaybackID = (DWORD)-1;
        Context->RecordingID = (DWORD)-1;
        Context->OtherLines = 0;
        Context->SelectedLine = (DWORD)-1;

        SndMixerGetProductName(Context->Mixer,
                               Context->DeviceName,
                               sizeof(Context->DeviceName) / sizeof(Context->DeviceName[0]));

        if (SndMixerEnumLines(Context->Mixer,
                              PrefDlgAddLine,
                              Context))
        {
            UINT SelBox = 0;

            /* enable/disable controls and make default selection */
            EnableWindow(GetDlgItem(Context->hwndDlg,
                                    IDC_PLAYBACK),
                         Context->PlaybackID != (DWORD)-1);
            CheckDlgButton(Context->hwndDlg,
                           IDC_PLAYBACK,
                           (Context->PlaybackID != (DWORD)-1 && SelBox++ == 0) ?
                               BST_CHECKED : BST_UNCHECKED);

            EnableWindow(GetDlgItem(Context->hwndDlg,
                                    IDC_RECORDING),
                         Context->RecordingID != (DWORD)-1);
            CheckDlgButton(Context->hwndDlg,
                           IDC_RECORDING,
                           (Context->RecordingID != (DWORD)-1 && SelBox++ == 0) ?
                               BST_CHECKED : BST_UNCHECKED);

            if (Context->OtherLines != 0)
            {
                /* select the first item in the other lines combo box by default */
                SendDlgItemMessage(Context->hwndDlg,
                                   IDC_LINE,
                                   CB_SETCURSEL,
                                   0,
                                   0);
            }
            EnableWindow(GetDlgItem(Context->hwndDlg,
                                    IDC_LINE),
                         FALSE);
            EnableWindow(GetDlgItem(Context->hwndDlg,
                                    IDC_OTHER),
                         Context->OtherLines != 0);
            CheckDlgButton(Context->hwndDlg,
                           IDC_LINE,
                           (Context->OtherLines != 0 && SelBox++ == 0) ?
                               BST_CHECKED : BST_UNCHECKED);

            /* disable the OK button if the device doesn't have any lines */
            EnableWindow(GetDlgItem(Context->hwndDlg,
                                    IDOK),
                         Context->PlaybackID != (DWORD)-1 ||
                         Context->RecordingID != (DWORD)-1 ||
                         Context->OtherLines != 0);

            LineID = Context->SelectedLine;
        }
    }

    /* update the line sources list */
    if ((MixerID != OldID && Context->SelectedLine != (DWORD)-1) ||
        (Context->SelectedLine != LineID && LineID != (DWORD)-1))
    {
        Context->SelectedLine = LineID;

        (void)ListView_DeleteAllItems(GetDlgItem(Context->hwndDlg,
                                      IDC_CONTROLS));

        Context->tmp = 0;
        SndMixerEnumConnections(Context->Mixer,
                                LineID,
                                PrefDlgAddConnection,
                                Context);
    }
}

static
VOID
WriteLineSettings(PPREFERENCES_CONTEXT Context, HWND hwndDlg)
{
    HWND hwndControls;
    INT Count, Index;
    WCHAR LineName[MIXER_LONG_NAME_CHARS];
    WCHAR DestinationName[MIXER_LONG_NAME_CHARS];
    DWORD Flags;
    PSNDVOL_REG_LINESTATE LineStates;

    /* get list view */
    hwndControls = GetDlgItem(hwndDlg, IDC_CONTROLS);

    /* get list item count */
    Count = ListView_GetItemCount(hwndControls);

    /* sanity check */
    assert(Count);

    if (SndMixerGetLineName(Context->Mixer, Context->SelectedLine, DestinationName, MIXER_LONG_NAME_CHARS, TRUE) == -1)
    {
        /* failed to get destination line name */
        return;
    }

    /* allocate line states array */
    LineStates = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SNDVOL_REG_LINESTATE) * Count);
    if (LineStates == NULL)
    {
        /* failed to allocate line states array */
        return;
    }


    for(Index = 0; Index < Count; Index++)
    {
        /* set to empty */
        LineName[0] = L'\0';

        /* get item text */
        ListView_GetItemText(hwndControls, Index, 0, LineName, MIXER_LONG_NAME_CHARS);

        /* make sure it is null terminated */
        LineName[MIXER_LONG_NAME_CHARS-1] = L'\0';

        /* get check state */
        Flags = (ListView_GetCheckState(hwndControls, Index) == 0 ? 0x4 : 0);

        /* copy line name */
        wcscpy(LineStates[Index].LineName, LineName);

        /* store flags */
        LineStates[Index].Flags = Flags;
    }

    /* now write the line config */
    WriteLineConfig(Context->DeviceName, DestinationName, LineStates, sizeof(SNDVOL_REG_LINESTATE) * Count);

    /* free line states */
    HeapFree(GetProcessHeap(), 0, LineStates);
}

static INT_PTR CALLBACK
DlgPreferencesProc(HWND hwndDlg,
                   UINT uMsg,
                   WPARAM wParam,
                   LPARAM lParam)
{
    PPREFERENCES_CONTEXT Context;

    switch (uMsg)
    {
        case WM_COMMAND:
        {
            Context = GetDialogData(hwndDlg,
                                    PREFERENCES_CONTEXT);
            switch (LOWORD(wParam))
            {
                case IDC_MIXERDEVICE:
                {
                    if (HIWORD(wParam) == CBN_SELCHANGE)
                    {
                        UpdatePrefDlgControls(Context,
                                              (DWORD)-1);
                    }
                    break;
                }

                case IDC_LINE:
                {
                    if (HIWORD(wParam) == CBN_SELCHANGE)
                    {
                        INT LineID;
                        INT Index;

                        Index = SendDlgItemMessage(hwndDlg,
                                                   IDC_LINE,
                                                   CB_GETCURSEL,
                                                   0,
                                                   0);
                        if (Index != CB_ERR)
                        {
                            LineID = SendDlgItemMessage(hwndDlg,
                                                        IDC_LINE,
                                                        CB_GETITEMDATA,
                                                        Index,
                                                        0);
                            if (LineID != CB_ERR)
                            {
                                UpdatePrefDlgControls(Context,
                                                      LineID);
                            }
                        }
                    }
                    break;
                }

                case IDC_PLAYBACK:
                {
                    UpdatePrefDlgControls(Context,
                                          Context->PlaybackID);
                    EnableWindow(GetDlgItem(hwndDlg,
                                            IDC_LINE),
                                 FALSE);
                    break;
                }

                case IDC_RECORDING:
                {
                    UpdatePrefDlgControls(Context,
                                          Context->RecordingID);
                    EnableWindow(GetDlgItem(hwndDlg,
                                            IDC_LINE),
                                 FALSE);
                    break;
                }

                case IDC_OTHER:
                {
                    INT LineCbIndex;
                    INT LineID;

                    EnableWindow(GetDlgItem(hwndDlg,
                                            IDC_LINE),
                                 TRUE);

                    LineCbIndex = SendDlgItemMessage(hwndDlg,
                                                     IDC_LINE,
                                                     CB_GETCURSEL,
                                                     0,
                                                     0);
                    if (LineCbIndex != CB_ERR)
                    {
                        LineID = SendDlgItemMessage(hwndDlg,
                                                    IDC_LINE,
                                                    CB_GETITEMDATA,
                                                    LineCbIndex,
                                                    0);
                        if (LineID != CB_ERR)
                        {
                            UpdatePrefDlgControls(Context,
                                                  LineID);
                        }
                    }
                    break;
                }

                case IDOK:
                {
                    /* write line settings */
                    WriteLineSettings(Context, hwndDlg);

                    /* fall through */
                }
                case IDCANCEL:
                {
                    EndDialog(hwndDlg,
                              LOWORD(wParam));
                    break;
                }
            }
            break;
        }

        case WM_INITDIALOG:
        {
            PREFERENCES_FILL_DEVICES FillDevContext;
            LVCOLUMN lvc;
            RECT rcClient;
            HWND hwndControls;

            SetWindowLongPtr(hwndDlg,
                             DWLP_USER,
                             (LONG_PTR)lParam);
            Context = (PPREFERENCES_CONTEXT)((LONG_PTR)lParam);
            Context->hwndDlg = hwndDlg;
            Context->Mixer = SndMixerCreate(hwndDlg, Context->MixerWindow->MixerId);
            Context->Selected = (UINT)-1;

            FillDevContext.PrefContext = Context;
            FillDevContext.hComboBox = GetDlgItem(hwndDlg,
                                                  IDC_MIXERDEVICE);
            FillDevContext.Selected = SndMixerGetSelection(Context->Mixer);
            SndMixerEnumProducts(Context->Mixer,
                                 FillDeviceComboBox,
                                 &FillDevContext);

            /* initialize the list view control */
            hwndControls = GetDlgItem(hwndDlg,
                                      IDC_CONTROLS);
            (void)ListView_SetExtendedListViewStyle(hwndControls,
                                                    LVS_EX_CHECKBOXES);

            GetClientRect(hwndControls,
                          &rcClient);
            lvc.mask = LVCF_TEXT | LVCF_WIDTH;
            lvc.pszText = TEXT("");
            lvc.cx = rcClient.right;
            SendMessage(hwndControls,
                        LVM_INSERTCOLUMN,
                        0,
                        (LPARAM)&lvc);

            /* update all controls */
            UpdatePrefDlgControls(Context,
                                  (DWORD)Context->SelectedLine);
            return TRUE;
        }

        case WM_CLOSE:
        {
            EndDialog(hwndDlg,
                      IDCANCEL);
            break;
        }

        case WM_SYSCOLORCHANGE:
        {
            HWND hwndControls;

            /* Forward WM_SYSCOLORCHANGE */
            hwndControls = GetDlgItem(hwndDlg, IDC_CONTROLS);
            SendMessage(hwndControls, WM_SYSCOLORCHANGE, 0, 0);
            break;
        }
    }

    return 0;
}

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

static VOID
DeleteMixerWindowControls(PMIXER_WINDOW MixerWindow)
{
    DWORD Index;

    for(Index = 0; Index < MixerWindow->WindowCount; Index++)
    {
        /* destroys the window */
        DestroyWindow(MixerWindow->Window[Index]);
    }

    /* free memory */
    HeapFree(GetProcessHeap(), 0, MixerWindow->Window);

    /* set to null */
    MixerWindow->Window = NULL;
    MixerWindow->WindowCount = 0;
}

static BOOL
RebuildMixerWindowControls(PPREFERENCES_CONTEXT PrefContext)
{
    /* delete existing mixer controls */
    DeleteMixerWindowControls(PrefContext->MixerWindow);

    /* load new mixer controls */
    LoadDialogCtrls(PrefContext);

    return TRUE;
}

static
BOOL
CALLBACK
SetVolumeCallback(PSND_MIXER Mixer, DWORD LineID, LPMIXERLINE Line, PVOID Ctx)
{
    UINT ControlCount = 0, Index;
    LPMIXERCONTROL Control = NULL;
    PMIXERCONTROLDETAILS_UNSIGNED puDetails = NULL;
    MIXERCONTROLDETAILS_BOOLEAN bDetails;
    PSET_VOLUME_CONTEXT Context = (PSET_VOLUME_CONTEXT)Ctx;

    /* check if the line name is equal */
    if (_wcsicmp(Line->szName, Context->LineName))
    {
        /* it is not */
        return TRUE;
    }

    /* query controls */
    if (SndMixerQueryControls(Mixer, &ControlCount, Line, &Control) == FALSE)
    {
        /* failed to query for controls */
        return FALSE;
    }

    puDetails = HeapAlloc(GetProcessHeap(), 0, Line->cChannels * sizeof(MIXERCONTROLDETAILS_UNSIGNED));
    if (puDetails == NULL)
        return FALSE;

    /* now go through all controls and compare control ids */
    for (Index = 0; Index < ControlCount; Index++)
    {
        if (Context->bVertical)
        {
            if (Control[Index].dwControlType == MIXERCONTROL_CONTROLTYPE_VOLUME)
            {
                DWORD LineOffset, volumePosition, balancePosition;
                DWORD volumeStep, balanceStep;

                LineOffset = Context->SliderPos;

                volumePosition = (DWORD)SendDlgItemMessage(Preferences.MixerWindow->hWnd, LineOffset * IDC_LINE_SLIDER_VERT, TBM_GETPOS, 0, 0);
                volumeStep = (Control[Index].Bounds.dwMaximum - Control[Index].Bounds.dwMinimum) / (VOLUME_MAX - VOLUME_MIN);

                if (Line->cChannels == 1)
                {
                    /* set up details */
                    puDetails[0].dwValue = ((VOLUME_MAX - volumePosition) * volumeStep) + Control[Index].Bounds.dwMinimum;
                }
                else if (Line->cChannels == 2)
                {
                    balancePosition = (DWORD)SendDlgItemMessage(Preferences.MixerWindow->hWnd, LineOffset * IDC_LINE_SLIDER_HORZ, TBM_GETPOS, 0, 0);
                    if (balancePosition == BALANCE_CENTER)
                    {
                        puDetails[0].dwValue = ((VOLUME_MAX - volumePosition) * volumeStep) + Control[Index].Bounds.dwMinimum;
                        puDetails[1].dwValue = ((VOLUME_MAX - volumePosition) * volumeStep) + Control[Index].Bounds.dwMinimum;
                    }
                    else if (balancePosition == BALANCE_LEFT)
                    {
                        puDetails[0].dwValue = ((VOLUME_MAX - volumePosition) * volumeStep) + Control[Index].Bounds.dwMinimum;
                        puDetails[1].dwValue = Control[Index].Bounds.dwMinimum;
                    }
                    else if (balancePosition == BALANCE_RIGHT)
                    {
                        puDetails[0].dwValue = Control[Index].Bounds.dwMinimum;
                        puDetails[1].dwValue = ((VOLUME_MAX - volumePosition) * volumeStep) + Control[Index].Bounds.dwMinimum;
                    }
                    else if (balancePosition < BALANCE_CENTER) // Left
                    {
                        puDetails[0].dwValue = ((VOLUME_MAX - volumePosition) * volumeStep) + Control[Index].Bounds.dwMinimum;

                        balanceStep = (puDetails[0].dwValue - Control[Index].Bounds.dwMinimum) / (BALANCE_STEPS / 2);

                        puDetails[1].dwValue = (balancePosition * balanceStep) + Control[Index].Bounds.dwMinimum;
                    }
                    else if (balancePosition > BALANCE_CENTER) // Right
                    {
                        puDetails[1].dwValue = ((VOLUME_MAX - volumePosition) * volumeStep) + Control[Index].Bounds.dwMinimum;

                        balanceStep = (puDetails[1].dwValue - Control[Index].Bounds.dwMinimum) / (BALANCE_STEPS / 2);

                        puDetails[0].dwValue = ((BALANCE_RIGHT - balancePosition) * balanceStep) + Control[Index].Bounds.dwMinimum;
                    }
                }
                else
                {
                    SndMixerGetVolumeControlDetails(Preferences.MixerWindow->Mixer, Control[Index].dwControlID, Line->cChannels, sizeof(MIXERCONTROLDETAILS_UNSIGNED), (LPVOID)puDetails);

                    /* FIXME */
                }

                /* set volume */
                SndMixerSetVolumeControlDetails(Preferences.MixerWindow->Mixer, Control[Index].dwControlID, Line->cChannels, sizeof(MIXERCONTROLDETAILS_UNSIGNED), (LPVOID)puDetails);

                /* done */
                break;
            }
        }
        else if (Context->bSwitch)
        {
            if (Control[Index].dwControlType == MIXERCONTROL_CONTROLTYPE_MUTE)
            {
                /* set up details */
                bDetails.fValue = Context->SliderPos;

                /* set volume */
                SndMixerSetVolumeControlDetails(Preferences.MixerWindow->Mixer, Control[Index].dwControlID, 1, sizeof(MIXERCONTROLDETAILS_BOOLEAN), (LPVOID)&bDetails);

                /* done */
                break;
            }
        }
    }

    if (puDetails != NULL)
        HeapFree(GetProcessHeap(), 0, puDetails);

    /* free controls */
    HeapFree(GetProcessHeap(), 0, Control);


    /* done */
    return TRUE;
}

static
BOOL
CALLBACK
MixerControlChangeCallback(PSND_MIXER Mixer, DWORD LineID, LPMIXERLINE Line, PVOID Context)
{
    PMIXERCONTROLDETAILS_UNSIGNED pVolumeDetails = NULL;
    UINT ControlCount = 0, Index;
    LPMIXERCONTROL Control = NULL;

    /* check if the line has controls */
    if (Line->cControls == 0)
    {
        /* no controls */
        return TRUE;
    }

    /* query controls */
    if (SndMixerQueryControls(Mixer, &ControlCount, Line, &Control) == FALSE)
    {
        /* failed to query for controls */
        return FALSE;
    }

    pVolumeDetails = HeapAlloc(GetProcessHeap(),
                               0,
                               Line->cChannels * sizeof(MIXERCONTROLDETAILS_UNSIGNED));
    if (pVolumeDetails == NULL)
        goto done;

    /* now go through all controls and compare control ids */
    for (Index = 0; Index < ControlCount; Index++)
    {
        if (Control[Index].dwControlID == PtrToUlong(Context))
        {
            if (Control[Index].dwControlType == MIXERCONTROL_CONTROLTYPE_MUTE)
            {
                MIXERCONTROLDETAILS_BOOLEAN Details;

                /* get volume control details */
                if (SndMixerGetVolumeControlDetails(Preferences.MixerWindow->Mixer, Control[Index].dwControlID, 1, sizeof(MIXERCONTROLDETAILS_BOOLEAN), (LPVOID)&Details) != -1)
                {
                    /* update dialog control */
                    UpdateDialogLineSwitchControl(&Preferences, Line, Details.fValue);
                }
            }
            else if (Control[Index].dwControlType == MIXERCONTROL_CONTROLTYPE_VOLUME)
            {
                /* get volume control details */
                if (SndMixerGetVolumeControlDetails(Preferences.MixerWindow->Mixer, Control[Index].dwControlID, Line->cChannels, sizeof(MIXERCONTROLDETAILS_UNSIGNED), (LPVOID)pVolumeDetails) != -1)
                {
                    /* update dialog control */
                    DWORD volumePosition, volumeStep, maxVolume, i;
                    DWORD balancePosition, balanceStep;

                    volumeStep = (Control[Index].Bounds.dwMaximum - Control[Index].Bounds.dwMinimum) / (VOLUME_MAX - VOLUME_MIN);

                    maxVolume = 0;
                    for (i = 0; i < Line->cChannels; i++)
                    {
                        if (pVolumeDetails[i].dwValue > maxVolume)
                            maxVolume = pVolumeDetails[i].dwValue;
                    }

                    volumePosition = (maxVolume - Control[Index].Bounds.dwMinimum) / volumeStep;

                    if (Line->cChannels == 1)
                    {
                        balancePosition = BALANCE_CENTER;
                    }
                    else if (Line->cChannels == 2)
                    {
                        if (pVolumeDetails[0].dwValue == pVolumeDetails[1].dwValue)
                        {
                            balancePosition = BALANCE_CENTER;
                        }
                        else if (pVolumeDetails[0].dwValue == Control[Index].Bounds.dwMinimum)
                        {
                            balancePosition = BALANCE_RIGHT;
                        }
                        else if (pVolumeDetails[1].dwValue == Control[Index].Bounds.dwMinimum)
                        {
                            balancePosition = BALANCE_LEFT;
                        }
                        else
                        {
                            balanceStep = (maxVolume - Control[Index].Bounds.dwMinimum) / (BALANCE_STEPS / 2);

                            if (pVolumeDetails[0].dwValue < pVolumeDetails[1].dwValue)
                            {
                                balancePosition = (pVolumeDetails[0].dwValue - Control[Index].Bounds.dwMinimum) / balanceStep;
                                balancePosition = BALANCE_RIGHT - balancePosition;
                            }
                            else if (pVolumeDetails[1].dwValue < pVolumeDetails[0].dwValue)
                            {
                                balancePosition = (pVolumeDetails[1].dwValue - Control[Index].Bounds.dwMinimum) / balanceStep;
                                balancePosition = BALANCE_LEFT + balancePosition;
                            }
                        }
                    }

                    /* Update the volume control slider */
                    UpdateDialogLineSliderControl(&Preferences, Line, IDC_LINE_SLIDER_VERT, VOLUME_MAX - volumePosition);

                    /* Update the balance control slider */
                    UpdateDialogLineSliderControl(&Preferences, Line, IDC_LINE_SLIDER_HORZ, balancePosition);
                }
            }
            break;
        }
    }

done:
    /* Free the volume details */
    if (pVolumeDetails)
        HeapFree(GetProcessHeap(), 0, pVolumeDetails);

    /* free controls */
    HeapFree(GetProcessHeap(), 0, Control);

    /* done */
    return TRUE;
}

static LRESULT CALLBACK
MainWindowProc(HWND hwnd,
               UINT uMsg,
               WPARAM wParam,
               LPARAM lParam)
{
    PMIXER_WINDOW MixerWindow;
    DWORD CtrlID, LineOffset;
    BOOL bRet;
    LRESULT Result = 0;
    SET_VOLUME_CONTEXT Context;

    switch (uMsg)
    {
        case WM_COMMAND:
        {
            MixerWindow = GetWindowData(hwnd,
                                        MIXER_WINDOW);

            switch (LOWORD(wParam))
            {
                case IDM_PROPERTIES:
                {
                    PREFERENCES_CONTEXT Pref;

                    Pref.MixerWindow = MixerWindow;
                    Pref.Mixer = NULL;
                    Pref.SelectedLine = Preferences.SelectedLine;

                    if (DialogBoxParam(hAppInstance,
                                       MAKEINTRESOURCE(IDD_PREFERENCES),
                                       hwnd,
                                       DlgPreferencesProc,
                                       (LPARAM)&Pref) == IDOK)
                    {
                        /* update window */
                        TCHAR szProduct[MAXPNAMELEN];

                        /* get mixer product name */
                        if (SndMixerGetProductName(Pref.Mixer,
                                                   szProduct,
                                                   sizeof(szProduct) / sizeof(szProduct[0])) == -1)
                        {
                            /* failed to get name */
                            szProduct[0] = L'\0';
                        }
                        else
                        {
                            /* copy product */
                            wcscpy(Preferences.DeviceName, szProduct);
                        }

                        /* destroy old status bar */
                        if (MixerWindow->Mode == NORMAL_MODE)
                            DestroyWindow(MixerWindow->hStatusBar);

                        /* update details */
                        Preferences.SelectedLine = Pref.SelectedLine;

                        /* destroy old mixer */
                        SndMixerDestroy(Preferences.MixerWindow->Mixer);

                        /* use new selected mixer */
                        Preferences.MixerWindow->Mixer = Pref.Mixer;

                        /* create status window */
                        if (MixerWindow->Mode == NORMAL_MODE)
                        {
                            MixerWindow->hStatusBar = CreateStatusWindow(WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS,
                                                                         NULL,
                                                                         hwnd,
                                                                         0);
                            if (MixerWindow->hStatusBar)
                            {
                                /* Set status bar */
                                SendMessage(MixerWindow->hStatusBar,
                                    WM_SETTEXT,
                                    0,
                                    (LPARAM)szProduct);
                            }
                        }

                        /* rebuild dialog controls */
                        RebuildMixerWindowControls(&Preferences);
                    }
                    break;
                }

                case IDM_ADVANCED_CONTROLS:
                    MixerWindow->bShowExtendedControls = !MixerWindow->bShowExtendedControls;
                    CheckMenuItem(GetMenu(hwnd),
                                  IDM_ADVANCED_CONTROLS,
                                  MF_BYCOMMAND | (MixerWindow->bShowExtendedControls ? MF_CHECKED : MF_UNCHECKED));
                    RebuildMixerWindowControls(&Preferences);
                    break;

                case IDM_EXIT:
                {
                    PostQuitMessage(0);
                    UnregisterHotKey(hwnd, HOTKEY_CTRL_S);
                    break;
                }

                case IDM_ABOUT:
                {
                    HICON hAppIcon = (HICON)GetClassLongPtrW(hwnd,
                                                             GCLP_HICON);
                    ShellAbout(hwnd,
                               lpAppTitle,
                               NULL,
                               hAppIcon);
                    break;
                }

                default:
                {
                    /* get button id */
                    CtrlID = LOWORD(wParam);

                    /* check if the message is from the line switch */
                    if (HIWORD(wParam) == BN_CLICKED)
                    {
                        if (CtrlID % IDC_LINE_SWITCH == 0)
                        {
                            /* compute line offset */
                            LineOffset = CtrlID / IDC_LINE_SWITCH;

                            /* compute window id of line name static control */
                            CtrlID = LineOffset * IDC_LINE_NAME;

                            if (Preferences.MixerWindow->Mixer->MixerId == PLAY_MIXER)
                            {
                                /* get line name */
                                if (GetDlgItemTextW(hwnd, CtrlID, Context.LineName, MIXER_LONG_NAME_CHARS) != 0)
                                {
                                    /* setup context */
                                    Context.SliderPos = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0);
                                    Context.bVertical = FALSE;
                                    Context.bSwitch = TRUE;

                                    /* set volume */
                                    SndMixerEnumConnections(Preferences.MixerWindow->Mixer, Preferences.SelectedLine, SetVolumeCallback, (LPVOID)&Context);
                                }
                            }
                            else if (Preferences.MixerWindow->Mixer->MixerId == RECORD_MIXER)
                            {
                                UINT i;

                                for (i = 0; i < Preferences.MixerWindow->DialogCount; i++)
                                {
                                    SendDlgItemMessageW(hwnd, (i + 1) * IDC_LINE_SWITCH, BM_SETCHECK, (WPARAM)((i + 1) == LineOffset), 0);
                                }
                            }
                        }
                        else if (CtrlID % IDC_LINE_ADVANCED == 0)
                        {
                            ADVANCED_CONTEXT AdvancedContext;

                            /* compute line offset */
                            LineOffset = CtrlID / IDC_LINE_ADVANCED;

                            /* compute window id of line name static control */
                            CtrlID = LineOffset * IDC_LINE_NAME;

                            /* get line name */
                            if (GetDlgItemTextW(hwnd, CtrlID, AdvancedContext.LineName, MIXER_LONG_NAME_CHARS) != 0)
                            {
                                AdvancedContext.MixerWindow = Preferences.MixerWindow;
                                AdvancedContext.Mixer = Preferences.MixerWindow->Mixer;
                                AdvancedContext.Line = SndMixerGetLineByName(Preferences.MixerWindow->Mixer,
                                                                             Preferences.SelectedLine,
                                                                             AdvancedContext.LineName);
                                if (AdvancedContext.Line)
                                {
                                    DialogBoxParam(hAppInstance,
                                                   MAKEINTRESOURCE(IDD_ADVANCED),
                                                   hwnd,
                                                   AdvancedDlgProc,
                                                   (LPARAM)&AdvancedContext);
                                }
                            }
                        }
                    }
                }
            }
            break;
        }

        case MM_MIXM_LINE_CHANGE:
        {
            DPRINT("MM_MIXM_LINE_CHANGE\n");
            break;
        }

        case MM_MIXM_CONTROL_CHANGE:
        {
            DPRINT("MM_MIXM_CONTROL_CHANGE\n");

            /* get mixer window */
            MixerWindow = GetWindowData(hwnd,
                                        MIXER_WINDOW);

            /* sanity checks */
            assert(MixerWindow);
            assert(MixerWindow->Mixer->hmx == (HMIXER)wParam);

            SndMixerEnumConnections(MixerWindow->Mixer, Preferences.SelectedLine, MixerControlChangeCallback, (PVOID)lParam);
            break;
        }

        case WM_VSCROLL:
            switch (LOWORD(wParam))
            {
                case TB_THUMBTRACK:
                    /* get dialog item ctrl */
                    CtrlID = GetDlgCtrlID((HWND)lParam);

                    /* get line index */
                    LineOffset = CtrlID / IDC_LINE_SLIDER_VERT;

                    /* compute window id of line name static control */
                    CtrlID = LineOffset * IDC_LINE_NAME;

                    /* get line name */
                    if (GetDlgItemTextW(hwnd, CtrlID, Context.LineName, MIXER_LONG_NAME_CHARS) != 0)
                    {
                        /* setup context */
                        Context.SliderPos = LineOffset;
                        Context.bVertical = TRUE;
                        Context.bSwitch = FALSE;

                        /* set volume */
                        SndMixerEnumConnections(Preferences.MixerWindow->Mixer, Preferences.SelectedLine, SetVolumeCallback, (LPVOID)&Context);
                    }
                    break;

                case TB_ENDTRACK:
                    MixerWindow = GetWindowData(hwnd,
                                                MIXER_WINDOW);

                    /* get dialog item ctrl */
                    CtrlID = GetDlgCtrlID((HWND)lParam);

                    /* get line index */
                    LineOffset = CtrlID / IDC_LINE_SLIDER_VERT;

                    if (LineOffset == 1 && MixerWindow->Mixer->MixerId == 0)
                        PlaySound((LPCTSTR)SND_ALIAS_SYSTEMDEFAULT, NULL, SND_ASYNC | SND_ALIAS_ID);
                    break;

                default:
                    break;
            }
            break;

        case WM_HSCROLL:
            switch (LOWORD(wParam))
            {
                case TB_THUMBTRACK:
                    /* get dialog item ctrl */
                    CtrlID = GetDlgCtrlID((HWND)lParam);

                    /* get line index */
                    LineOffset = CtrlID / IDC_LINE_SLIDER_HORZ;

                    /* compute window id of line name static control */
                    CtrlID = LineOffset * IDC_LINE_NAME;

                    /* get line name */
                    if (GetDlgItemTextW(hwnd, CtrlID, Context.LineName, MIXER_LONG_NAME_CHARS) != 0)
                    {
                        /* setup context */
                        Context.SliderPos = LineOffset;
                        Context.bVertical = TRUE;
                        Context.bSwitch = FALSE;

                        /* set volume */
                        SndMixerEnumConnections(Preferences.MixerWindow->Mixer, Preferences.SelectedLine, SetVolumeCallback, (LPVOID)&Context);
                    }
                    break;

                case TB_ENDTRACK:
                    MixerWindow = GetWindowData(hwnd,
                                                MIXER_WINDOW);

                    /* get dialog item ctrl */
                    CtrlID = GetDlgCtrlID((HWND)lParam);

                    /* get line index */
                    LineOffset = CtrlID / IDC_LINE_SLIDER_HORZ;

                    if (LineOffset == 1 && MixerWindow->Mixer->MixerId == 0)
                        PlaySound((LPCTSTR)SND_ALIAS_SYSTEMDEFAULT, NULL, SND_ASYNC | SND_ALIAS_ID);
                    break;

                default:
                    break;
            }
            break;


        case WM_CREATE:
        {
            MixerWindow = ((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLongPtr(hwnd,
                             GWL_USERDATA,
                             (LONG_PTR)MixerWindow);
            MixerWindow->hWnd = hwnd;
            MixerWindow->Mixer = SndMixerCreate(MixerWindow->hWnd, MixerWindow->MixerId);
            if (MixerWindow->Mixer != NULL)
            {
                TCHAR szProduct[MAXPNAMELEN];

                /* get mixer product name */
                if (SndMixerGetProductName(MixerWindow->Mixer,
                                           szProduct,
                                           sizeof(szProduct) / sizeof(szProduct[0])) == -1)
                {
                    /* failed to get name */
                    szProduct[0] = L'\0';
                }


                /* initialize preferences */
                ZeroMemory(&Preferences, sizeof(Preferences));

                /* store mixer */
                Preferences.Mixer = MixerWindow->Mixer;

                /* store mixer window */
                Preferences.MixerWindow = MixerWindow;

                /* first destination line id */
                Preferences.SelectedLine = 0xFFFF0000;

                /* copy product */
                wcscpy(Preferences.DeviceName, szProduct);

                /* Disable the 'Advanced Controls' menu item */
                EnableMenuItem(GetMenu(hwnd), IDM_ADVANCED_CONTROLS, MF_BYCOMMAND | MF_GRAYED);

                /* Load the placement coordinate data of the window */
                bRet = LoadXYCoordWnd(&Preferences);
                if (bRet)
                {
                    /*
                     * LoadXYCoordWnd() might fail for the first time of opening the application which is normal as
                     * the Sound Control's applet settings haven't been saved yet to the Registry. At this point SetWindowPos()
                     * call is skipped.
                     */
                    SetWindowPos(hwnd, NULL, MixerWindow->WndPosX, MixerWindow->WndPosY, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
                }

                /* create status window */
                if (MixerWindow->Mode == NORMAL_MODE)
                {
                    MixerWindow->hStatusBar = CreateStatusWindow(WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS,
                                                                 NULL,
                                                                 hwnd,
                                                                 0);
                    if (MixerWindow->hStatusBar)
                    {
                        SendMessage(MixerWindow->hStatusBar,
                                    WM_SETTEXT,
                                    0,
                                   (LPARAM)szProduct);
                    }
                }

                if (!RebuildMixerWindowControls(&Preferences))
                {
                    DPRINT("Rebuilding mixer window controls failed!\n");
                    SndMixerDestroy(MixerWindow->Mixer);
                    MixerWindow->Mixer = NULL;
                    Result = -1;
                }
            }

            RegisterHotKey(hwnd, HOTKEY_CTRL_S, MOD_CONTROL, 'S');
            break;
        }

        case WM_DESTROY:
        {
            MixerWindow = GetWindowData(hwnd,
                                        MIXER_WINDOW);
            if (MixerWindow != NULL)
            {
                if (MixerWindow->Mixer != NULL)
                {
                    SndMixerDestroy(MixerWindow->Mixer);
                }
                if (MixerWindow->hFont)
                    DeleteObject(MixerWindow->hFont);
                HeapFree(hAppHeap, 0, MixerWindow);
            }
            break;
        }

        case WM_CLOSE:
        {
            SaveXYCoordWnd(hwnd, &Preferences);
            PostQuitMessage(0);
            break;
        }

        case WM_HOTKEY:
        {
            if (wParam == HOTKEY_CTRL_S) 
            {
                Preferences.MixerWindow->Mode = (Preferences.MixerWindow->Mode == NORMAL_MODE ? SMALL_MODE : NORMAL_MODE);
                RebuildMixerWindowControls(&Preferences);
            }
            break;
        }

        default:
        {
            Result = DefWindowProc(hwnd,
                                   uMsg,
                                   wParam,
                                   lParam);
            break;
        }
    }

    return Result;
}

static BOOL
RegisterApplicationClasses(VOID)
{
    WNDCLASSEX wc;

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = MainWindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = sizeof(PMIXER_WINDOW);
    wc.hInstance = hAppInstance;
    wc.hIcon = LoadIcon(hAppInstance,
                        MAKEINTRESOURCE(IDI_MAINAPP));
    wc.hCursor = LoadCursor(NULL,
                            IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = SZ_APP_CLASS;
    wc.hIconSm = NULL;
    MainWindowClass = RegisterClassEx(&wc);

    return MainWindowClass != 0;
}

static VOID
UnregisterApplicationClasses(VOID)
{
    UnregisterClass(SZ_APP_CLASS,
                    hAppInstance);
}

static HWND
CreateApplicationWindow(
    WINDOW_MODE WindowMode,
    UINT MixerId)
{
    HWND hWnd;

    PMIXER_WINDOW MixerWindow = HeapAlloc(hAppHeap,
                                          HEAP_ZERO_MEMORY,
                                          sizeof(MIXER_WINDOW));
    if (MixerWindow == NULL)
    {
        return NULL;
    }

    MixerWindow->Mode = WindowMode;
    MixerWindow->MixerId = MixerId;

    if (mixerGetNumDevs() > 0)
    {
        hWnd = CreateWindowEx(WS_EX_WINDOWEDGE | WS_EX_CONTROLPARENT,
                              SZ_APP_CLASS,
                              lpAppTitle,
                              WS_DLGFRAME | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE,
                              0, 0, 300, 315,
                              NULL,
                              LoadMenu(hAppInstance,
                                       MAKEINTRESOURCE(IDM_MAINMENU)),
                              hAppInstance,
                              MixerWindow);
    }
    else
    {
        LPTSTR lpErrMessage;

        /*
         * no mixer devices are available!
         */

        hWnd = NULL;
        if (AllocAndLoadString(&lpErrMessage,
                               hAppInstance,
                               IDS_NOMIXERDEVICES))
        {
            MessageBox(NULL,
                       lpErrMessage,
                       lpAppTitle,
                       MB_ICONINFORMATION);
            LocalFree(lpErrMessage);
        }
    }

    if (hWnd == NULL)
    {
        HeapFree(hAppHeap,
                 0,
                 MixerWindow);
    }

    return hWnd;
}

static
BOOL
HandleCommandLine(LPTSTR cmdline,
                  DWORD dwStyle,
                  PWINDOW_MODE pMode,
                  PUINT pMixerId)
{
    TCHAR option;

    *pMixerId = PLAY_MIXER;
    *pMode = (dwStyle & 0x20) ? SMALL_MODE : NORMAL_MODE;

    while (*cmdline == _T(' ') || *cmdline == _T('-') || *cmdline == _T('/'))
    {
        if (*cmdline++ == _T(' '))
            continue;

        option = *cmdline;
        if (option)
            cmdline++;
        while (*cmdline == _T(' '))
            cmdline++;

        switch (option)
        {
            case 'd': /* Device */
            case 'D':
                break;

            case 'n': /* Small size */
            case 'N':
                *pMode = NORMAL_MODE;
                break;

            case 's': /* Small size */
            case 'S':
                *pMode = SMALL_MODE;
                break;

            case 't': /* Tray size */
            case 'T':
                *pMode = TRAY_MODE;
                break;

            case 'p': /* Play mode */
            case 'P':
                *pMixerId = PLAY_MIXER;
                break;

            case 'r': /* Record mode */
            case 'R':
                *pMixerId = RECORD_MIXER;
                break;

            default:
                return FALSE;
        }
    }

    return TRUE;
}

int WINAPI
_tWinMain(HINSTANCE hInstance,
          HINSTANCE hPrevInstance,
          LPTSTR lpszCmdLine,
          int nCmdShow)
{
    MSG Msg;
    int Ret = 1;
    INITCOMMONCONTROLSEX Controls;
    WINDOW_MODE WindowMode = SMALL_MODE;
    UINT MixerId = 0;
    DWORD dwStyle;

    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(nCmdShow);

    hAppInstance = hInstance;
    hAppHeap = GetProcessHeap();

    if (InitAppConfig())
    {
        dwStyle = GetStyleValue();
        HandleCommandLine(lpszCmdLine, dwStyle, &WindowMode, &MixerId);

        /* load the application title */
        if (!AllocAndLoadString(&lpAppTitle,
                                hAppInstance,
                                IDS_SNDVOL32))
        {
            lpAppTitle = NULL;
        }

        Controls.dwSize = sizeof(INITCOMMONCONTROLSEX);
        Controls.dwICC = ICC_BAR_CLASSES | ICC_STANDARD_CLASSES;

        InitCommonControlsEx(&Controls);

        if (WindowMode == TRAY_MODE)
        {
            DialogBoxParam(hAppInstance,
                           MAKEINTRESOURCE(IDD_TRAY_MASTER),
                           NULL,
                           TrayDlgProc,
                           0);
        }
        else
        {
            if (RegisterApplicationClasses())
            {
                hMainWnd = CreateApplicationWindow(WindowMode, MixerId);
                if (hMainWnd != NULL)
                {
                    BOOL bRet;
                    while ((bRet =GetMessage(&Msg,
                                             NULL,
                                             0,
                                             0)) != 0)
                    {
                        if (bRet != -1)
                        {
                            TranslateMessage(&Msg);
                            DispatchMessage(&Msg);
                        }
                    }

                    DestroyWindow(hMainWnd);
                    Ret = 0;
                }
                else
                {
                    DPRINT("Failed to create application window (LastError: %d)!\n", GetLastError());
                }

                UnregisterApplicationClasses();
            }
            else
            {
                DPRINT("Failed to register application classes (LastError: %d)!\n", GetLastError());
            }
        }

        if (lpAppTitle != NULL)
        {
            LocalFree(lpAppTitle);
        }

        CloseAppConfig();
    }
    else
    {
        DPRINT("Unable to open the Volume Control registry key!\n");
    }

    return Ret;
}
