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

import com.jniwrapper.Parameter;
import com.jniwrapper.Pointer;
import com.jniwrapper.Structure;
import com.jniwrapper.win32.hook.*;
import com.jniwrapper.win32.ui.Wnd;
import com.jniwrapper.win32.Rect;

import java.util.HashMap;
import java.util.Map;

/**
 * This class represents structure which contains a hook events info.
 *
 * @author Serge Piletsky
 */
public class HooksData extends Structure
{
    private MouseHookData _mouseHookData = new MouseHookData();
    private KeyboardHookData _keyboardHookData = new KeyboardHookData();
    private WndProcHookData _wndProcHookData = new WndProcHookData();
    private WndProcRetHookData _wndProcRetHookData = new WndProcRetHookData();
    private GetMsgHookData _getMsgHookData = new GetMsgHookData();
    private SysMsgProcHookData _sysMsgProcHookData = new SysMsgProcHookData();
    private ShellHookData _shellHookData = new ShellHookData();
    private ForegroungIdleHookData _foregroungIdleHookData = new ForegroungIdleHookData();
    private JournalRecordHookData _journalRecordHookData = new JournalRecordHookData();
    private CBTProcData _cbtHookData = new CBTProcData();

    private interface HookEventReader
    {
        HookEventObject readEvent(HooksData hookData);
    }

    private static final Map DESCRIPTOR2READER = new HashMap();

    static
    {
        DESCRIPTOR2READER.put(Hook.Descriptor.MOUSE, new MouseHookEventReader());
        DESCRIPTOR2READER.put(Hook.Descriptor.KEYBOARD, new KeyboardHookEventReader());
        DESCRIPTOR2READER.put(Hook.Descriptor.CALLWNDPROC, new CallWndProcEventReader());
        DESCRIPTOR2READER.put(Hook.Descriptor.CALLWNDPROCRET, new CallWndProcRetEventReader());
        DESCRIPTOR2READER.put(Hook.Descriptor.GETMESSAGE, new GetMsgHookEventReader());
        DESCRIPTOR2READER.put(Hook.Descriptor.SYSMSGFILTER, new SysMsgProcHookEventReader());
        DESCRIPTOR2READER.put(Hook.Descriptor.SHELL, new ShellHookEventReader());
        DESCRIPTOR2READER.put(Hook.Descriptor.FOREGROUNDIDLE, new ForegroungIdleHookEventReader());
        DESCRIPTOR2READER.put(Hook.Descriptor.JOURNALRECORD, new JournalRecordHookEventReader());
        DESCRIPTOR2READER.put(Hook.Descriptor.CBT, new CBTProcHookEventReader());
    }

    public HooksData()
    {
        init(new Parameter[] {
            _mouseHookData,
            _keyboardHookData,
            _wndProcHookData,
            _wndProcRetHookData,
            _getMsgHookData,
            _sysMsgProcHookData,
            _shellHookData,
            _foregroungIdleHookData,
            _journalRecordHookData,
            _cbtHookData,
        }, (short) 1);
    }

    public HooksData(HooksData that)
    {
        this();
        initFrom(that);
    }

    public Object clone()
    {
        return new HooksData(this);
    }

    /**
     * Reads event data depending on a specified hook descriptor.
     *
     * @param descriptor describes what data to read.
     * @return a hook event.
     */
    public HookEventObject readEvent(Hook.Descriptor descriptor)
    {
        final HookEventReader reader = (HookEventReader) DESCRIPTOR2READER.get(descriptor);
        return reader.readEvent(this);
    }

    /**
     * MouseHookEventReader class represents a mouse hook events data reader
     */
    private static class MouseHookEventReader implements HookEventReader
    {
        public HookEventObject readEvent(HooksData hookData)
        {
            final MouseHookData mouseHookData = hookData._mouseHookData;
            final MouseHookStructure mouseHookStruct = mouseHookData.getMouseHookStruct();
            MouseEvent result = new MouseEvent(mouseHookData.getEventDescriptor(),
                    mouseHookData.getWParam(),
                    mouseHookStruct.getPoint(),
                    mouseHookStruct.getWnd(),
                    mouseHookStruct.getHitTestCode(),
                    mouseHookStruct.getExtraInfo());
            return result;
        }
    }

    /**
     * KeyboardHookEventReader class represents a keyboard hook events data reader
     */
    private static class KeyboardHookEventReader implements HookEventReader
    {
        public HookEventObject readEvent(HooksData hookData)
        {
            final KeyboardHookData keyboardHookData = hookData._keyboardHookData;
            KeyboardEvent result = new KeyboardEvent(keyboardHookData.getEventDescriptor(),
                    keyboardHookData.getWParam(),
                    keyboardHookData.getLParam());
            return result;
        }
    }

