using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
using Autofac;
using BitFactory.Logging;
using MusicMetaTagger.Client.AllMusicGuide.RemoteDataAccess;
using MusicMetaTagger.Client.AllMusicGuide.Services;
using MusicMetaTagger.Core.Model;
using MusicMetaTagger.Core.Services;
using IContainer = Autofac.IContainer;

namespace MusicMetaTagger.UI
{
	public partial class Interface : Form, IProgressCallback
	{
		#region Private members

		private const string LogFilename = "tagger.log";

		private readonly Logger _logger;

		private readonly BindingList<TrackQueryStatus> _trackQueryStatuses;

		private CancellationTokenSource _cancellationTokenSource;

		private bool _exitClicked;
		private IMusicGuide _musicGuide;
		private ITrackSearcher _trackSearcher;
		private IMusicLibraryInterface _musicLibraryInterface;
		private ITrackRater _trackRater;

		private Options _options;
		private readonly IContainer _container;
		private ITrackOriginalRelease _trackOriginalRelease;

		#endregion
		
		#region Form Event Handlers

		/// <summary>
		///   Initializes a new instance of the <see cref = "Interface" /> class.
		/// </summary>
		public Interface()
		{
			InitializeComponent();
			_trackQueryStatuses = new BindingList<TrackQueryStatus>();
			logDataGridView.DataSource = _trackQueryStatuses;
			_logger = new FileLogger(LogFilename);
			_cancellationTokenSource = new CancellationTokenSource();

			var builder = new ContainerBuilder();
			builder
				.RegisterAssemblyTypes(typeof(MusicGuideScraper).Assembly)
				.AsImplementedInterfaces()
				.Except<MusicGuideScraper>()
				.Except<MusicGuideCache>();
			builder.RegisterType<TrackSearcher>().As<ITrackSearcher>();
			builder.RegisterType<MusicGuideScraper>().Named<IMusicGuide>("MusicGuideScraper");
			builder.RegisterDecorator<IMusicGuide>((c, inner) => new MusicGuideCache(inner), fromKey: "MusicGuideScraper");
			_container = builder.Build();
		}

		/// <summary>
		///   Handles the Load event of the Interface control.
		/// </summary>
		/// <param name = "sender">The source of the event.</param>
		/// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
		private void Interface_Load(object sender, EventArgs e)
		{
			_options = new Options();
			components.Add(_options);
			optionsLabel.Text = _options.LibraryQuery.ToString();
			var t = new Thread(OpenOrCreateAllMusicGuide);
			t.Start();
		}

		/// <summary>
		///   Handles the Click event of the startButton control.
		/// </summary>
		/// <param name = "sender">The source of the event.</param>
		/// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
		private void startButton_Click(object sender, EventArgs e)
		{
			librarySelector.Enabled = false;
			startButton.Enabled = false;
			optionsButton.Enabled = false;
			_musicLibraryInterface = librarySelector.SelectedLibrary;

			var t = new Thread(LibraryProcess);
			t.Start();
		}

		/// <summary>
		///   Handles the Click event of the cancelButton control.
		/// </summary>
		/// <param name = "sender">The source of the event.</param>
		/// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
		private void cancelButton_Click(object sender, EventArgs e)
		{
			UiInvoke(() => { toolStripStatusLabel.Text = "Cancelling..."; });
			cancelButton.Enabled = false;
			_cancellationTokenSource.Cancel();
		}


		/// <summary>
		///   Handles the FormClosing event of the Interface control.
		/// </summary>
		/// <param name = "sender">The source of the event.</param>
		/// <param name = "e">The <see cref = "System.Windows.Forms.FormClosingEventArgs" /> instance containing the event data.</param>
		private void Interface_FormClosing(object sender, FormClosingEventArgs e)
		{
			if (!exitButton.Enabled)
			{
				_exitClicked = true;
				_cancellationTokenSource.Cancel();
				e.Cancel = true;
			}
		}

		/// <summary>
		///   Handles the Click event of the exitButton control.
		/// </summary>
		/// <param name = "sender">The source of the event.</param>
		/// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
		private void exitButton_Click(object sender, EventArgs e)
		{
			Close();
		}

		private void optionsButton_Click(object sender, EventArgs e)
		{
			_options.ShowDialog();
			optionsLabel.Text = _options.LibraryQuery.ToString();
		}

		#endregion

		#region Music Guide threaded methods

