using MediaBrowser.Common;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media.Imaging;

namespace MediaBrowser.ServerApplication
{
    /// <summary>
    /// Interaction logic for LibraryExplorer.xaml
    /// </summary>
    public partial class LibraryExplorer : Window
    {
        private readonly ILogger _logger;

        private readonly IJsonSerializer _jsonSerializer;
        private readonly ILibraryManager _libraryManager;
        private readonly IDisplayPreferencesRepository _displayPreferencesManager;

        /// <summary>
        /// The current user
        /// </summary>
        private User CurrentUser;
        /// <summary>
        /// Initializes a new instance of the <see cref="LibraryExplorer" /> class.
        /// </summary>
        /// <param name="jsonSerializer">The json serializer.</param>
        /// <param name="logger">The logger.</param>
        /// <param name="appHost">The app host.</param>
        /// <param name="userManager">The user manager.</param>
        /// <param name="libraryManager">The library manager.</param>
        /// <param name="displayPreferencesManager">The display preferences manager.</param>
        public LibraryExplorer(IJsonSerializer jsonSerializer, ILogger logger, IApplicationHost appHost, IUserManager userManager, ILibraryManager libraryManager, IDisplayPreferencesRepository displayPreferencesManager)
        {
            _logger = logger;
            _jsonSerializer = jsonSerializer;
            _libraryManager = libraryManager;
            _displayPreferencesManager = displayPreferencesManager;

            InitializeComponent();
            lblVersion.Content = "Version: " + appHost.ApplicationVersion;
            foreach (var user in userManager.Users)
                ddlProfile.Items.Add(user);
            ddlProfile.Items.Insert(0, new User { Name = "Physical" });
            ddlProfile.SelectedIndex = 0;
            ddlIndexBy.Visibility = ddlSortBy.Visibility = lblIndexBy.Visibility = lblSortBy.Visibility = Visibility.Hidden;

        }

        /// <summary>
        /// Handles the Click event of the btnLoad control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param>
        private void btnLoad_Click(object sender, RoutedEventArgs e)
        {
        }

        /// <summary>
        /// Loads the tree.
        /// </summary>
        /// <returns>Task.</returns>
        private async Task LoadTree()
        {
            tvwLibrary.Items.Clear();
            lblLoading.Visibility = Visibility.Visible;
            //grab UI context so we can update within the below task
            var ui = TaskScheduler.FromCurrentSynchronizationContext();
            //this whole async thing doesn't really work in this instance since all my work pretty much needs to be on the UI thread...
            Cursor = Cursors.Wait;
            await Task.Run(() =>
                {
                    IEnumerable<BaseItem> children = CurrentUser.Name == "Physical" ? new[] { _libraryManager.RootFolder } : _libraryManager.RootFolder.GetChildren(CurrentUser, true);
                    children = OrderByName(children, CurrentUser);

                    foreach (Folder folder in children)
                    {

                        var currentFolder = folder;
                        Task.Factory.StartNew(() =>
                         {
                             var prefs = ddlProfile.SelectedItem != null ? _displayPreferencesManager.GetDisplayPreferences(currentFolder.DisplayPreferencesId, (ddlProfile.SelectedItem as User).Id, "LibraryExplorer") ?? new DisplayPreferences { SortBy = ItemSortBy.SortName } : new DisplayPreferences { SortBy = ItemSortBy.SortName };
                             var node = new TreeViewItem { Tag = currentFolder };

                             var subChildren = currentFolder.GetChildren(CurrentUser, true);
                             subChildren = OrderByName(subChildren, CurrentUser);
                             AddChildren(node, subChildren, CurrentUser);
                             node.Header = currentFolder.Name + " (" +
                                         node.Items.Count + ")";
                             tvwLibrary.Items.Add(node);
                         }, CancellationToken.None, TaskCreationOptions.None, ui);
                    }
                });
            lblLoading.Visibility = Visibility.Hidden;
            Cursor = Cursors.Arrow;

        }

