using Common.Debug;
using Common.Helpers;
using Common.IO;
using Common.Wpf.Extensions;
using FeedCenter.Options;
using FeedCenter.Properties;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Deployment.Application;
using System.IO;
using System.Linq;
using System.Threading;
using System.Web.UI;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;

namespace FeedCenter
{
    public partial class MainWindow
    {
        #region Member variables

        private int _feedIndex;
        private Feed _currentFeed;
        private DateTime _lastFeedDisplay;
        private DateTime _lastFeedRead;
        private System.Windows.Forms.Timer _mainTimer;
        private FeedCenterEntities _database;

        private InterprocessMessageListener _commandLineListener;
        private BackgroundWorker _feedReadWorker;

        #endregion

        #region Constructor

        public MainWindow()
        {
            InitializeComponent();
        }

        private bool _activated;
        protected override void OnActivated(EventArgs e)
        {
            base.OnActivated(e);

            if (_activated)
                return;

            _activated = true;

            // Load the lock state
            HandleWindowLockState();

            // Watch for size and location changes
            SizeChanged += HandleWindowSizeChanged;
            LocationChanged += HandleWindowLocationChanged;

            // Watch for setting changes
            Settings.Default.PropertyChanged += HandlePropertyChanged;
        }

        public void Initialize()
        {
            // Show the notification icon
            NotificationIcon.Initialize(this);

            // Load window settings
            LoadWindowSettings();

            // Set the foreground color to something that can be seen
            linkTextList.Foreground = (System.Drawing.SystemColors.Desktop.GetBrightness() < 0.5) ? Brushes.White : Brushes.Black;
            headerLabel.Foreground = linkTextList.Foreground;

            // Create the background worker that does the actual reading
            _feedReadWorker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
            _feedReadWorker.DoWork += HandleFeedReadWorkerStart;
            _feedReadWorker.ProgressChanged += HandleFeedReadWorkerProgressChanged;
            _feedReadWorker.RunWorkerCompleted += HandleFeedReadWorkerCompleted;

            // Setup the database
            _database = new FeedCenterEntities();

            // Initialize the command line listener
            _commandLineListener = new InterprocessMessageListener(Properties.Resources.ApplicationName);
            _commandLineListener.MessageReceived += HandleCommandLine;

            // Handle any command line we were started with
            HandleCommandLine(null, new InterprocessMessageListener.InterprocessMessageEventArgs(Environment.CommandLine));

            // Create a timer to keep track of things we need to do
            InitializeTimer();

            // Initialize the feed display
            InitializeFeed();

            Tracer.WriteLine("MainForm creation finished");
        }

        #endregion

        #region Window overrides

        protected override void OnClosing(CancelEventArgs e)
        {
            base.OnClosing(e);

            // Ditch the worker
            if (_feedReadWorker != null)
            {
                _feedReadWorker.CancelAsync();
                _feedReadWorker.Dispose();
            }

            // Get rid of the timer
            TerminateTimer();

            // Save current window settings
            SaveWindowSettings();

            // Save settings
            Settings.Default.Save();

            // Save options
            _database.SaveChanges();

            // Get rid of the notification icon
            NotificationIcon.Dispose();
        }

        #endregion

        #region Setting events

        private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // Make sure we're on the right thread
            if (!Dispatcher.CheckAccess())
            {
                Dispatcher.Invoke(new EventHandler<PropertyChangedEventArgs>(HandlePropertyChanged), sender, e);
                return;
            }

            if (e.PropertyName == Reflection.GetPropertyName(() => Settings.Default.MultipleLineDisplay))
            {
                // Update the current feed
                DisplayFeed();
            }
            else if (e.PropertyName == Reflection.GetPropertyName(() => Settings.Default.WindowLocked))
            {
                // Update the window for the new window lock value
                HandleWindowLockState();
            }
            else if (e.PropertyName == Reflection.GetPropertyName(() => Settings.Default.ToolbarLocation))
            {
                // Update the window for the toolbar location
                switch (Settings.Default.ToolbarLocation)
                {
                    case Dock.Top:
                        Grid.SetRow(navigationToolbarTray, mainGrid.GetRowIndex(topToolbarRow));

                        break;
                    case Dock.Bottom:
                        Grid.SetRow(navigationToolbarTray, mainGrid.GetRowIndex(bottomToolbarRow));
                        break;
                }
            }
        }

        #endregion

