#region The MIT License (MIT)
//
// Copyright (c) 2011 Robert Prouse http://www.alteridem.net
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of 
// this software and associated documentation files (the "Software"), to deal in 
// the Software without restriction, including without limitation the rights to use, 
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 
// Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all 
// copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#endregion

#region Using Directives

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Threading.Tasks;
using System.Windows.Forms;

#endregion

namespace MaterialSkin.Gesture
{
    [PermissionSet( SecurityAction.Demand, Name = "FullTrust" )]
    public sealed class GestureListener : NativeWindow
    {
        #region Private Members

        // Saved state
        private Point _lastPanPoint;
        private double _lastRotation;
        private long _lastZoom;

        private readonly Control _parent;
        private readonly GestureConfig[] m_configs;
        private int _touchInputSize;

        #endregion

        #region Public Events

        public event EventHandler<PanEventArgs> Pan;
        public event EventHandler<PressAndTapEventArgs> PressAndTap;
        public event EventHandler<RotateEventArgs> Rotate;
        public event EventHandler<TwoFingerTapEventArgs> TwoFingerTap;
        public event EventHandler<ZoomEventArgs> Zoom;

        #endregion

        #region Construction

        /// <summary>
        /// Initializes a new instance of the <see cref="GestureListener"/> class to receive all gestures.
        /// </summary>
        /// <param name="parent">The parent.</param>
        public GestureListener( Control parent )
            : this( parent, new[] { new GestureConfig( 0, GestureConfigurationFlag.GC_ALLGESTURES, 0 ) } )
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GestureListener"/> class to receive specific gestures.
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <param name="configs">The gesture configurations.</param>
        public GestureListener( Control parent, GestureConfig[] configs )
        {
            if ( parent.IsHandleCreated )
            {
                Initialize( parent );
            }
            else
            {
                parent.HandleCreated += OnHandleCreated;
            }
            parent.HandleDestroyed += OnHandleDestroyed;

            _parent = parent;
            m_configs = configs;
            _touchInputSize = Marshal.SizeOf(new TOUCHINPUT());
        }

        #endregion