        /// <summary>
        /// Orders the name of the by.
        /// </summary>
        /// <param name="items">The items.</param>
        /// <param name="user">The user.</param>
        /// <returns>IEnumerable{BaseItem}.</returns>
        private IEnumerable<BaseItem> OrderByName(IEnumerable<BaseItem> items, User user)
        {
            return OrderBy(items, user, ItemSortBy.SortName);
        }

        /// <summary>
        /// Orders the name of the by.
        /// </summary>
        /// <param name="items">The items.</param>
        /// <param name="user">The user.</param>
        /// <returns>IEnumerable{BaseItem}.</returns>
        private IEnumerable<BaseItem> OrderBy(IEnumerable<BaseItem> items, User user, string order)
        {
            return _libraryManager.Sort(items, user, new[] { order }, SortOrder.Ascending);
        }

        /// <summary>
        /// Adds the children.
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <param name="children">The children.</param>
        /// <param name="user">The user.</param>
        private void AddChildren(TreeViewItem parent, IEnumerable<BaseItem> children, User user)
        {
            foreach (var item in children)
            {
                var node = new TreeViewItem { Tag = item };
                var subFolder = item as Folder;
                if (subFolder != null)
                {
                    var prefs = _displayPreferencesManager.GetDisplayPreferences(subFolder.DisplayPreferencesId, user.Id, "LibraryExplorer");

                    AddChildren(node, OrderBy(subFolder.GetChildren(user, true), user, prefs.SortBy), user);
                    node.Header = item.Name + " (" + node.Items.Count + ")";
                }
                else
                {
                    node.Header = item.Name;
                }
                parent.Items.Add(node);
            }
        }

        /// <summary>
        /// TVWs the library_ selected item changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private async void tvwLibrary_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            if (tvwLibrary.SelectedItem != null)
            {
                var item = (BaseItem)(tvwLibrary.SelectedItem as TreeViewItem).Tag;
                lblObjType.Content = "Type: " + item.GetType().Name;

                var movie = item as Movie;

                var folder = item as Folder;
                if (folder != null)
                {
                    lblIndexBy.Visibility = ddlIndexBy.Visibility = ddlSortBy.Visibility = lblSortBy.Visibility = Visibility.Visible;
                    ddlIndexBy.ItemsSource = folder.IndexByOptionStrings;

                    ddlSortBy.ItemsSource = new[]
                        {
                            ItemSortBy.SortName, 
                            ItemSortBy.Album, 
                            ItemSortBy.AlbumArtist, 
                            ItemSortBy.Artist, 
                            ItemSortBy.CommunityRating, 
                            ItemSortBy.DateCreated, 
                            ItemSortBy.DatePlayed, 
                            ItemSortBy.PremiereDate, 
                            ItemSortBy.ProductionYear, 
                            ItemSortBy.Random, 
                            ItemSortBy.Runtime
                        };

                    var prefs = _displayPreferencesManager.GetDisplayPreferences(folder.DisplayPreferencesId, (ddlProfile.SelectedItem as User).Id, "LibraryExplorer");

                    ddlIndexBy.SelectedItem = prefs != null
                                                  ? prefs.IndexBy ?? LocalizedStrings.Instance.GetString("NoneDispPref")
                                                  : LocalizedStrings.Instance.GetString("NoneDispPref");
                    ddlSortBy.SelectedItem = prefs != null
                                                  ? prefs.SortBy ?? ItemSortBy.SortName
                                                  : ItemSortBy.SortName;
                }
                else
                {
                    lblIndexBy.Visibility = ddlIndexBy.Visibility = ddlSortBy.Visibility = lblSortBy.Visibility = Visibility.Hidden;

                }
                txtData.Text = FormatJson(_jsonSerializer.SerializeToString(item));

                var previews = new List<PreviewItem>();
                await Task.Run(() =>
                                   {
                                       if (!string.IsNullOrEmpty(item.PrimaryImagePath))
                                       {
                                           previews.Add(new PreviewItem(item.PrimaryImagePath, "Primary"));
                                       }
                                       if (item.HasImage(ImageType.Banner))
                                       {
                                           previews.Add(new PreviewItem(item.GetImage(ImageType.Banner), "Banner"));
                                       }
                                       if (item.HasImage(ImageType.Logo))
                                       {
                                           previews.Add(new PreviewItem(item.GetImage(ImageType.Logo), "Logo"));
                                       }
                                       if (item.HasImage(ImageType.Art))
                                       {
                                           previews.Add(new PreviewItem(item.GetImage(ImageType.Art), "Art"));
                                       }
                                       if (item.HasImage(ImageType.Thumb))
                                       {
                                           previews.Add(new PreviewItem(item.GetImage(ImageType.Thumb), "Thumb"));
                                       }
                                       previews.AddRange(
                                           item.BackdropImagePaths.Select(
                                               image => new PreviewItem(image, "Backdrop")));
                                   });
                lstPreviews.ItemsSource = previews;
                lstPreviews.Items.Refresh();
            }
        }

