using System;
using System.Linq;
using System.Threading;
using LeafWeb.Core.DAL;
using LeafWeb.Core.Entities;
using LeafWeb.Core.Remote;
using NLog;

namespace LeafWeb.Web.Services
{
	public class PiscalQueueManager : IDisposable
	{
		private readonly DataService _dataService;
		private readonly PiscalService _piscalService;

		public PiscalQueueManager(DataService dataService, PiscalService piscalService)
		{
			_dataService = dataService;
			_piscalService = piscalService;
		}

		public PiscalQueueManager() : this(new DataService(), new PiscalService()) {}

		private static readonly object ProcessQueueLock = new object();

		public void ProcessQueue()
		{
			var logger = LogManager.GetCurrentClassLogger();

			if (Monitor.TryEnter(ProcessQueueLock))
			{
				logger.Trace("ProcessQueue entered");

				ProcessRunning(logger);

				ProcessQueue(logger);

				logger.Trace("ProcessQueue completed");

				Monitor.Exit(ProcessQueueLock);
			}
			else
			{
				logger.Trace("ProcessQueue locked, queue already processing");
			}
		}

		private void ProcessQueue(ILogger logger)
		{
			var runningLeafInputFiles = _dataService.GetLeafInputFiles(LeafInputStatusType.Running).ToList();
			if (runningLeafInputFiles.Any())
				return;

			var queuedFile =
				_dataService
					.GetLeafInputFiles(LeafInputStatusType.Queued)
					.OrderBy(l => l.Id)
					.FirstOrDefault();

			if (queuedFile == null)
				return;

			logger.Info("LeafInputFile: {0}, Start", queuedFile.Id);
			_piscalService.Run(queuedFile);
			_dataService.SetLeafInputFileStatus(queuedFile, LeafInputStatusType.Running);
		}

		private void ProcessRunning(ILogger logger)
		{
			var runningLeafInputFiles = _dataService.GetLeafInputFiles(LeafInputStatusType.Running).ToList();
			foreach (var file in runningLeafInputFiles)
			{
				var status = _piscalService.GetStatus(file);
				switch (status)
				{
					case PiscalStatus.Running:
						logger.Trace("LeafInputFile: {0}, {1}", file.Id, status);
						// continue running
						break;
					case PiscalStatus.Success:
						logger.Info("LeafInputFile: {0}, {1}", file.Id, status);
						// collect the leaf output
						var leafOutputFiles = _piscalService.RetrieveOutputFiles(file).ToList();
						foreach (var outputFile in leafOutputFiles)
							_dataService.AddLeafOutputFile(outputFile);

						logger.Info("LeafInputFile: {0}, output files: {1}", file.Id,
							string.Join(", ", leafOutputFiles.Select(o => o.Filename)));

						// update db
						_dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Complete);

						// remove working data from the server
						logger.Info("LeafInputFile: {0}, cleanup", file.Id);
						_piscalService.Cleanup(file);
						break;
					case PiscalStatus.Error:
						logger.Info("LeafInputFile: {0}, error", file.Id);
						_dataService.SetLeafInputFileStatus(file, LeafInputStatusType.Error);
						break;
				}
			}
		}

		public void Dispose()
		{
			_dataService.Dispose();
		}
	}
}