#define UNICODE
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <mmsystem.h>

#include <resrc1.h>
#include "internal.h"

#define WINMM_DLL               TEXT("winmm.dll")
#define WAVE_GET_NUM_DEV_FN_NAME "waveOutGetNumDevs"
#define PLAY_SOUND_FN_NAME      "PlaySoundW"


typedef UINT (* WAVE_NUM_DEV_FN)(VOID);
typedef BOOL (* PLAY_SOUND_FN)( IN LPCWSTR pszSound, IN HMODULE hmod, IN DWORD fdwSound);


const WCHAR *InRangeLabelKey    = TEXT("AppEvents\\EventLabels\\InfraredInRange");
const WCHAR *OutOfRangeLabelKey = TEXT("AppEvents\\EventLabels\\InfraredOutOfRange");
const WCHAR *InterruptLabelKey  = TEXT("AppEvents\\EventLabels\\InfraredInterrupt");
const WCHAR *WirelessLinkKey    = TEXT("AppEvents\\Schemes\\Apps\\WirelessLink");
const WCHAR *InRangeSoundKey    = TEXT("AppEvents\\Schemes\\Apps\\WirelessLink\\InfraredInRange");
const WCHAR *OutOfRangeSoundKey = TEXT("AppEvents\\Schemes\\Apps\\WirelessLink\\InfraredOutOfRange");
const WCHAR *InterruptSoundKey  = TEXT("AppEvents\\Schemes\\Apps\\WirelessLink\\InfraredInterrupt");
const WCHAR *CurrentSoundKey    = TEXT(".Current");
const WCHAR *DefaultSoundKey    = TEXT(".Default");
const WCHAR *InRangeWav         = TEXT("ir_begin.wav");
const WCHAR *OutOfRangeWav      = TEXT("ir_end.wav");
const WCHAR *InterruptWav       = TEXT("ir_inter.wav");
const WCHAR *SystemInfoKey      = TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion");
const WCHAR *SystemRootVal      = TEXT("SystemRoot");
const WCHAR *MediaPath          = TEXT("\\Media\\");


TCHAR                       gSystemRoot[64];
TCHAR                       InRangeWavPath[128];
TCHAR                       OutOfRangeWavPath[128];
TCHAR                       InterruptWavPath[128];

HKEY                        ghInRangeKey = 0;
HKEY                        ghOutOfRangeKey = 0;
HKEY                        ghInterruptKey = 0;


WAVE_NUM_DEV_FN             WaveNumDev;
PLAY_SOUND_FN               PlaySoundF;


BOOL
InitializeSound(
    HKEY   CurrentUserKey,
    HANDLE Event
    )

{

    if (CurrentUserKey)
    {

        CreateRegSoundData();

        // Open the wave file keys so we can monitor them for changes

        RegOpenKeyEx(CurrentUserKey, InRangeSoundKey, 0, KEY_READ, &ghInRangeKey);
        RegOpenKeyEx(CurrentUserKey, OutOfRangeSoundKey, 0, KEY_READ, &ghOutOfRangeKey);
        RegOpenKeyEx(CurrentUserKey, InterruptSoundKey, 0, KEY_READ, &ghInterruptKey);

        GetRegSoundData(Event);

    }


    return TRUE;
}

VOID
UninitializeSound(
    VOID
    )

{
    if (ghInRangeKey)
    {
        RegCloseKey(ghInRangeKey);
        ghInRangeKey = 0;
    }

    if (ghInterruptKey)
    {
        RegCloseKey(ghInterruptKey);
        ghInterruptKey = 0;
    }

    if (ghOutOfRangeKey)
    {
        RegCloseKey(ghOutOfRangeKey);
        ghOutOfRangeKey = 0;
    }


    return;
}


VOID
LoadSoundApis()
{
    HMODULE     hWinMm;

    // Load multimedia module and get wave player entry points

    if ((hWinMm = LoadLibrary(WINMM_DLL)) == NULL)
    {
        DEBUGMSG(("IRMON: failed to load winmm.dll\n")) ;
    }
    else
    {
        if (!(WaveNumDev = (WAVE_NUM_DEV_FN) GetProcAddress(hWinMm,
                                            WAVE_GET_NUM_DEV_FN_NAME)))
        {
            DEBUGMSG(("IRMON: GetProcAddress failed on WaveGetNumDevs\n"));
        }

        if (!(PlaySoundF = (PLAY_SOUND_FN) GetProcAddress(hWinMm,
                                            PLAY_SOUND_FN_NAME)))
        {
            DEBUGMSG(("IRMON: GetProcAddress failed on PlaySound\n"));
        }

    }
}