        /// <summary>
        /// The INDEN t_ STRING
        /// </summary>
        private const string INDENT_STRING = "    ";
        /// <summary>
        /// Formats the json.
        /// </summary>
        /// <param name="str">The STR.</param>
        /// <returns>System.String.</returns>
        private static string FormatJson(string str)
        {
            var indent = 0;
            var quoted = false;
            var sb = new StringBuilder();
            for (var i = 0; i < str.Length; i++)
            {
                var ch = str[i];
                switch (ch)
                {
                    case '{':
                    case '[':
                        sb.Append(ch);
                        if (!quoted)
                        {
                            sb.AppendLine();
                            Enumerable.Range(0, ++indent).ForEach(item => sb.Append(INDENT_STRING));
                        }
                        break;
                    case '}':
                    case ']':
                        if (!quoted)
                        {
                            sb.AppendLine();
                            Enumerable.Range(0, --indent).ForEach(item => sb.Append(INDENT_STRING));
                        }
                        sb.Append(ch);
                        break;
                    case '"':
                        sb.Append(ch);
                        bool escaped = false;
                        var index = i;
                        while (index > 0 && str[--index] == '\\')
                            escaped = !escaped;
                        if (!escaped)
                            quoted = !quoted;
                        break;
                    case ',':
                        sb.Append(ch);
                        if (!quoted)
                        {
                            sb.AppendLine();
                            Enumerable.Range(0, indent).ForEach(item => sb.Append(INDENT_STRING));
                        }
                        break;
                    case ':':
                        sb.Append(ch);
                        if (!quoted)
                            sb.Append(" ");
                        break;
                    default:
                        sb.Append(ch);
                        break;
                }
            }
            return sb.ToString();
        }