		private void OpenOrCreateAllMusicGuide()
		{
			UiInvoke(() => { startButton.Enabled = false; });
			UiInvoke(() => { exitButton.Enabled = false; });
			UiInvoke(() => { toolStripStatusLabel.Text = "Loading DataLayer..."; });
			UiInvoke(() => { toolStripStatusLabel.Text = "Loading Complete."; });
			UiInvoke(() => { startButton.Enabled = true; });
			UiInvoke(() => { exitButton.Enabled = true; });

			_musicGuide = _container.Resolve<IMusicGuide>();
			_trackSearcher = _container.Resolve<ITrackSearcher>();
			_trackRater = _container.Resolve<ITrackRater>();
			_trackOriginalRelease = _container.Resolve<ITrackOriginalRelease>();

			if (_exitClicked)
			{
				UiInvoke(() => { toolStripStatusLabel.Text = "Exiting..."; });
				UiInvoke(() => exitButton_Click(this, new EventArgs()));
			}
		}

		#endregion

		#region Utility Methods

		private void LogStatusUpdate(StatusEventArgs data)
		{
			_logger.LogInfo(data.Status);
		}

		private static string ConcatToString(string original, string additional)
		{
			return !string.IsNullOrEmpty(original) ? string.Join(", ", additional, original) : additional;
		}

		#endregion

		#region Library Process methods

		private Track SearchTrack(TrackQueryStatus trackQuery)
		{
			_trackSearcher.SearchStatusUpdate += trackQuery.UpdateStatus;
			_trackSearcher.SearchStatusUpdate += LogStatusUpdate;
			var result = _trackSearcher.SearchTrack(trackQuery, _cancellationTokenSource.Token);
			_trackSearcher.SearchStatusUpdate -= trackQuery.UpdateStatus;
			_trackSearcher.SearchStatusUpdate -= LogStatusUpdate;
			return result;
		}

		/// <summary>
		///   Updates the track information to the library.
		/// </summary>
		/// <param name = "trackQuery">The track query.</param>
		/// <param name = "track">The track info.</param>
		private void UpdateTrack(TrackQueryStatus trackQuery, Track track)
		{
			var updateText = "";

			var albumInfo = string.IsNullOrEmpty(track.AlbumId)
			                	? null
			                	: _musicGuide.GetAlbum(track.AlbumId);
			var artistInfo = string.IsNullOrEmpty(track.PerformerIds[0])
			                 	? null
								: _musicGuide.GetArtist(track.PerformerIds[0]);

			// If Track is not null, update ActualTrack from Track
			if (albumInfo != null)
			{
				// set album artwork
				if (!string.IsNullOrEmpty(albumInfo.CoverUrl))
				{
					try
					{
						// TODO: Cover Art Reimplementation

						//var fileInfo = albumInfo.DownloadCoverArt();
						//if (fileInfo != null)
						//{
						//    if (_musicLibraryInterface.UpdateAlbumArt(trackQuery, fileInfo, _options.LibraryUpdate.AlbumArtOverwrite))
						//    {
						//        updateText = ConcatToString(updateText, "Added artwork");
						//        trackQuery.SetText(updateText);
						//    }
						//    if (mp3FileInfo != null)
						//        _mp3Interface.UpdateAlbumArt(mp3FileInfo, fileInfo, _options.LibraryUpdate.AlbumArtOverwrite);
						//}
					}
					catch (Exception ex)
					{
						_logger.LogError(ex.ToString());
						updateText = ConcatToString(updateText, "Artwork error: " + ex.Message);
						trackQuery.Status = updateText;
					}
				}

				if (artistInfo != null)
				{
					if (_options.LibraryUpdate.GenreUpdate)
					{
						// set genre
						string genre;
						if (artistInfo.Genre == "Rock" && albumInfo.Styles != null && albumInfo.Styles.Count > 0)
							genre = albumInfo.Styles[0];
						else
							genre = artistInfo.Genre;
						if (_musicLibraryInterface.UpdateGenre(trackQuery, genre, _options.LibraryUpdate.GenreOverwrite))
						{
							updateText = ConcatToString(updateText, "Genre: " + genre);
							trackQuery.Status = updateText;
						}
					}
				}

				// set year
				if (_options.LibraryUpdate.YearUpdate && !string.IsNullOrEmpty(albumInfo.ReleaseDate))
				{
					var year = albumInfo.GetReleaseYear();
					if (_musicLibraryInterface.UpdateYear(trackQuery, year, _options.LibraryUpdate.YearOverwrite))
					{
						updateText = ConcatToString(updateText, "Year: " + year);
						trackQuery.Status = updateText;
					}
				}
			}

			if (_options.LibraryUpdate.RatingUpdate)
			{
				// update rating
				var rating = _trackRater.RateTrack(track);

				if (!rating.HasValue)
				{
					updateText = ConcatToString(updateText, "Track has no rating");
					trackQuery.Status = updateText;
				}
				else
				{
					if (track.Pick)
						_musicLibraryInterface.AddComment(trackQuery, "-AMG Pick-");

					var ratingInt = Convert.ToInt32(rating.Value*10);
					if (_musicLibraryInterface.UpdateRating(trackQuery, ratingInt, _options.LibraryUpdate.RatingOverwrite))
					{
						updateText = ConcatToString(updateText, "Rating: " + ratingInt + " / 100");
						trackQuery.Status = updateText;
					}
				}
			}
		}