VOID
CreateRegSoundEventLabel(
    const WCHAR     *LabelKey,
    DWORD           LabelId)
{
    TCHAR           LabelStr[64];

    // Load the localizable string label for the sound event

    if (!LoadString(ghInstance, LabelId, LabelStr, sizeof(LabelStr)/sizeof(TCHAR)))
    {
        DEBUGMSG(("IRMON: LoadString failed %d\n", GetLastError()));
        return;
    }

    if (RegSetValue(ghCurrentUserKey, LabelKey, REG_SZ, LabelStr,
                   lstrlen(LabelStr)))
    {
        DEBUGMSG(("IRMON: RegSetValue failed %d\n", GetLastError()));
    }
}

VOID
CreateRegSoundScheme(
    TCHAR       *SystemRoot,
    const WCHAR *SoundKey,
    const WCHAR *WavFile)
{
    HKEY    hSoundKey;
    DWORD   Disposition;
    TCHAR   WavPath[128];


    if (RegCreateKeyEx(ghCurrentUserKey,
                       SoundKey,
                       0,                      // reserved MBZ
                       0,                      // class name
                       REG_OPTION_NON_VOLATILE,
                       KEY_ALL_ACCESS,
                       0,                      // security attributes
                       &hSoundKey,
                       &Disposition))
    {
        DEBUGMSG(("IRMON: RegCreateKey failed %d\n", GetLastError()));
        return;
    }

    if (Disposition == REG_CREATED_NEW_KEY)
    {

        if (RegSetValue(hSoundKey, CurrentSoundKey, REG_SZ, WavFile,
                        lstrlen(WavFile)))
        {
            DEBUGMSG(("IRMON: RegSetValue failed %d\n", GetLastError()));
        }

        lstrcpy(WavPath, SystemRoot);
        lstrcat(WavPath, MediaPath);
        lstrcat(WavPath, WavFile);

        if (RegSetValue(hSoundKey, DefaultSoundKey, REG_SZ, WavPath,
                        lstrlen(WavPath)))
        {
            DEBUGMSG(("IRMON: RegSetValue failed %d\n", GetLastError()));
        }
    }

    RegCloseKey(hSoundKey);
}

VOID
CreateRegSoundData()
{
    DWORD           ValType;
    HKEY            hKey, hUserKey;
    LONG            Len;
    TCHAR           WirelessLinkStr[64];

    // Get the system root so we can add default registry values
    // i.e. Schemes\WirelessLink\InfraredInRange\.Default = "C:\winnt\media\irin.wav"
    //                                                       ^^^^^^^^

    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, SystemInfoKey, 0, KEY_READ, &hKey))
    {
        DEBUGMSG(("IRMON: RegOpenKey2 failed %d\n", GetLastError()));
        return;
    }

    Len = sizeof(gSystemRoot);

    if (RegQueryValueEx(hKey, SystemRootVal, 0, &ValType,
                        (LPBYTE) gSystemRoot, &Len))
    {
        DEBUGMSG(("IRMON: RegQueryValue failed %d\n", GetLastError()));
        return;
    }

    RegCloseKey(hKey);

    // Create the sound EventLabels and schemes if they don't exist

    CreateRegSoundEventLabel(InRangeLabelKey, IDS_INRANGE_LABEL);
    CreateRegSoundEventLabel(OutOfRangeLabelKey, IDS_OUTOFRANGE_LABEL);
    CreateRegSoundEventLabel(InterruptLabelKey, IDS_INTERRUPT_LABEL);

    if (!LoadString(ghInstance, IDS_WIRELESSLINK, WirelessLinkStr, sizeof(WirelessLinkStr)/sizeof(TCHAR)))
    {
        DEBUGMSG(("IRMON: LoadString failed %d\n", GetLastError()));
        return;
    }

    if (RegSetValue(ghCurrentUserKey, WirelessLinkKey, REG_SZ, WirelessLinkStr,
                   lstrlen(WirelessLinkStr)))
    {
        DEBUGMSG(("IRMON: RegSetValue failed %d\n", GetLastError()));
    }

    CreateRegSoundScheme(gSystemRoot, InRangeSoundKey, InRangeWav);
    CreateRegSoundScheme(gSystemRoot, OutOfRangeSoundKey, OutOfRangeWav);
    CreateRegSoundScheme(gSystemRoot, InterruptSoundKey, InterruptWav);

}

