using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media.Animation;
using System.Collections.Generic;
using System.Collections.Specialized;

namespace C1.Silverlight.FlexGridExcel
{
    /// <summary>
    /// Similar to <see cref="MenuItem"/>, but with support for sub-items.
    /// </summary>
    public class C1MenuItem : MenuItem
    {
        //--------------------------------------------------------------------------------
        #region ** fields

        Popup _popup;

        #endregion

        //--------------------------------------------------------------------------------
        #region ** ctor

        /// <summary>
        /// Initializes a new instance of a <see cref="C1MenuItem"/>.
        /// </summary>
        public C1MenuItem()
        {
            // get style from generic.xaml
            this.DefaultStyleKey = typeof(C1MenuItem);

            // mouse entered item: update state now
            this.MouseEnter += (s, e) =>
            {
                _mouseItem = this;
                _sb.Stop();
                UpdateMenuState(true);
            };

            // mouse left item: update state after a short delay
            this.MouseLeave += (s, e) =>
            {
                _mouseItem = this;
                _sb.Seek(TimeSpan.Zero);
                _sb.Begin();
            };

            // user clicked: close everything
            this.Click += (s, e) =>
            {
                // close submenus
                _mouseItem = this;
                UpdateMenuState(false);

                // and close context menu
                var ctx = GetContextMenu(this);
                if (ctx != null)
                {
                    ctx.IsOpen = false;
                }
            };

            // update visual state when IsEnabled changes
            this.IsEnabledChanged += (s, e) => 
            { 
                this.ChangeVisualState(false); 
            };
        }
        /// <summary>
        /// Overridden to get a reference to the popup menu.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _popup = (Popup)this.GetTemplateChild("PART_Popup");
            _popup.Closed += (s, e) =>
            {
                this.IsSubmenuOpen = false;
            };
        }

        #endregion

        //--------------------------------------------------------------------------------
        #region ** properties

        /// <summary>
        /// Gets or sets a value that determines whether this menu has sub-items.
        /// </summary>
        public Visibility HasSubItems
        {
            get { return (Visibility)GetValue(HasSubItemsProperty); }
            set { SetValue(HasSubItemsProperty, value); }
        }
        /// <summary>
        /// Identifies the <see cref="HasSubItemsProperty"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty HasSubItemsProperty =
            DependencyProperty.Register("HasSubItems", typeof(Visibility), typeof(C1MenuItem), new PropertyMetadata(Visibility.Collapsed));

        /// <summary>
        /// Gets or sets a value that determines whether the submenu for this item is open.
        /// </summary>
        public bool IsSubmenuOpen
        {
            get { return (bool)GetValue(IsSubmenuOpenProperty); }
            set { SetValue(IsSubmenuOpenProperty, value); }
        }
        /// <summary>
        /// Identifies the <see cref="IsSubmenuOpenProperty"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty IsSubmenuOpenProperty =
            DependencyProperty.Register("IsSubmenuOpen", typeof(bool), typeof(C1MenuItem), new PropertyMetadata(false, OnSubMenuPropertyChanged));
        static void OnSubMenuPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var mi = d as C1MenuItem;
            if ((bool)e.NewValue)
            {
                // update horizontal offset when the popup opens
                // since the width of the popup may change and
                // the binding in the xaml template only works the first time (argh!)
                mi._popup.HorizontalOffset = mi.ActualWidth;
            }
        }

        #endregion

        //--------------------------------------------------------------------------------
        #region ** implementation

        /// <summary>
        /// Updates the <see cref="HasSubItems"/> property when the items collection changes.
        /// </summary>
        /// <param name="e"><see cref="NotifyCollectionChangedEventArgs"/> that contains the event data.</param>
        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
        {
            this.HasSubItems = Items.Count > 0
                ? Visibility.Visible
                : Visibility.Collapsed;
        }

        #endregion

        //--------------------------------------------------------------------------------
        #region ** static members

        static Storyboard _sb;
        static C1MenuItem _mouseItem;

        // static ctor: 
        // build and handle the Storyboard the controls the menu
        static C1MenuItem()
        {
            _sb = new Storyboard();
            _sb.Duration = new Duration(TimeSpan.FromMilliseconds(150));
            _sb.Completed += (s, e) => { UpdateMenuState(false); };
        }

        // show the current item and all its ancestors;
        // close everyone else
        static void UpdateMenuState(bool hasMouse)
        {
            // check that we have an item to work with
            var item = _mouseItem;
            if (item != null)
            {
                // form list of items that must be open
                var list = new List<MenuItem>();
                if (hasMouse)
                {
                    var p = hasMouse ? item : item.Parent as MenuItem;
                    while (p != null)
                    {
                        list.Add(p);
                        p = p.Parent as C1MenuItem;
                    }
                }

                // get root context menu
                var ctx = GetContextMenu(item);

                // scan the whole tree and show/hide the members
                if (ctx != null)
                {
                    UpdateMenuState(ctx.Items, list);
                }
            }
        }
        static void UpdateMenuState(ItemCollection items, List<MenuItem> list)
        {
            foreach (var item in items)
            {
                var mi = item as C1MenuItem;
                if (mi != null && mi.Items.Count > 0)
                {
                    UpdateMenuState(mi.Items, list);
                    mi.IsSubmenuOpen = list.Contains(mi);
                }
            }
        }
        static ContextMenu GetContextMenu(MenuItem item)
        {
            while (item != null && item.Parent is MenuItem)
            {
                item = item.Parent as MenuItem;
            }
            return item != null ? item.Parent as ContextMenu : null;
        }

        #endregion
    }
}