		private void UiInvoke(Action action)
		{
			BeginInvoke(new MethodInvoker(action));
		}

		private void LibraryProcess()
		{
			UiInvoke(
				() =>
					{
						cancelButton.Enabled = true;
						cancelButton.Visible = true;
						exitButton.Enabled = false;
					});

			Begin();

			// Use LibraryQuery to build a list of trackQueries
			List<TrackQueryStatus> trackQueries;
			try
			{
				trackQueries = _musicLibraryInterface.GetTrackQueries(_options.LibraryQuery, this);
			}
			catch (Exception ex)
			{
				_logger.LogError(ex.ToString());
				trackQueries = new List<TrackQueryStatus>();
				UiInvoke(() => MessageBox.Show(ex.Message));
			}

			End();
			SetRange(0, trackQueries.Count);
			StepTo(0);

			// Search for TrackQuery
			var i = 1;
			foreach (var trackQuery in trackQueries)
			{
				Increment(1);
				SetText(string.Format("{0} / {1}", i++, trackQueries.Count));

				if (_cancellationTokenSource.IsCancellationRequested)
					break;

				try
				{
					// add the row to the dataview
					var trackQueryStatus = trackQuery;
					UiInvoke(() => _trackQueryStatuses.Add(trackQueryStatus));

					// scroll the row if it's at the bottom
					UiInvoke(
						() =>
					    {
					        if (logDataGridView.RowCount > 2
					            && logDataGridView.Rows[logDataGridView.RowCount - 2].Displayed)
					        {
					            logDataGridView.FirstDisplayedScrollingRowIndex = logDataGridView.RowCount - 1;
					        }
					    });

					// run the track query
					var track = SearchTrack(trackQuery);

					if (_cancellationTokenSource.IsCancellationRequested)
						break;

					// no results, continue on to the next track
					if (track == null)
					{
						trackQuery.Status = "Track not found";
						continue;
					}

					// check that this is not a compilation track
					if (_options.LibraryUpdate.FindCompilationTrackOrigins)
					{
						track = _trackOriginalRelease.
							GetTrackOriginalRelease(track) ?? track;
					}

					UpdateTrack(trackQuery, track);
				}
				catch (Exception ex)
				{
					_logger.LogError(ex.ToString());
					trackQuery.Status = ex.Message;
				}
			}

			if (_cancellationTokenSource.IsCancellationRequested)
			{
				SetText("Cancel complete.");
				_cancellationTokenSource = new CancellationTokenSource();
			}

			UiInvoke(
			       	() =>
			       		{
			       			exitButton.Enabled = true;
			       			cancelButton.Enabled = false;
			       			cancelButton.Visible = false;
			       			startButton.Enabled = true;
			       			librarySelector.Enabled = true;
			       			optionsButton.Enabled = true;
			       		});

			if (_exitClicked)
			{
				SetText("Exiting...");
				UiInvoke(() => exitButton_Click(this, new EventArgs()));
			}
		}

		private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
		{
			var a = new About();
			a.ShowDialog(this);
		}

		#endregion

		#region IProgressCallback
		
		public void Begin()
		{
			UiInvoke(() => toolStripProgressBar.Value = toolStripProgressBar.Minimum);
		}

		public void SetRange(int minimum, int maximum)
		{
			UiInvoke(() => toolStripProgressBar.Minimum = minimum);
			UiInvoke(() => toolStripProgressBar.Maximum = maximum);
		}

		public void SetText(string text)
		{
			UiInvoke(() => toolStripStatusLabel.Text = text);
		}

		public void StepTo(int val)
		{
			UiInvoke(() => toolStripProgressBar.Value = val);
		}

		public void Increment(int val)
		{
			UiInvoke(() => toolStripProgressBar.Value += val);
		}

		public bool IsAborting
		{
			get { return _cancellationTokenSource.IsCancellationRequested; }
		}

		public void End()
		{
			StepTo(toolStripProgressBar.Maximum);
		}

		#endregion
	}
}