using System;
using System.Collections.Generic;
using System.Reflection;
using Windows.Foundation;
using Windows.Graphics.Printing;
using Windows.UI;
using Windows.UI.ViewManagement;
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 PrintableClassHierarchy
{
    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 drukowania
        List<TextBlock> printerTextBlocks = new List<TextBlock>();
        Brush blackBrush = new SolidColorBrush(Colors.Black);

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

        public MainPage()
        {
            this.InitializeComponent();
            highlightBrush =
                new SolidColorBrush(new UISettings().UIElementColor(UIElementType.Highlight));

            // 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)
        {
            args.Request.CreatePrintTask("Hierarchia klas właściwości zależnych", (requestArgs) =>
            {
                requestArgs.SetSource(printDocumentSource);
            });
        }

        void OnPrintDocumentPaginate(object sender, PaginateEventArgs args)
        {
            // Ustawienie zmiennych marginesów strony
            double leftMargin = 96;
            double topMargin = 96;
            double rightMargin = 96;
            double bottomMargin = 96;

            // Wyczyszczenie poprzedniej 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();
        }
    }
}
