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

namespace VisiMath.Collisions
{
    /// <summary>
    /// Any object which can be used for spatial partioning must implement this interface
    /// </summary>
    public interface ISpatialyIndexable
    {
        /// <summary>
        /// Flags for collision groups
        /// </summary>
        UInt64 CollisionGroup { get; }
        /// <summary>
        /// Spatial index of the object
        /// </summary>
        ISpatialIndex SpatialIndex { get; set; }
        /// <summary>
        /// Spatial portion which is currently holding the object
        /// </summary>
        object Cell { get; set; }        //ISpatialPortion Cell { get; set; }
    }

    public struct SpatialPair<TObject>
        where TObject : ISpatialyIndexable
    {
        public TObject Item1;
        public TObject Item2;

        public SpatialPair(TObject i1, TObject i2)
        {
            Item1 = i1;
            Item2 = i2;
        }
    }

    
    /// <summary>
    /// Class dividing world space in portions for optimalization
    /// </summary>
    public interface ISpatialCell<TObject>
        where TObject : ISpatialyIndexable
    {
        /// <summary>
        /// Owner of the spatial cell for hiearchical indexing
        /// </summary>
        ISpatialCell<TObject> Owner { get; }
        

        /// <summary>
        /// Spatial index associated to this cell
        /// </summary>
        ISpatialIndex Index { get; }
                      
        /// <summary>
        /// Returns true if spatial portion contains spatial index fully
        /// </summary>
        /// <param name="index">Spatial index</param>
        bool ContainsFully(ref ISpatialIndex index);

        /// <summary>
        /// Returns true if spatial portion contains spatial index at least partially
        /// </summary>
        /// <param name="index">Spatial index</param>
        bool Contains(ref ISpatialIndex index);

        /// <summary>
        /// Number of portals to adjacent spatialcells
        /// </summary>
        int PortalCount { get; }

        /// <summary>
        /// Returns spatial portal with specified index (0 to PortalCount)
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        ISpatialPortal<TObject> GetPortal(int index);

        /// <summary>
        /// Returns spatial portion which contains position
        /// </summary>
        /// <param name="position">Position vector</param>
        /// <returns>Spatial node containing object</returns>
        ISpatialCell<TObject> GetNode(ref Vector3 position);

        /// <summary>
        /// Returns smallest spatial portion which fully contains sphere
        /// </summary>
        /// <param name="center">Sphere center</param>
        /// <param name="radius">Radius of the sphere</param>
        /// <returns>Spatial node fully containing object</returns>
        ISpatialCell<TObject> GetNode(ref Vector3 center, float radius);

        /// <summary>
        /// Returns smallest spatial portion which fully contains bounding box
        /// </summary>
        /// <param name="minimum">Minimal corner of the box</param>
        /// <param name="maximum">Maximal corner of the box</param>
        /// <returns>Spatial node fully containing object</returns>
        ISpatialCell<TObject> GetNode(ref Vector3 minimum, ref Vector3 maximum);

        /// <summary>
        /// Returns smallest spatial portion which fully contains spatial index
        /// </summary>
        /// <param name="index">Spatial index</param>
        ISpatialCell<TObject> GetNode(ref ISpatialIndex index);
        
        /// <summary>
        /// Number of the objects contained directly in the node
        /// </summary>
        int ObjectCount { get; }

        /// <summary>
        /// Returns list of the objects directly in the node
        /// </summary>
        /// <param name="CollisionGroup">Collision group flags</param>
        /// <returns>List of objects in node with correct collision group</returns>
        IEnumerable<TObject> GetObjects(UInt64 CollisionGroup = UInt64.MaxValue);

        /// <summary>
        /// Adds objects directly in the node with correct collision group to the list
        /// </summary>
        /// <param name="CollisionGroup">Collision group flags</param>
        /// <param name="list"> List of the objects</param>
        /// <param name="startindex">Starting index in the list</param>
        int GetObjects(List<TObject> list, int startindex, UInt64 CollisionGroup = UInt64.MaxValue);

        /// <summary>
        /// Returns number of all objects in the node (and subnodes) with specified collision group
        /// </summary>
        /// <param name="CollisionGroup">Collision group flags</param>
        /// <returns>Number of object</returns>
        int GetObjectCount(UInt64 CollisionGroup = UInt64.MaxValue);

        /// <summary>
        /// Returns number of all objects in the node (and subnodes) with specified collision group on speficied spatialindex
        /// </summary>
        /// <param name="CollisionGroup">Collision group flags</param>
        /// <param name="index">Spatial index to check</param>
        /// <returns>Number of object</returns>
        int GetObjectCount(ref ISpatialIndex index, UInt64 CollisionGroup = UInt64.MaxValue);

        /// <summary>
        /// Adds object to spatialindexer (to any subnode unless forced adding is set), should set it's data (cell and spatial index)
        /// </summary>
        /// <param name="obj">Object to add</param>
        /// <param name="force">If true, will place object directly in the node, won't search for better fit</param>
        void AddObject(TObject obj, bool force = false);

        /// <summary>
        /// Adds object to spatial indexer on specified index (should move objects internal index to new index)
        /// </summary>
        /// <param name="obj">object to add</param>
        /// <param name="index">Index</param>
        /// <param name="force">If true, will place object directly in the node, won't search for better fit</param>
        void AddObject(TObject obj, ref ISpatialIndex index, bool force = false);

        /// <summary>
        /// Removes objects from indexer (including subnodes)
        /// </summary>
        /// <param name="obj">Object to remove</param>
        /// <returns>True if object removed</returns>
        bool RemoveObject(TObject obj);


        /// <summary>
        /// Moves object to new index
        /// </summary>
        /// <param name="obj">Object to move</param>
        /// <param name="newindex">New spatial index</param>
        /// <returns>New spatial portion where the object was moved</returns>
        ISpatialCell<TObject> MoveObject(TObject obj, ref ISpatialIndex newindex);

        /// <summary>
        /// Generates list of all current collisions in the node for specified collision group
        /// </summary>
        /// <param name="collision">Collision flags</param>
        /// <returns>List of colliding pairs</returns>
        List<SpatialPair<TObject>> GenerateCollisions(UInt64 collision = UInt64.MaxValue);

        /// <summary>
        /// Generates list of all current collisions in the node for specified collision group and adds it on the existing list at starting index
        /// </summary>
        /// <param name="collision">Collision flags</param>
        /// <param name="list">List to add collisions</param>
        /// <param name="startindex">Starting index to save collisions</param>
        /// <returns>Number of added collision pairs</returns>
        int GenerateCollisions(List<SpatialPair<TObject>> list, int startindex, UInt64 collision = UInt64.MaxValue);

    }