        #region Window methods

        private void LoadWindowSettings()
        {
            // Get the last window location
            var windowLocation = Settings.Default.WindowLocation;

            // Set the window into position
            Left = windowLocation.X;
            Top = windowLocation.Y;

            // Get the last window size
            var windowSize = Settings.Default.WindowSize;

            // Set the window into the previous size if it is valid
            if (!windowSize.Width.Equals(0) && !windowSize.Height.Equals(0))
            {
                Width = windowSize.Width;
                Height = windowSize.Height;
            }

            // Set the location of the navigation tray
            switch (Settings.Default.ToolbarLocation)
            {
                case Dock.Top:
                    Grid.SetRow(navigationToolbarTray, mainGrid.GetRowIndex(topToolbarRow));
                    break;
                case Dock.Bottom:
                    Grid.SetRow(navigationToolbarTray, mainGrid.GetRowIndex(bottomToolbarRow));
                    break;
            }

            // Load the lock state
            HandleWindowLockState();
        }

        private void SaveWindowSettings()
        {
            // Set the last window location
            Settings.Default.WindowLocation = new Point(Left, Top);

            // Set the last window size
            Settings.Default.WindowSize = new Size(Width, Height);

            // Save the dock on the navigation tray
            Settings.Default.ToolbarLocation = Grid.GetRow(navigationToolbarTray) == mainGrid.GetRowIndex(topToolbarRow) ? Dock.Top : Dock.Bottom;

            // Save settings
            Settings.Default.Save();
        }

        private void HandleWindowLockState()
        {
            // Set the resize mode for the window
            ResizeMode = Settings.Default.WindowLocked ? ResizeMode.NoResize : ResizeMode.CanResize;

            // Show or hide the border
            windowBorder.BorderBrush = Settings.Default.WindowLocked ? SystemColors.ActiveBorderBrush : Brushes.Transparent;

            // Update the borders
            UpdateBorder();
        }

        #endregion

        #region Header events

        private void HandleHeaderLabelMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // Ignore if the window is locked
            if (Settings.Default.WindowLocked)
                return;