    /**
     * CallWndProcEventReader represents a hook events data reader for CallWndProc hook procedure
     * which is an application-defined callback function.
     */
    private static class CallWndProcEventReader implements HookEventReader
    {
        public HookEventObject readEvent(HooksData hookData)
        {
            final WndProcHookData wndProcHookData = hookData._wndProcHookData;
            final CWndProcStructure cwpStruct = wndProcHookData.getCwpStruct();

            CallWndProcEvent result = new CallWndProcEvent(wndProcHookData.getEventDescriptor(),
                    cwpStruct.getLParam(),
                    cwpStruct.getWParam(),
                    cwpStruct.getMessage(),
                    cwpStruct.getWnd(),
                    cwpStruct.wParam.getValue() != 0);
            return result;
        }
    }

    /**
     * CallWndProcRetEventReader represents a hook events data reader for CallWndRetProc hook procedure
     * which is an application-defined callback function and called after the SendMessage function call.
     */
    private static class CallWndProcRetEventReader implements HookEventReader
    {
        public HookEventObject readEvent(HooksData hookData)
        {
            final WndProcRetHookData wndProcRetHookData = hookData._wndProcRetHookData;
            final CWndProcRetStructure cwpRetStruct = wndProcRetHookData.getCwpRetStruct();
            CallWndProcRetEvent result = new CallWndProcRetEvent(wndProcRetHookData.getEventDescriptor(),
                    cwpRetStruct.getResult(),
                    cwpRetStruct.getLParam(),
                    cwpRetStruct.getWParam(),
                    cwpRetStruct.getMessage(),
                    cwpRetStruct.getWnd(),
                    cwpRetStruct.wParam.getValue() != 0);
            return result;
        }
    }

    /**
     * GetMsgHookEventReader represents a hook events data reader for GetMsgProc hook procedure
     * which is an application-defined callback function and called whenever the GetMessage or
     * PeekMessage function has retrieved a message from an application message queue.
     */
    private static class GetMsgHookEventReader implements HookEventReader
    {
        //        private final static int PM_NOREMOVE = 0x0000;
        private final static int PM_REMOVE = 0x0001;

        public HookEventObject readEvent(HooksData hookData)
        {
            final GetMsgHookData getMsgHookData = hookData._getMsgHookData;
            GetMsgEvent result = new GetMsgEvent(getMsgHookData.getEventDescriptor(),
                    getMsgHookData.getWParam() == PM_REMOVE,
                    getMsgHookData.getMsg());
            return result;
        }
    }

    /**
     * SysMsgProcHookEventReader represents a hook events data reader for SysMsgProc hook procedure
     * which is an application-defined callback function and called after an input event occurs in
     * a dialog box, message box, menu, or scroll bar, but before the message generated by the input
     * event is processed.
     */
    private static class SysMsgProcHookEventReader implements HookEventReader
    {
        public HookEventObject readEvent(HooksData hookData)
        {
            final SysMsgProcHookData sysMsgProcHookData = hookData._sysMsgProcHookData;
            SysMsgProcEvent result = new SysMsgProcEvent(sysMsgProcHookData.getEventDescriptor(),
                    sysMsgProcHookData.getCode(),
                    sysMsgProcHookData.getMsg());
            return result;
        }
    }

    /**
     * ShellHookEventReader represents a hook events data reader for ShellProc hook procedure
     * which is an application-defined callback function and called when notifications of
     * shell events are received from the system.
     */
    private static class ShellHookEventReader implements HookEventReader
    {
        public HookEventObject readEvent(HooksData hookData)
        {
            final ShellHookData shellHookData = hookData._shellHookData;
            ShellEvent result = new ShellEvent(shellHookData.getEventDescriptor(),
                    shellHookData.getCode(),
                    shellHookData.getWParam(),
                    shellHookData.getLParam());
            return result;
        }
    }

    /**
     * ForegroungIdleHookEventReader represents a hook events data reader for ForegroundIdleProc hook
     * procedure which is an application-defined callback function and called whenever
     * the foreground thread is about to become idle.
     */
    private static class ForegroungIdleHookEventReader implements HookEventReader
    {
        public HookEventObject readEvent(HooksData hookData)
        {
            final ForegroungIdleHookData foregroungIdleHookData = hookData._foregroungIdleHookData;
            ForegroungIdleEvent result = new ForegroungIdleEvent(foregroungIdleHookData.getEventDescriptor());
            return result;
        }
    }

