/*
 * Copyright (c) 2000-2009 TeamDev Ltd. All rights reserved.
 * TeamDev PROPRIETARY and CONFIDENTIAL.
 * Use is subject to license terms.
 */
import com.jniwrapper.*;
import com.jniwrapper.win32.Handle;

public class MixerHelper
{
    static final int MMSYSERR_NOERROR = 0;
    static final int MAXPNAMELEN = 32;
    static final int MIXER_LONG_NAME_CHARS = 64;
    static final int MIXER_SHORT_NAME_CHARS = 16;
    static final int MIXER_GETLINEINFOF_COMPONENTTYPE = 0x3;
    static final int MIXER_GETCONTROLDETAILSF_VALUE = 0x0;
    static final int MIXER_GETLINECONTROLSF_ONEBYTYPE = 0x2;
    static final int MIXER_SETCONTROLDETAILSF_VALUE = 0x0;

    static final int MIXERLINE_COMPONENTTYPE_SRC_FIRST = 0x1000;
    static final int MIXERLINE_COMPONENTTYPE_DST_FIRST = 0x0;

    static final int MIXERLINE_COMPONENTTYPE_DST_SPEAKERS = (MIXERLINE_COMPONENTTYPE_DST_FIRST + 4);
    static final int MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE = (MIXERLINE_COMPONENTTYPE_SRC_FIRST + 3);
    static final int MIXERLINE_COMPONENTTYPE_SRC_LINE = (MIXERLINE_COMPONENTTYPE_SRC_FIRST + 2);
    static final int MIXERLINE_COMPONENTTYPE_DST_WAVEIN = (MIXERLINE_COMPONENTTYPE_DST_FIRST + 7);

    static final int MIXERCONTROL_CT_CLASS_FADER = 0x50000000;
    static final int MIXERCONTROL_CT_UNITS_UNSIGNED = 0x30000;
    static final int MIXERCONTROL_CONTROLTYPE_FADER = (MIXERCONTROL_CT_CLASS_FADER | MIXERCONTROL_CT_UNITS_UNSIGNED);
    static final int MIXERCONTROL_CONTROLTYPE_VOLUME = (MIXERCONTROL_CONTROLTYPE_FADER + 1);


    static final Library WINMM = new Library("winmm");

    static final Function mixerClose = WINMM.getFunction("mixerClose");
    static final Function mixerOpen = WINMM.getFunction("mixerOpen");

    static final Function mixerGetControlDetails = WINMM.getFunction("mixerGetControlDetailsA");
    static final Function mixerGetLineControls = WINMM.getFunction("mixerGetLineControlsA");
    static final Function mixerGetLineInfo = WINMM.getFunction("mixerGetLineInfoA");
    static final Function mixerSetControlDetails = WINMM.getFunction("mixerSetControlDetails");

    /**
     * Wrapper for <code>MIXERCAPS</code> structure.
     */
    static class MixerCaps extends Structure
    {
        UInt16 wMid = new UInt16();
        UInt16 wPid = new UInt16();
        UInt16 vDriverVersion = new UInt16();
        AnsiString szPname = new AnsiString(MAXPNAMELEN);
        UInt32 fdwSupport = new UInt32();
        UInt32 cDestinations = new UInt32();

        public MixerCaps()
        {
            init(new Parameter[]{wMid, wPid, vDriverVersion, szPname, fdwSupport, cDestinations});
        }
    }

    /**
     * Wrapper for <code>MIXERCONTROL</code> structure.
     */
    static class MixerControl extends Structure
    {
        UInt32 cbStruct = new UInt32();
        UInt32 dwControlID = new UInt32();
        UInt32 dwControlType = new UInt32();
        UInt32 fdwControl = new UInt32();
        UInt32 cMultipleItems = new UInt32();
        AnsiString szShortName = new AnsiString(MIXER_SHORT_NAME_CHARS);
        AnsiString szName = new AnsiString(MIXER_LONG_NAME_CHARS);
        ULongInt lMinimum = new ULongInt();
        ULongInt lMaximum = new ULongInt();
        PrimitiveArray reserved = new PrimitiveArray(UInt32.class, 10);

        public MixerControl()
        {
            init(new Parameter[]{cbStruct, dwControlID, dwControlType, fdwControl, cMultipleItems, szShortName, szName, lMinimum, lMaximum, reserved});
        }
    }

    /**
     * Wrapper for <code>MIXERCONTROLDETAILS</code> structure.
     */
    static class MixerControlDetails extends Structure
    {
        UInt32 cbStruct = new UInt32();
        UInt32 dwControlID = new UInt32();
        UInt32 cChannels = new UInt32();
        UInt32 item = new UInt32();
        UInt32 cbDetails = new UInt32();
        Pointer.Void paDetails = new Pointer.Void();

        public MixerControlDetails()
        {
            init(new Parameter[]{cbStruct, dwControlID, cChannels, item, cbDetails, paDetails});
        }
    }

