/*public static void AddRelator<A, B>(Func<A, B, RSpatialRelation> func)
         * {
         *  relators.Register((object a, object b) => func((A)a, (B)b), typeof(A), typeof(B));
         *
         *  if (typeof(A) != typeof(B))
         *      relators.Register((object a, object b) =>
         *      {
         *          RSpatialRelation r = func((A)b, (B)a);
         *          if (r == RSpatialRelation.AInsideB) r = RSpatialRelation.BInsideA;
         *          else if (r == RSpatialRelation.BInsideA) r = RSpatialRelation.AInsideB;
         *          return r;
         *      }, typeof(B), typeof(A));
         * }
         *
         * static MultipleDispatcher<Func<object, object, RSpatialRelation>> relators = new MultipleDispatcher<Func<object, object, RSpatialRelation>>();
         */

        public static void AddRelator <A, B>(Func <A, B, RSpatialRelation> func)
        {
            if (!relators.ContainsKey(typeof(A)))
            {
                relators[typeof(A)] = new Dictionary <Type, Func <object, object, RSpatialRelation> >();
            }
            if (!relators.ContainsKey(typeof(B)))
            {
                relators[typeof(B)] = new Dictionary <Type, Func <object, object, RSpatialRelation> >();
            }

            relators[typeof(A)].Add(typeof(B),
                                    (object a, object b) => func((A)a, (B)b));

            if (typeof(A) != typeof(B))
            {
                relators[typeof(B)].Add(typeof(A),
                                        (object a, object b) =>
                {
                    RSpatialRelation r = func((A)b, (B)a);
                    if (r == RSpatialRelation.AInsideB)
                    {
                        r = RSpatialRelation.BInsideA;
                    }
                    else if (r == RSpatialRelation.BInsideA)
                    {
                        r = RSpatialRelation.AInsideB;
                    }
                    return(r);
                });
            }
        }
        public static RSpatialRelation Relation(BoundingBox a, Bounding.Frustum b)
        {
            Vector3          min, max;
            RSpatialRelation rel = RSpatialRelation.AInsideB;

            for (int i = 0; i < b.planes.Length; i++)
            {
                if (b.planes[i].Normal.X > 0)
                {
                    min.X = a.Minimum.X;
                    max.X = a.Maximum.X;
                }
                else
                {
                    min.X = a.Maximum.X;
                    max.X = a.Minimum.X;
                }

                if (b.planes[i].Normal.Y > 0)
                {
                    min.Y = a.Minimum.Y;
                    max.Y = a.Maximum.Y;
                }
                else
                {
                    min.Y = a.Maximum.Y;
                    max.Y = a.Minimum.Y;
                }

                if (b.planes[i].Normal.Z > 0)
                {
                    min.Z = a.Minimum.Z;
                    max.Z = a.Maximum.Z;
                }
                else
                {
                    min.Z = a.Maximum.Z;
                    max.Z = a.Minimum.Z;
                }

                if (Vector3.Dot(b.planes[i].Normal, min) + b.planes[i].D > 0)
                {
                    return(RSpatialRelation.Outside);
                }
                if (Vector3.Dot(b.planes[i].Normal, max) + b.planes[i].D >= 0)
                {
                    rel = RSpatialRelation.Intersect;
                }
            }
            return(rel);
        }
        // ----------------------------------------------------------------------------------------------
        // -- Chain -------------------------------------------------------------------------------------
        // ----------------------------------------------------------------------------------------------
        public static RSpatialRelation Relation(Bounding.Chain a, object b)
        {
            if (a.Shallow)
            {
                return(Relation(a.Boundings[0], b));
            }

            RSpatialRelation rel = RSpatialRelation.Outside;

            foreach (object o in a.Boundings)
            {
                rel = Relation(o, b);
                if (rel == RSpatialRelation.Outside)
                {
                    return(RSpatialRelation.Outside);
                }
                else if (rel == RSpatialRelation.AInsideB)
                {
                    return(RSpatialRelation.AInsideB);
                }
            }
            return(rel);
        }
        public static RSpatialRelation Relation(BoundingBox a, Bounding.Cylinder b)
        {
            // NOTE: Not tested thoroughly!

            /*
             * This all is based on finding the intersections between the axises of the box
             * */

            float m, n;

            float boxMinZ = a.Minimum.Z; float boxMaxZ = a.Maximum.Z;
            float cylMinZ = b.Position.Z; float cylMaxZ = cylMinZ + b.Height;

            RSpatialRelation verticalRelation = Relation1DLines(boxMinZ, boxMaxZ, cylMinZ, cylMaxZ);

            if (verticalRelation == RSpatialRelation.Outside)
            {
                return(RSpatialRelation.Outside);
            }

            // from this point on we know that the cylinder at least intersects the box on the vertical axis

            //X Axis, first find intersections
            CylP x1 = CylP.NotIntersecting;

            if (Math.CircleXAxisIntersection(b.Position, b.Radius, a.Minimum.Y, out m, out n))
            {
                if (m > a.Maximum.X || n < a.Minimum.X)
                {
                    x1 = CylP.Outside;
                }
                else if (m < a.Minimum.X && n > a.Maximum.X)
                {
                    x1 = CylP.AInsideB;
                }
                else
                {
                    return(RSpatialRelation.Intersect);
                }
            }

            CylP x2 = CylP.NotIntersecting;

            if (Math.CircleXAxisIntersection(b.Position, b.Radius, a.Maximum.Y, out m, out n))
            {
                if (m > a.Maximum.X || n < a.Minimum.X)
                {
                    x2 = CylP.Outside;
                }
                else if (m < a.Minimum.X && n > a.Maximum.X)
                {
                    x2 = CylP.AInsideB;
                }
                else
                {
                    return(RSpatialRelation.Intersect);
                }
            }

            //Y Axis, first find intersections
            CylP y1 = CylP.NotIntersecting;

            if (Math.CircleYAxisIntersection(b.Position, b.Radius, a.Minimum.X, out m, out n))
            {
                if (m > a.Maximum.Y || n < a.Minimum.Y)
                {
                    y1 = CylP.Outside;
                }
                else if (m < a.Minimum.Y && n > a.Maximum.Y)
                {
                    y1 = CylP.AInsideB;
                }
                else
                {
                    return(RSpatialRelation.Intersect);
                }
            }

            CylP y2 = CylP.NotIntersecting;

            if (Math.CircleYAxisIntersection(b.Position, b.Radius, a.Maximum.X, out m, out n))
            {
                if (m > a.Maximum.Y || n < a.Minimum.Y)
                {
                    y2 = CylP.Outside;
                }
                else if (m < a.Minimum.Y && n > a.Maximum.Y)
                {
                    y2 = CylP.AInsideB;
                }
                else
                {
                    return(RSpatialRelation.Intersect);
                }
            }

            if (x1 == CylP.AInsideB && x2 == CylP.AInsideB && y1 == CylP.AInsideB && y2 == CylP.AInsideB)
            {
                if (verticalRelation == RSpatialRelation.AInsideB)
                {
                    return(RSpatialRelation.AInsideB);
                }
                return(RSpatialRelation.Intersect);
            }

            //var horizontalRelation = RectCircleRelation(new RectangleF(new PointF(a.Minimum.X, a.Minimum.Y), new SizeF(a.Maximum.X - a.Minimum.X, a.Maximum.Y - a.Minimum.Y)), b);

            //if (horizontalRelation == RSpatialRelation.Outside)
            //    return RSpatialRelation.Outside;
            //else if (horizontalRelation == verticalRelation)
            //    return horizontalRelation;
            //else
            //    return RSpatialRelation.Intersect;

            if (x1 == CylP.NotIntersecting && x2 == CylP.NotIntersecting &&
                y1 == CylP.NotIntersecting && y2 == CylP.NotIntersecting &&
                Intersection.Intersect(a, Math.ToVector2(b.Position)))
            {
                if (verticalRelation == RSpatialRelation.BInsideA)
                {
                    return(RSpatialRelation.BInsideA);
                }
                return(RSpatialRelation.Intersect);
            }

            else
            {
                return(RSpatialRelation.Outside);
            }
#if false
            //We start by checking if the circle is so large it covers the whole rectangle
            bool intersects = false, ainsideb = true;

            if (p.CullPoint(position.X, position.Y) == ECull.Intersect)
            {
                intersects = true;
            }
            else
            {
                ainsideb = false;
            }

            if (p.CullPoint(position.X + size.X, position.Y) == ECull.Intersect)
            {
                intersects = true;
            }
            else
            {
                ainsideb = false;
            }

            if (p.CullPoint(position.X, position.Y + size.Y) == ECull.Intersect)
            {
                intersects = true;
            }
            else
            {
                ainsideb = false;
            }

            if (p.CullPoint(position.X + size.X, position.Y + size.Y) == ECull.Intersect)
            {
                intersects = true;
            }
            else
            {
                ainsideb = false;
            }

            if (ainsideb)
            {
                return(RSpatialRelation.AInsideB);
            }
            if (intersects)
            {
                return(RSpatialRelation.Intersect);
            }

            //The find the point on the circle which is closest to the center of the rectangle
            Vector3 k = center - p.Position;
            if (k.LengthSquared() == 0)
            {
                k.X = 1;
            }
            Vector3 d = Vector3.Normalize(k) * p.Radius;

            Vector3 a = p.Position + d;

            //And check if it is outside the rectangle
            if (CullPoint(a.X, a.Y) == ECull.Outside)
            {
                return(RSpatialRelation.Outside);
            }

            //The we take the point furthest away from the center
            Vector3 b = p.Position - d;
            if (CullPoint(b.X, b.Y) == ECull.Outside)
            {
                return(RSpatialRelation.Intersect);
            }

            return(RSpatialRelation.BInsideA);
#endif
        }