using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Mvc;
using NzbDrone.Core.Model;
using NzbDrone.Core.Providers;
using NzbDrone.Core.Repository;
using NzbDrone.Web.Models;
using Telerik.Web.Mvc;
using TvdbLib.Data;
using EpisodeModel = NzbDrone.Web.Models.EpisodeModel;

namespace NzbDrone.Web.Controllers
{
    [HandleError]
    public class SeriesController : Controller
    {
        private readonly ISeriesProvider _seriesProvider;
        private readonly IEpisodeProvider _episodeProvider;
        private readonly ISyncProvider _syncProvider;
        private readonly IRssSyncProvider _rssSyncProvider;
        private readonly IQualityProvider _qualityProvider;
        private readonly IMediaFileProvider _mediaFileProvider;
        private readonly IRenameProvider _renameProvider;
        private readonly IRootDirProvider _rootDirProvider;
        private readonly ITvDbProvider _tvDbProvider;
        private readonly IDiskProvider _diskProvider;
        private readonly IConfigProvider _configProvider;

        //
        // GET: /Series/

        public SeriesController(ISyncProvider syncProvider, ISeriesProvider seriesProvider,
            IEpisodeProvider episodeProvider, IRssSyncProvider rssSyncProvider,
            IQualityProvider qualityProvider, IMediaFileProvider mediaFileProvider,
            IRenameProvider renameProvider, IRootDirProvider rootDirProvider,
            ITvDbProvider tvDbProvider, IDiskProvider diskProvider,
            IConfigProvider configProvider)
        {
            _seriesProvider = seriesProvider;
            _episodeProvider = episodeProvider;
            _syncProvider = syncProvider;
            _rssSyncProvider = rssSyncProvider;
            _qualityProvider = qualityProvider;
            _mediaFileProvider = mediaFileProvider;
            _renameProvider = renameProvider;
            _rootDirProvider = rootDirProvider;
            _tvDbProvider = tvDbProvider;
            _diskProvider = diskProvider;
            _configProvider = configProvider;
        }

        public ActionResult Index()
        {
            ViewData.Model = _seriesProvider.GetAllSeries().ToList();
            return View();
        }

        public ActionResult AddExisting()
        {
            var defaultQuality = Convert.ToInt32(_configProvider.GetValue("DefaultQualityProfile", "1", true));
            var profiles = _qualityProvider.GetAllProfiles();
            var selectList = new SelectList(profiles, "QualityProfileId", "Name");

            ViewData["QualityProfileId"] = defaultQuality;
            ViewData["QualitySelectList"] = selectList;

            return View();
        }

        public ActionResult AddNew()
        {
            ViewData["RootDirs"] = _rootDirProvider.GetAll();
            ViewData["DirSep"] = Path.DirectorySeparatorChar;

            var profiles = _qualityProvider.GetAllProfiles();
            var selectList = new SelectList(profiles, "QualityProfileId", "Name");
            var defaultQuality = Convert.ToInt32(_configProvider.GetValue("DefaultQualityProfile", "1", true));

            var model = new AddNewSeriesModel
                            {
                                DirectorySeparatorChar = Path.DirectorySeparatorChar.ToString(),
                                RootDirectories = _rootDirProvider.GetAll(),
                                QualityProfileId = defaultQuality,
                                QualitySelectList = selectList
                            };

            return View(model);
        }

        public ActionResult AddExistingManual(string path)
        {
            var profiles = _qualityProvider.GetAllProfiles();
            var selectList = new SelectList(profiles, "QualityProfileId", "Name");
            var defaultQuality = Convert.ToInt32(_configProvider.GetValue("DefaultQualityProfile", "1", true));

            var model = new AddExistingManualModel();
            model.Path = path;
            model.FolderName = _diskProvider.GetFolderName(path);
            model.QualityProfileId = defaultQuality;
            model.QualitySelectList = selectList;

            return View(model);
        }

        public ActionResult RssSync()
        {
            _rssSyncProvider.Begin();
            return RedirectToAction("Index");
        }

        public ActionResult UnMapped(string path)
        {
            return View(_syncProvider.GetUnmappedFolders(path).Select(c => new MappingModel() { Id = 1, Path = c }).ToList());
        }

        public ActionResult LoadEpisodes(int seriesId)
        {
            _episodeProvider.RefreshEpisodeInfo(seriesId);
            return RedirectToAction("Details", new
            {
                seriesId = seriesId
            });
        }

