using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;

namespace ImageFileIO
{
    public sealed partial class MainPage : Page
    {
        double dpiX, dpiY;

        public MainPage()
        {
            this.InitializeComponent();
        }

    async void OnOpenAppBarButtonClick(object sender, RoutedEventArgs args)
    {
        // Tworzenie FileOpenPicker
        FileOpenPicker picker = new FileOpenPicker();
        picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;

        // Inicjowanie rozszerzeń nazw plików
        IReadOnlyList<BitmapCodecInformation> codecInfos = 
                                BitmapDecoder.GetDecoderInformationEnumerator();

        foreach (BitmapCodecInformation codecInfo in codecInfos)
            foreach (string extension in codecInfo.FileExtensions)
                picker.FileTypeFilter.Add(extension);

        // Pobranie wybranego pliku
        StorageFile storageFile = await picker.PickSingleFileAsync();

        if (storageFile == null)
            return;

        // Otwarcie strumienia i utworzenie dekodera
        BitmapDecoder decoder = null;

        using (IRandomAccessStreamWithContentType stream = await storageFile.OpenReadAsync())
        {
            string exception = null;

            try
            {
                decoder = await BitmapDecoder.CreateAsync(stream);
            }
            catch (Exception exc)
            {
                exception = exc.Message;
            }

            if (exception != null)
            {
                MessageDialog msgdlg = 
                    new MessageDialog("Ten plik obrazu nie może być załadowany. " +
                                        "System informuje o błędzie: " + exception);
                await msgdlg.ShowAsync();
                return;
            }

            // Pobranie pierwszej ramki
            BitmapFrame bitmapFrame = await decoder.GetFrameAsync(0);

            // Ustawienie tytułu
            txtblk.Text = String.Format("{0}: {1} x {2} {3} {4} x {5} DPI", 
                                        storageFile.Name, 
                                        bitmapFrame.PixelWidth, bitmapFrame.PixelHeight, 
                                        bitmapFrame.BitmapPixelFormat, 
                                        bitmapFrame.DpiX, bitmapFrame.DpiY);
            // Zapis rozdzieczości
            dpiX = bitmapFrame.DpiX;
            dpiY = bitmapFrame.DpiY;

            // Odczyt pikseli
            PixelDataProvider dataProvider = 
                await bitmapFrame.GetPixelDataAsync(BitmapPixelFormat.Bgra8, 
                                                    BitmapAlphaMode.Premultiplied, 
                                                    new BitmapTransform(), 
                                                    ExifOrientationMode.RespectExifOrientation, 
                                                    ColorManagementMode.ColorManageToSRgb);

            byte[] pixels = dataProvider.DetachPixelData();

            // Tworzenie WriteableBitmap i ustawienie pikseli
            WriteableBitmap bitmap = new WriteableBitmap((int)bitmapFrame.PixelWidth, 
                                                            (int)bitmapFrame.PixelHeight);

            using (Stream pixelStream = bitmap.PixelBuffer.AsStream())
            {
                await pixelStream.WriteAsync(pixels, 0, pixels.Length);
            }

            // Unieważnienie WriteableBitmap i ustawienie jako źródło Image
            bitmap.Invalidate();
            image.Source = bitmap;
        }

        // Włączenie pozostałych przycisków
        saveAsButton.IsEnabled = true;
        rotateLeftButton.IsEnabled = true;
        rotateRightButton.IsEnabled = true;
    }

