using System;
using System.Collections.Generic;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Input;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;
using Petzold.Windows8.VectorDrawing;

namespace FingerPaint5
{
    public sealed partial class MainPage : Page
    {
        struct PointerInfo
        {
            public Brush Brush;
            public Point PreviousPoint;
            public double PreviousRadius;
        }

        Dictionary<uint, PointerInfo> pointerDictionary = new Dictionary<uint, PointerInfo>();
        Random rand = new Random();
        byte[] rgb = new byte[3];

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

        protected override void OnPointerPressed(PointerRoutedEventArgs args)
        {
            // Pobranie danych z argumentu zdarzenia
            uint id = args.Pointer.PointerId;
            PointerPoint pointerPoint = args.GetCurrentPoint(this);

            // Tworzenie losowego koloru
            rand.NextBytes(rgb);
            Color color = Color.FromArgb(255, rgb[0], rgb[1], rgb[2]);

            // Tworzenie PointerInfo
            PointerInfo pointerInfo = new PointerInfo
            {
                PreviousPoint = pointerPoint.Position,
                PreviousRadius = 24 * pointerPoint.Properties.Pressure,
                Brush = new SolidColorBrush(color)
            };

            // Dodawanie do słownika
            pointerDictionary.Add(id, pointerInfo);

            // Przechwycenie wskaźnika
            CapturePointer(args.Pointer);
            base.OnPointerPressed(args);
        }

        protected override void OnPointerMoved(PointerRoutedEventArgs args)
        {
            // Pobranie ID z argumentów zdarzenia
            uint id = args.Pointer.PointerId;

            // Jeżeli ID znajduje się w słowniku, uruchom pętlę
            if (pointerDictionary.ContainsKey(id))
            {
                PointerInfo pointerInfo = pointerDictionary[id];
                IList<PointerPoint> pointerpoints = args.GetIntermediatePoints(this);

                for (int i = pointerpoints.Count - 1; i >= 0; i--)
                {
                    PointerPoint pointerPoint = pointerpoints[i];

                    // Dla każdego punktu utwórz element Path i dodaj go do Grid
                    Point point = pointerPoint.Position;
                    float pressure = pointerPoint.Properties.Pressure;
                    double radius = 24 * pressure;

                    Geometry geometry = 
                        CreateTaperedLineGeometry(pointerInfo.PreviousPoint,
                                                    pointerInfo.PreviousRadius,
                                                    point,
                                                    radius);
                    Path path = new Path
                    {
                        Data = geometry,
                        Fill = pointerInfo.Brush
                    };
                    contentGrid.Children.Add(path);

                    // Aktualizuj PointerInfo
                    pointerInfo.PreviousPoint = point;
                    pointerInfo.PreviousRadius = radius;
                }
                // Zapisz PointerInfo w słowniku
                pointerDictionary[id] = pointerInfo;
            }
            base.OnPointerMoved(args);
        }

        protected override void OnPointerReleased(PointerRoutedEventArgs args)
        {
            // Pobranie danych z argumentu zdarzenia
            uint id = args.Pointer.PointerId;

            // Jeżeli ID jest w słowniku, usuń go
            if (pointerDictionary.ContainsKey(id))
                pointerDictionary.Remove(id);

            base.OnPointerReleased(args);
        }

        protected override void OnPointerCaptureLost(PointerRoutedEventArgs args)
        {
            // Pobranie danych z argumentu zdarzenia
            uint id = args.Pointer.PointerId;

            // Jeżeli ID jest nadal w słowniku, usuń go
            if (pointerDictionary.ContainsKey(id))
                pointerDictionary.Remove(id);

            base.OnPointerCaptureLost(args);
        }

        Geometry CreateTaperedLineGeometry(Point c0, double r0, Point c1, double r1)
        {
            // Zamień środki i promienie, aby c0
            // było środkiem mniejszego okręgu.
            if (r1 < r0)
            {
                Point point = c0;
                c0 = c1;
                c1 = point;

                double radius = r0;
                r0 = r1;
                r1 = radius;
            }

            // Utwórz wektor od c1 do c0
            Vector2 vCenters = new Vector2(c0) - new Vector2(c1);

            // Pobierz długość i wersję znormalizowaną
            double d = vCenters.Length;
            vCenters = vCenters.Normalized;

            // Sprawdź, czy jeden okrąg zawiera się w drugim
            bool enclosed = r0 + d < r1;

            // Określ punkty styczne na podstawie obu algorytmó
            Point t0R = new Point();
            Point t0L = new Point();
            Point t1R = new Point();
            Point t1L = new Point();
             
            // Przypadek dwóch okręgów o tej samej wielkości
            if (r0 == r1 || enclosed)
            {
                // Obrócenie środków wektora o 90 stopni
                Vector2 vLeft = new Vector2(-vCenters.Y, vCenters.X);
                
                // Obrót o -90 stopni
                Vector2 vRight = -vLeft;

                // Określ punlty styczne
                t0R = c0 + r0 * vRight;
                t0L = c0 + r0 * vLeft;
                t1R = c1 + r1 * vRight;
                t1L = c1 + r1 * vLeft;
            }
            // Nieco trudniejszy przypadek dwóch okręgów o różnej wielkości
            else
            {
                // Tworzenie punktu skupienia F oddalonego o c0
                double e = d * r0 / (r1 - r0);
                Point F = c0 + e * vCenters;

                // Określenie kąta i długości prawego ramienia trójkąta
                double alpha = 180 * Math.Asin(r0 / e) / Math.PI;
                double leg0 = Math.Sqrt(e * e - r0 * r0);
                double leg1 = Math.Sqrt((e + d) * (e + d) - r1 * r1);

                // Wektory linii stycznych
                Vector2 vRight = -vCenters.Rotate(alpha);
                Vector2 vLeft = -vCenters.Rotate(-alpha);

                // Szukanie punktów stycznych
                t0R = F + leg0 * vRight;
                t0L = F + leg0 * vLeft;
                t1R = F + leg1 * vRight;
                t1L = F + leg1 * vLeft;
            }

            // Tworzenie PathGeometry z domyślną linią zamykającą
            PathGeometry pathGeometry = new PathGeometry();
            PathFigure pathFigure = new PathFigure
            {
                StartPoint = t0R,
                IsClosed = true,
                IsFilled = true
            };
            pathGeometry.Figures.Add(pathFigure);

            // Łuk wokół mniejszego okręgu
            ArcSegment arc0Segment = new ArcSegment
            {
                Point = t0L,
                Size = new Size(r0, r0),
                SweepDirection = SweepDirection.Clockwise,
                IsLargeArc = false
            };
            pathFigure.Segments.Add(arc0Segment);

            // Linia łącząca mniejszy okrąg z większym
            LineSegment lineSegment = new LineSegment
            {
                Point = t1L
            };
            pathFigure.Segments.Add(lineSegment);

            // Łuk wokół większego okręgu
            ArcSegment arc1Segment = new ArcSegment
            {
                Point = t1R,
                Size = new Size(r1, r1),
                SweepDirection = SweepDirection.Clockwise,
                IsLargeArc = true
            };
            pathFigure.Segments.Add(arc1Segment);

            return pathGeometry;
        }
    }
}