        /// <summary>
        /// Handles the SelectionChanged event of the ddlProfile control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="SelectionChangedEventArgs" /> instance containing the event data.</param>
        private void ddlProfile_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            CurrentUser = ddlProfile.SelectedItem as User;
            if (CurrentUser != null)
                LoadTree().ConfigureAwait(false);
        }

        /// <summary>
        /// Handles the Click event of the btnRefresh control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param>
        private void btnRefresh_Click(object sender, RoutedEventArgs e)
        {
            if (tvwLibrary.SelectedItem != null)
            {
                var item = ((TreeViewItem)tvwLibrary.SelectedItem).Tag as BaseItem;
                if (item != null)
                {
                    item.RefreshMetadata(CancellationToken.None, forceRefresh: cbxForce.IsChecked.Value);
                    tvwLibrary_SelectedItemChanged(this, null);
                }
            }
        }

        /// <summary>
        /// Handles the SelectionChanged event of the ddlIndexBy control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="SelectionChangedEventArgs" /> instance containing the event data.</param>
        private async void ddlIndexBy_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (ddlIndexBy.SelectedItem != null)
            {
                var treeItem = tvwLibrary.SelectedItem as TreeViewItem;
                var folder = treeItem != null
                                 ? treeItem.Tag as Folder
                                 : null;
                var prefs = folder != null ? _displayPreferencesManager.GetDisplayPreferences(folder.DisplayPreferencesId, CurrentUser.Id, "LibraryExplorer") : new DisplayPreferences { SortBy = ItemSortBy.SortName };
                if (folder != null && prefs.IndexBy != ddlIndexBy.SelectedItem as string)
                {
                    //grab UI context so we can update within the below task
                    var ui = TaskScheduler.FromCurrentSynchronizationContext();
                    Cursor = Cursors.Wait;
                    await Task.Factory.StartNew(() =>
                                                    {
                                                        using (
                                                            new Profiler("Explorer full index expansion for " +
                                                                         folder.Name, _logger))
                                                        {
                                                            //re-build the current item's children as an index
                                                            prefs.IndexBy = ddlIndexBy.SelectedItem as string;
                                                            treeItem.Items.Clear();
                                                            AddChildren(treeItem, OrderBy(folder.GetChildren(CurrentUser, true), CurrentUser, prefs.SortBy), CurrentUser);
                                                            treeItem.Header = folder.Name + "(" +
                                                                              treeItem.Items.Count + ")";
                                                            Cursor = Cursors.Arrow;

                                                        }
                                                    }, CancellationToken.None, TaskCreationOptions.None,
                                                ui);

                }
            }
        }

        /// <summary>
        /// Handles the SelectionChanged event of the ddlSortBy control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="SelectionChangedEventArgs" /> instance containing the event data.</param>
        private async void ddlSortBy_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (ddlSortBy.SelectedItem != null)
            {
                var treeItem = tvwLibrary.SelectedItem as TreeViewItem;
                var folder = treeItem != null
                                 ? treeItem.Tag as Folder
                                 : null;
                var prefs = folder != null ? _displayPreferencesManager.GetDisplayPreferences(folder.DisplayPreferencesId, CurrentUser.Id, "LibraryExplorer") : new DisplayPreferences();
                if (folder != null && prefs.SortBy != ddlSortBy.SelectedItem as string)
                {
                    //grab UI context so we can update within the below task
                    var ui = TaskScheduler.FromCurrentSynchronizationContext();
                    Cursor = Cursors.Wait;
                    await Task.Factory.StartNew(() =>
                                                    {
                                                        using (
                                                            new Profiler("Explorer sorting by " + ddlSortBy.SelectedItem + " for " +
                                                                         folder.Name, _logger))
                                                        {
                                                            //re-sort
                                                            prefs.SortBy = ddlSortBy.SelectedItem as string;
                                                            treeItem.Items.Clear();
                                                            AddChildren(treeItem, OrderBy(folder.GetChildren(CurrentUser, true), CurrentUser, prefs.SortBy ?? ItemSortBy.SortName), CurrentUser);
                                                            treeItem.Header = folder.Name + "(" +
                                                                              treeItem.Items.Count + ")";
                                                            Cursor = Cursors.Arrow;

                                                        }
                                                    }, CancellationToken.None, TaskCreationOptions.None,
                                                ui);

                }
            }
        }

    }

    /// <summary>
    /// Class PreviewItem
    /// </summary>
    public class PreviewItem
    {

        /// <summary>
        /// The preview
        /// </summary>
        private readonly string preview;
        /// <summary>
        /// The name
        /// </summary>
        private readonly string name;

        /// <summary>
        /// Gets the preview.
        /// </summary>
        /// <value>The preview.</value>
        public string Preview
        {
            get { return preview; }
        }

        /// <summary>
        /// Gets the name.
        /// </summary>
        /// <value>The name.</value>
        public string Name
        {
            get { return name; }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PreviewItem" /> class.
        /// </summary>
        /// <param name="p">The p.</param>
        /// <param name="n">The n.</param>
        public PreviewItem(string p, string n)
        {
            preview = p;
            name = n;
        }
    }

    /// <summary>
    /// Class Extensions
    /// </summary>
    static class Extensions
    {
        /// <summary>
        /// Fors the each.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="ie">The ie.</param>
        /// <param name="action">The action.</param>
        public static void ForEach<T>(this IEnumerable<T> ie, Action<T> action)
        {
            foreach (var i in ie)
            {
                action(i);
            }
        }
    }
    #region ItemToImageConverter

    /// <summary>
    /// Class ItemToImageConverter
    /// </summary>
    [ValueConversion(typeof(string), typeof(bool))]
    public class ItemToImageConverter : IValueConverter
    {
        /// <summary>
        /// The instance
        /// </summary>
        public static ItemToImageConverter Instance =
            new ItemToImageConverter();

        /// <summary>
        /// Converts a value.
        /// </summary>
        /// <param name="value">The value produced by the binding source.</param>
        /// <param name="targetType">The type of the binding target property.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>A converted value. If the method returns null, the valid null value is used.</returns>
        public object Convert(object value, Type targetType,
            object parameter, CultureInfo culture)
        {
            var item = value as BaseItem ?? new Folder();
            switch (item.DisplayMediaType)
            {
                case "DVD":
                case "HD DVD":
                case "Blu-ray":
                case "Blu-Ray":
                case "Movie":
                    {
                        var uri = new Uri
                            ("pack://application:,,,/Resources/Images/movie.png");
                        var source = new BitmapImage(uri);
                        return source;
                    }
                case "Series":
                    {
                        var uri = new Uri
                            ("pack://application:,,,/Resources/Images/series.png");
                        var source = new BitmapImage(uri);
                        return source;
                    }
                case "Season":
                    {
                        var uri = new Uri
                            ("pack://application:,,,/Resources/Images/season.png");
                        var source = new BitmapImage(uri);
                        return source;
                    }
                case "Episode":
                    {
                        var uri = new Uri
                            ("pack://application:,,,/Resources/Images/episode.png");
                        var source = new BitmapImage(uri);
                        return source;
                    }
                case "BoxSet":
                    {
                        var uri = new Uri
                            ("pack://application:,,,/Resources/Images/boxset.png");
                        var source = new BitmapImage(uri);
                        return source;
                    }
                case "Audio":
                    {
                        var uri = new Uri
                            ("pack://application:,,,/Resources/Images/audio.png");
                        var source = new BitmapImage(uri);
                        return source;
                    }
                case "Person":
                    {
                        var uri = new Uri
                            ("pack://application:,,,/Resources/Images/persons.png");
                        var source = new BitmapImage(uri);
                        return source;
                    }
                case "MusicArtist":
                    {
                        var uri = new Uri
                            ("pack://application:,,,/Resources/Images/artist.png");
                        var source = new BitmapImage(uri);
                        return source;
                    }
                case "MusicAlbum":
                    {
                        var uri = new Uri
                            ("pack://application:,,,/Resources/Images/album.png");
                        var source = new BitmapImage(uri);
                        return source;
                    }
                case "Trailer":
                    {
                        var uri = new Uri
                            ("pack://application:,,,/Resources/Images/trailer.png");
                        var source = new BitmapImage(uri);
                        return source;
                    }
                case "None":
                    {
                        Uri uri;
                        if (item is Movie)
                            uri = new Uri("pack://application:,,,/Resources/Images/movie.png");
                        else if (item is Series)
                            uri = new Uri("pack://application:,,,/Resources/Images/series.png");
                        else if (item is BoxSet)
                            uri = new Uri("pack://application:,,,/Resources/Images/boxset.png");
                        else
                            uri = new Uri("pack://application:,,,/Resources/Images/folder.png");

                        return new BitmapImage(uri);
                    }
                default:
                    {
                        var uri = new Uri("pack://application:,,,/Resources/Images/folder.png");
                        var source = new BitmapImage(uri);
                        return source;
                    }
            }
        }

        /// <summary>
        /// Converts a value.
        /// </summary>
        /// <param name="value">The value that is produced by the binding target.</param>
        /// <param name="targetType">The type to convert to.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>A converted value. If the method returns null, the valid null value is used.</returns>
        /// <exception cref="System.NotSupportedException">Cannot convert back</exception>
        public object ConvertBack(object value, Type targetType,
            object parameter, CultureInfo culture)
        {
            throw new NotSupportedException("Cannot convert back");
        }
    }

    #endregion // ItemToImageConverter
}