using System;
using Xamarin.Forms;

/// <summary>
/// The namespace for the Gallery application.
/// </summary>
namespace Gallery
{
    /// <summary>
    /// The class which creates, maintains and handles the image viewer.
    /// ImageViewer inherits from ContentPage.
    /// </summary>
    class ImageViewer : ContentPage
    {
        private ScrollView scrollview;
        private StackLayout stack;
        // Initially the slideshow will not be playing
        private bool _playing = false;
        // Initially the slideshow will move forward one picture at a time
        private double _slideDirection = 1.0;
        private bool _toRight;
        private double _x;

        /// <summary>
        /// This method constructs an image viewer and opens the image viewer on
        /// the correct image.
        /// </summary>
        /// <param name="numberOfImages">The number of images in the image viewer.</param>
        /// <param name="imageNumber">The image to open the viewer on.</param>
        public ImageViewer(int numberOfImages, int imageNumber)
        {
            // Remove the back button
            NavigationPage.SetHasBackButton(this, false);
            Title = "Image Viewer";

            // Create a stack layout to add the images into
            stack = new StackLayout
            {
                HorizontalOptions = LayoutOptions.FillAndExpand,
                VerticalOptions = LayoutOptions.FillAndExpand,
                Orientation = StackOrientation.Horizontal,
                Spacing = 0,
            };

            // Put the stack in a scrollview so that the user can scroll between pictures
            // This is how to make a carousel view
            scrollview = new ScrollView
            {
                Content = stack,
                Orientation = ScrollOrientation.Horizontal,
            };

            // Add each image to create a long StackLayout and make each image fill the screen
            for (int i = 0; i < numberOfImages; i++)
            {
                // Construct the filename of the image to retrieve
                string filename = i.ToString() + ".jpg";
                Image image = new Image
                {
                    // These images are in the res folder
                    Source = filename,
                    Aspect = Aspect.AspectFit,
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    VerticalOptions = LayoutOptions.FillAndExpand,
                };

                // Bind the height of the image to the height of the image viewer so it fills the screen
                image.SetBinding(Image.HeightRequestProperty, new Binding("Height", source: this));
                // Bind the width of the image to the width of the image viewer so it fills the screen
                image.SetBinding(Image.WidthRequestProperty, new Binding("Width", source: this));
                // Add each image to the stack
                stack.Children.Add(image);
            }

            // Use PanGestureRecognizer to detect user swipes (for mobile)
            PanGestureRecognizer panGesture = new PanGestureRecognizer();
            panGesture.PanUpdated += (s, e) =>
            {
                // Use the gesture status to complete different operations
                switch (e.StatusType)
                {
                    case GestureStatus.Running:
                        // Use the event arguments to determine the direction of the swipe
                        // while the gesture is in progress
                        _toRight = e.TotalX < 0 ? true : false;
                        break;

                    case GestureStatus.Completed:
                        // Scroll once the gesture is complete
                        if (_toRight)
                        {
                            // Update _x depending on if stack's width is exceeded
                            _x = _x + this.Width >= stack.Width ? _x : _x + this.Width;
                            scrollview.ScrollToAsync(_x, 0, true);
                        }
                        else
                        {
                            // Update _x depending on if it is a sensible position in the stack
                            _x = _x - this.Width < 0 ? _x : _x - this.Width;
                            scrollview.ScrollToAsync(_x, 0, true);
                        }

                        break;
                }
            };

            // Add the gesture recognizer to the scrollview
            scrollview.GestureRecognizers.Add(panGesture);

            // So that the ImageViewer opens on the image which was tapped on GridPage
            scrollview.PropertyChanged += (s, e) =>
            {
                if (e.PropertyName == "Width")
                {
                    // Calcualte the position to open the image viewer
                    double forward = imageNumber * (scrollview.Width);
                    _x = forward;
                    scrollview.ScrollToAsync(_x, 0, false);
                }
            };

            Button playButton = new Button
            {
                Text = "Play slideshow"
            };

            // Callback to be invoked when the play button is clicked
            playButton.Clicked += PlayButtonClicked;

            Button previousButton = new Button
            {
                HorizontalOptions = LayoutOptions.FillAndExpand,
                VerticalOptions = LayoutOptions.End,
                Text = "Previous"
            };

            // Callback to be invoked when the previous button is clicked
            previousButton.Clicked += (s, e) =>
            {
                _x = _x - this.Width < 0 ? _x : _x - this.Width;
                scrollview.ScrollToAsync(_x, 0, true);
            };

            Button nextButton = new Button
            {
                HorizontalOptions = LayoutOptions.FillAndExpand,
                VerticalOptions = LayoutOptions.End,
                Text = "Next"
            };

            // Callback to be invoked when the next button is clicked
            nextButton.Clicked += (s, e) =>
            {
                _x = _x + this.Width >= stack.Width ? _x : _x + this.Width;
                scrollview.ScrollToAsync(_x, 0, true);
            };

            // Add the previous and next buttons into a StackLayout
            StackLayout navButtonStack = new StackLayout
            {
                Orientation = StackOrientation.Horizontal,
                HorizontalOptions = LayoutOptions.FillAndExpand,
                VerticalOptions = LayoutOptions.FillAndExpand,
                Children =
                {
                    previousButton, nextButton
                }
            };

            // Build the page
            Content = new StackLayout
            {
                HorizontalOptions = LayoutOptions.FillAndExpand,
                VerticalOptions = LayoutOptions.FillAndExpand,
                Children =
                {
                    scrollview,
                    playButton,
                }
            };

            // On non-mobile devices, the user cannot use touch, so buttons are required
            if (Device.Idiom != TargetIdiom.Phone)
            {
                (this.Content as StackLayout).Children.Add(navButtonStack);
            }

            // The user should not directly focus and control scrollview
            // The scrollview can be prevented from being focused by switching the
            // focus to the play button if it is focused
            scrollview.Focused += (s, e) =>
            {
                playButton.Focus();
            };

            // Set the focus on the play button
            // This is likely to be the first operation that the user does
            // when the image viewer opens
            // This is especially useful on a TV when the user has to use a remote
            // because this button will already be focused
            Appearing += (s, e) =>
            {
                playButton.Focus();
            };
        }