        #region Private Methods
        [DllImport("user32")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool RegisterTouchWindow(System.IntPtr hWnd, ulong ulFlags);
        private void Initialize( Control parent )
        {
            AssignHandle( parent.Handle );
            NativeMethods.SetGestureConfig( parent.Handle, m_configs );
            
        }

        public void LoadMultitouch()
        {
            Task.Run(async () =>
            {
                await Task.Delay(1000);
                Action a = () =>
                {
                    if (!RegisterTouchWindow(_parent.Handle, 0))
                    {
                        
                    }
                };
                _parent.Invoke(a);

            });
            
        }
        private void OnHandleCreated( object sender, EventArgs e )
        {
            // Window is now created, assign handle to NativeWindow.
            var control = sender as Control;
            if ( control != null )
            {
                Initialize( control );
            }
        }

        private void OnHandleDestroyed( object sender, EventArgs e )
        {
            // Window was destroyed, release hook.
            ReleaseHandle();
        }

        #endregion

        #region WndProc

        /// <summary>
        /// Invokes the default window procedure associated with this window.
        /// </summary>
        /// <param name="m">A <see cref="T:System.Windows.Forms.Message"/> that is associated with the current Windows message.</param>
        [PermissionSet( SecurityAction.Demand, Name = "FullTrust" )]
        protected override void WndProc( ref Message m )
        {
            bool handled = false;

            // Listen for operating system messages
            switch ( m.Msg )
            {
                case WindowMessage.WM_GESTURE:
                    GestureInfo info;
                    if ( NativeMethods.GetGestureInfo( m.LParam, out info ) )
                    {
                        switch ( (GestureId)info.id )
                        {
                            case GestureId.Pan:
                                handled = OnPan( info );
                                break;
                            case GestureId.PressAndTap:
                                handled = OnPressAndTap( info );
                                break;
                            case GestureId.Rotate:
                                handled = OnRotate( info );
                                break;
                            case GestureId.TwoFingerTap:
                                handled = OnTwoFingerTap( info );
                                break;
                            case GestureId.Zoom:
                                handled = OnZoom( info );
                                break;
                        }
                        if ( handled )
                        {
                            NativeMethods.CloseGestureInfoHandle( m.LParam );
                        }
                    }
                    break;
                case WindowMessage.WM_TOUCH:
                    MessageBox.Show("touch");
                    this.DecodeTouch(ref m);
                    break;
                //case WindowMessage.WM_POINTERUPDATE:
                //    MessageBox.Show("ptr");
                //    break;
            }
            if ( !handled )
            {
                base.WndProc( ref m );
            }
        }
        private static int LoWord(int number)
        {
            return (number & 0xffff);
        }
        [StructLayout(LayoutKind.Sequential)]
        private struct TOUCHINPUT
        {
            public int x;
            public int y;
            public System.IntPtr hSource;
            public int dwID;
            public int dwFlags;
            public int dwMask;
            public int dwTime;
            public System.IntPtr dwExtraInfo;
            public int cxContact;
            public int cyContact;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct POINTS
        {
            public short x;
            public short y;
        }
        public event EventHandler<WMTouchEventArgs> Touchdown;   // touch down event handler
        public event EventHandler<WMTouchEventArgs> Touchup;     // touch up event handler
        public event EventHandler<WMTouchEventArgs> TouchMove;   // touch move event handler

        // EventArgs passed to Touch handlers
        public class WMTouchEventArgs : System.EventArgs
        {
            // Private data members
            private int x;                  // touch x client coordinate in pixels
            private int y;                  // touch y client coordinate in pixels
            private int id;                 // contact ID
            private int mask;               // mask which fields in the structure are valid
            private int flags;              // flags
            private int time;               // touch event time
            private int contactX;           // x size of the contact area in pixels
            private int contactY;           // y size of the contact area in pixels

            // Access to data members
            public int LocationX
            {
                get { return x; }
                set { x = value; }
            }
            public int LocationY
            {
                get { return y; }
                set { y = value; }
            }
            public int Id
            {
                get { return id; }
                set { id = value; }
            }
            public int Flags
            {
                get { return flags; }
                set { flags = value; }
            }
            public int Mask
            {
                get { return mask; }
                set { mask = value; }
            }
            public int Time
            {
                get { return time; }
                set { time = value; }
            }
            public int ContactX
            {
                get { return contactX; }
                set { contactX = value; }
            }
            public int ContactY
            {
                get { return contactY; }
                set { contactY = value; }
            }
            public bool IsPrimaryContact
            {
                get { return (flags & WindowMessage.TOUCHEVENTF_PRIMARY) != 0; }
            }

            // Constructor
            public WMTouchEventArgs()
            {
            }
        }
        [DllImport("user32")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetTouchInputInfo(System.IntPtr hTouchInput, int cInputs, [In, Out] TOUCHINPUT[] pInputs, int cbSize);
        [DllImport("user32")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern void CloseTouchInputHandle(System.IntPtr lParam);
        public bool DecodeTouch(ref Message m)
        {
            // More than one touchinput may be associated with a touch message,
            // so an array is needed to get all event information.
            int inputCount = LoWord(m.WParam.ToInt32()); // Number of touch inputs, actual per-contact messages

            TOUCHINPUT[] inputs; // Array of TOUCHINPUT structures
            inputs = new TOUCHINPUT[inputCount]; // Allocate the storage for the parameters of the per-contact messages

            // Unpack message parameters into the array of TOUCHINPUT structures, each
            // representing a message for one single contact.
            if (!GetTouchInputInfo(m.LParam, inputCount, inputs, _touchInputSize))
            {
                // Get touch info failed.
                return false;
            }

            // For each contact, dispatch the message to the appropriate message
            // handler.
            bool handled = false; // Boolean, is message handled
            for (int i = 0; i < inputCount; i++)
            {
                TOUCHINPUT ti = inputs[i];

                // Assign a handler to this message.
                EventHandler<WMTouchEventArgs> handler = null;     // Touch event handler
                if ((ti.dwFlags & WindowMessage.TOUCHEVENTF_DOWN) != 0)
                {
                    handler = Touchdown;
                }
                else if ((ti.dwFlags & WindowMessage.TOUCHEVENTF_UP) != 0)
                {
                    handler = Touchup;
                }
                else if ((ti.dwFlags & WindowMessage.TOUCHEVENTF_MOVE) != 0)
                {
                    handler = TouchMove;
                }

                // Convert message parameters into touch event arguments and handle the event.
                if (handler != null)
                {
                    // Convert the raw touchinput message into a touchevent.
                    WMTouchEventArgs te = new WMTouchEventArgs(); // Touch event arguments

                    // TOUCHINFO point coordinates and contact size is in 1/100 of a pixel; convert it to pixels.
                    // Also convert screen to client coordinates.
                    te.ContactY = ti.cyContact / 100;
                    te.ContactX = ti.cxContact / 100;
                    te.Id = ti.dwID;
                    {
                        Point pt = _parent.PointToClient(new Point(ti.x / 100, ti.y / 100));
                        te.LocationX = pt.X;
                        te.LocationY = pt.Y;
                    }
                    te.Time = ti.dwTime;
                    te.Mask = ti.dwMask;
                    te.Flags = ti.dwFlags;

                    // Invoke the event handler.
                    handler(this, te);

                    // Mark this event as handled.
                    handled = true;
                }
            }

            CloseTouchInputHandle(m.LParam);

            return handled;
        }
    
    private bool OnPan( GestureInfo info )
        {
            if ( Pan != null )
            {
                if ( info.Begin )
                {
                    _lastPanPoint = new Point( info.location.x, info.location.y );
                }
                var args = new PanEventArgs( info, _lastPanPoint );
                _lastPanPoint = new Point( info.location.x, info.location.y );
                Pan( this, args );
                return args.Handled;
            }
            return false;
        }

        private bool OnPressAndTap( GestureInfo info )
        {
            if ( PressAndTap != null )
            {
                var args = new PressAndTapEventArgs( info );
                PressAndTap( this, args );
                return args.Handled;
            }
            return false;
        }

        private bool OnRotate( GestureInfo info )
        {
            if ( Rotate != null )
            {
                if ( info.Begin )
                {
                    _lastRotation = 0;
                }
                var args = new RotateEventArgs( info, _lastRotation );
                if ( !info.Begin )
                {
                    // First rotation is the angle the fingers are at, so don't use it
                    _lastRotation = args.TotalAngle;
                }
                Rotate( this, args );
                return args.Handled;
            }
            return false;
        }

        private bool OnTwoFingerTap( GestureInfo info )
        {
            if ( TwoFingerTap != null )
            {
                var args = new TwoFingerTapEventArgs( info );
                TwoFingerTap( this, args );
                return args.Handled;
            }
            return false;
        }

        private bool OnZoom( GestureInfo info )
        {
            if ( Zoom != null )
            {
                if ( info.Begin )
                {
                    _lastZoom = info.arguments;
                }
                var args = new ZoomEventArgs( info, _lastZoom );
                _lastZoom = args.Distance;
                Zoom( this, args );
                return args.Handled;
            }
            return false;
        }

        #endregion
    }
}
