using System;
using System.Collections.Generic;
using System.Reflection;
using Windows.Foundation;
using Windows.Graphics.Printing;
using Windows.Graphics.Printing.OptionDetails;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.UI.Xaml.Printing;

namespace CustomizableClassHierarchy
{
    public sealed partial class MainPage : Page
    {
        Type rootType = typeof(DependencyObject);
        TypeInfo rootTypeInfo = typeof(DependencyObject).GetTypeInfo();
        List<Type> classes = new List<Type>();
        Brush highlightBrush;

        // Obsługa drykowania
        List<TextBlock> printerTextBlocks = new List<TextBlock>();
        Brush blackBrush = new SolidColorBrush(Colors.Black);

        PrintDocument printDocument;
        IPrintDocumentSource printDocumentSource;
        List<UIElement> printerPages = new List<UIElement>();

        // Wartości domyślne ustawień drukarki
        double fontSize = new TextBlock().FontSize;
        double leftMargin = 96;     // 25 mm
        double topMargin = 72;      // 18,75 mm
        double rightMargin = 96;
        double bottomMargin = 72;

        public MainPage()
        {
            this.InitializeComponent();
            highlightBrush = this.Resources["ApplicationPressedForegroundThemeBrush"] as Brush;

            // Zebranie wszystkich klas dziedziczących po DependencyObject 
            AddToClassList(typeof(Windows.UI.Xaml.DependencyObject));

            // Sortowanie alfabetyczne według nazwy
            classes.Sort((t1, t2) =>
            {
                return String.Compare(t1.GetTypeInfo().Name, t2.GetTypeInfo().Name);
            });

            // Umieszczenie posortowanych klas w strukturze drzewiastej
            ClassAndSubclasses rootClass = new ClassAndSubclasses(rootType);
            AddToTree(rootClass, classes);

            // Wyświetlenie drzewa za pomocą elementu TextBlock dodanego do StackPanel
            DisplayAndPrinterPrep(rootClass, 0);

            // utworzenie PrintDocument i dołączenie metod obsługi
            printDocument = new PrintDocument();
            printDocumentSource = printDocument.DocumentSource;
            printDocument.Paginate += OnPrintDocumentPaginate;
            printDocument.GetPreviewPage += OnPrintDocumentGetPreviewPage;
            printDocument.AddPages += OnPrintDocumentAddPages;
        }

        protected override void OnNavigatedTo(NavigationEventArgs args)
        {
            // Dołączenie metody obsługi do PrintManager
            PrintManager.GetForCurrentView().PrintTaskRequested += OnPrintManagerPrintTaskRequested;

            base.OnNavigatedTo(args);
        }

        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            // Odłączenie metody obsługi od PrintManager
            PrintManager.GetForCurrentView().PrintTaskRequested -= OnPrintManagerPrintTaskRequested;

            base.OnNavigatedFrom(e);
        }

        void AddToClassList(Type sampleType)
        {
            Assembly assembly = sampleType.GetTypeInfo().Assembly;

            foreach (Type type in assembly.ExportedTypes)
            {
                TypeInfo typeInfo = type.GetTypeInfo();

                if (typeInfo.IsPublic && rootTypeInfo.IsAssignableFrom(typeInfo))
                    classes.Add(type);
            }
        }

        void AddToTree(ClassAndSubclasses parentClass, List<Type> classes)
        {
            foreach (Type type in classes)
            {
                Type baseType = type.GetTypeInfo().BaseType;

                if (baseType == parentClass.Type)
                {
                    ClassAndSubclasses subClass = new ClassAndSubclasses(type);
                    parentClass.Subclasses.Add(subClass);
                    AddToTree(subClass, classes);
                }
            }
        }

        void DisplayAndPrinterPrep(ClassAndSubclasses parentClass, int indent)
        {
            TypeInfo typeInfo = parentClass.Type.GetTypeInfo();

            // Utworzenie elementu TextBlock i dodanie go do StackPanel
            TextBlock txtblk = CreateTextBlock(typeInfo, indent);
            stackPanel.Children.Add(txtblk);

            // Utworzenie elementu TextBlock i dodanie go do listy drukowania
            txtblk = CreateTextBlock(typeInfo, indent);
            txtblk.Foreground = blackBrush;
            printerTextBlocks.Add(txtblk);

            // Wywołanie rekurencyjne dla wszystkich klas pochodnych
            foreach (ClassAndSubclasses subclass in parentClass.Subclasses)
                DisplayAndPrinterPrep(subclass, indent + 1);
        }