    /**
     * Wrapper for <code>MIXERCONTROLDETAILS_UNSIGNED</code> structure.
     */
    static class MixerControlDetails_Unsigned extends Structure
    {
        UInt32 dwValue = new UInt32();

        public MixerControlDetails_Unsigned()
        {
            init(new Parameter[]{dwValue});
        }
    }

    /**
     * Wrapper for <code>MIXERLINE</code> structure.
     */
    static class MixerLine extends Structure
    {
        UInt32 cbStruct = new UInt32();
        UInt32 dwDestination = new UInt32();
        UInt32 dwSource = new UInt32();
        UInt32 dwLineID = new UInt32();
        UInt32 fdwLine = new UInt32();
        UInt32 dwUser = new UInt32();
        UInt32 dwComponentType = new UInt32();
        UInt32 cChannels = new UInt32();
        UInt32 cConnections = new UInt32();
        UInt32 cControls = new UInt32();
        AnsiString szShortName = new AnsiString(MIXER_SHORT_NAME_CHARS);
        AnsiString szName = new AnsiString(MIXER_LONG_NAME_CHARS);
        UInt32 dwType = new UInt32();
        UInt32 dwDeviceID = new UInt32();
        UInt16 wMid = new UInt16();
        UInt16 wPid = new UInt16();
        UInt vDriverVersion = new UInt();
        AnsiString szPname = new AnsiString(MAXPNAMELEN);

        public MixerLine()
        {
            init(new Parameter[]{cbStruct, dwDestination, dwSource, dwLineID, fdwLine, dwUser, dwComponentType, cChannels,
                    cConnections, cControls, szShortName, szName, dwType, dwDeviceID, wMid, wPid, vDriverVersion, szPname});
        }
    }


    /**
     * Wrapper for <code>MIXERLINECONTROLS</code> structure.
     */
    static class MixerLineControls extends Structure
    {
        UInt32 cbStruct = new UInt32();
        UInt32 dwLineID = new UInt32();
        UInt32 dwControl = new UInt32();
        UInt32 cControls = new UInt32();
        UInt32 cbmxctrl = new UInt32();
        Pointer pamxctrl = new Pointer(MixerControl.class);

        public MixerLineControls()
        {
            init(new Parameter[]{cbStruct, dwLineID, dwControl, cControls, cbmxctrl, pamxctrl});
        }
    }

    static class MIXERCONTROLDETAILS_BOOLEAN extends Structure {
        LongInt fValue;

        public MIXERCONTROLDETAILS_BOOLEAN() {
            init(new Parameter[] {fValue});
        }
    }


    /**
     * This function attempts to obtain a mixer control
     *
     * @return true if successful.
     */
    private static boolean getVolumeControl(Handle hmixer, int componentType, int ctrlType, /*out*/ MixerControl mxc, /*out*/ Int vCurrentVol)
    {
        MixerLineControls mxlc = new MixerLineControls();
        MixerLine mxl = new MixerLine();
        MixerControlDetails pmxcd = new MixerControlDetails();
        MixerControlDetails_Unsigned du = new MixerControlDetails_Unsigned();

        boolean retValue;
        vCurrentVol.setValue(-1);

        mxl.cbStruct.setValue(mxl.getLength());
        mxl.dwComponentType.setValue(componentType);

        UInt rc = new UInt();
        mixerGetLineInfo.invoke(rc, hmixer, new Pointer(mxl), new UInt32(MIXER_GETLINEINFOF_COMPONENTTYPE));

        if (MMSYSERR_NOERROR == rc.getValue())
        {
            int sizeofMIXERCONTROL = 152;

            PrimitiveArray buffer = new PrimitiveArray(UInt8.class, sizeofMIXERCONTROL);
            mxlc.pamxctrl.setReferencedObject(buffer);
            mxlc.cbStruct.setValue(mxlc.getLength());
            mxlc.dwLineID.setValue(mxl.dwLineID.getValue());
            mxlc.dwControl.setValue(ctrlType);
            mxlc.cControls.setValue(1);
            mxlc.cbmxctrl.setValue(sizeofMIXERCONTROL);

            // Allocate a buffer for the control
            mxc.cbStruct.setValue(sizeofMIXERCONTROL);

            // Get the control
            mixerGetLineControls.invoke(rc, hmixer, new Pointer(mxlc), new UInt32(MIXER_GETLINECONTROLSF_ONEBYTYPE));

            if (MMSYSERR_NOERROR == rc.getValue())
            {
                retValue = true;

                // Copy the control into the destination structure
                Pointer mxcPtr = new Pointer(mxc);
                mxlc.pamxctrl.castTo(mxcPtr);
            } else
            {
                return false;
            }

            int sizeofMIXERCONTROLDETAILS = new MixerControlDetails().getLength();
            int sizeofMIXERCONTROLDETAILS_UNSIGNED = new MixerControlDetails_Unsigned().getLength();

            pmxcd.cbStruct.setValue(sizeofMIXERCONTROLDETAILS);
            pmxcd.dwControlID.setValue(mxc.dwControlID.getValue());

            PrimitiveArray detailsBuffer = new PrimitiveArray(UInt8.class, sizeofMIXERCONTROLDETAILS_UNSIGNED);
            Pointer detailsBufferPtr = new Pointer(detailsBuffer);
            detailsBufferPtr.castTo(pmxcd.paDetails);
            pmxcd.cChannels.setValue(1);
            pmxcd.item.setValue(0);
            pmxcd.cbDetails.setValue(sizeofMIXERCONTROLDETAILS_UNSIGNED);

            mixerGetControlDetails.invoke(rc, hmixer, new Pointer(pmxcd), new UInt32(MIXER_GETCONTROLDETAILSF_VALUE));
            Pointer duPtr = new Pointer(du);
            pmxcd.paDetails.castTo(duPtr);

            vCurrentVol.setValue(du.dwValue.getValue());
            return retValue;
        }
        return false;
    }

