using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;

namespace AIStudio.Wpf.DiagramDesigner
{
    public class BoundaryPathFinder : IPathFinder
    {
        public List<PointInfoBase> UpdateConnectionPoints(IDiagramViewModel diagramViewModel, Point sourceA, Point sourceB, FullyCreatedConnectorInfo sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo)
        {
            List<PointInfoBase> connectionPoints;
            var isFullConnection = sinkConnectorInfo is FullyCreatedConnectorInfo;

            var points = new List<Point>()
                                   {
                                       sourceA,
                                       sourceB
                                   };

            ConnectorInfo sourceInfo = ConnectorInfo(sourceConnectorInfo.Orientation,
                                            points[0].X,
                                            points[0].Y,
                                            sourceConnectorInfo.DataItem.ItemWidth,
                                            sourceConnectorInfo.DataItem.ItemHeight,
                                            points[0]);


            //StartPoint = points[0];
            if (isFullConnection)
            {

                ConnectorInfo sinkInfo = ConnectorInfo(sinkConnectorInfo.Orientation,
                                  points[1].X,
                                  points[1].Y,
                                  ((FullyCreatedConnectorInfo)sinkConnectorInfo).DataItem.ItemWidth,
                                  ((FullyCreatedConnectorInfo)sinkConnectorInfo).DataItem.ItemHeight,
                                  points[1]);

                connectionPoints = PointInfoBase.ToList(GetConnectionLine(diagramViewModel, sourceInfo, sinkInfo, false, sourceConnectorInfo.IsInnerPoint));
                //EndPoint = ConnectionPoints.Last();
            }
            else
            {
                connectionPoints = PointInfoBase.ToList(GetConnectionLine(diagramViewModel, sourceInfo, points[1], sourceConnectorInfo.Orientation, false, sourceConnectorInfo.IsInnerPoint));
                //EndPoint = new Point();
            }

            return connectionPoints;
        }

        public ConnectorInfo ConnectorInfo(ConnectorOrientation orientation, double left, double top, double width, double height, Point position)
        {
            return new ConnectorInfo()
            {
                Orientation = orientation,
                DesignerItemSize = new Size(width, height),
                DesignerItemLeft = left,
                DesignerItemTop = top,
                Position = position
            };
        }

        public List<Point> GetConnectionLine(IDiagramViewModel diagramViewModel, ConnectorInfo source, ConnectorInfo sink, bool showLastLine, bool sourceInnerPoint = false)
        {
            var points = new List<Point>();
            var ends = new List<Point> { source.Position, sink.Position };

            points.Add(ends[0]);
            points.AddRange(GetMiddlePoints(source, sink, diagramViewModel.GridCellSize, diagramViewModel.GridMargin, true));
            points.Add(ends[1]);
            var res = points.ToArray();
            DoShift(res);
            return res.ToList();
        }

        public List<Point> GetConnectionLine(IDiagramViewModel diagramViewModel, ConnectorInfo source, Point sinkPoint, ConnectorOrientation preferredOrientation, bool showLastLine, bool isInnerPoint = false)
        {
            var points = new List<Point>();
            var ends = new List<Point> { source.Position, sinkPoint };

            points.Add(ends[0]);
            points.AddRange(GetMiddlePoints(source, new ConnectorInfo() { Orientation = ConnectorOrientation.Top, Position = sinkPoint }, diagramViewModel.GridCellSize, diagramViewModel.GridMargin, false));
            points.Add(ends[1]);
            var res = points.ToArray();
            DoShift(res);
            return res.ToList();
        }

        private IEnumerable<Point> GetMiddlePoints(ConnectorInfo source, ConnectorInfo sink, Size gridCellSize, double gridMargin, bool isFullConnection)
        {
            var points = new List<Point>();
            if (isFullConnection)
            {
                var p0 = GetFirstSegment(source.Orientation, source.Position, gridCellSize, gridMargin);
                var p1 = GetFirstSegment(sink.Orientation, sink.Position, gridCellSize, gridMargin);

                if (p0 == p1)
                    return points;


                var p2 = new Point(GetNearestCross(p0.X, p1.X), GetNearestCross(p0.Y, p1.Y));
                var p3 = new Point(GetNearestCross(p1.X, p0.X), GetNearestCross(p1.Y, p0.Y));
                if (p2 == p3)
                {
                    points.Add(p0);
                    points.Add(p2);
                    points.Add(p1);
                }
                else
                {
                    points.Add(p0);
                    points.Add(p2);
                    if (!(Math.Abs(p2.X - p3.X) < 0.0001) && !(Math.Abs(p2.Y - p3.Y) < 0.0001))
                        points.Add(new Point(p2.X, p3.Y));
                    points.Add(p3);
                    points.Add(p1);
                }
                DoScale(points, gridCellSize, gridMargin);
            }
            return points;
        }

        private Point GetFirstSegment(ConnectorOrientation orientation, Point point, Size cellSize, double margin)
        {
            double x = (int)((point.X - margin) / cellSize.Width) + 0.5;
            double y = (int)((point.Y - margin) / cellSize.Height) + 0.5;
            if (orientation == ConnectorOrientation.Top)
                return new Point(x, y - 0.5);
            else if (orientation == ConnectorOrientation.Bottom)
                return new Point(x, y + 0.5);
            else if (orientation == ConnectorOrientation.Left)
                return new Point(x - 0.5, y);
            else
                return new Point(x + 0.5, y);
        }

        public static double GetNearestCross(double a, double b)
        {
            if (Math.Abs(a - b) < 0.0001 && (int)a == a)
                return a;
            else if (a < b)
                return Math.Ceiling(a);
            else
                return Math.Floor(a);
        }

        public static Point SegmentMiddlePoint(Point p1, Point p2)
        {
            return new Point((p1.X + p2.X) / 2, (p1.Y + p2.Y) / 2);
        }


        private void DoScale(List<Point> points, Size cellSize, double margin)
        {
            for (int i = 0; i < points.Count; i++)
            {
                points[i] = new Point(points[i].X * cellSize.Width + margin,
                    points[i].Y * cellSize.Height + margin);
            }
        }

        private void DoShift(Point[] points)
        {
            double left = new Point[] { points.FirstOrDefault(), points.LastOrDefault() }.Min(p => p.X);
            double top = new Point[] { points.FirstOrDefault(), points.LastOrDefault() }.Min(p => p.Y);

            for (int i = 0; i < points.Length; i++)
            {
                points[i].X = points[i].X - left;
                points[i].Y = points[i].Y - top;
            }
        }




    }
}
