using MetadataExtractor;
using MetadataExtractor.Formats.Exif;
using System.Globalization;

namespace View_by_Distance.Metadata.Models.Stateless;

internal abstract class Exif
{

    private static DateTime? GetDateTime(string? value)
    {
        DateTime? result;
        string dateTimeFormat = "yyyy:MM:dd HH:mm:ss";
        string alternateFormat = "ddd MMM dd HH:mm:ss yyyy";
        if (value is not null && DateTime.TryParse(value, out DateTime dateTime))
            result = dateTime;
        else if (value is not null && value.Length == dateTimeFormat.Length && DateTime.TryParseExact(value, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime))
            result = dateTime;
        else if (value is not null && value.Length == alternateFormat.Length && DateTime.TryParseExact(value, alternateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime))
            result = dateTime;
        else
            result = null;
        return result;
    }

    private static Shared.Models.AviDirectory GetAviDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
    {
        Shared.Models.AviDirectory result;
        MetadataExtractor.Formats.Avi.AviDirectory? aviDirectory = directories.OfType<MetadataExtractor.Formats.Avi.AviDirectory>().FirstOrDefault();
        if (aviDirectory is null)
            result = new(null, null, null, null);
        else
        {
            DateTime? dateTimeOriginal;
            string? duration = aviDirectory.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagDuration);
            string? height = aviDirectory.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagHeight);
            string? width = aviDirectory.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagWidth);
            if (aviDirectory.TryGetDateTime(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal, out DateTime checkDateTime))
                dateTimeOriginal = checkDateTime;
            else
                dateTimeOriginal = GetDateTime(aviDirectory.GetString(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal));
            result = new(dateTimeOriginal, duration, height, width);
        }
        return result;
    }

    private static Shared.Models.ExifDirectoryBase GetExifDirectoryBase(IReadOnlyList<MetadataExtractor.Directory> directories)
    {
        Shared.Models.ExifDirectoryBase result;
        ExifDirectoryBase? exifDirectoryBase = directories.OfType<ExifDirectoryBase>().FirstOrDefault();
        if (exifDirectoryBase is null)
            result = new(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
        else
        {
            DateTime? dateTime;
            DateTime checkDateTime;
            DateTime? dateTimeOriginal;
            DateTime? dateTimeDigitized;
            string? aperture = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagAperture);
            string? applicationNotes = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagApplicationNotes);
            string? artist = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagArtist);
            string? bitsPerSample = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagBitsPerSample);
            string? bodySerialNumber = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagBodySerialNumber);
            string? cameraOwnerName = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCameraOwnerName);
            string? compressedAverageBitsPerPixel = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCompressedAverageBitsPerPixel);
            string? compression = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCompression);
            string? copyright = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCopyright);
            string? documentName = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagDocumentName);
            string? exifVersion = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagExifVersion);
            string? exposureTime = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagExposureTime);
            string? fileSource = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagFileSource);
            string? imageDescription = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageDescription);
            string? imageHeight = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageHeight);
            string? imageNumber = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageNumber);
            string? imageUniqueId = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageUniqueId);
            string? imageWidth = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageWidth);
            string? isoSpeed = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagIsoSpeed);
            string? lensMake = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagLensMake);
            string? lensModel = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagLensModel);
            string? lensSerialNumber = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagLensSerialNumber);
            string? make = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagMake);
            string? makerNote = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagMakernote);
            string? model = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagModel);
            string? orientation = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagOrientation);
            int? orientationValue = orientation is null ? null : exifDirectoryBase.GetInt32(ExifDirectoryBase.TagOrientation);
            string? rating = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagRating);
            string? ratingPercent = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagRatingPercent);
            string? securityClassification = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagSecurityClassification);
            string? shutterSpeed = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagShutterSpeed);
            string? software = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagSoftware);
            string? timeZone = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagTimeZone);
            string? timeZoneDigitized = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagTimeZoneDigitized);
            string? timeZoneOriginal = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagTimeZoneOriginal);
            string? userComment = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagUserComment);
            string? winAuthor = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinAuthor);
            string? winComment = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinComment);
            string? winKeywords = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinKeywords);
            string? winSubject = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinSubject);
            string? winTitle = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinTitle);
            string? xResolution = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagXResolution);
            string? yResolution = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagYResolution);
            if (exifDirectoryBase.TryGetDateTime(ExifDirectoryBase.TagDateTime, out checkDateTime))
                dateTime = checkDateTime;
            else
                dateTime = GetDateTime(exifDirectoryBase.GetString(ExifDirectoryBase.TagDateTime));
            if (exifDirectoryBase.TryGetDateTime(ExifDirectoryBase.TagDateTimeOriginal, out checkDateTime))
                dateTimeOriginal = checkDateTime;
            else
                dateTimeOriginal = GetDateTime(exifDirectoryBase.GetString(ExifDirectoryBase.TagDateTimeOriginal));
            if (exifDirectoryBase.TryGetDateTime(ExifDirectoryBase.TagDateTimeDigitized, out checkDateTime))
                dateTimeDigitized = checkDateTime;
            else
                dateTimeDigitized = GetDateTime(exifDirectoryBase.GetString(ExifDirectoryBase.TagDateTimeDigitized));
            result = new(aperture,
                         applicationNotes,
                         artist,
                         bitsPerSample,
                         bodySerialNumber,
                         cameraOwnerName,
                         compressedAverageBitsPerPixel,
                         compression,
                         copyright,
                         dateTime,
                         dateTimeDigitized,
                         dateTimeOriginal,
                         documentName,
                         exifVersion,
                         exposureTime,
                         fileSource,
                         imageDescription,
                         imageHeight,
                         imageNumber,
                         imageUniqueId,
                         imageWidth,
                         isoSpeed,
                         lensMake,
                         lensModel,
                         lensSerialNumber,
                         make,
                         makerNote,
                         model,
                         orientation,
                         orientationValue,
                         rating,
                         ratingPercent,
                         securityClassification,
                         shutterSpeed,
                         software,
                         timeZone,
                         timeZoneDigitized,
                         timeZoneOriginal,
                         userComment,
                         winAuthor,
                         winComment,
                         winKeywords,
                         winSubject,
                         winTitle,
                         xResolution,
                         yResolution);
        }
        return result;
    }

    private static Shared.Models.FileMetadataDirectory GetFileMetadataDirectory(string file, IReadOnlyList<MetadataExtractor.Directory> directories)
    {
        Shared.Models.FileMetadataDirectory result;
        MetadataExtractor.Formats.FileSystem.FileMetadataDirectory? fileMetadataDirectory = directories.OfType<MetadataExtractor.Formats.FileSystem.FileMetadataDirectory>().FirstOrDefault();
        if (fileMetadataDirectory is null)
            result = new(null, null, null);
        else
        {
            DateTime? fileModifiedDate;
            string? fileName = fileMetadataDirectory.GetDescription(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileName);
            string? fileSize = fileMetadataDirectory.GetDescription(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileSize);
            if (fileMetadataDirectory.TryGetDateTime(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileModifiedDate, out DateTime checkDateTime))
                fileModifiedDate = checkDateTime;
            else
                fileModifiedDate = GetDateTime(fileMetadataDirectory.GetString(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileModifiedDate));
            if (fileName is null || !file.EndsWith(fileName))
                throw new NotSupportedException($"!{file}.EndsWith({fileName})");
            result = new(fileModifiedDate, fileName, fileSize);
        }
        return result;
    }

    private static Shared.Models.GifHeaderDirectory GetGifHeaderDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
    {
        Shared.Models.GifHeaderDirectory result;
        MetadataExtractor.Formats.Gif.GifHeaderDirectory? gifHeaderDirectory = directories.OfType<MetadataExtractor.Formats.Gif.GifHeaderDirectory>().FirstOrDefault();
        if (gifHeaderDirectory is null)
            result = new(null, null);
        else
        {
            string? imageHeight = gifHeaderDirectory.GetDescription(MetadataExtractor.Formats.Gif.GifHeaderDirectory.TagImageHeight);
            string? imageWidth = gifHeaderDirectory.GetDescription(MetadataExtractor.Formats.Gif.GifHeaderDirectory.TagImageWidth);
            result = new(imageHeight, imageWidth);
        }
        return result;
    }

    private static Shared.Models.PhotoshopDirectory GetPhotoshopDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
    {
        Shared.Models.PhotoshopDirectory result;
        MetadataExtractor.Formats.Photoshop.PhotoshopDirectory? PhotoshopDirectory = directories.OfType<MetadataExtractor.Formats.Photoshop.PhotoshopDirectory>().FirstOrDefault();
        if (PhotoshopDirectory is null)
            result = new(null, null);
        else
        {
            string? jpegQuality = PhotoshopDirectory.GetDescription(MetadataExtractor.Formats.Photoshop.PhotoshopDirectory.TagJpegQuality);
            string? url = PhotoshopDirectory.GetDescription(MetadataExtractor.Formats.Photoshop.PhotoshopDirectory.TagUrl);
            result = new(jpegQuality, url);
        }
        return result;
    }

    private static Shared.Models.GpsDirectory GetGpsDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
    {
        Shared.Models.GpsDirectory result;
        GpsDirectory? gpsDirectory = directories.OfType<GpsDirectory>().FirstOrDefault();
        if (gpsDirectory is null)
            result = new(null, null, null, null, null, null);
        else
        {
            DateTime? timeStamp;
            string? altitude = gpsDirectory.GetDescription(GpsDirectory.TagAltitude);
            string? latitude = gpsDirectory.GetDescription(GpsDirectory.TagLatitude);
            string? latitudeRef = gpsDirectory.GetDescription(GpsDirectory.TagLatitudeRef);
            string? longitude = gpsDirectory.GetDescription(GpsDirectory.TagLongitude);
            string? longitudeRef = gpsDirectory.GetDescription(GpsDirectory.TagLongitudeRef);
            if (gpsDirectory.TryGetDateTime(GpsDirectory.TagTimeStamp, out DateTime checkDateTime))
                timeStamp = checkDateTime;
            else
                timeStamp = GetDateTime(gpsDirectory.GetString(GpsDirectory.TagTimeStamp));
            result = new(altitude,
                         latitude,
                         latitudeRef,
                         longitude,
                         longitudeRef,
                         timeStamp);
        }
        return result;
    }

    private static Shared.Models.JpegDirectory GetJpegDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
    {
        Shared.Models.JpegDirectory result;
        MetadataExtractor.Formats.Jpeg.JpegDirectory? jpegDirectory = directories.OfType<MetadataExtractor.Formats.Jpeg.JpegDirectory>().FirstOrDefault();
        if (jpegDirectory is null)
            result = new(null, null);
        else
        {
            string? imageHeight = jpegDirectory.GetDescription(MetadataExtractor.Formats.Jpeg.JpegDirectory.TagImageHeight);
            string? imageWidth = jpegDirectory.GetDescription(MetadataExtractor.Formats.Jpeg.JpegDirectory.TagImageWidth);
            result = new(imageHeight, imageWidth);
        }
        return result;
    }

    private static Shared.Models.PngDirectory GetPngDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
    {
        Shared.Models.PngDirectory result;
        MetadataExtractor.Formats.Png.PngDirectory? pngDirectory = directories.OfType<MetadataExtractor.Formats.Png.PngDirectory>().FirstOrDefault();
        if (pngDirectory is null)
            result = new(null, null);
        else
        {
            string? imageHeight = pngDirectory.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagImageHeight);
            string? imageWidth = pngDirectory.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagImageWidth);
            result = new(imageHeight, imageWidth);
        }
        return result;
    }

    private static Shared.Models.QuickTimeMovieHeaderDirectory GetQuickTimeMovieHeaderDirectoryDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
    {
        Shared.Models.QuickTimeMovieHeaderDirectory result;
        MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory? aviDirectory = directories.OfType<MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory>().FirstOrDefault();
        if (aviDirectory is null)
            result = new(null);
        else
        {
            DateTime? created;
            if (aviDirectory.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated, out DateTime checkDateTime))
                created = checkDateTime;
            else
                created = GetDateTime(aviDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated));
            result = new(created);
        }
        return result;
    }

    private static Shared.Models.QuickTimeTrackHeaderDirectory GetQuickTimeTrackHeaderDirectoryDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
    {
        Shared.Models.QuickTimeTrackHeaderDirectory result;
        MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory? aviDirectory = directories.OfType<MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory>().FirstOrDefault();
        if (aviDirectory is null)
            result = new(null);
        else
        {
            DateTime? created;
            if (aviDirectory.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated, out DateTime checkDateTime))
                created = checkDateTime;
            else
                created = GetDateTime(aviDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated));
            result = new(created);
        }
        return result;
    }

    private static Shared.Models.WebPDirectory GetWebPDirectory(IReadOnlyList<MetadataExtractor.Directory> directories)
    {
        Shared.Models.WebPDirectory result;
        MetadataExtractor.Formats.WebP.WebPDirectory? WebPDirectory = directories.OfType<MetadataExtractor.Formats.WebP.WebPDirectory>().FirstOrDefault();
        if (WebPDirectory is null)
            result = new(null, null);
        else
        {
            string? imageHeight = WebPDirectory.GetDescription(MetadataExtractor.Formats.WebP.WebPDirectory.TagImageHeight);
            string? imageWidth = WebPDirectory.GetDescription(MetadataExtractor.Formats.WebP.WebPDirectory.TagImageWidth);
            result = new(imageHeight, imageWidth);
        }
        return result;
    }

    internal static Shared.Models.ExifDirectory Covert(Shared.Models.FilePath filePath, Shared.Models.DeterministicHashCode deterministicHashCode, System.Drawing.Size? size, IReadOnlyList<MetadataExtractor.Directory> directories)
    {
        Shared.Models.ExifDirectory results;
        Shared.Models.AviDirectory aviDirectory = GetAviDirectory(directories);
        Shared.Models.GpsDirectory gpsDirectory = GetGpsDirectory(directories);
        Shared.Models.PngDirectory pngDirectory = GetPngDirectory(directories);
        Shared.Models.JpegDirectory jpegDirectory = GetJpegDirectory(directories);
        Shared.Models.WebPDirectory webPDirectory = GetWebPDirectory(directories);
        Shared.Models.ExifDirectoryBase exifDirectoryBase = GetExifDirectoryBase(directories);
        Shared.Models.GifHeaderDirectory gifHeaderDirectory = GetGifHeaderDirectory(directories);
        Shared.Models.PhotoshopDirectory photoshopDirectory = GetPhotoshopDirectory(directories);
        Shared.Models.FileMetadataDirectory fileMetadataDirectory = GetFileMetadataDirectory(filePath.FullName, directories);
        Shared.Models.QuickTimeMovieHeaderDirectory quickTimeMovieHeaderDirectory = GetQuickTimeMovieHeaderDirectoryDirectory(directories);
        Shared.Models.QuickTimeTrackHeaderDirectory quickTimeTrackHeaderDirectory = GetQuickTimeTrackHeaderDirectoryDirectory(directories);
        results = new(aviDirectory,
                      exifDirectoryBase,
                      fileMetadataDirectory,
                      gifHeaderDirectory,
                      gpsDirectory,
                      size?.Height,
                      deterministicHashCode.Id ?? filePath.Id,
                      jpegDirectory,
                      filePath.Name,
                      photoshopDirectory,
                      pngDirectory,
                      quickTimeMovieHeaderDirectory,
                      quickTimeTrackHeaderDirectory,
                      webPDirectory,
                      size?.Width);
        return results;
    }

    internal static string GetMaker(Shared.Models.ExifDirectoryBase exifDirectoryBase)
    {
        string result;
        if (string.IsNullOrEmpty(exifDirectoryBase.Make?.ToString().Trim()))
            result = "Unknown";
        else
            result = $"{exifDirectoryBase.Make[0].ToString().ToUpper()}{exifDirectoryBase.Make[1..].ToLower()}".Trim();
        return result;
    }

}