    /**
     * This function sets the value for a volume control.
     *
     * @return true if successful
     */
    private static boolean setVolumeControl(Handle hmixer, MixerControl mxc, int volume)
    {
        boolean retValue;
        Int rc = new Int();

        MixerControlDetails mxcd = new MixerControlDetails();
        MixerControlDetails_Unsigned vol = new MixerControlDetails_Unsigned();

        mxcd.item.setValue(0);
        mxcd.dwControlID.setValue(mxc.dwControlID.getValue());
        mxcd.cbStruct.setValue(mxcd.getLength());
        mxcd.cbDetails.setValue(vol.getLength());

        // Allocate a buffer for the control value buffer
        mxcd.cChannels.setValue(1);
        vol.dwValue.setValue(volume);

        // Copy the data into the control value buffer
        mxcd.paDetails.setValue(new MixerControlDetails_Unsigned().getLength());
        Pointer volPtr = new Pointer(vol);
        volPtr.castTo(mxcd.paDetails);

        // Set the control value
        mixerSetControlDetails.invoke(rc, hmixer, new Pointer(mxcd), new Int(MIXER_SETCONTROLDETAILSF_VALUE));
        if (MMSYSERR_NOERROR == rc.getValue())
        {
            retValue = true;
        } else
        {
            retValue = false;
        }
        return retValue;
    }

    /**
     * Returns current master volume.
     *
     * @return current master volume
     */
    public static int getMasterVolume()
    {
        Handle mixer = new Handle();
        MixerControl volCtrl = new MixerControl();
        Int currentVol = new Int();
        int type = MIXERCONTROL_CONTROLTYPE_VOLUME;


        mixerOpen.invoke(null, new Parameter[] {
                new Pointer(mixer),
                new UInt(0),
                new Pointer.Void(),
                new Pointer.Void(),
                new UInt32()
        });

        getVolumeControl(mixer, MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, type, volCtrl, currentVol);

        mixerClose.invoke(null, mixer);

        return (int) currentVol.getValue();
    }

    /**
     * Sets master volume
     *
     * @param vVolume
     */
    public static void setMasterVolume(int vVolume)
    {
        Handle mixer = new Handle();
        MixerControl volCtrl = new MixerControl();

        Int currentVol = new Int();
        int type = MIXERCONTROL_CONTROLTYPE_VOLUME;

        mixerOpen.invoke(null, new Parameter[]{
                new Pointer(mixer),
                new UInt(0),
                new Pointer.Void(),
                new Pointer.Void(),
                new UInt32()
        });
        getVolumeControl(mixer, MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, type, volCtrl, currentVol);

        if (vVolume > volCtrl.lMaximum.getValue())
        {
            vVolume = (int) volCtrl.lMaximum.getValue();
        }

        if (vVolume < volCtrl.lMinimum.getValue())
        {
            vVolume = (int) volCtrl.lMinimum.getValue();
        }

        setVolumeControl(mixer, volCtrl, vVolume);
        getVolumeControl(mixer, MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, type, volCtrl, currentVol);

        if (vVolume != currentVol.getValue())
        {
            throw new RuntimeException("Cannot Set Volume");
        }
        mixerClose.invoke(null, mixer);
    }

    public static int getMicrophoneVolume() {
        Handle mixer = new Handle();
        MixerControl volCtrl = new MixerControl();
        Int currentVol = new Int();

        mixerOpen.invoke(null, new Parameter[] {
                new Pointer(mixer),
                new UInt(0),
                new Pointer.Void(),
                new Pointer.Void(),
                new UInt32()
        });

        getVolumeControl(mixer, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, MIXERCONTROL_CONTROLTYPE_FADER, volCtrl, currentVol);

        mixerClose.invoke(null, mixer);

        return (int) currentVol.getValue();
    }

    public static void main(String[] args) {
//        int value = getMasterVolume();
//        System.out.println("Master Volume = " + value + " or " + (value * 100 / 65535) + "%");
//        setMasterVolume(0);
        int value = getMicrophoneVolume();
        System.out.println("Microphone Volume = " + value + " or " + (value * 100 / 65535) + "%");
    }
}