        TextBlock CreateTextBlock(TypeInfo typeInfo, int indent)
        {
            // Utworzenie TextBlock z nazwą typu
            TextBlock txtblk = new TextBlock();
            txtblk.Inlines.Add(new Run { Text = new string(' ', 8 * indent) });
            txtblk.Inlines.Add(new Run { Text = typeInfo.Name });

            // Wskazanie, czy klasa jest zamknięta
            if (typeInfo.IsSealed)
                txtblk.Inlines.Add(new Run
                {
                    Text = " (zamknięta)",
                    Foreground = highlightBrush
                });

            // Wskazanie czy klasa nie może być konkretyzowana
            IEnumerable<ConstructorInfo> constructorInfos = typeInfo.DeclaredConstructors;
            int publicConstructorCount = 0;

            foreach (ConstructorInfo constructorInfo in constructorInfos)
                if (constructorInfo.IsPublic)
                    publicConstructorCount += 1;

            if (publicConstructorCount == 0)
                txtblk.Inlines.Add(new Run
                {
                    Text = " (nie-konkretyzowalna)",
                    Foreground = highlightBrush
                });

            return txtblk;
        }

        void OnPrintManagerPrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs args)
        {
            PrintTask printTask = args.Request.CreatePrintTask("Hierarchia klas właściwości zależnych",
                                                                (requestArgs) =>
                                                                {
                                                                    requestArgs.SetSource(printDocumentSource);
                                                                });

            PrintTaskOptionDetails optionDetails =
                            PrintTaskOptionDetails.GetFromPrintTaskOptions(printTask.Options);

            // Dodanir elementu na wielkość czcionki
            optionDetails.CreateTextOption("idFontSize", "Rozmiar czcionki (w punktach)");
            optionDetails.DisplayedOptions.Add("idFontSize");
            optionDetails.Options["idFontSize"].TrySetValue((72 * fontSize / 96).ToString());

            // Dodanie elementów na marginesy strony
            optionDetails.CreateTextOption("idLeftMargin", "Lewy margines (w milimetrach)");
            optionDetails.DisplayedOptions.Add("idLeftMargin");
            optionDetails.Options["idLeftMargin"].TrySetValue((leftMargin * 25 / 96).ToString());

            optionDetails.CreateTextOption("idTopMargin", "Górny margines (w milimetrach)");
            optionDetails.DisplayedOptions.Add("idTopMargin");
            optionDetails.Options["idTopMargin"].TrySetValue((topMargin * 25 / 96).ToString());

            optionDetails.CreateTextOption("idRightMargin", "Prawy margines (w milimetrach)");
            optionDetails.DisplayedOptions.Add("idRightMargin");
            optionDetails.Options["idRightMargin"].TrySetValue((rightMargin * 25 / 96).ToString());

            optionDetails.CreateTextOption("idBottomMargin", "Dolny margines (w milimetrach)");
            optionDetails.DisplayedOptions.Add("idBottomMargin");
            optionDetails.Options["idBottomMargin"].TrySetValue((bottomMargin * 25 / 96).ToString());

            // Ustawienie metody obsługi zmiany opcji
            optionDetails.OptionChanged += OnOptionDetailsOptionChanged;
        }

        async void OnOptionDetailsOptionChanged(PrintTaskOptionDetails sender,
                                                PrintTaskOptionChangedEventArgs args)
        {
            if (args.OptionId == null)
                return;

            string optionId = args.OptionId.ToString();
            string strValue = sender.Options[optionId].Value.ToString();
            string errorText = String.Empty;
            double value = 0;

            switch (optionId)
            {
                case "idFontSize":
                    if (!Double.TryParse(strValue, out value))
                        errorText = "Wartość musi być liczbą";

                    else if (value < 4 || value > 36)
                        errorText = "Wartość musi być większa od 4 i mniejsza od 36";
                    break;

                case "idLeftMargin":
                case "idTopMargin":
                case "idRightMargin":
                case "idBottomMargin":
                    if (!Double.TryParse(strValue, out value))
                        errorText = "Wartość musi być liczbą";

                    else if (value < 0 || value > 50)
                        errorText = "Wartość musi być większa od 0 i mniejsza od 50";
                    break;
            }

            sender.Options[optionId].ErrorText = errorText;

            // Jeżeli nie ma błędów, unieważnienie podglądu
            if (String.IsNullOrEmpty(errorText))
            {
                await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                    {
                        printDocument.InvalidatePreview();
                    });
            }
        }

        void OnPrintDocumentPaginate(object sender, PaginateEventArgs args)
        {
            // Pobranie wartosci własnych opcji
            PrintTaskOptionDetails optionDetails = PrintTaskOptionDetails.GetFromPrintTaskOptions(args.PrintTaskOptions);
            fontSize = 96 * Double.Parse(optionDetails.Options["idFontSize"].Value.ToString()) / 72;
            leftMargin = 96 * Double.Parse(optionDetails.Options["idLeftMargin"].Value.ToString()) / 25;
            topMargin = 96 * Double.Parse(optionDetails.Options["idTopMargin"].Value.ToString()) / 25;
            rightMargin = 96 * Double.Parse(optionDetails.Options["idRightMargin"].Value.ToString()) / 25;
            bottomMargin = 96 * Double.Parse(optionDetails.Options["idBottomMargin"].Value.ToString()) / 25;

            // Ustawienie FontSize zapamiętanych elementów TextBlock
            foreach (TextBlock txtblk in printerTextBlocks)
                txtblk.FontSize = fontSize;

            // Wyczyszczenie popzedniej kolekcji printerPage
            foreach (UIElement printerPage in printerPages)
                ((printerPage as Border).Child as Panel).Children.Clear();

            printerPages.Clear();

            // Inicjowanie konstrukcji strony
            Border border = null;
            StackPanel stackPanel = null;
            double maxPageHeight = 0;
            double pageHeight = 0;

            // Przeglądanie listy elementów TextBlock
            for (int index = 0; index < printerTextBlocks.Count; index++)
            {
                // Wartość null dla obiektu Border sygnalizuje nową stronę
                if (border == null)
                {
                    // Obliczenie wysokości dostępnej dla tekstu
                    uint pageNumber = (uint)printerPages.Count;
                    maxPageHeight =
                        args.PrintTaskOptions.GetPageDescription(pageNumber).PageSize.Height;
                    maxPageHeight -= topMargin + bottomMargin;
                    pageHeight = 0;

                    // Tworzenie obiektów StackPanel i Border
                    stackPanel = new StackPanel();
                    border = new Border
                    {
                        Padding = new Thickness(leftMargin, topMargin, rightMargin, bottomMargin),
                        Child = stackPanel
                    };

                    // Dodanie do listy stron
                    printerPages.Add(border);
                }

                // Pobranie obiektu TextBlock i zmierzenie go
                TextBlock txtblk = printerTextBlocks[index];
                txtblk.Measure(Size.Empty);

                // Sprawdzenie, czy można dodać TextBlock do tej strony
                if (stackPanel.Children.Count == 0 ||
                    pageHeight + txtblk.ActualHeight < maxPageHeight)
                {
                    stackPanel.Children.Add(txtblk);
                    pageHeight += Math.Ceiling(txtblk.ActualHeight);
                }
                // W przeciwnym razie jest to koniec strony
                else
                {
                    // Koniec pracy z tym obiektem Border
                    border = null;

                    // Ponowne przetwarzanie tego obiektu TextBlock
                    index--;
                }
            }

            // Powiadomienie o końcowej liczbie stron
            printDocument.SetPreviewPageCount(printerPages.Count, PreviewPageCountType.Final);
        }

        void OnPrintDocumentGetPreviewPage(object sender, GetPreviewPageEventArgs args)
        {
            printDocument.SetPreviewPage(args.PageNumber, printerPages[args.PageNumber - 1]);
        }

        void OnPrintDocumentAddPages(object sender, AddPagesEventArgs args)
        {
            foreach (UIElement printerPage in printerPages)
                printDocument.AddPage(printerPage);

            printDocument.AddPagesComplete();
        }
    }
}
