
using System;
using System.Drawing;
using System.IO;
using System.Threading.Tasks;
using AVFoundation;
using CoreGraphics;
using CoreMedia;
using Foundation;
using UIKit;
using Xamarin.Essentials;
using MobileCoreServices;

[assembly: Xamarin.Forms.Dependency(typeof(InABox.Mobile.iOS.ImageToolsiOS))]
namespace InABox.Mobile.iOS
{
    
    public class ImageToolsiOS : IImageTools
    {
        public byte[] CreateVideoThumbnail(byte[] video, int maxwidth, int maxheight)
        {
            byte[] result = null;
            var filename = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
                $"{Guid.NewGuid().ToString()}.tmp"
            );
            File.WriteAllBytes(filename,video);
            // perhaps? on IOS, try to use - NSUrl.FromFilename (path) instead of - new Foundation.NSUrl(url)
            var asset = AVAsset.FromUrl(NSUrl.FromFilename(filename));
            AVAssetImageGenerator imageGenerator = new AVAssetImageGenerator(asset);
            //AVAssetImageGenerator imageGenerator = new AVAssetImageGenerator(AVAsset.FromUrl((new Foundation.NSUrl(filename))));
            imageGenerator.AppliesPreferredTrackTransform = true;
            CMTime actualTime;
            NSError error;
            CGImage cgImage = imageGenerator.CopyCGImageAtTime(new CMTime(1, 1), out actualTime, out error);
            using (var ms = new MemoryStream())
            {
                var png = new UIImage(cgImage).AsPNG().AsStream();
                png.CopyTo(ms);
                result = ms.ToArray();
            }

            if (result != null)
                result = ScaleImage(result, new Size(maxwidth, maxheight), 60);
            
            File.Delete(filename);
            return result;
        }
        
        public byte[] CreateThumbnail(byte[] source, int maxwidth, int maxheight)
        {
            return ScaleImage(source, new Size(maxwidth, maxheight), 60);
        }
        
        public async Task<FileResult> PickPhotoAsync(int? compression, Size? constraints)
        {
            return await MainThread.InvokeOnMainThreadAsync(async () =>
                await InternalGetPhotoAsync<Permissions.Photos>(UIImagePickerControllerSourceType.PhotoLibrary, compression, constraints));
        }
        
         public async Task<FileResult> CapturePhotoAsync(int? compression, Size? constraints)
        {
            return await MainThread.InvokeOnMainThreadAsync(async () =>
                await InternalGetPhotoAsync<Permissions.Camera>(UIImagePickerControllerSourceType.Camera, compression, constraints));
        }

        private async Task<FileResult> InternalGetPhotoAsync<TPermission>(UIImagePickerControllerSourceType source, int? compression, Size? constraints)
            where TPermission : Permissions.BasePermission, new()
        {
            var taskCompletionSource = new TaskCompletionSource<FileResult>();
            if (await Permissions.RequestAsync<TPermission>() == PermissionStatus.Granted)
            {
                var imagePicker = new UIImagePickerController
                {
                    SourceType = source,
                    MediaTypes = new string[] { UTType.Image }
                };
                
                var viewController = Platform.GetCurrentUIViewController();

                imagePicker.AllowsEditing = false;
                imagePicker.FinishedPickingMedia += async (sender, e) =>
                {
                    var jpegFilename = Path.Combine(FileSystem.CacheDirectory, $"{Guid.NewGuid()}.jpg");
                    var source = e.Info[UIImagePickerController.OriginalImage] as UIImage;
                    var rotated = AutoRotateImage(source);
                    var scaled = ScaleImage(rotated, constraints);
                    var result = scaled.AsJPEG(new nfloat(compression ?? 100)/100);
                    await viewController.DismissViewControllerAsync(true);
                    if (result.Save(jpegFilename, false, out var error))
                    {
                        taskCompletionSource.TrySetResult(new FileResult(jpegFilename));
                    }
                    else
                    {
                        taskCompletionSource.TrySetException(new Exception($"Error saving the image: {error}"));
                    }
                    imagePicker?.Dispose();
                    imagePicker = null;
                };

                imagePicker.Canceled += async (sender, e) =>
                {
                    await viewController.DismissViewControllerAsync(true);
                    taskCompletionSource.TrySetResult(null);
                    imagePicker?.Dispose();
                    imagePicker = null;
                };

                await viewController.PresentViewControllerAsync(imagePicker, true);
            }
            else
            {
                taskCompletionSource.TrySetResult(null);
                taskCompletionSource.TrySetException(new PermissionException("Camera permission not granted"));
            }

            return await taskCompletionSource.Task;
        }
        