        [GridAction]
        public ActionResult _AjaxSeasonGrid(int seasonId)
        {
            var episodes = _episodeProvider.GetEpisodeBySeason(seasonId).Select(c => new EpisodeModel()
                                                                                     {
                                                                                         EpisodeId = c.EpisodeId,
                                                                                         EpisodeNumber = c.EpisodeNumber,
                                                                                         SeasonNumber = c.SeasonNumber,
                                                                                         Title = c.Title,
                                                                                         Overview = c.Overview,
                                                                                         AirDate = c.AirDate,
                                                                                         Path = GetEpisodePath(c.EpisodeFile)
                                                                                     });
            return View(new GridModel(episodes));
        }

        [GridAction]
        public ActionResult _CustomBinding(GridCommand command, int seasonId)
        {
            IEnumerable<Episode> data = GetData(command);
            return View(new GridModel
            {
                Data = data,
                Total = data.Count()
            });
        }

        [GridAction]
        public ActionResult _AjaxUnmappedFoldersGrid()
        {
            var unmappedList = new List<AddExistingSeriesModel>();

            foreach (var folder in _rootDirProvider.GetAll())
            {
                foreach (var unmappedFolder in _syncProvider.GetUnmappedFolders(folder.Path))
                {
                    var tvDbSeries = _seriesProvider.MapPathToSeries(unmappedFolder);

                    //We still want to show this series as unmapped, but we don't know what it will be when mapped
                    //Todo: Provide the user with a way to manually map a folder to a TvDb series (or make them rename the folder...)
                    if (tvDbSeries == null)
                        tvDbSeries = new TvdbSeries {Id = 0, SeriesName = String.Empty};
                    
                    unmappedList.Add(new AddExistingSeriesModel
                                            {
                                                IsWanted = true,
                                                Path = unmappedFolder,
                                                PathEncoded = Url.Encode(unmappedFolder),
                                                TvDbId = tvDbSeries.Id,
                                                TvDbName = tvDbSeries.SeriesName
                                            });
                }
            }

            return View(new GridModel(unmappedList));
        }

        public ActionResult SyncSelectedSeries(List<String> checkedRecords)
        {
            var unmappedList = new List<SeriesMappingModel>();

            foreach (var checkedRecord in checkedRecords)
            {
                NameValueCollection nvc = HttpUtility.ParseQueryString(checkedRecord);

                var path = HttpUtility.UrlDecode(nvc["path"]);
                var tvDbId = Convert.ToInt32(HttpUtility.UrlDecode(nvc["tvdbid"]));
                var qualityProfileId = Convert.ToInt32(HttpUtility.UrlDecode(nvc["qualityProfileId"]));

                //If the TvDbId for this show is 0 then skip it... User made a mistake... They will have to manually map it
                if (tvDbId < 1) continue;

                unmappedList.Add(new SeriesMappingModel{Path = path, TvDbId = tvDbId, QualityProfileId = qualityProfileId});
            }

            if(_syncProvider.BeginSyncUnmappedFolders(unmappedList))
                return Content("Sync Started for Selected Series");

            return Content("Sync already in progress, please wait for it to complete before retrying.");
        }

        public ActionResult AddNewSeries(string dir, int seriesId, string seriesName, int qualityProfileId)
        {
            //Get TVDB Series Name
            //Create new folder for series
            //Add the new series to the Database

            if (_syncProvider.BeginAddNewSeries(dir, seriesId, seriesName, qualityProfileId))
                return Content("Adding new series has started.");
            
            return Content("Unable to add new series, please wait for previous scans to complete first.");
        }

        public ActionResult AddExistingSeries(string path, int seriesId, int qualityProfileId)
        {
            //Get TVDB Series Name
            //Create new folder for series
            //Add the new series to the Database

            if (_syncProvider.BeginAddExistingSeries(path, seriesId, qualityProfileId))
                return Content("Manual adding of existing series has started");

            return Content("Unable to add existing series, please wait for previous scans to complete first.");
        }

        public ActionResult SearchForSeries(string seriesName)
        {
            var model = new List<SeriesSearchResultModel>();

            //Get Results from TvDb and convert them to something we can use.
            foreach (var tvdbSearchResult in _tvDbProvider.SearchSeries(seriesName))
            {
                model.Add(new SeriesSearchResultModel
                              {
                                  TvDbId = tvdbSearchResult.Id,
                                  TvDbName = tvdbSearchResult.SeriesName,
                                  FirstAired = tvdbSearchResult.FirstAired
                              });
            }

            //model.Add(new SeriesSearchResultModel{ TvDbId = 12345, TvDbName = "30 Rock", FirstAired = DateTime.Today });
            //model.Add(new SeriesSearchResultModel { TvDbId = 65432, TvDbName = "The Office (US)", FirstAired = DateTime.Today.AddDays(-100) });

            return PartialView("SeriesSearchResults", model);
        }

