/*
 * 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.FunctionName;
import com.jniwrapper.win32.Handle;
import com.jniwrapper.win32.MessageLoopThread;
import com.jniwrapper.win32.Point;
import com.jniwrapper.win32.system.Module;
import com.jniwrapper.win32.ui.User32;

/**
 * This sample demonstrates how to install the low level mouse hook without a native library.
 *
 * @author Serge Piletsky
 */
public class LowLevelMouseHookSample {
    static final FunctionName FUNCTION_SET_WINDOWS_HOOK = new FunctionName("SetWindowsHookEx");
    static final String FUNCTION_CALL_NEXT_HOOK = "CallNextHookEx";
    static final String FUNCTION_UNHOOK_WINDOWS_HOOK = "UnhookWindowsHookEx";

    static final int WH_MOUSE_LL = 14;

    private Handle mouseHookHandle = new Handle();
    private MessageLoopThread _dispatchThread;

    public LowLevelMouseHookSample() {
        _dispatchThread = new MessageLoopThread("HookDispatchThread");
    }

    /**
     * Class MSLLHOOKSTRUCT represents MSLLHOOKSTRUCT native structure
     */
    public static class MSLLHOOKSTRUCT extends Structure {
        private Point pt = new Point();
        private UInt32 mouseData = new UInt32();
        private UInt32 flags = new UInt32();
        private UInt32 time = new UInt32();
        private UInt32 dwExtraInfo = new UInt32();

        public MSLLHOOKSTRUCT() {
            init(new Parameter[]{pt, mouseData, flags, time, dwExtraInfo});
        }


        public Object clone() {
            MSLLHOOKSTRUCT clone = new MSLLHOOKSTRUCT();
            clone.initFrom(this);
            return clone;
        }
    }

    // callback proc
    static class CallbackProc extends Callback {
        private Int code = new Int();
        private UInt32 wParam = new UInt32();
        private UInt32 lParam = new UInt32();
        private UInt32 lResult = new UInt32();

        public CallbackProc() {
            init(new Parameter[]{code, wParam, lParam}, lResult);
        }

        public void callback() {
            Pointer.Void structureHandle = new Pointer.Void(lParam.getValue());

            MSLLHOOKSTRUCT mouseStructure = new MSLLHOOKSTRUCT();
            Pointer structurePointer = new Pointer(mouseStructure);
            // cast the pointers
            structureHandle.castTo(structurePointer);

            Point pt = mouseStructure.pt;

            byte delta = (byte)((0xFFFF0000 & mouseStructure.mouseData.getValue()) >> 16);
            String message = "Mouse callback: wParam = " + wParam.getValue() + ", Point: [x=" + pt.getX() + ", y=" + pt.getY() + "]; delta: " + delta;
            System.out.println(message);
        }
    }

    public void start() {
        _dispatchThread.doStart();

        // install a hook in the dispatch thread
        try {
            _dispatchThread.doInvokeAndWait(new Runnable() {
                public void run() {
                    User32 user32 = User32.getInstance();
                    Function setWindowsHook = user32.getFunction(FUNCTION_SET_WINDOWS_HOOK.toString());
                    UInt32 threadID = new UInt32(0);

                    CallbackProc trackingCallback = new CallbackProc();
                    mouseHookHandle = new Handle();
                    final Module currentModule = Module.getCurrent();
                    setWindowsHook.invoke(mouseHookHandle, new Parameter[]{
                            new Int(WH_MOUSE_LL),
                            trackingCallback,
                            currentModule,
                            threadID
                    });
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void stop() {
        try {
            _dispatchThread.doInvokeAndWait(new Runnable() {
                public void run() {
                    User32 user32 = User32.getInstance();
                    Function unhook = user32.getFunction(FUNCTION_UNHOOK_WINDOWS_HOOK);
                    unhook.invoke(null, mouseHookHandle);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        _dispatchThread.doStop();
    }

    public static void main(String[] args) throws Exception {
        LowLevelMouseHookSample lowLevelMouseTracker = new LowLevelMouseHookSample();
        lowLevelMouseTracker.start();

        System.out.println("Press 'Enter' to terminate the sample.");
        System.in.read();

        lowLevelMouseTracker.stop();
    }
}