using C1.Xaml;
using C1.Xaml.Imaging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Storage.Pickers;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace ControlExplorer.Controls.Imaging
{
    public partial class ImageManipulation
        : UserControl, IExposeApplicationBarItems, IFileOpenPickerContinuable
    {
        class ImageManipulationState
        {
            public Stream Stream { get; set; }
            public ImageManipulationStep Step { get; set; }
        }

        enum ImageManipulationStep
        {
            Start,
            PickingPhoto,
            PickedPhoto,
            Ready
        }
        enum ImageManipulationGestureType
        {
            Select,
            Warp
        }

        const int imageSize = 400 * 400;

        C1Bitmap originalBitmap = new C1Bitmap();
        C1Bitmap bitmap = new C1Bitmap();
        C1Bitmap screen = new C1Bitmap();
        Rect selection;

        ImageManipulationState currentState = new ImageManipulationState()
        {
            Step = ImageManipulationStep.Start,
            Stream = null
        };
        ImageManipulationGestureType manipulationType = ImageManipulationGestureType.Select;

        AppBarButton btnWarp = new AppBarButton();
        AppBarButton btnSelect = new AppBarButton();
        AppBarButton btnCrop = new AppBarButton();
        AppBarButton btnNewPhoto = new AppBarButton();

        Point pointOrigin = new Point();
        Point pointEnd = new Point();


        public ImageManipulation()
        {
            InitializeComponent();

            // warp
            btnWarp.Label = "warp";
            btnWarp.IsEnabled = false;
            btnWarp.Icon = new BitmapIcon { UriSource = new Uri("ms-appx:///Resources/AppBar/imaging_warp.png") };
            btnWarp.Click += (s, e) => manipulationType = ImageManipulationGestureType.Warp;

            // select
            btnSelect.Label = "select";
            btnSelect.IsEnabled = false;
            btnSelect.Icon = new BitmapIcon { UriSource = new Uri("ms-appx:///Resources/AppBar/imaging_select.png") };
            btnSelect.Click += (s, e) => manipulationType = ImageManipulationGestureType.Select;

            // crop
            btnCrop.Label = "crop";
            btnCrop.IsEnabled = false;
            btnCrop.Icon = new BitmapIcon { UriSource = new Uri("ms-appx:///Resources/AppBar/imaging_crop.png") };
            btnCrop.Click += (s, e) =>
            {
                Crop();
                btnCrop.IsEnabled = false;
            };

            // new photo
            btnNewPhoto.Label = "new photo";
            btnNewPhoto.IsEnabled = false;
            btnNewPhoto.Click += (s, e) => GoToState(ImageManipulationStep.PickingPhoto);


            // gesture handling
            var dragListener = new C1DragHelper(image);
            image.Tapped += (s, e) =>
            {
                if (manipulationType == ImageManipulationGestureType.Select)
                {
                    selection = new Rect(0, 0, bitmap.Width, bitmap.Height);
                    UpdateMask();
                    btnCrop.IsEnabled = false;
                }
            };
            dragListener.DragStarted += (s, e) =>
            {
                if (manipulationType == ImageManipulationGestureType.Warp)
                {
                    pointOrigin = e.GetPosition(image);
                }
                else if (manipulationType == ImageManipulationGestureType.Select)
                {
                    pointOrigin = e.GetPosition(image);
                    pointEnd = e.GetPosition(image);
                    UpdateSelection();
                }
            };
            dragListener.DragDelta += (s, e) =>
            {
                if (manipulationType == ImageManipulationGestureType.Select)
                {
                    pointEnd = e.GetPosition(image);
                    UpdateSelection();
                }
            };
            dragListener.DragCompleted += (s, e) =>
            {
                if (manipulationType == ImageManipulationGestureType.Warp)
                {
                    var pointEnd = e.GetPosition(image);

                    bitmap = new C1Bitmap(screen);
                    Warp(bitmap, screen, pointOrigin, pointEnd);
                }
                else if (manipulationType == ImageManipulationGestureType.Select)
                {
                    btnCrop.IsEnabled = true;
                }
            };

            // try to retrieve the state from the ViewInstance
            // useful when we went to dormant state
            if (ViewModel.Instance.SampleState is ImageManipulationState)
            {
                var tmp = (ImageManipulationState)ViewModel.Instance.SampleState;
                if (tmp.Step == ImageManipulationStep.PickedPhoto)
                {
                    GoToState(ImageManipulationStep.Ready, tmp.Stream);
                }
            }
        }

        private void GoToState(ImageManipulationStep newStep)
        {
            GoToState(newStep, null);
        }

        private void GoToState(ImageManipulationStep newStep, Stream stream)
        {
            currentState.Step = newStep;
            currentState.Stream = stream;
            switch (newStep)
            {
                case ImageManipulationStep.Start:
                    image.Source = null;

                    btnPickPicture.Visibility = Visibility.Visible;
                    image.Visibility = Visibility.Collapsed;

                    btnWarp.IsEnabled = false;
                    btnSelect.IsEnabled = false;
                    btnCrop.IsEnabled = false;
                    btnNewPhoto.IsEnabled = false;
                    break;

                case ImageManipulationStep.PickingPhoto:
                    image.Source = null;
                    btnPickPicture_Click(null, null);

                    btnWarp.IsEnabled = false;
                    btnSelect.IsEnabled = false;
                    btnCrop.IsEnabled = false;
                    btnNewPhoto.IsEnabled = false;
                    break;

                case ImageManipulationStep.PickedPhoto:
                    // save current state into the ViewModel
                    ViewModel.Instance.SampleState = currentState;
                    break;

                case ImageManipulationStep.Ready:
                    // retrieve current state from the ViewModel
                    try
                    {
                        LoadImageStream(currentState.Stream);

                        btnPickPicture.Visibility = Visibility.Collapsed;
                        image.Visibility = Visibility.Visible;

                        btnWarp.IsEnabled = true;
                        btnSelect.IsEnabled = true;
                        btnCrop.IsEnabled = false;
                        btnNewPhoto.IsEnabled = true;
                    }
                    catch (BadImageFormatException exc)
                    {
                        var dialog = new MessageDialog(exc.Message);
                        dialog.ShowAsync();
                        GoToState(ImageManipulationStep.Start);
                    }
                    break;

                default:
                    break;
            }
        }

        #region Warp

        void Warp(C1Bitmap src, C1Bitmap dst, Point start, Point end)
        {
            dst.BeginUpdate();
            dst.Copy(src, false);

            var dist = Distance(start, end);
            var affectedDist = dist * 1.5;
            var affectedDistSquared = affectedDist * affectedDist;
            for (int row = 0; row < dst.Height; ++row)
            {
                for (int col = 0; col < dst.Width; ++col)
                {
                    var point = new Point(col, row);
                    if (DistanceSq(start, point) > affectedDistSquared)
                    {
                        continue;
                    }
                    if (DistanceSq(end, point) < 0.25)
                    {
                        dst.SetPixel(col, row, src.GetPixel((int)start.X, (int)start.Y));
                        continue;
                    }
                    var dir = new Point(point.X - end.X, point.Y - end.Y);
                    var t = IntersectRayCircle(end, dir, start, affectedDist);
                    TryT(-end.X / dir.X, ref t);
                    TryT(-end.Y / dir.Y, ref t);
                    TryT((dst.Width - end.X) / dir.X, ref t);
                    TryT((dst.Height - end.X) / dir.X, ref t);
                    var anchor = new Point(end.X + (point.X - end.X) * t, end.Y + (point.Y - end.Y));
                    var x = start.X + (anchor.X - start.X) / t;
                    var y = start.Y + (anchor.Y - start.Y) / t;
                    dst.SetPixel(col, row, src.GetInterpolatedPixel(x, y));
                }
            }
            dst.EndUpdate();
        }

        static double Distance(Point a, Point b)
        {
            return Math.Sqrt(DistanceSq(a, b));
        }

        static double DistanceSq(Point a, Point b)
        {
            var dx = a.X - b.X;
            var dy = a.Y - b.Y;
            return dx * dx + dy * dy;
        }

        static void TryT(double t2, ref double t)
        {
            if (t2 > 0 && t2 < t)
            {
                t = t2;
            }
        }

        static double IntersectRayCircle(Point rayOri, Point rayDir, Point center, double radius)
        {
            var a = rayDir.X;
            var b = rayOri.X;
            var c = center.X;
            var d = rayDir.Y;
            var e = rayOri.Y;
            var f = center.Y;
            var g = radius * radius;

            var num1 = Math.Sqrt(d * (2 * a * (b - c) * (e - f) - d * (b * b - 2 * b * c + c * c - g)) - a * a * (e * e - 2 * e * f + f * f - g));
            var num2 = a * (c - b) + d * (f - e);
            return (num1 + num2 > 0 ? num1 + num2 : num1 - num2) / (a * a + d * d);
        }

        #endregion

        #region Crop

        void UpdateSelection()
        {
            var start = pointOrigin;
            var end = pointEnd;

            start.X = Math.Min(Math.Max(start.X, 0), bitmap.Width);
            end.X = Math.Min(Math.Max(end.X, 0), bitmap.Width);
            start.Y = Math.Min(Math.Max(start.Y, 0), bitmap.Height);
            end.Y = Math.Min(Math.Max(end.Y, 0), bitmap.Height);

            selection = new Rect(new Point(
                Math.Round(Math.Min(start.X, end.X)),
                Math.Round(Math.Min(start.Y, end.Y))),
                new Size(Math.Round(Math.Abs(start.X - end.X)), Math.Round(Math.Abs(start.Y - end.Y))));

            UpdateMask();
        }

        void UpdateMask()
        {
            topMask.Height = selection.Top;
            bottomMask.Height = bitmap.Height - selection.Bottom;
            leftMask.Width = selection.Left;
            rightMask.Width = bitmap.Width - selection.Right;
        }

        void Crop()
        {
            var crop = new C1Bitmap((int)selection.Width, (int)selection.Height);
            crop.BeginUpdate();
            for (int x = 0; x < selection.Width; ++x)
            {
                for (int y = 0; y < selection.Height; ++y)
                {
                    crop.SetPixel(x, y, bitmap.GetPixel(x + (int)selection.X, y + (int)selection.Y));
                }
            }
            crop.EndUpdate();

            LoadImageStream(crop.GetStream(ImageFormat.Png, true));
        }

        #endregion

        #region load image

        private void btnPickPicture_Click(object sender, RoutedEventArgs e)
        {
            var filePicker = new FileOpenPicker();
            filePicker.FileTypeFilter.Add(".jpeg");
            filePicker.FileTypeFilter.Add(".jpg");
            filePicker.PickSingleFileAndContinue();
        }

        public async void ContinueFileOpenPicker(FileOpenPickerContinuationEventArgs args)
        {
            try
            {
                var file = args.Files.FirstOrDefault();
                GoToState(ImageManipulationStep.Ready, await file.OpenStreamForReadAsync());
            }
            catch
            {
                // error picking photo
                var dialog = new MessageDialog("Error picking photo.\n\nPlease disconnect the device from the computer, if connected.");
                dialog.ShowAsync();
                GoToState(ImageManipulationStep.Start);
            }
        }

        void LoadImageStream(Stream stream)
        {
            bitmap.SetStream(stream);
            if (bitmap.Width * bitmap.Height > imageSize)
            {
                var scale = Math.Sqrt(imageSize * 1.0 / (bitmap.Width * bitmap.Height));
                var resizedBitmap = new C1Bitmap((int)(bitmap.Width * scale), (int)(bitmap.Height * scale));
                resizedBitmap.Copy(bitmap, true);
                bitmap = resizedBitmap;
            }
            originalBitmap.Copy(bitmap, false);
            screen.Copy(bitmap, false);

            image.Source = screen.ImageSource;
            image.Width = screen.Width;
            image.Height = screen.Height;

            // clear selection
            selection = new Rect(0, 0, bitmap.Width, bitmap.Height);
            UpdateMask();
        }

        #endregion

        public IEnumerable<ICommandBarElement> ApplicationBarItems
        {
            get
            {
                yield return btnWarp;
                yield return btnSelect;
                yield return btnCrop;

                yield return btnNewPhoto;
            }
        }
    }
}