        private IEnumerable<Episode> GetData(GridCommand command)
        {

            return null;
            /*    
            IQueryable<Episode> data = .Orders;
            //Apply filtering
            if (command.FilterDescriptors.Any())
            {
                data = data.Where(ExpressionBuilder.Expression<Order>(command.FilterDescriptors));
            }
            // Apply sorting
            foreach (SortDescriptor sortDescriptor in command.SortDescriptors)
            {
                if (sortDescriptor.SortDirection == ListSortDirection.Ascending)
                {
                    switch (sortDescriptor.Member)
                    {
                        case "OrderID":
                            data = data.OrderBy(ExpressionBuilder.Expression<Order, int>(sortDescriptor.Member));
                            break;
                        case "Customer.ContactName":
                            data = data.OrderBy(order => order.Customer.ContactName);
                            break;
                        case "ShipAddress":
                            data = data.OrderBy(order => order.ShipAddress);
                            break;
                        case "OrderDate":
                            data = data.OrderBy(order => order.OrderDate);
                            break;
                    }
                }
                else
                {
                    switch (sortDescriptor.Member)
                    {
                        case "OrderID":
                            data = data.OrderByDescending(order => order.OrderID);
                            break;
                        case "Customer.ContactName":
                            data = data.OrderByDescending(order => order.Customer.ContactName);
                            break;
                        case "ShipAddress":
                            data = data.OrderByDescending(order => order.ShipAddress);
                            break;
                        case "OrderDate":
                            data = data.OrderByDescending(order => order.OrderDate);
                            break;
                    }
                }
            }
            count = data.Count();
            // ... and paging
            if (command.PageSize > 0)
            {
                data = data.Skip((command.Page - 1) * command.PageSize);
            }
            data = data.Take(command.PageSize);
            return data;*/
        }

        [AcceptVerbs(HttpVerbs.Post)]
        [GridAction]
        public ActionResult _SaveAjaxEditing(string id)
        {
            return RedirectToAction("UnMapped");
        }

        public ActionResult Details(int seriesId)
        {
            var series = _seriesProvider.GetSeries(seriesId);
            return View(series);
        }

        public ActionResult Edit(int seriesId)
        {
            var profiles = _qualityProvider.GetAllProfiles();
            ViewData["SelectList"] = new SelectList(profiles, "QualityProfileId", "Name");

            var series = _seriesProvider.GetSeries(seriesId);
            return View(series);
        }

        [HttpPost]
        public ActionResult Edit(Series series)
        {
            //Need to add seriesProvider.Update
            _seriesProvider.UpdateSeries(series);
            return Content("Series Updated Successfully");
        }

        public ActionResult Delete(int seriesId)
        {
            //Need to add seriesProvider.Update
            _seriesProvider.DeleteSeries(seriesId);

            return RedirectToAction("Index", "Series");
        }

        public ActionResult SyncEpisodesOnDisk(int seriesId)
        {
            //Syncs the episodes on disk for the specified series
            var series = _seriesProvider.GetSeries(seriesId);
            _mediaFileProvider.Scan(series);

            return RedirectToAction("Details", new { seriesId });
        }

        public ActionResult RenameAll()
        {
            _renameProvider.RenameAll();
            return RedirectToAction("Index");
        }

        public ActionResult RenameSeries(int seriesId)
        {
            _renameProvider.RenameSeries(seriesId);
            return RedirectToAction("Details", new { seriesId });
        }

        public ActionResult RenameSeason(int seasonId)
        {
            //Todo: Stay of Series Detail... AJAX?
            _renameProvider.RenameSeason(seasonId);
            return RedirectToAction("Index");
        }

        public ActionResult RenameEpisode(int episodeId)
        {
            //Todo: Stay of Series Detail... AJAX?
            _renameProvider.RenameEpisode(episodeId);
            return RedirectToAction("Index");
        }

        public ActionResult ReScanFiles(int seriesId)
        {
            var epFiles = _mediaFileProvider.GetEpisodeFiles().Where(s => s.SeriesId == seriesId).ToList();
            _mediaFileProvider.CleanUp(epFiles);
            _mediaFileProvider.Scan(_seriesProvider.GetSeries(seriesId));

            return RedirectToAction("Details", "Series", new { seriesId });
        }

        //Local Helpers
        private string GetEpisodePath(EpisodeFile file)
        {
            if (file == null)
                return String.Empty;

            //Return the path relative to the Series' Folder
            return file.Path.Replace(file.Series.Path, "").Trim(Path.DirectorySeparatorChar);
        }
    }
}