    /**
     * JournalRecordHookEventReader represents a hook events data reader for JournalRecordProc hook
     * procedure which is an application-defined callback function and called whenever
     * system removes messages from the system message queue.
     */
    private static class JournalRecordHookEventReader implements HookEventReader
    {
        public HookEventObject readEvent(HooksData hookData)
        {
            final JournalRecordHookData jornalRecordHookData = hookData._journalRecordHookData;

            JournalRecordEvent result = null;
            final long code = jornalRecordHookData.getCode();
            if (code == 0)
            {
                final EventMessageStructure eventMessage = jornalRecordHookData.getEventMessage();
                result = new JournalRecordEvent(jornalRecordHookData.getEventDescriptor(),
                        code,
                        eventMessage.getMessage(),
                        eventMessage.getParamL(),
                        eventMessage.getParamH(),
                        eventMessage.getTime(),
                        eventMessage.getHwnd());
            }
            else
            {
                result = new JournalRecordEvent(jornalRecordHookData.getEventDescriptor(), code);
            }
            return result;
        }
    }

    /**
     * CBTHookEventReader represents a hook events data reader for CBT hook procedure.
     */
    private static class CBTProcHookEventReader implements HookEventReader
    {
        /**
         * Specifies the handle to the window to be moved or sized.
         */
        static final int HCBT_MOVESIZE = 0;

        /**
         * Specifies the handle to the window being minimized or maximized.
         */
        static final int HCBT_MINMAX = 1;

        /**
         * Is undefined and must be zero.
         */
        static final int HCBT_QS = 2;

        /**
         * Specifies the handle to the new window.
         */
        static final int HCBT_CREATEWND = 3;

        /**
         * Specifies the handle to the window about to be destroyed.
         */
        static final int HCBT_DESTROYWND = 4;

        /**
         * Specifies a window activation event.
         */
        static final int HCBT_ACTIVATE = 5;

        /**
         * Specifies the mouse message removed from the system message queue.
         */
        static final int HCBT_CLICKSKIPPED = 6;

        /**
         * Specifies the virtual-key code.
         */
        static final int HCBT_KEYSKIPPED = 7;

        /**
         * Specifies a system command.
         */
        static final int HCBT_SYSCOMMAND = 8;

        /**
         * Specifies the handle to the window gaining the keyboard focus.
         */
        static final int HCBT_SETFOCUS = 9;

        public HookEventObject readEvent(HooksData hookData)
        {
            CBTEvent result = null;

            final CBTProcData cbtHookData = hookData._cbtHookData;

            final long wParam = cbtHookData.getWParam();
            final long lParam = cbtHookData.getLParam();
            final int code = cbtHookData.getCode();

            switch (code)
            {
                case HCBT_ACTIVATE:
                    CBTACTIVATESTRUCT struct = cbtHookData.getActiveStruct();
                    result = new CBTEvent.Activate(cbtHookData.getEventDescriptor(), cbtHookData.getAllow(),
                            struct.getHWndActive(),
                            new Wnd(wParam),
                            struct.getFMouse().getValue());
                    break;

                case HCBT_CLICKSKIPPED:
                    MouseHookStructure mouseHookStructure = cbtHookData.getMouseHookStruct();
                    result = new CBTEvent.ClickSkipped(cbtHookData.getEventDescriptor(),
                            wParam,
                            mouseHookStructure.getPoint(),
                            mouseHookStructure.getWnd(),
                            mouseHookStructure.getHitTestCode());
                    break;

                case HCBT_CREATEWND:
                    result = new CBTEvent.CreateWnd(cbtHookData.getEventDescriptor(),
                            cbtHookData.getAllow(),
                            new Wnd(wParam),
                            cbtHookData.getCreatestruct(),
                            cbtHookData.getInsertAfter());
                    break;

                case HCBT_DESTROYWND:
                    result = new CBTEvent.DestroyWnd(cbtHookData.getEventDescriptor(), cbtHookData.getAllow(), new Wnd(wParam));
                    break;

                case HCBT_KEYSKIPPED:
                    result = new CBTEvent.KeySkipped(cbtHookData.getEventDescriptor(), wParam, lParam);
                    break;

                case HCBT_MINMAX:
                    result = new CBTEvent.MinMax(cbtHookData.getEventDescriptor(), cbtHookData.getAllow(), new Wnd(wParam), lParam);
                    break;

                case HCBT_MOVESIZE:
                    Rect rect = cbtHookData.getRect();
                    result = new CBTEvent.MoveSize(cbtHookData.getEventDescriptor(), cbtHookData.getAllow(), new Wnd(wParam), rect);
                    break;

                case HCBT_QS:
                    result = new CBTEvent.QS(cbtHookData.getEventDescriptor());
                    break;

                case HCBT_SETFOCUS:
                    result = new CBTEvent.SetFocus(cbtHookData.getEventDescriptor(), cbtHookData.getAllow(), new Wnd(wParam), new Wnd(lParam));
                    break;

                case HCBT_SYSCOMMAND:
                    result = new CBTEvent.SysCommand(cbtHookData.getEventDescriptor(), cbtHookData.getAllow(), wParam, lParam);
                    break;
            }
            return result;
        }
    }

