// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

using System;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;
using OpenLiveWriter.CoreServices.UI;
using OpenLiveWriter.Localization.Bidi;

namespace OpenLiveWriter.Controls
{
    /// <summary>
    /// Type T is the type of the objects each entry in the list will hold.
    /// </summary>
    /// <typeparam name="T"></typeparam>
	public class SidebarListBox<T> : ListBox
	{
		#region Initialization and Cleanup

		public SidebarListBox()
		{
			_theme = new ControlTheme(this, true);
		}

		public void Initialize()
		{
			// configure owner draw
			DrawMode = DrawMode.OwnerDrawFixed ;
			SelectionMode = SelectionMode.One ;
			HorizontalScrollbar = false ;
			IntegralHeight = false ;
			ItemHeight = CalculateItemHeight() ;			
		}


        public void SetEntry(T item, Bitmap image, string text)
		{
            Items.Add(new ListBoxEntryItem(text, item, image, this));
		}

        public bool SelectEntry(string text)
        {
            for (int i = 0; i < Items.Count; i++)
            {
                ListBoxEntryItem item = (ListBoxEntryItem)Items[i];
                if (item.Name == text)
                {
                    SelectedIndex = i;
                    return true;
                }
            }
            return false;
        }


		public new T SelectedValue
		{
			get
			{
                return (SelectedItem as ListBoxEntryItem).Value ;
			}
		}

		protected override void Dispose(bool disposing)
		{
			if ( disposing )
			{
				
			}
			base.Dispose (disposing);
		}

		#endregion

		#region Painting

		protected override void OnDrawItem(DrawItemEventArgs e)
		{
			// screen invalid drawing states
			if ( DesignMode || e.Index == -1 )
				return ;

            BidiGraphics g = new BidiGraphics(e.Graphics,e.Bounds);
			// get post-source we are rendering
			ListBoxEntryItem item = Items[e.Index] as ListBoxEntryItem;
	
			// determine image and text to use
			Bitmap postSourceImage = item.Image;		
				
			// determine state
			bool selected = (e.State & DrawItemState.Selected) > 0;

			// calculate colors
			Color backColor, textColor ;
			if ( selected )
			{
				if ( Focused )
				{
					backColor = _theme.backColorSelectedFocused;
					textColor = _theme.textColorSelectedFocused;
				}
				else
				{
					backColor = _theme.backColorSelected;
					textColor = _theme.textColorSelected;
				}
			}
			else
			{
				backColor = _theme.backColor;
				textColor = _theme.textColor;
			}

			// draw background
			using (SolidBrush solidBrush = new SolidBrush(backColor))
                g.FillRectangle(solidBrush, e.Bounds);

			// center the image within the list box
			int imageLeft = e.Bounds.Left + ((e.Bounds.Width / 2) - (ScaleX(postSourceImage.Width) / 2 )) ;
			int imageTop = e.Bounds.Top + ScaleY(LARGE_TOP_INSET) ;
            g.DrawImage(false,postSourceImage, new Rectangle(imageLeft, imageTop, ScaleX(postSourceImage.Width), ScaleY(postSourceImage.Height)));


			// calculate standard text drawing metrics
			float leftMargin = ScaleX(ELEMENT_PADDING) ;
			float topMargin = imageTop + ScaleY(postSourceImage.Height) + ScaleY(ELEMENT_PADDING) ;
			float fontHeight = e.Font.GetHeight() ;

			// caption
			// calculate layout rectangle
			Rectangle layoutRectangle = new Rectangle(
				(int)leftMargin,
                (int)topMargin,
				e.Bounds.Width - (2 * ScaleX(ELEMENT_PADDING)),
                (int)fontHeight * TITLE_LINES);

			// draw caption
            g.DrawText(item.Name, e.Font, layoutRectangle, textColor, TextFormatFlags.EndEllipsis | TextFormatFlags.HorizontalCenter | TextFormatFlags.WordBreak);

			

			// draw focus rectange if necessary
			e.DrawFocusRectangle();
		}
		
		private class ControlTheme : ControlUITheme
		{
			internal Color backColor;
			internal Color textColor;
			internal Color backColorSelected;
			internal Color textColorSelected;
			internal Color backColorSelectedFocused;
			internal Color textColorSelectedFocused;
			internal Color topLineColor;
			
			public ControlTheme(Control control, bool applyTheme) : base(control, applyTheme)
			{
			}

