using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Collections.Generic;
using C1.Silverlight.Legacy;

namespace StockPortfolio
{
    /// <summary>
    /// C1TextBox with autosearch for a large number of items (several thousand).
    /// This is similar to the stock symbol search box in Google finance. As the
    /// user types into the C1TextBox, the list is searched and matches are 
    /// displayed in a popup window.
    /// </summary>
    public class SearchBox : C1TextBox
    {
        //------------------------------------------------------------------------------
        #region ** fields

        C1Popup     _popup;
        C1ListBox   _listBox;
        bool        _updating;
        double      _keyWidth, _textWidth;
        int         _maxItems;
        Dictionary<string, string> _items = new Dictionary<string, string>();

        #endregion

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

        public SearchBox()
        {
            // set default properties
            _keyWidth = 80;
            _textWidth = 180;
            _maxItems = 9;

            // initialize listbox
            _listBox = new C1ListBox();
            _listBox.AutoSize = AutoSize.Both;
            _listBox.Click += _listBox_Click;
            _listBox.Focusable = false;

            // put list in a popup
            _popup = new C1Popup(_listBox);
        }

        #endregion

        //------------------------------------------------------------------------------
        #region ** object model

        public Dictionary<string, string> Items
        {
            get { return _items; }
            set { _items = value; }
        }
        public double KeyWidth
        {
            get { return _keyWidth; }
            set { _keyWidth = value; }
        }
        public double TextWidth
        {
            get { return _textWidth; }
            set { _textWidth = value; }
        }
        public int MaxDropDownItems
        {
            get { return _maxItems; }
            set { _maxItems = value; }
        }
        public C1Popup Popup
        {
            get { return _popup; }
        }
        public event EventHandler SelectionChangeCommitted;
        protected void OnSelectionChangeCommitted(EventArgs e)
        {
            if (SelectionChangeCommitted != null)
                SelectionChangeCommitted(this, e);
        }


        #endregion

        //------------------------------------------------------------------------------
        #region ** overrides/event handlers

        // change selection with up/down keys
        protected override void OnKeyUp(KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Up:
                case Key.Down:
                    int itemCount = _listBox.Items.Count;
                    if (_popup.Visibility == Visibility.Visible && itemCount > 0)
                    {
                        _listBox.SelectedIndex = (e.Key == Key.Down)
                            ? (_listBox.SelectedIndex + 1) % itemCount
                            : (_listBox.SelectedIndex + itemCount - 1) % itemCount;
                    }
                    break;

                case Key.Enter:
                case Key.Escape:
                    CommitListSelection();
                    break;
            }
            base.OnKeyUp(e);
        }

        // fire event when losing focus
        protected override void OnLostFocus(EventArgs e)
        {
            CommitSelection();
            base.OnLostFocus(e);
        }

        // update popup as the text changes
        protected override void OnTextChanged(EventArgs e)
        {
            if (!_updating)
            {
                string text = Text;
                List<string> items = BuildList(text);
                if (items.Count == 0)
                {
                    _popup.Hide();
                }
                else
                {
                    _listBox.BeginUpdate();
                    _listBox.Items.Clear();
                    foreach (string key in items)
                    {
                        _listBox.Items.Add(BuildItem(text, key, _items[key]));
                    }
                    _listBox.EndUpdate();
                    _listBox.Padding = Padding;
                    _popup.Show(this, false);
                }
            }

            // fire event as usual
            base.OnTextChanged(e);
        }

        // close the popup when the listbox is clicked
        void _listBox_Click(object sender, EventArgs e)
        {
            CommitListSelection();
        }

        #endregion

        //------------------------------------------------------------------------------
        #region ** private stuff

        // commit listbox selection
        private void CommitListSelection()
        {
            C1StackPanel sp = (C1StackPanel)_listBox.SelectedItem;
            if (sp != null)
            {
                // apply text to textbox, hide popup
                _updating = true;
                TextBlock tb = sp.Children[0] as TextBlock;
                Text = tb.Text;
                _popup.Hide();
                _updating = false;

                // fire event to indicate a choice was made
                CommitSelection();
            }
        }

        // commit C1TextBox change
        private void CommitSelection()
        {
            if (_items.ContainsKey(Text))
            {
                OnSelectionChangeCommitted(EventArgs.Empty);
            }
        }

        // methods used for building the pick list
        private List<string> BuildList(string text)
        {
            List<string> list = new List<string>();
            if (text.Length > 0)
            {
                // add matches on symbol first
                foreach (string key in _items.Keys)
                {
                    if (key.StartsWith(text, StringComparison.InvariantCultureIgnoreCase))
                    {
                        list.Add(key);
                        if (list.Count >= _maxItems)
                            break;
                    }
                }

                // add matches on name second
                foreach (string key in _items.Keys)
                {
                    if (_items[key].IndexOf(text, StringComparison.InvariantCultureIgnoreCase) > -1 &&
                        !list.Contains(key))
                    {
                        list.Add(key);
                        if (list.Count >= _maxItems)
                            break;
                    }
                }
            }
            return list;
        }
        private C1StackPanel BuildItem(string search, string key, string text)
        {
            C1StackPanel p = new C1StackPanel();
            p.IsHorizontal = true;
            p.Children.Add(BuildSubItem(search, key, _keyWidth));
            p.Children.Add(BuildSubItem(search, text, Math.Max(_textWidth, Width - _keyWidth - 4)));
            return p;
        }
        private TextBlock BuildSubItem(string search, string text, double width)
        {
            TextBlock tb = new TextBlock();
            tb.FontFamily = TextBlock.FontFamily;
            tb.FontSize = TextBlock.FontSize;
            tb.Text = string.Empty;

            for (int start = 0; ; )
            {
                // look for a match
                int index = text.IndexOf(search, start, StringComparison.InvariantCultureIgnoreCase);

                // match not found, add remainder and be done
                if (index < 0)
                {
                    CreateRun(tb, false, text.Substring(start));
                    break;
                }

                // match found: add regular and bold parts
                if (index > start)
                {
                    CreateRun(tb, false, text.Substring(start, index - start));
                }
                CreateRun(tb, true, text.Substring(index, search.Length));
                start = index + search.Length;
            }

            tb.Width = width;
            return tb;
        }
        private void CreateRun(TextBlock tb, bool highlight, string text)
        {
            Run run = new Run();
            run.Text = text;
            run.FontFamily = tb.FontFamily;
            run.FontSize = tb.FontSize;
            run.FontWeight = highlight ? FontWeights.Bold : FontWeights.Normal;
            tb.Inlines.Add(run);
        }

        #endregion
    }
}