            // Start dragging
            DragMove();
        }

        private void HandleCloseButtonClick(object sender, RoutedEventArgs e)
        {
            // Close the window
            Close();
        }

        #endregion

        #region Timer handling

        private void InitializeTimer()
        {
            _mainTimer = new System.Windows.Forms.Timer { Interval = 1000 };
            _mainTimer.Tick += HandleMainTimerTick;
        }

        private void TerminateTimer()
        {
            StopTimer();

            _mainTimer.Dispose();
        }

        private void StartTimer()
        {
            _mainTimer.Start();
        }

        private void StopTimer()
        {
            _mainTimer.Stop();
        }

        private void HandleMainTimerTick(object sender, EventArgs e)
        {
            // If the background worker is busy then don't do anything
            if (_feedReadWorker.IsBusy)
                return;

            // Stop the timer for now
            StopTimer();

            // Move to the next feed if the scroll interval has expired and the mouse isn't hovering
            if (linkTextList.IsMouseOver)
                _lastFeedDisplay = DateTime.Now;
            else if (DateTime.Now - _lastFeedDisplay >= Settings.Default.FeedScrollInterval)
                NextFeed();

            // Check to see if we should try to read the feeds
            if (DateTime.Now - _lastFeedRead >= Settings.Default.FeedCheckInterval)
                ReadFeeds();

            // Get the timer going again
            StartTimer();
        }

        #endregion

        #region Feed display

        private void InitializeFeed()
        {
            // Cache the feed count to save (a little) time
            var feedCount = _database.Feeds.Count();

            // Set button states
            previousToolbarButton.IsEnabled = (feedCount > 1);
            nextToolbarButton.IsEnabled = (feedCount > 1);
            refreshToolbarButton.IsEnabled = (feedCount > 0);
            feedLabel.Visibility = (feedCount == 0 ? Visibility.Hidden : Visibility.Visible);
            feedButton.Visibility = (feedCount > 1 ? Visibility.Hidden : Visibility.Visible);

            // Clear the link list
            linkTextList.Items.Clear();

            // Reset the feed index
            _feedIndex = -1;

            // Start the timer
            StartTimer();

            // Don't go further if we have no feeds
            if (feedCount == 0)
                return;

            // Get the first feed
            NextFeed();
        }

        private void NextFeed()
        {
            var feedCount = _database.Feeds.Count();

            if (feedCount == 0)
                return;

            if (Settings.Default.DisplayEmptyFeeds)
            {
                // Increment the index and adjust if we've gone around the end
                _feedIndex = (_feedIndex + 1) % feedCount;

                // Get the feed
                _currentFeed = _database.Feeds.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
            }
            else
            {
                // Keep track if we found something
                var found = false;

                // Remember our starting position
                var startIndex = (_feedIndex == -1 ? 0 : _feedIndex);

                // Increment the index and adjust if we've gone around the end
                _feedIndex = (_feedIndex + 1) % feedCount;

                // Loop until we come back to the start index
                do
                {
                    // Get the feed
                    _currentFeed = _database.Feeds.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);

                    // If the current feed has unread items then we can display it
                    if (_currentFeed.Items.Count(item => !item.BeenRead) > 0)
                    {
                        found = true;
                        break;
                    }

                    // Increment the index and adjust if we've gone around the end
                    _feedIndex = (_feedIndex + 1) % feedCount;
                }
                while (startIndex != _feedIndex);

                // If nothing was found then clear the current feed
                if (!found)
                {
                    _feedIndex = -1;
                    _currentFeed = null;
                }
            }

            // Update the feed timestamp
            _lastFeedDisplay = DateTime.Now;

            // Update the display
            DisplayFeed();
        }

        private void PreviousFeed()
        {
            var feedCount = _database.Feeds.Count();

            if (feedCount == 0)
                return;

            if (Settings.Default.DisplayEmptyFeeds)
            {
                // Decrement the feed index
                _feedIndex--;

                // If we've gone below the start of the list then reset to the end
                if (_feedIndex < 0)
                    _feedIndex = feedCount - 1;

                // Get the feed
                _currentFeed = _database.Feeds.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
            }
            else
            {
                // Keep track if we found something
                var found = false;

                // Remember our starting position
                var startIndex = (_feedIndex == -1 ? 0 : _feedIndex);

                // Decrement the feed index
                _feedIndex--;

                // If we've gone below the start of the list then reset to the end
                if (_feedIndex < 0)
                    _feedIndex = feedCount - 1;

                // Loop until we come back to the start index
                do
                {
                    // Get the feed
                    _currentFeed = _database.Feeds.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);

                    // If the current feed has unread items then we can display it
                    if (_currentFeed.Items.Count(item => !item.BeenRead) > 0)
                    {
                        found = true;
                        break;
                    }

                    // Decrement the feed index
                    _feedIndex--;

                    // If we've gone below the start of the list then reset to the end
                    if (_feedIndex < 0)
                        _feedIndex = feedCount - 1;
                }
                while (startIndex != _feedIndex);

                // If nothing was found then clear the current feed
                if (!found)
                {
                    _feedIndex = -1;
                    _currentFeed = null;
                }
            }

            // Update the feed timestamp
            _lastFeedDisplay = DateTime.Now;

            // Update the display
            DisplayFeed();
        }

        private void UpdateOpenAllButton()
        {
            var multipleOpenAction = _currentFeed.MultipleOpenAction;

            switch (multipleOpenAction)
            {
                case MultipleOpenAction.IndividualPages:
                    openAllToolbarButton.ToolTip = Properties.Resources.openAllMultipleToolbarButton;
                    break;
                case MultipleOpenAction.SinglePage:
                    openAllToolbarButton.ToolTip = Properties.Resources.openAllSingleToolbarButton;
                    break;
            }
        }

        private void DisplayFeed()
        {
            // Just clear the display if we have no feed
            if (_currentFeed == null)
            {
                feedLabel.Text = string.Empty;
                feedButton.Visibility = Visibility.Hidden;
                linkTextList.Items.Clear();

                return;
            }

            // Set the header to the feed title
            feedLabel.Text = (_currentFeed.Name.Length > 0 ? _currentFeed.Name : _currentFeed.Title);
            feedButton.Visibility = _database.Feeds.Count() > 1 ? Visibility.Visible : Visibility.Hidden;

            // Clear the current list
            linkTextList.Items.Clear();

            // Sort the items by sequence
            var sortedItems = _currentFeed.Items.Where(item => !item.BeenRead).OrderBy(item => item.Sequence);

            // Loop over all items in the current feed
            foreach (var feedItem in sortedItems)
            {
                // Add the list item
                linkTextList.Items.Add(feedItem);
            }

            UpdateOpenAllButton();
        }

        private void MarkAllItemsAsRead()
        {
            // Loop over all items and mark them as read
            foreach (FeedItem feedItem in linkTextList.Items)
                feedItem.BeenRead = true;

            // Save the changes
            _database.SaveChanges();

            // Clear the list
            linkTextList.Items.Clear();
        }

        #endregion

        #region Feed reading

        private class FeedReadWorkerInput
        {
            public bool ForceRead;
            public Feed Feed;
        }

        private class FeedReadWorkerOutput
        {
            public UpdateCheckInfo UpdateResult;
        }

        private void SetProgressMode(bool value, int feedCount)
        {
            // Reset the progress bar if we need it
            if (value)
            {
                feedReadProgress.Value = 0;
                feedReadProgress.Maximum = feedCount + 2;
                feedReadProgress.Visibility = Visibility.Visible;
            }
            else
            {
                feedReadProgress.Visibility = Visibility.Collapsed;
            }
        }

        private void ReadCurrentFeed(bool forceRead = false)
        {
            // Don't read if we're already working
            if (_feedReadWorker.IsBusy)
                return;

            // Don't read if there is nothing to read
            if (!_database.Feeds.Any())
                return;

            // Switch to progress mode
            SetProgressMode(true, 1);

            // Create the input class
            var workerInput = new FeedReadWorkerInput { ForceRead = forceRead, Feed = _currentFeed };

            // Start the worker
            _feedReadWorker.RunWorkerAsync(workerInput);
        }

        private void ReadFeeds(bool forceRead = false)
        {
            // Don't read if we're already working
            if (_feedReadWorker.IsBusy)
                return;

            // Don't read if there is nothing to read
            if (!_database.Feeds.Any())
                return;

            // Switch to progress mode
            SetProgressMode(true, _database.Feeds.Count());

            // Create the input class
            var workerInput = new FeedReadWorkerInput { ForceRead = forceRead, Feed = null };

            // Start the worker
            _feedReadWorker.RunWorkerAsync(workerInput);
        }

        private void HandleFeedReadWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // Set progress
            feedReadProgress.Value = e.ProgressPercentage;
        }

        private void HandleFeedReadWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // Get the state info
            var workerOutput = (FeedReadWorkerOutput) e.Result;

            // Reset the database to current settings
            ResetDatabase();

            // Save settings
            Settings.Default.Save();

            // Set the read timestamp
            _lastFeedRead = DateTime.Now;

            // Update the current feed
            DisplayFeed();

            // Switch to normal mode
            SetProgressMode(false, 0);

            // Check for update information
            var updateCheckInfo = workerOutput.UpdateResult;

            if (updateCheckInfo != null && updateCheckInfo.UpdateAvailable)
                newVersionLink.Visibility = Visibility.Visible;

            UpdateErrorLink();
        }

        private void UpdateErrorLink()
        {
            var feedErrorCount = _database.Feeds.Count(f => f.LastReadResult != FeedReadResult.Success);

            // Set the visibility of the error link
            feedErrorsLink.Visibility = feedErrorCount == 0 ? Visibility.Collapsed : Visibility.Visible;

            // Set the text to match the number of errors
            feedErrorsLink.Text = feedErrorCount == 1
                                      ? Properties.Resources.FeedErrorLink
                                      : string.Format(Properties.Resources.FeedErrorsLink, feedErrorCount);
        }

        private static void HandleFeedReadWorkerStart(object sender, DoWorkEventArgs e)
        {
            // Create a new database instance for just this thread
            var database = new FeedCenterEntities();

            // Get the worker
            var worker = (BackgroundWorker) sender;

            // Get the input information
            var workerInput = (FeedReadWorkerInput) e.Argument;

            // Create the output 
            var workerOutput = new FeedReadWorkerOutput();

            // Setup for progress
            var currentProgress = 0;

            // Create the list of feeds to read
            var feedsToRead = new List<Feed>();

            // If we have a single feed then add it to the list - otherwise add them all
            if (workerInput.Feed != null)
                feedsToRead.Add(database.Feeds.First(feed => feed.ID == workerInput.Feed.ID));
            else
                feedsToRead.AddRange(database.Feeds);

            // Loop over each feed and read it
            foreach (var feed in feedsToRead)
            {
                // Read the feed
                feed.Read(database, workerInput.ForceRead);

                // Increment progress
                currentProgress += 1;

                // Report progress
                worker.ReportProgress(currentProgress);
            }

            // Save the changes
            database.SaveChanges();

            // Increment progress
            currentProgress += 1;

            // Report progress
            worker.ReportProgress(currentProgress);

            // See if we're due for a version check
            if (DateTime.Now - Settings.Default.LastVersionCheck >= Settings.Default.VersionCheckInterval && ApplicationDeployment.IsNetworkDeployed)
            {
                UpdateCheckInfo updateCheckInfo = null;

                try
                {
                    // Get the update information
                    updateCheckInfo = ApplicationDeployment.CurrentDeployment.CheckForDetailedUpdate(false);
                }
                catch (Exception exception)
                {
                    Tracer.WriteException(exception);
                }

                // Store the update information in the output
                workerOutput.UpdateResult = updateCheckInfo;

                // Update the last check time
                Settings.Default.LastVersionCheck = DateTime.Now;
            }

            // Increment progress
            currentProgress += 1;

            // Report progress
            worker.ReportProgress(currentProgress);

            // Save the output
            e.Result = workerOutput;

            // Sleep for a little bit so the user can see the update
            Thread.Sleep(Settings.Default.ProgressSleepInterval * 3);
        }

        #endregion

        #region Remote command line handling

        private void HandleCommandLine(object sender, InterprocessMessageListener.InterprocessMessageEventArgs e)
        {
            // If the command line is blank then ignore it
            if (e.Message.Length == 0)
                return;

            // Pad the command line with a trailing space just to be lazy in parsing
            var commandLine = e.Message + " ";

            // Look for the feed URL in the command line
            var startPosition = commandLine.IndexOf("feed://", StringComparison.Ordinal);

            // If we found one then we should extract and process it
            if (startPosition > 0)
            {
                // Advance past the protocol
                startPosition += 7;

                // Starting at the URL position look for the next space
                var endPosition = commandLine.IndexOf(" ", startPosition, StringComparison.Ordinal);

                // Extract the feed URL
                var feedUrl = commandLine.Substring(startPosition, endPosition - startPosition);

                // Add the HTTP protocol by default
                feedUrl = "http://" + feedUrl;

                // Create a new feed using the URL
                HandleNewFeed(feedUrl);
            }
        }

        private delegate void NewFeedDelegate(string feedUrl);
        private void HandleNewFeed(string feedUrl)
        {
            // Create and configure the new feed
            var feed = Feed.Create();
            feed.Source = feedUrl;
            feed.Category = _database.Categories.ToList().First(category => category.IsDefault);

            // Read the feed for the first time
            var feedReadResult = feed.Read(_database);

            // See if we read the feed okay
            if (feedReadResult == FeedReadResult.Success)
            {
                // Update the feed name to be the title
                feed.Name = feed.Title;

                // Add the feed to the feed table
                _database.Feeds.Add(feed);

                // Save the changes
                _database.SaveChanges();

                // Show a tip
                NotificationIcon.ShowBalloonTip(string.Format(Properties.Resources.FeedAddedNotification, feed.Name), System.Windows.Forms.ToolTipIcon.Info);

                // Re-initialize the feed display
                DisplayFeed();
            }
            else
            {
                // Feed read failed - ceate a new feed window
                var feedForm = new FeedWindow();

                var dialogResult = feedForm.Display(_database, feed, this);

                // Display the new feed form
                if (dialogResult.HasValue && dialogResult.Value)
                {
                    // Add the feed to the feed table
                    _database.Feeds.Add(feed);

                    // Save the changes
                    _database.SaveChanges();

                    // Re-initialize the feed display
                    DisplayFeed();
                }
            }
        }

        #endregion

        #region Database helpers

        private void ResetDatabase()
        {
            // Get the ID of the current feed
            var currentId = _currentFeed.ID;

            // Create a new database object
            _database = new FeedCenterEntities();

            // Get a list of feeds ordered by name
            var feedList = _database.Feeds.OrderBy(f => f.Name).ToList();

            // First try to find the current feed by ID to see if it is still there
            var newIndex = feedList.FindIndex(f => f.ID == currentId);

            if (newIndex == -1)
            {
                // The current feed isn't there anymore so see if we can find a feed at the old index
                if (feedList.ElementAtOrDefault(_feedIndex) != null)
                    newIndex = _feedIndex;

                // If there is no feed at the old location then give up and go back to the start
                if (newIndex == -1 && feedList.Count > 0)
                    newIndex = 0;
            }

            // Set the current index to the new index
            _feedIndex = newIndex;

            // Re-get the current feed
            _currentFeed = (_feedIndex == -1 ? null : _database.Feeds.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex));
        }

        #endregion

        #region Drag and drop

        private void HandleDragOver(object sender, DragEventArgs e)
        {
            // Default to not allowed
            e.Effects = DragDropEffects.None;
            e.Handled = true;

            // If there isn't any text in the data then it is not allowed
            if (!e.Data.GetDataPresent(DataFormats.Text))
                return;

            // Get the data as a string
            var data = (string) e.Data.GetData(DataFormats.Text);

            // If the data doesn't look like a URI then it is not allowed
            if (!Uri.IsWellFormedUriString(data, UriKind.Absolute))
                return;

            // Allowed
            e.Effects = DragDropEffects.Copy;
        }

        private void HandleDragDrop(object sender, DragEventArgs e)
        {
            // Get the data as a string
            var data = (string) e.Data.GetData(DataFormats.Text);

            // Handle the new feed but allow the drag/drop to complete
            Dispatcher.BeginInvoke(new NewFeedDelegate(HandleNewFeed), data);
        }

        #endregion

        #region Link list events

        private void HandleLinkTextListMouseUp(object sender, MouseButtonEventArgs e)
        {
            switch (e.ChangedButton)
            {
                case MouseButton.XButton1:

                    PreviousFeed();
                    break;

                case MouseButton.XButton2:

                    NextFeed();
                    break;
            }
        }

        private void HandleLinkTextListListItemMouseUp(object sender, MouseButtonEventArgs e)
        {
            // Only handle the middle button
            if (e.ChangedButton != MouseButton.Middle)
                return;

            // Get the feed item
            var feedItem = (FeedItem) ((ListBoxItem) sender).DataContext;

            // The feed item has been read and is no longer new
            feedItem.BeenRead = true;
            feedItem.New = false;

            // Remove the item from the list
            linkTextList.Items.Remove(feedItem);

            // Save the changes
            _database.SaveChanges();

        }

        private void HandleLinkTextListListItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            // Get the feed item
            var feedItem = (FeedItem) ((ListBoxItem) sender).DataContext;

            // Open the item link
            if (BrowserCommon.OpenLink(feedItem.Link))
            {
                // The feed item has been read and is no longer new
                feedItem.BeenRead = true;
                feedItem.New = false;

                // Remove the item from the list
                linkTextList.Items.Remove(feedItem);

                // Save the changes
                _database.SaveChanges();
            }
        }

        #endregion

        #region Feed list menu

        private void HandleFeedButtonClick(object sender, RoutedEventArgs e)
        {
            // Create a new context menu
            var contextMenu = new ContextMenu();

            // Loop over each feed
            foreach (var feed in _database.Feeds.OrderBy(feed => feed.Name))
            {
                // Build a string to display the feed name and the unread count
                var display = string.Format("{0} ({1:d})", feed.Name, feed.Items.Count(item => !item.BeenRead));

                // Create a menu item
                var menuItem = new MenuItem
                                        {
                                            Header = display,
                                            Tag = feed,

                                            // Set the current item to bold
                                            FontWeight = feed == _currentFeed ? FontWeights.Bold : FontWeights.Normal
                                        };


                // Handle the click
                menuItem.Click += HandleFeedMenuItemClick;

                // Add the item to the list
                contextMenu.Items.Add(menuItem);
            }

            // Set the context menu placement to this button
            contextMenu.PlacementTarget = this;

            // Open the context menu
            contextMenu.IsOpen = true;
        }

        private void HandleFeedMenuItemClick(object sender, RoutedEventArgs e)
        {
            // Get the menu item clicked
            var menuItem = (MenuItem) sender;

            // Get the feed from the menu item tab
            var feed = (Feed) menuItem.Tag;

            // Loop over all feeds and look for the index of the new one		
            var feedIndex = 0;
            foreach (var loopFeed in _database.Feeds.OrderBy(loopFeed => loopFeed.Name))
            {
                if (loopFeed == feed)
                {
                    _feedIndex = feedIndex;
                    break;
                }

                feedIndex++;
            }

            // Set the current feed
            _currentFeed = feed;

            // Update the feed timestamp
            _lastFeedDisplay = DateTime.Now;

            // Update the display
            DisplayFeed();
        }

        #endregion

        #region Window border

        private void UpdateBorder()
        {
            var windowInteropHelper = new WindowInteropHelper(this);

            var screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle);

            var rectangle = new System.Drawing.Rectangle
                                                     {
                                                         X = (int) Left,
                                                         Y = (int) Top,
                                                         Width = (int) Width,
                                                         Height = (int) Height
                                                     };

            var borderThickness = new Thickness();

            if (rectangle.Right != screen.WorkingArea.Right)
                borderThickness.Right = 1;

            if (rectangle.Left != screen.WorkingArea.Left)
                borderThickness.Left = 1;

            if (rectangle.Top != screen.WorkingArea.Top)
                borderThickness.Top = 1;

            if (rectangle.Bottom != screen.WorkingArea.Bottom)
                borderThickness.Bottom = 1;

            windowBorder.BorderThickness = borderThickness;
        }

        private DelayedMethod _windowStateDelay;

        private void HandleWindowSizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (_windowStateDelay == null)
                _windowStateDelay = new DelayedMethod(500, UpdateWindowSettings);

            _windowStateDelay.Reset();
        }

        private void HandleWindowLocationChanged(object sender, EventArgs e)
        {
            if (_windowStateDelay == null)
                _windowStateDelay = new DelayedMethod(500, UpdateWindowSettings);

            _windowStateDelay.Reset();
        }

        private void UpdateWindowSettings()
        {
            // Save current window settings
            SaveWindowSettings();

            // Update the border
            UpdateBorder();
        }

        #endregion

        #region Feed header

        private void HandleFeedLabelMouseDown(object sender, MouseButtonEventArgs e)
        {
            // Open the link for the current feed on a left double click
            if (e.ClickCount == 2 && e.ChangedButton == MouseButton.Left)
                BrowserCommon.OpenLink(_currentFeed.Link);
        }

        #endregion

        #region Navigation toolbar

        private void HandlePreviousToolbarButtonClick(object sender, RoutedEventArgs e)
        {
            PreviousFeed();
        }

        private void HandleNextToolbarButtonClick(object sender, RoutedEventArgs e)
        {
            NextFeed();
        }

        private void OpenAllFeedItemsIndividually()
        {
            // Create a new list of feed items
            var feedItems = (from FeedItem feedItem in linkTextList.Items select feedItem).ToList();

            // Get the browser 
            var browser = BrowserCommon.FindBrowser(Settings.Default.Browser);

            // Cache the settings object
            var settings = Settings.Default;

            // Start with a longer sleep interval to give time for the browser to come up
            var sleepInterval = settings.OpenAllSleepIntervalFirst;

            // Loop over all items 
            foreach (var feedItem in feedItems)
            {
                // Try to open the link
                if (BrowserCommon.OpenLink(browser, feedItem.Link))
                {
                    // Mark the feed as read
                    feedItem.BeenRead = true;

                    // Remove the item
                    linkTextList.Items.Remove(feedItem);
                }

                // Wait a little bit
                Thread.Sleep(sleepInterval);

                // Switch to the normal sleep interval
                sleepInterval = settings.OpenAllSleepInterval;
            }

            // Save the changes
            _database.SaveChanges();
        }

        private void HandleOptionsToolbarButtonClick(object sender, RoutedEventArgs e)
        {
            // Create the options form
            var optionsWindow = new OptionsWindow { Owner = this };

            // Show the options form and get the result
            var result = optionsWindow.ShowDialog();

            // If okay was selected
            if (result.HasValue && result.Value)
            {
                // Reset the database to current settings
                ResetDatabase();

                // Re-initialize the feed display
                DisplayFeed();
            }
        }

        private void HandleMarkReadToolbarButtonClick(object sender, RoutedEventArgs e)
        {
            MarkAllItemsAsRead();
        }

        private void HandleShowErrorsButtonClick(object sender, RoutedEventArgs e)
        {
            // Create the feed error window
            var feedErrorWindow = new FeedErrorWindow();

            // Display the window
            var result = feedErrorWindow.Display(this);

            // If okay was selected
            if (result.GetValueOrDefault())
            {
                // Reset the database to current settings
                ResetDatabase();

                // Re-initialize the feed display
                DisplayFeed();

                UpdateErrorLink();
            }
        }

        private void HandleRefreshMenuItemClick(object sender, RoutedEventArgs e)
        {
            var menuItem = (MenuItem) e.Source;

            if (Equals(menuItem, menuRefresh))
                ReadCurrentFeed(true);
            else if (Equals(menuItem, menuRefreshAll))
                ReadFeeds(true);
        }

        private void HandleRefreshToolbarButtonClick(object sender, RoutedEventArgs e)
        {
            ReadFeeds(true);
        }

        private void HandleOpenAllMenuItemClick(object sender, RoutedEventArgs e)
        {
            var menuItem = (MenuItem) e.Source;

            if (Equals(menuItem, menuOpenAllSinglePage))
                OpenAllFeedItemsOnSinglePage();
            else if (Equals(menuItem, menuOpenAllMultiplePages))
                OpenAllFeedItemsIndividually();
        }

        private void HandleOpenAllToolbarButtonClick(object sender, RoutedEventArgs e)
        {
            var multipleOpenAction = (MultipleOpenAction) _currentFeed.MultipleOpenAction;

            switch (multipleOpenAction)
            {
                case MultipleOpenAction.IndividualPages:
                    OpenAllFeedItemsIndividually();
                    break;
                case MultipleOpenAction.SinglePage:
                    OpenAllFeedItemsOnSinglePage();
                    break;
            }
        }

        private void HandleEditCurrentFeedMenuItemClick(object sender, RoutedEventArgs e)
        {
            // Create a new feed window
            var feedWindow = new FeedWindow();

            // Display the feed window and get the result
            var result = feedWindow.Display(_database, _currentFeed, this);

            // If OK was clicked...
            if (result.HasValue && result.Value)
            {
                // Save
                _database.SaveChanges();

                // Update feed
                DisplayFeed();
            }
        }

        private void HandleDeleteCurrentFeedMenuItemClick(object sender, RoutedEventArgs e)
        {
            // Confirm this delete since it is for real
            if (MessageBox.Show(this, Properties.Resources.ConfirmDelete, string.Empty, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
                return;

            // Get the current feed
            var feedToDelete = _currentFeed;

            // Move to the next feed
            NextFeed();

            // Delete all items
            foreach (var item in feedToDelete.Items.ToList())
                _database.FeedItems.Remove(item);

            // Delete the feed
            _database.Feeds.Remove(feedToDelete);

            // Save
            _database.SaveChanges();
        }

        #endregion

        #region Single page reading

        private void OpenAllFeedItemsOnSinglePage()
        {
            var fileName = Path.GetTempFileName() + ".html";
            TextWriter textWriter = new StreamWriter(fileName);

            using (var htmlTextWriter = new HtmlTextWriter(textWriter))
            {
                htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Html);

                htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Head);

                htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Title);
                htmlTextWriter.Write(_currentFeed.Title);
                htmlTextWriter.RenderEndTag();

                htmlTextWriter.AddAttribute("http-equiv", "Content-Type");
                htmlTextWriter.AddAttribute("content", "text/html; charset=utf-8");
                htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Meta);
                htmlTextWriter.RenderEndTag();

                htmlTextWriter.RenderEndTag();

                htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Body);

                var sortedItems = from item in _currentFeed.Items where !item.BeenRead orderby item.Sequence ascending select item;

                var firstItem = true;

                foreach (var item in sortedItems)
                {
                    if (!firstItem)
                    {
                        htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Hr);
                        htmlTextWriter.RenderEndTag();
                    }

                    htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Div);

                    htmlTextWriter.AddAttribute(HtmlTextWriterAttribute.Href, item.Link);
                    htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.A);
                    htmlTextWriter.Write(item.Title.Length == 0 ? item.Link : item.Title);
                    htmlTextWriter.RenderEndTag();

                    htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Br);
                    htmlTextWriter.RenderEndTag();

                    htmlTextWriter.Write(item.Description);

                    htmlTextWriter.RenderEndTag();

                    firstItem = false;
                }

                htmlTextWriter.RenderEndTag();
                htmlTextWriter.RenderEndTag();
            }

            textWriter.Flush();
            textWriter.Close();

            BrowserCommon.OpenLink(fileName);

            MarkAllItemsAsRead();
        }

        #endregion

        private void HandleNewVersionLinkClick(object sender, RoutedEventArgs e)
        {
            // Display update information
            VersionCheck.DisplayUpdateInformation(true);
        }
    }
}