    /// <summary>
    /// Hiearchical spatial cell which contains more sub nodes
    /// </summary>
    public interface IHierarchicalSpatialCell<TObject> : ISpatialCell<TObject>
        where TObject : ISpatialyIndexable
    {
        /// <summary>
        /// Root of the spatial indexer (owner of the spatial cell list / tree)
        /// </summary>
        ISpatialCell<TObject> Root { get; set; }               
        
        /// <summary>
        /// Number of the subnodes in indexer for hiearchical indexing
        /// </summary>
        int SubNodeCount { get; }

        /// <summary>
        /// Total number of the subnodes in indexer for hiearchical indexing
        /// </summary>
        int TotalSubNodeCount { get; }

        /// <summary>
        /// Returns spatial index for subnode
        /// </summary>
        /// <param name="index">index of the subnode (valid are 0 to SubNodeCount</param>
        /// <returns>Spatial index</returns>
        ISpatialIndex GetSubNodeIndex(int index);

        /// <summary>
        /// Returns subnode with specified integer index
        /// </summary>
        /// <param name="index">index of the subnode (valid are 0 to SubNodeCount</param>
        /// <returns>Subnode</returns>
        ISpatialCell<TObject> GetSubNode(int index);

        /// <summary>
        /// Returns this or subnode with specified spatial index (unlike GetNode doesn't move up in hiearchy)
        /// </summary>
        /// <param name="index">Spatial index</param>
        /// <returns>Subnode containg index or this</returns>
        ISpatialCell<TObject> GetSubNode(ref ISpatialIndex index);
    }



}