        private UIImage AutoRotateImage(UIImage source)
        {
            var rotation = source.Orientation switch
            {
                UIImageOrientation.Right => 90F,
                UIImageOrientation.Up => 0F,
                UIImageOrientation.Left => -90F,
                UIImageOrientation.Down => 180F,
                _ => 0F
            };

            return RotateImage(source, rotation);
        }

        private UIImage RotateImage(UIImage source, float rotation)
        {
            CGImage imgRef = source.CGImage;
            float width = imgRef.Width;
            float height = imgRef.Height;
            CGAffineTransform transform = CGAffineTransform.MakeIdentity();
            RectangleF bounds = new RectangleF(0, 0, width, height);

            float angle = Convert.ToSingle((rotation / 180f) * Math.PI);
            transform = CGAffineTransform.MakeRotation(angle);

            UIGraphics.BeginImageContext(bounds.Size);

            CGContext context = UIGraphics.GetCurrentContext();

            context.TranslateCTM(width / 2, height / 2);
            context.SaveState();
            context.ConcatCTM(transform);
            context.SaveState();
            context.ConcatCTM(CGAffineTransform.MakeScale(1.0f, -1.0f));

            context.DrawImage(new RectangleF(-width / 2, -height / 2, width, height), imgRef);
            context.RestoreState();

            UIImage result = UIGraphics.GetImageFromCurrentImageContext();
            UIGraphics.EndImageContext();
            return result;
        }

        private UIImage ScaleImage(UIImage sourceImage, Size? constraints)
        {
            var maxwidth = constraints?.Width ?? sourceImage.Size.Width;
            var maxheight = constraints?.Height ?? sourceImage.Size.Height;
            var wRatio = maxwidth < sourceImage.Size.Width
                ? maxwidth / (double)sourceImage.Size.Width
                : 1.0F;
            var hRatio = maxheight < sourceImage.Size.Height
                ? maxheight / (double)sourceImage.Size.Height
                : 1.0F;
            var ratio = Math.Min(hRatio, wRatio);
            if (ratio < 1.0F)
            {
                var width = ratio * sourceImage.Size.Width;
                var height = ratio * sourceImage.Size.Height;
                UIGraphics.BeginImageContext(new CGSize(width, height));
                sourceImage.Draw(new CGRect(0, 0, width, height));
                var resultImage = UIGraphics.GetImageFromCurrentImageContext();
                UIGraphics.EndImageContext();
                return resultImage;
            }
            return sourceImage;
        }

        public byte[] RotateImage(byte[] source, float angle, int quality = 100)
        {
            byte[] result = { };
            using (UIImage src = UIImage.LoadFromData(NSData.FromArray(source)))
            {
                if (src != null)
                {
                    var scaled = RotateImage(src, angle);
                    using (NSData imageData = scaled.AsJPEG(new nfloat((float)quality / 100F)))
                    {
                        result = new byte[imageData.Length];
                        System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, result, 0,
                            Convert.ToInt32(imageData.Length));
                    }
                }
            }
            return result;
        }

        public byte[] ScaleImage(byte[] source, Size? constraints, int quality = 100)
        {
            byte[] result = { };
            using (UIImage src = UIImage.LoadFromData(NSData.FromArray(source)))
            {
                if (src != null)
                {
                    var scaled = ScaleImage(src, constraints);
                    using (NSData imageData = scaled.AsJPEG(new nfloat((float)quality / 100F)))
                    {
                        result = new byte[imageData.Length];
                        System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, result, 0,
                            Convert.ToInt32(imageData.Length));
                    }
                }
            }
            return result;
        }
    }

    
}