			protected override void ApplyTheme(bool highContrast)
			{
				base.ApplyTheme(highContrast);
				
				backColor = SystemColors.Window ;
				textColor = SystemColors.ControlText ;
				backColorSelected = !highContrast ? SystemColors.ControlLight : SystemColors.InactiveCaption;
				textColorSelected = !highContrast ? SystemColors.ControlText : SystemColors.InactiveCaptionText ;
				backColorSelectedFocused = SystemColors.Highlight;
				textColorSelectedFocused = SystemColors.HighlightText ;
				topLineColor = SystemColors.ControlLight;
			}			
		}
		private ControlTheme _theme;

		
		private int CalculateItemHeight()
		{
			int textHeight = Convert.ToInt32(Font.GetHeight()*TITLE_LINES);

			return	ScaleY(LARGE_TOP_INSET) + ScaleY(40) + ScaleY(ELEMENT_PADDING) + textHeight + ScaleY(LARGE_BOTTOM_INSET)  ;
		}
		
	

		#endregion
		
		#region Accessibility
		internal class BlogPostSourceListBoxAccessibility : ControlAccessibleObject
		{
			private SidebarListBox<T> _listBox;
			public BlogPostSourceListBoxAccessibility(SidebarListBox<T> ownerControl) : base(ownerControl)
			{
				_listBox = ownerControl;
			}

			public override AccessibleObject GetChild(int index)
			{
				return _listBox.Items[index] as AccessibleObject;
			}

			public override int GetChildCount()
			{
				return _listBox.Items.Count;
			}
			
			public void NotifySelectionChanged(int index)
			{
				if(index >= 0)
					NotifyClients(AccessibleEvents.Focus, index);
			}
			
			public void NotifySelectionChanged()
			{
				NotifySelectionChanged(_listBox.SelectedIndex);
			}

			public override AccessibleRole Role
			{
				get { return AccessibleRole.List; }
			}
		}
		
		class ListBoxEntryItem : AccessibleObject, IComparable
		{
			private string _title;
			private Bitmap _image;
			private SidebarListBox<T> _listbox;

            private T _value;

            public ListBoxEntryItem(string title, T value, Bitmap image, SidebarListBox<T> ownerControl)

			{
				_image = image;
				_title = title;

                _value = value;
				_listbox = ownerControl;
			}

			public override string Name
			{
				get { return _title; }
				set { base.Name = value; }
			}

                        public override string ToString()
                        {
                                return _title;
                        }
		
			public Bitmap Image
			{
				get { return _image; }
			}

			public new T Value
			{

                get { return _value; }
			}

			private bool IsSelected()
			{
				return _listbox.SelectedItem == this && _listbox.Focused;
			}

			public override AccessibleStates State
			{
				get
				{
					AccessibleStates state = AccessibleStates.Focusable;
					if(IsSelected())
						state = state | AccessibleStates.Focused;
					return state;
				}
			}
			
			public override AccessibleRole Role
			{
				get { return AccessibleRole.ListItem; }
			}
			
			public override AccessibleObject Parent
			{
				get { return _listbox.AccessibilityObject; }
			}

			public int CompareTo(object obj)
			{
				try
				{
					return string.Compare(Name, ((ListBoxEntryItem) obj).Name, true, CultureInfo.CurrentCulture);
				}
				catch (InvalidCastException)
				{
					Debug.Fail("InvalidCastException");
					return 0;
				}
				catch (NullReferenceException)
				{
					Debug.Fail("NullRefException");
					return 0;
				}
			}
		}

		protected override AccessibleObject CreateAccessibilityInstance()
		{
			if(_accessibleObject == null)
				_accessibleObject = new BlogPostSourceListBoxAccessibility(this);
			return _accessibleObject;
		}
		BlogPostSourceListBoxAccessibility _accessibleObject;

		#endregion
		
		#region High DPI Scaling
        protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
        {
            SaveScale(factor.Width, factor.Height);
            base.ScaleControl(factor, specified);
        }

        protected override void ScaleCore(float dx, float dy)
        {
            SaveScale(dx, dy);
            base.ScaleCore(dx, dy);
        }

        private void SaveScale(float dx, float dy)
        {
            scale = new PointF(scale.X * dx, scale.Y * dy);
        }
        private PointF scale = new PointF(1f, 1f);
		
		protected int ScaleX(int x)
		{
			return (int) (x*scale.X);
		}
		
		protected int ScaleY(int y)
		{
			return (int) (y*scale.Y);
		}
		#endregion

		#region Private Data
	
		// item metrics
		private const int LARGE_TOP_INSET = 7 ;
		private const int LARGE_BOTTOM_INSET = 4 ;
		private const int ELEMENT_PADDING = 3;
		private const int TITLE_LINES = 3 ;

		#endregion
	}
}