VOID
GetRegSoundWavPath(
    const WCHAR     *SoundKey,
    TCHAR           *SoundWavPath,
    LONG            PathLen)
{
    TCHAR   CurrRegPath[128];
    DWORD   ValType;
    HKEY    hSoundKey;
    int     i;
    BOOLEAN FullPath = FALSE;

    lstrcpy(CurrRegPath, SoundKey);
    lstrcat(CurrRegPath, TEXT("\\"));
    lstrcat(CurrRegPath, CurrentSoundKey);

    if (RegOpenKeyEx(ghCurrentUserKey, CurrRegPath, 0, KEY_READ, &hSoundKey))
    {
        DEBUGMSG(("IRMON: RegOpenKey3 failed %d\n", GetLastError()));
        return;
    }

    if (RegQueryValueEx(hSoundKey, NULL, 0, &ValType,
                        (LPBYTE) SoundWavPath, &PathLen))
    {
        DEBUGMSG(("IRMON: RegQueryValue failed %d\n", GetLastError()));
        RegCloseKey(hSoundKey);
        return;
    }


    // the PlaySound API does not look in \winnt\media for
    // wav files when a filename is specified, so if this is not a full
    // pathname then we'll need to add "c:\winnt\media" to the WavPath.
    // I'm counting on a path without '\' as an indication that it is relative.

    for (i = 0; i < lstrlen(SoundWavPath); i++)
    {
        if (SoundWavPath[i] == TEXT('\\'))
        {
            FullPath = TRUE;
            break;
        }
    }

    if (!FullPath && lstrlen(SoundWavPath) != 0)
    {
         TCHAR  TempStr[64];

         lstrcpy(TempStr, SoundWavPath);
         lstrcpy(SoundWavPath, gSystemRoot);
         lstrcat(SoundWavPath, MediaPath);
         lstrcat(SoundWavPath, TempStr);
    }

    RegCloseKey(hSoundKey);
}

VOID
GetRegSoundData(
    HANDLE    Event
    )
{
    GetRegSoundWavPath(InRangeSoundKey, InRangeWavPath, sizeof(InRangeWavPath));

//    DEBUGMSG(("IRMON: In range wav: %ws\n", InRangeWavPath));

    GetRegSoundWavPath(OutOfRangeSoundKey, OutOfRangeWavPath, sizeof(OutOfRangeWavPath));

//    DEBUGMSG(("IRMON: Out of range wav: %ws\n", OutOfRangeWavPath));

    GetRegSoundWavPath(InterruptSoundKey, InterruptWavPath, sizeof(InterruptWavPath));

//    DEBUGMSG(("IRMON: Interrupt wav: %ws\n", InterruptWavPath));

    RegNotifyChangeKeyValue(ghInRangeKey,
                            TRUE,              // watch child keys
                            REG_NOTIFY_CHANGE_LAST_SET,
                            Event,
                            TRUE);               // async

    RegNotifyChangeKeyValue(ghOutOfRangeKey,
                            TRUE,              // watch child keys
                            REG_NOTIFY_CHANGE_LAST_SET,
                            Event,
                            TRUE);               // async

    RegNotifyChangeKeyValue(ghInterruptKey,
                            TRUE,              // watch child keys
                            REG_NOTIFY_CHANGE_LAST_SET,
                            Event,
                            TRUE);               // async

}

VOID
PlayIrSound(IRSOUND_EVENT SoundEvent)
{
    int     Beep1, Beep2, Beep3;
    BOOL    SoundPlayed = FALSE;
    LPWSTR  WaveSound;
    DWORD   Flags = 0;


    if (!WaveNumDev)
    {
        LoadSoundApis();
    }

    switch (SoundEvent)
    {
        case INRANGE_SOUND:
            WaveSound = InRangeWavPath;

            Beep1 = 200;
            Beep2 = 250;
            Beep3 = 300;
            break;

        case OUTOFRANGE_SOUND:
            WaveSound = OutOfRangeWavPath;
            Beep1 = 300;
            Beep2 = 250;
            Beep3 = 200;
            break;

        case INTERRUPTED_SOUND:
            WaveSound = InterruptWavPath;
            Flags = SND_LOOP;
            Beep1 = 500;
            Beep2 = 350;
            Beep3 = 500;
            break;

        case END_INTERRUPTED_SOUND:
            WaveSound = NULL;
            SoundPlayed = TRUE;
            break;
    }

    if (SoundEvent != END_INTERRUPTED_SOUND && lstrlen(WaveSound) == 0) {
        //
        //  the path of sound file is a null string, can't play anything
        //
        SoundPlayed = TRUE;

    } else if (WaveNumDev && PlaySoundF && WaveNumDev() > 0) {
        //
        //  the functions are availible and there is at least on wave device
        //
        SoundPlayed = PlaySoundF(
                          WaveSound,
                          (HMODULE) NULL,
                          SND_FILENAME | SND_ASYNC | Flags
                          );

        if (WaveSound == NULL) {
            //
            //  we just wanted to stop the wave, set this to true so it will not try to beep
            //
            SoundPlayed=TRUE;
        }

    }

    if (!SoundPlayed) {
        //
        //  could not play a wave, just uses beeps
        //
        DEBUGMSG(("Not Wave enabled\n"));

        Beep(Beep1, 100);
        Beep(Beep2, 100);
        Beep(Beep3, 100);
    }
}