using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using MetadataExtractor;
using MetadataExtractor.Formats.Exif;
using MetadataExtractor.Formats.QuickTime;
using Microsoft.Extensions.Configuration;
using Serilog;
using ShellProgressBar;
using Directory = System.IO.Directory;

namespace PhotoRenamer
{
    internal class Renamer
    {
        private readonly string _sourcePath;
    private readonly string _targetPath;
    private readonly ProgressBarOptions _childOptions;

    public Renamer(IConfiguration configuration)
        {
            _sourcePath = Path.GetFullPath(configuration["Source"]);
            _targetPath = Path.GetFullPath(configuration["Target"]);
            Log.Information($"Source path: {_sourcePath}");
            Log.Information($"Target path: {_targetPath}");
            _childOptions = new ProgressBarOptions
            {
                ForegroundColor = ConsoleColor.Yellow,
                BackgroundColor = ConsoleColor.DarkGreen,
                ProgressCharacter = '─',
                CollapseWhenFinished = true
            };
        }

        public int Run()
        {
            var files = Directory.GetFiles(_sourcePath, "*", SearchOption.AllDirectories);
            var options = new ProgressBarOptions
            {
                ForegroundColor = ConsoleColor.Yellow,
                ForegroundColorDone = ConsoleColor.DarkGreen,
                BackgroundColor = ConsoleColor.DarkGray,
            BackgroundCharacter = '\u2593'
        };
            var i = 0;
            using var progressBar = new ProgressBar(files.Length, "Copying files", options);
            var po = new ParallelOptions { MaxDegreeOfParallelism = 4};
            Parallel.ForEach(files, po, file =>
            {
                try
                {
                    var directories = ImageMetadataReader.ReadMetadata(file);
                    var dateTime = GetDateTimeFromExif(directories) ?? GetDateTimeFromMp4(directories);
                    if (dateTime is null) return;
                    var folder = CreateFolder(dateTime.GetValueOrDefault());
                    CopyFile(folder, file, progressBar);
                }
                catch (ImageProcessingException)
                {
                    //silently ignore
                }
                finally
                {
                    System.Threading.Interlocked.Increment(ref i);
                    progressBar.Tick(i);
                }
            });

            return 0;
        }

        private void CopyFile(string folder, string file, ProgressBarBase progressBar)
        {
            var destination = new FileInfo(Path.Combine(folder, Path.GetFileName(file)));
            var source = new FileInfo(file);
            if (destination.Exists && destination.Length == source.Length) return;

            using var child = progressBar.Spawn(100, destination.FullName, _childOptions);
            using var client = new WebClient();

            void OnClientOnDownloadProgressChanged(object o, DownloadProgressChangedEventArgs args) => child.Tick(args.ProgressPercentage);

            client.DownloadProgressChanged += OnClientOnDownloadProgressChanged;
            client.DownloadFileAsync(new Uri(source.FullName), destination.FullName);
            while (client.IsBusy)
            {
                Thread.Sleep(100);
            }
            client.DownloadProgressChanged -= OnClientOnDownloadProgressChanged;
            child.Tick(100);
        }

        private string CreateFolder(DateTime dateTime)
        {
            var folder = Path.Combine(_targetPath, dateTime.Year.ToString(), dateTime.Month.ToString("D2"));
            if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
            return folder;
        }

        private static DateTime? GetDateTimeFromExif(IEnumerable<MetadataExtractor.Directory> directories)
        {
            DateTime dateTime = default;
            return directories
                .OfType<ExifIfd0Directory>()
                .FirstOrDefault()?
                .TryGetDateTime(ExifDirectoryBase.TagDateTime, out dateTime) == true
                ? (DateTime?) dateTime
                : null;
        }

        private static DateTime? GetDateTimeFromMp4(IEnumerable<MetadataExtractor.Directory> directories)
        {
            DateTime dateTime = default;
            return directories
                .OfType<QuickTimeMovieHeaderDirectory>()
                .FirstOrDefault()?
                .TryGetDateTime(QuickTimeMovieHeaderDirectory.TagCreated, out dateTime) == true
                ? (DateTime?) dateTime
                : null;
        }
    }
}