    public void setSynchronous(Hook hook, boolean value) {
        if (hook.getDescriptor().equals(Hook.Descriptor.MOUSE)) {
            _mouseHookData.setSyncronous(value);
        } else if (hook.getDescriptor().equals(Hook.Descriptor.KEYBOARD)) {
            _keyboardHookData.setSyncronous(value);
        } else if (hook.getDescriptor().equals(Hook.Descriptor.CALLWNDPROC)) {
            _wndProcHookData.setSyncronous(value);
        } else if (hook.getDescriptor().equals(Hook.Descriptor.CALLWNDPROCRET)) {
            _wndProcRetHookData.setSyncronous(value);
        } else if (hook.getDescriptor().equals(Hook.Descriptor.GETMESSAGE)) {
            _getMsgHookData.setSyncronous(value);
        } else if (hook.getDescriptor().equals(Hook.Descriptor.SYSMSGFILTER)) {
            _sysMsgProcHookData.setSyncronous(value);
        } else if (hook.getDescriptor().equals(Hook.Descriptor.SHELL)) {
            _shellHookData.setSyncronous(value);
        } else if (hook.getDescriptor().equals(Hook.Descriptor.FOREGROUNDIDLE)) {
            _foregroungIdleHookData.setSyncronous(value);
        } else if (hook.getDescriptor().equals(Hook.Descriptor.JOURNALRECORD)) {
            _journalRecordHookData.setSyncronous(value);
        } else if (hook.getDescriptor().equals(Hook.Descriptor.CBT)) {
            _cbtHookData.setSyncronous(value);
        } else {
            throw new IllegalArgumentException("Unknown hook type: " + hook);
        }
    }

    public boolean getSynchronous(Hook hook) {
        if (hook.getDescriptor().equals(Hook.Descriptor.MOUSE)) {
            return _mouseHookData.isSyncronous();
        } else if (hook.getDescriptor().equals(Hook.Descriptor.KEYBOARD)) {
            return _keyboardHookData.isSyncronous();
        } else if (hook.getDescriptor().equals(Hook.Descriptor.CALLWNDPROC)) {
            return _wndProcHookData.isSyncronous();
        } else if (hook.getDescriptor().equals(Hook.Descriptor.CALLWNDPROCRET)) {
            return _wndProcRetHookData.isSyncronous();
        } else if (hook.getDescriptor().equals(Hook.Descriptor.GETMESSAGE)) {
            return _getMsgHookData.isSyncronous();
        } else if (hook.getDescriptor().equals(Hook.Descriptor.SYSMSGFILTER)) {
            return _sysMsgProcHookData.isSyncronous();
        } else if (hook.getDescriptor().equals(Hook.Descriptor.SHELL)) {
            return _shellHookData.isSyncronous();
        } else if (hook.getDescriptor().equals(Hook.Descriptor.FOREGROUNDIDLE)) {
            return _foregroungIdleHookData.isSyncronous();
        } else if (hook.getDescriptor().equals(Hook.Descriptor.JOURNALRECORD)) {
            return _journalRecordHookData.isSyncronous();
        } else if (hook.getDescriptor().equals(Hook.Descriptor.CBT)) {
            return _cbtHookData.isSyncronous();
        } else {
            throw new IllegalArgumentException("Unknown hook type: " + hook);
        }
    }

    public void setHookFilter(Hook.Descriptor descriptor, EventsFilter filter)
    {
        if (descriptor.equals(Hook.Descriptor.CALLWNDPROC))
        {
            _wndProcHookData.setFilter(filter);
        }
        else
        {
            throw new IllegalArgumentException("Events filtering for hook: " + descriptor + " is not implemented");
        }
    }
    
    public EventsFilter getHookFilter(Hook.Descriptor descriptor)
    {
        if (descriptor.equals(Hook.Descriptor.CALLWNDPROC))
        {
            return _wndProcHookData.getFilter();
        }
        else
        {
            throw new IllegalArgumentException("Events filtering for hook: " + descriptor + " is not implemented");
        }
    }
}