        /// <summary>
        /// The method to be invoked when the button to play or stop the slideshow
        /// is clicked.
        /// </summary>
        /// <param name="sender">The object which invoked this method.</param>
        /// <param name="e">The event arguments for this event.</param>
        private void PlayButtonClicked(object sender, EventArgs e)
        {
            Button playButton = sender as Button;
            // Use the _playing flag to change the button text
            if (!_playing)
            {
                _playing = true;
                playButton.Text = "Stop slideshow";
            }
            else
            {
                _playing = false;
                playButton.Text = "Start slideshow";
            }
            // Move to the next picture every two seconds by invoking PlaySlideshow
            // every two seconds
            Device.StartTimer(new TimeSpan(0, 0, 2), PlaySlideshow);
        }

        /// <summary>
        /// This method changes the current image in teh image viewer to the next
        /// image. It recurs and is invoked by the Xamarin.Forms.Device.StartTimer()
        /// method which is called in PlayButtonClicked.
        /// </summary>
        /// <returns>If true is returned, the method will keep recurring. If false
        /// it will stop recurring.</returns>
        private bool PlaySlideshow()
        {
            if (_playing)
            {
                _x += (this.Width * _slideDirection);
                scrollview.ScrollToAsync(_x, 0, true);
                // Reverse the play direction if the end of the StackLayout will be reached next time
                if ((_x + (this.Width * _slideDirection)) >= stack.Width || (_x + (this.Width * _slideDirection)) < 0)
                {
                    _slideDirection *= -1;
                }

                // Return true to ensure this method keeps recurring when _playing is true
                return true;
            }
            else
            {
                // Return false to stop this method from recurring when _playing is false
                return false;
            }
        }
    }
}