        async void OnSaveAsAppBarButtonClick(object sender, RoutedEventArgs args)
        {
            FileSavePicker picker = new FileSavePicker();
            picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;

            // Pobranie danych kodera
            Dictionary<string, Guid> imageTypes = new Dictionary<string, Guid>();
            IReadOnlyList<BitmapCodecInformation> codecInfos = 
                                    BitmapEncoder.GetEncoderInformationEnumerator();

            foreach (BitmapCodecInformation codecInfo in codecInfos)
            {
                List<string> extensions = new List<string>();

                foreach (string extension in codecInfo.FileExtensions)
                    extensions.Add(extension);

                string filetype = codecInfo.FriendlyName.Split(' ')[0];
                picker.FileTypeChoices.Add(filetype, extensions);

                foreach (string mimeType in codecInfo.MimeTypes)
                    imageTypes.Add(mimeType, codecInfo.CodecId);
            }

            // Pobranie wybranego StorageFile
            StorageFile storageFile = await picker.PickSaveFileAsync();

            if (storageFile == null)
                return;

            // Otwarcie StorageFile
            using (IRandomAccessStream fileStream = 
                                    await storageFile.OpenAsync(FileAccessMode.ReadWrite))
            {
                // Tworzenie losowego kodera
                Guid codecId = imageTypes[storageFile.ContentType];
                BitmapEncoder encoder = await BitmapEncoder.CreateAsync(codecId, fileStream);

                // Pobranie pikseli z istniejącej WriteableBitmap
                WriteableBitmap bitmap = image.Source as WriteableBitmap;
                byte[] pixels = new byte[4 * bitmap.PixelWidth * bitmap.PixelHeight];

                using (Stream pixelStream = bitmap.PixelBuffer.AsStream())
                {
                    await pixelStream.ReadAsync(pixels, 0, pixels.Length);
                }

                // Zapisanie pikseli do pierwszej ramki
                encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied,
                                        (uint)bitmap.PixelWidth, (uint)bitmap.PixelHeight,
                                        dpiX, dpiY, pixels);

                await encoder.FlushAsync();
            }
        }

        void OnRotateLeftAppBarButtonClick(object sender, RoutedEventArgs args)
        {
            Rotate((BitmapSource bitmap, int x, int y) =>
                {
                    return 4 * (bitmap.PixelWidth * x + (bitmap.PixelWidth - y - 1));
                });
        }

        void OnRotateRightAppBarButtonClick(object sender, RoutedEventArgs args)
        {
            Rotate((BitmapSource bitmap, int x, int y) =>
                {
                    return 4 * (bitmap.PixelWidth * (bitmap.PixelHeight - x - 1) + y);
                });
        }

        async void Rotate(Func<BitmapSource, int, int, int> calculateSourceIndex)
        {
            // Pobranie pikseli źródłowej mapy bitowej
            WriteableBitmap srcBitmap = image.Source as WriteableBitmap;
            byte[] srcPixels = new byte[4 * srcBitmap.PixelWidth * srcBitmap.PixelHeight];

            using (Stream pixelStream = srcBitmap.PixelBuffer.AsStream())
            {
                await pixelStream.ReadAsync(srcPixels, 0, srcPixels.Length);
            }

            // Tworzenie docelowej mapy bitowej i tablicy pikseli
            WriteableBitmap dstBitmap = 
                    new WriteableBitmap(srcBitmap.PixelHeight, srcBitmap.PixelWidth);
            byte[] dstPixels = new byte[4 * dstBitmap.PixelWidth * dstBitmap.PixelHeight];

            // Przesłanie pikseli
            int dstIndex = 0;
            for (int y = 0; y < dstBitmap.PixelHeight; y++)
                for (int x = 0; x < dstBitmap.PixelWidth; x++)
                {
                    int srcIndex = calculateSourceIndex(srcBitmap, x, y);

                    for (int i = 0; i < 4; i++)
                        dstPixels[dstIndex++] = srcPixels[srcIndex++];
                }

            // Przeniesienie pikseli do docelowej mapy bitowej
            using (Stream pixelStream = dstBitmap.PixelBuffer.AsStream())
            {
                await pixelStream.WriteAsync(dstPixels, 0, dstPixels.Length);
            }
            dstBitmap.Invalidate();

            // Przełączenie DPI
            double dpi = dpiX;
            dpiX = dpiY;
            dpiY = dpi;

            // Wyświetlenie nowej mapy bitowej
            image.Source = dstBitmap;
        }
    }
}
