/// <summary>
        /// Creates WPF property infos from WM_POINTER device properties.  This appropriately maps and converts HID spec
        /// properties found in WM_POINTER to their WPF equivalents.  This is based on code from the WISP implementation
        /// that feeds the legacy WISP based stack.
        /// </summary>
        /// <param name="prop">The pointer property to convert</param>
        /// <returns>The equivalent WPF property info</returns>
        internal static StylusPointPropertyInfo CreatePropertyInfo(UnsafeNativeMethods.POINTER_DEVICE_PROPERTY prop)
        {
            StylusPointPropertyInfo result = null;

            // Get the mapped GUID for the HID usages
            Guid propGuid =
                StylusPointPropertyIds.GetKnownGuid(
                    (StylusPointPropertyIds.HidUsagePage)prop.usagePageId,
                    (StylusPointPropertyIds.HidUsage)prop.usageId);

            if (propGuid != Guid.Empty)
            {
                StylusPointProperty stylusProp = new StylusPointProperty(propGuid, StylusPointPropertyIds.IsKnownButton(propGuid));

                // Set Units
                StylusPointPropertyUnit?unit = StylusPointPropertyUnitHelper.FromPointerUnit(prop.unit);

                // If the parsed unit is invalid, set the default
                if (!unit.HasValue)
                {
                    unit = StylusPointPropertyInfoDefaults.GetStylusPointPropertyInfoDefault(stylusProp).Unit;
                }

                // Set to default resolution
                float resolution = StylusPointPropertyInfoDefaults.GetStylusPointPropertyInfoDefault(stylusProp).Resolution;

                short mappedExponent = 0;

                if (_hidExponentMap.TryGetValue((byte)(prop.unitExponent & HidExponentMask), out mappedExponent))
                {
                    float exponent = (float)Math.Pow(10, mappedExponent);

                    // Guard against divide by zero or negative resolution
                    if (prop.physicalMax - prop.physicalMin > 0)
                    {
                        // Calculated resolution is a scaling factor from logical units into the physical space
                        // at the given exponentiation.
                        resolution =
                            (prop.logicalMax - prop.logicalMin) / ((prop.physicalMax - prop.physicalMin) * exponent);
                    }
                }

                result = new StylusPointPropertyInfo(
                    stylusProp,
                    prop.logicalMin,
                    prop.logicalMax,
                    unit.Value,
                    resolution);
            }

            return(result);
        }
        /// <summary>
        /// Query all supported properties from WM_POINTER stack and convert to WPF equivalents.
        ///
        /// This maintains a set of properties from WM_POINTER that are supported and the equivalent
        /// properties in WPF.  This way, the WM_POINTER properties can be used to directly query
        /// raw data from the stack and associate it 1 to 1 with raw stylus data in WPF.
        /// </summary>
        /// <returns>
        /// True if initialization succeeds, false otherwise.
        /// </returns>
        /// <remarks>
        /// A note on the pressure property.  If the device does not support pressure as a property,
        /// we still insert pressure on the WPF side.  This is a requirement in many places in the
        /// stylus code.  However, the raw data queried from WM_POINTER will NOT have pressure data.
        /// This is ok because StylusPointCollection pivots on the ContainsTruePressure property in
        /// the StylusPointDescription.  If this is true, every point will have X, Y, Pressure and then
        /// additional raw data from index 3..N where N is the number of properties per StylusPoint.
        /// If this is false, it will add a default pressure so WPF ends up with X, Y, DefaultPressure
        /// and then 2..N per StylusPoint.  This can be slightly confusing at first glance, since pressure
        /// of one kind or another is always required in the StylusPointDescription, but it allows for
        /// backfilling of pressure data further up (as in towards the public boundary) and the deeper
        /// portions of the stack just worry about querying device capabilities.
        /// </remarks>
        private bool TryInitializeSupportedStylusPointProperties()
        {
            bool success = false;

            uint propCount = 0;

            // Initialize to having not seen a real pressure property
            PressureIndex     = -1;
            UsingFakePressure = true;

            // Retrieve all properties from the WM_POINTER stack
            success = UnsafeNativeMethods.GetPointerDeviceProperties(Device, ref propCount, null);

            if (success)
            {
                SupportedPointerProperties = new UnsafeNativeMethods.POINTER_DEVICE_PROPERTY[propCount];

                success = UnsafeNativeMethods.GetPointerDeviceProperties(Device, ref propCount, SupportedPointerProperties);

                if (success)
                {
                    // Prepare a location for X, Y, and Pressure
                    List <StylusPointProperty> properties = new List <StylusPointProperty>()
                    {
                        StylusPointPropertyInfoDefaults.X,
                        StylusPointPropertyInfoDefaults.Y,
                        StylusPointPropertyInfoDefaults.NormalPressure,
                    };

                    List <StylusPointProperty> buttonProperties = new List <StylusPointProperty>();

                    // Prepare a location for X and Y.  Pressure does not need a location as it will be added later if applicable.
                    List <UnsafeNativeMethods.POINTER_DEVICE_PROPERTY> supportedProperties = new List <UnsafeNativeMethods.POINTER_DEVICE_PROPERTY>()
                    {
                        new UnsafeNativeMethods.POINTER_DEVICE_PROPERTY(),
                        new UnsafeNativeMethods.POINTER_DEVICE_PROPERTY(),
                    };

                    List <UnsafeNativeMethods.POINTER_DEVICE_PROPERTY> supportedButtonProperties = new List <UnsafeNativeMethods.POINTER_DEVICE_PROPERTY>();

                    bool seenX = false, seenY = false, seenPressure = false;

                    foreach (var prop in SupportedPointerProperties)
                    {
                        StylusPointPropertyInfo propInfo = PointerStylusPointPropertyInfoHelper.CreatePropertyInfo(prop);

                        if (propInfo != null)
                        {
                            // If seeing a required property, just overwrite the default placeholder
                            // otherwise tack it onto the end of the appropriate list.
                            if (propInfo.Id == StylusPointPropertyIds.NormalPressure)
                            {
                                seenPressure = true;
                                properties[StylusPointDescription.RequiredPressureIndex] = propInfo;

                                // Pressure is not in the pointer properties by default so we must insert it.
                                supportedProperties.Insert(StylusPointDescription.RequiredPressureIndex, prop);
                            }
                            else if (propInfo.Id == StylusPointPropertyIds.X)
                            {
                                seenX = true;
                                properties[StylusPointDescription.RequiredXIndex]          = propInfo;
                                supportedProperties[StylusPointDescription.RequiredXIndex] = prop;
                            }
                            else if (propInfo.Id == StylusPointPropertyIds.Y)
                            {
                                seenY = true;
                                properties[StylusPointDescription.RequiredYIndex]          = propInfo;
                                supportedProperties[StylusPointDescription.RequiredYIndex] = prop;
                            }
                            else if (propInfo.IsButton)
                            {
                                buttonProperties.Add(propInfo);
                                supportedButtonProperties.Add(prop);
                            }
                            else
                            {
                                properties.Add(propInfo);
                                supportedProperties.Add(prop);
                            }
                        }
                    }

                    // If we saw a real pressure property, we should mark that down
                    if (seenPressure)
                    {
                        PressureIndex         = StylusPointDescription.RequiredPressureIndex;
                        UsingFakePressure     = false;
                        HardwareCapabilities |= TabletHardwareCapabilities.SupportsPressure;
                    }

                    Debug.Assert(properties[StylusPointDescription.RequiredXIndex /*0*/].Id == StylusPointPropertyIds.X || !seenX,
                                 "X isn't where we expect it! Fix pointer stack to ask for X at index 0");
                    Debug.Assert(properties[StylusPointDescription.RequiredYIndex /*1*/].Id == StylusPointPropertyIds.Y || !seenY,
                                 "Y isn't where we expect it! Fix pointer stack to ask for Y at index 1");
                    Debug.Assert(properties[StylusPointDescription.RequiredPressureIndex /*1*/].Id == StylusPointPropertyIds.NormalPressure /*2*/,
                                 "Fix pointer stack to ask for NormalPressure at index 2!");

                    // Append buttons to the end of normal properties
                    properties.AddRange(buttonProperties);

                    SupportedButtonPropertyIndex = supportedProperties.Count;
                    supportedProperties.AddRange(supportedButtonProperties);

                    // Reset the properties to only what we support, this way we can generate raw data directly from them
                    StylusPointProperties      = new ReadOnlyCollection <StylusPointProperty>(properties);
                    SupportedPointerProperties = supportedProperties.ToArray();
                }
            }

            return(success);
        }