/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var ALIGN_CONST = require('../../display/align/const');
var Axes = require('./lib/geometry/Axes');
var Bodies = require('./lib/factory/Bodies');
var Body = require('./lib/body/Body');
var BodyBounds = require('./BodyBounds');
var Bounds = require('./lib/geometry/Bounds');
var Class = require('../../utils/Class');
var Composite = require('./lib/body/Composite');
var Composites = require('./lib/factory/Composites');
var Constraint = require('./lib/constraint/Constraint');
var Detector = require('./lib/collision/Detector');
var DistanceBetween = require('../../math/distance/DistanceBetween');
var Factory = require('./Factory');
var GetFastValue = require('../../utils/object/GetFastValue');
var GetValue = require('../../utils/object/GetValue');
var Grid = require('./lib/collision/Grid');
var MatterAttractors = require('./lib/plugins/MatterAttractors');
var MatterCollisionEvents = require('./lib/plugins/MatterCollisionEvents');
var MatterLib = require('./lib/core/Matter');
var MatterWrap = require('./lib/plugins/MatterWrap');
var Merge = require('../../utils/object/Merge');
var Pair = require('./lib/collision/Pair');
var Pairs = require('./lib/collision/Pairs');
var Plugin = require('./lib/core/Plugin');
var PluginCache = require('../../plugins/PluginCache');
var Query = require('./lib/collision/Query');
var Resolver = require('./lib/collision/Resolver');
var SAT = require('./lib/collision/SAT');
var SceneEvents = require('../../scene/events');
var Svg = require('./lib/geometry/Svg');
var Vector = require('./lib/geometry/Vector');
var Vertices = require('./lib/geometry/Vertices');
var World = require('./World');

/**
 * @classdesc
 * The Phaser Matter plugin provides the ability to use the Matter JS Physics Engine within your Phaser games.
 * 
 * Unlike Arcade Physics, the other physics system provided with Phaser, Matter JS is a full-body physics system.
 * It features:
 * 
 * * Rigid bodies
 * * Compound bodies
 * * Composite bodies
 * * Concave and convex hulls
 * * Physical properties (mass, area, density etc.)
 * * Restitution (elastic and inelastic collisions)
 * * Collisions (broad-phase, mid-phase and narrow-phase)
 * * Stable stacking and resting
 * * Conservation of momentum
 * * Friction and resistance
 * * Constraints
 * * Gravity
 * * Sleeping and static bodies
 * * Rounded corners (chamfering)
 * * Views (translate, zoom)
 * * Collision queries (raycasting, region tests)
 * * Time scaling (slow-mo, speed-up)
 * 
 * Configuration of Matter is handled via the Matter World Config object, which can be passed in either the
 * Phaser Game Config, or Phaser Scene Config. Here is a basic example:
 * 
 * ```js
 * physics: {
 *     default: 'matter',
 *     matter: {
 *         enableSleeping: true,
 *         gravity: {
 *             y: 0
 *         },
 *         debug: {
 *             showBody: true,
 *             showStaticBody: true
 *         }
 *     }
 * }
 * ```
 * 
 * This class acts as an interface between a Phaser Scene and a single instance of the Matter Engine.
 * 
 * Use it to access the most common Matter features and helper functions.
 * 
 * You can find details, documentation and examples on the Matter JS website: https://brm.io/matter-js/
 *
 * @class MatterPhysics
 * @memberof Phaser.Physics.Matter
 * @constructor
 * @since 3.0.0
 *
 * @param {Phaser.Scene} scene - The Phaser Scene that owns this Matter Physics instance.
 */
var MatterPhysics = new Class({

    initialize:

    function MatterPhysics (scene)
    {
        /**
         * The Phaser Scene that owns this Matter Physics instance
         *
         * @name Phaser.Physics.Matter.MatterPhysics#scene
         * @type {Phaser.Scene}
         * @since 3.0.0
         */
        this.scene = scene;

        /**
         * A reference to the Scene Systems that belong to the Scene owning this Matter Physics instance.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#systems
         * @type {Phaser.Scenes.Systems}
         * @since 3.0.0
         */
        this.systems = scene.sys;

        /**
         * The parsed Matter Configuration object.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#config
         * @type {Phaser.Types.Physics.Matter.MatterWorldConfig}
         * @since 3.0.0
         */
        this.config = this.getConfig();

        /**
         * An instance of the Matter World class. This class is responsible for the updating of the
         * Matter Physics world, as well as handling debug drawing functions.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#world
         * @type {Phaser.Physics.Matter.World}
         * @since 3.0.0
         */
        this.world;

        /**
         * An instance of the Matter Factory. This class provides lots of functions for creating a
         * wide variety of physics objects and adds them automatically to the Matter World.
         * 
         * You can use this class to cut-down on the amount of code required in your game, however,
         * use of the Factory is entirely optional and should be seen as a development aid. It's
         * perfectly possible to create and add components to the Matter world without using it.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#add
         * @type {Phaser.Physics.Matter.Factory}
         * @since 3.0.0
         */
        this.add;

        /**
         * An instance of the Body Bounds class. This class contains functions used for getting the
         * world position from various points around the bounds of a physics body.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#bodyBounds
         * @type {Phaser.Physics.Matter.BodyBounds}
         * @since 3.22.0
         */
        this.bodyBounds;

        //  Body

        /**
         * A reference to the `Matter.Body` module.
         * 
         * The `Matter.Body` module contains methods for creating and manipulating body models.
         * A `Matter.Body` is a rigid body that can be simulated by a `Matter.Engine`.
         * Factories for commonly used body configurations (such as rectangles, circles and other polygons) can be found in the `Bodies` module.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#body
         * @type {MatterJS.BodyFactory}
         * @since 3.18.0
         */
        this.body = Body;

        /**
         * A reference to the `Matter.Composite` module.
         * 
         * The `Matter.Composite` module contains methods for creating and manipulating composite bodies.
         * A composite body is a collection of `Matter.Body`, `Matter.Constraint` and other `Matter.Composite`, therefore composites form a tree structure.
         * It is important to use the functions in this module to modify composites, rather than directly modifying their properties.
         * Note that the `Matter.World` object is also a type of `Matter.Composite` and as such all composite methods here can also operate on a `Matter.World`.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#composite
         * @type {MatterJS.CompositeFactory}
         * @since 3.22.0
         */
        this.composite = Composite;

        //  Collision:

        /**
         * A reference to the `Matter.Detector` module.
         * 
         * The `Matter.Detector` module contains methods for detecting collisions given a set of pairs.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#detector
         * @type {MatterJS.DetectorFactory}
         * @since 3.22.0
         */
        this.detector = Detector;

        /**
         * A reference to the `Matter.Grid` module.
         * 
         * The `Matter.Grid` module contains methods for creating and manipulating collision broadphase grid structures.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#grid
         * @type {MatterJS.GridFactory}
         * @since 3.22.0
         */
        this.grid = Grid;

        /**
         * A reference to the `Matter.Pair` module.
         * 
         * The `Matter.Pair` module contains methods for creating and manipulating collision pairs.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#pair
         * @type {MatterJS.PairFactory}
         * @since 3.22.0
         */
        this.pair = Pair;

        /**
         * A reference to the `Matter.Pairs` module.
         * 
         * The `Matter.Pairs` module contains methods for creating and manipulating collision pair sets.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#pairs
         * @type {MatterJS.PairsFactory}
         * @since 3.22.0
         */
        this.pairs = Pairs;

        /**
         * A reference to the `Matter.Query` module.
         * 
         * The `Matter.Query` module contains methods for performing collision queries.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#query
         * @type {MatterJS.QueryFactory}
         * @since 3.22.0
         */
        this.query = Query;

        /**
         * A reference to the `Matter.Resolver` module.
         * 
         * The `Matter.Resolver` module contains methods for resolving collision pairs.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#resolver
         * @type {MatterJS.ResolverFactory}
         * @since 3.22.0
         */
        this.resolver = Resolver;

        /**
         * A reference to the `Matter.SAT` module.
         * 
         * The `Matter.SAT` module contains methods for detecting collisions using the Separating Axis Theorem.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#sat
         * @type {MatterJS.SATFactory}
         * @since 3.22.0
         */
        this.sat = SAT;

        //  Constraint

        /**
         * A reference to the `Matter.Constraint` module.
         * 
         * The `Matter.Constraint` module contains methods for creating and manipulating constraints.
         * Constraints are used for specifying that a fixed distance must be maintained between two bodies (or a body and a fixed world-space position).
         * The stiffness of constraints can be modified to create springs or elastic.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#constraint
         * @type {MatterJS.ConstraintFactory}
         * @since 3.22.0
         */
        this.constraint = Constraint;

        //  Factory

        /**
         * A reference to the `Matter.Bodies` module.
         * 
         * The `Matter.Bodies` module contains factory methods for creating rigid bodies
         * with commonly used body configurations (such as rectangles, circles and other polygons).
         *
         * @name Phaser.Physics.Matter.MatterPhysics#bodies
         * @type {MatterJS.BodiesFactory}
         * @since 3.18.0
         */
        this.bodies = Bodies;

        /**
         * A reference to the `Matter.Composites` module.
         * 
         * The `Matter.Composites` module contains factory methods for creating composite bodies
         * with commonly used configurations (such as stacks and chains).
         *
         * @name Phaser.Physics.Matter.MatterPhysics#composites
         * @type {MatterJS.CompositesFactory}
         * @since 3.22.0
         */
        this.composites = Composites;

        //  Geometry

        /**
         * A reference to the `Matter.Axes` module.
         * 
         * The `Matter.Axes` module contains methods for creating and manipulating sets of axes.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#axes
         * @type {MatterJS.AxesFactory}
         * @since 3.22.0
         */
        this.axes = Axes;

        /**
         * A reference to the `Matter.Bounds` module.
         * 
         * The `Matter.Bounds` module contains methods for creating and manipulating axis-aligned bounding boxes (AABB).
         *
         * @name Phaser.Physics.Matter.MatterPhysics#bounds
         * @type {MatterJS.BoundsFactory}
         * @since 3.22.0
         */
        this.bounds = Bounds;

        /**
         * A reference to the `Matter.Svg` module.
         * 
         * The `Matter.Svg` module contains methods for converting SVG images into an array of vector points.
         *
         * To use this module you also need the SVGPathSeg polyfill: https://github.com/progers/pathseg
         *
         * @name Phaser.Physics.Matter.MatterPhysics#svg
         * @type {MatterJS.SvgFactory}
         * @since 3.22.0
         */
        this.svg = Svg;

        /**
         * A reference to the `Matter.Vector` module.
         * 
         * The `Matter.Vector` module contains methods for creating and manipulating vectors.
         * Vectors are the basis of all the geometry related operations in the engine.
         * A `Matter.Vector` object is of the form `{ x: 0, y: 0 }`.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#vector
         * @type {MatterJS.VectorFactory}
         * @since 3.22.0
         */
        this.vector = Vector;

        /**
         * A reference to the `Matter.Vertices` module.
         * 
         * The `Matter.Vertices` module contains methods for creating and manipulating sets of vertices.
         * A set of vertices is an array of `Matter.Vector` with additional indexing properties inserted by `Vertices.create`.
         * A `Matter.Body` maintains a set of vertices to represent the shape of the object (its convex hull).
         *
         * @name Phaser.Physics.Matter.MatterPhysics#vertices
         * @type {MatterJS.VerticesFactory}
         * @since 3.22.0
         */
        this.vertices = Vertices;

        /**
         * A reference to the `Matter.Vertices` module.
         * 
         * The `Matter.Vertices` module contains methods for creating and manipulating sets of vertices.
         * A set of vertices is an array of `Matter.Vector` with additional indexing properties inserted by `Vertices.create`.
         * A `Matter.Body` maintains a set of vertices to represent the shape of the object (its convex hull).
         *
         * @name Phaser.Physics.Matter.MatterPhysics#verts
         * @type {MatterJS.VerticesFactory}
         * @since 3.14.0
         */
        this.verts = Vertices;

        /**
         * An internal temp vector used for velocity and force calculations.
         *
         * @name Phaser.Physics.Matter.MatterPhysics#_tempVec2
         * @type {MatterJS.Vector}
         * @private
         * @since 3.22.0
         */
        this._tempVec2 = Vector.create();

        //  Matter plugins

        if (GetValue(this.config, 'plugins.collisionevents', true))
        {
            this.enableCollisionEventsPlugin();
        }

        if (GetValue(this.config, 'plugins.attractors', false))
        {
            this.enableAttractorPlugin();
        }

        if (GetValue(this.config, 'plugins.wrap', false))
        {
            this.enableWrapPlugin();
        }

        Resolver._restingThresh = GetValue(this.config, 'restingThresh', 4);
        Resolver._restingThreshTangent = GetValue(this.config, 'restingThreshTangent', 6);
        Resolver._positionDampen = GetValue(this.config, 'positionDampen', 0.9);
        Resolver._positionWarming = GetValue(this.config, 'positionWarming', 0.8);
        Resolver._frictionNormalMultiplier = GetValue(this.config, 'frictionNormalMultiplier', 5);

        scene.sys.events.once(SceneEvents.BOOT, this.boot, this);
        scene.sys.events.on(SceneEvents.START, this.start, this);
    },

    /**
     * This method is called automatically, only once, when the Scene is first created.
     * Do not invoke it directly.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#boot
     * @private
     * @since 3.5.1
     */
    boot: function ()
    {
        this.world = new World(this.scene, this.config);
        this.add = new Factory(this.world);
        this.bodyBounds = new BodyBounds();

        this.systems.events.once(SceneEvents.DESTROY, this.destroy, this);
    },

    /**
     * This method is called automatically by the Scene when it is starting up.
     * It is responsible for creating local systems, properties and listening for Scene events.
     * Do not invoke it directly.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#start
     * @private
     * @since 3.5.0
     */
    start: function ()
    {
        if (!this.world)
        {
            this.world = new World(this.scene, this.config);
            this.add = new Factory(this.world);
        }

        var eventEmitter = this.systems.events;

        eventEmitter.on(SceneEvents.UPDATE, this.world.update, this.world);
        eventEmitter.on(SceneEvents.POST_UPDATE, this.world.postUpdate, this.world);
        eventEmitter.once(SceneEvents.SHUTDOWN, this.shutdown, this);
    },

    /**
     * This internal method is called when this class starts and retrieves the final Matter World Config.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#getConfig
     * @since 3.0.0
     *
     * @return {Phaser.Types.Physics.Matter.MatterWorldConfig} The Matter World Config.
     */
    getConfig: function ()
    {
        var gameConfig = this.systems.game.config.physics;
        var sceneConfig = this.systems.settings.physics;

        var config = Merge(
            GetFastValue(sceneConfig, 'matter', {}),
            GetFastValue(gameConfig, 'matter', {})
        );

        return config;
    },

    /**
     * Enables the Matter Attractors Plugin.
     * 
     * The attractors plugin that makes it easy to apply continual forces on bodies.
     * It's possible to simulate effects such as wind, gravity and magnetism.
     * 
     * https://github.com/liabru/matter-attractors
     * 
     * This method is called automatically if `plugins.attractors` is set in the Matter World Config.
     * However, you can also call it directly from within your game.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#enableAttractorPlugin
     * @since 3.0.0
     * 
     * @return {this} This Matter Physics instance.
     */
    enableAttractorPlugin: function ()
    {
        Plugin.register(MatterAttractors);
        Plugin.use(MatterLib, MatterAttractors);

        return this;
    },

    /**
     * Enables the Matter Wrap Plugin.
     * 
     * The coordinate wrapping plugin that automatically wraps the position of bodies such that they always stay
     * within the given bounds. Upon crossing a boundary the body will appear on the opposite side of the bounds,
     * while maintaining its velocity.
     * 
     * https://github.com/liabru/matter-wrap
     * 
     * This method is called automatically if `plugins.wrap` is set in the Matter World Config.
     * However, you can also call it directly from within your game.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#enableWrapPlugin
     * @since 3.0.0
     * 
     * @return {this} This Matter Physics instance.
     */
    enableWrapPlugin: function ()
    {
        Plugin.register(MatterWrap);
        Plugin.use(MatterLib, MatterWrap);

        return this;
    },

    /**
     * Enables the Matter Collision Events Plugin.
     * 
     * Note that this plugin is enabled by default. So you should only ever need to call this
     * method if you have specifically disabled the plugin in your Matter World Config.
     * You can disable it by setting `plugins.collisionevents: false` in your Matter World Config.
     * 
     * This plugin triggers three new events on Matter.Body:
     * 
     * 1. `onCollide`
     * 2. `onCollideEnd`
     * 3. `onCollideActive`
     * 
     * These events correspond to the Matter.js events `collisionStart`, `collisionActive` and `collisionEnd`, respectively.
     * You can listen to these events via Matter.Events or they will also be emitted from the Matter World.
     * 
     * This plugin also extends Matter.Body with three convenience functions:
     * 
     * `Matter.Body.setOnCollide(callback)`
     * `Matter.Body.setOnCollideEnd(callback)`
     * `Matter.Body.setOnCollideActive(callback)`
     * 
     * You can register event callbacks by providing a function of type (pair: Matter.Pair) => void
     * 
     * https://github.com/dxu/matter-collision-events
     *
     * @method Phaser.Physics.Matter.MatterPhysics#enableCollisionEventsPlugin
     * @since 3.22.0
     * 
     * @return {this} This Matter Physics instance.
     */
    enableCollisionEventsPlugin: function ()
    {
        Plugin.register(MatterCollisionEvents);
        Plugin.use(MatterLib, MatterCollisionEvents);

        return this;
    },

    /**
     * Pauses the Matter World instance and sets `enabled` to `false`.
     * 
     * A paused world will not run any simulations for the duration it is paused.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#pause
     * @fires Phaser.Physics.Matter.Events#PAUSE
     * @since 3.0.0
     *
     * @return {Phaser.Physics.Matter.World} The Matter World object.
     */
    pause: function ()
    {
        return this.world.pause();
    },

    /**
     * Resumes this Matter World instance from a paused state and sets `enabled` to `true`.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#resume
     * @since 3.0.0
     *
     * @return {Phaser.Physics.Matter.World} The Matter World object.
     */
    resume: function ()
    {
        return this.world.resume();
    },

    /**
     * Sets the Matter Engine to run at fixed timestep of 60Hz and enables `autoUpdate`.
     * If you have set a custom `getDelta` function then this will override it.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#set60Hz
     * @since 3.4.0
     *
     * @return {this} This Matter Physics instance.
     */
    set60Hz: function ()
    {
        this.world.getDelta = this.world.update60Hz;
        this.world.autoUpdate = true;

        return this;
    },

    /**
     * Sets the Matter Engine to run at fixed timestep of 30Hz and enables `autoUpdate`.
     * If you have set a custom `getDelta` function then this will override it.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#set30Hz
     * @since 3.4.0
     *
     * @return {this} This Matter Physics instance.
     */
    set30Hz: function ()
    {
        this.world.getDelta = this.world.update30Hz;
        this.world.autoUpdate = true;

        return this;
    },

    /**
     * Manually advances the physics simulation by one iteration.
     * 
     * You can optionally pass in the `delta` and `correction` values to be used by Engine.update.
     * If undefined they use the Matter defaults of 60Hz and no correction.
     * 
     * Calling `step` directly bypasses any checks of `enabled` or `autoUpdate`.
     * 
     * It also ignores any custom `getDelta` functions, as you should be passing the delta
     * value in to this call.
     *
     * You can adjust the number of iterations that Engine.update performs internally.
     * Use the Scene Matter Physics config object to set the following properties:
     *
     * positionIterations (defaults to 6)
     * velocityIterations (defaults to 4)
     * constraintIterations (defaults to 2)
     *
     * Adjusting these values can help performance in certain situations, depending on the physics requirements
     * of your game.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#step
     * @since 3.4.0
     *
     * @param {number} [delta=16.666] - The delta value.
     * @param {number} [correction=1] - Optional delta correction value.
     */
    step: function (delta, correction)
    {
        this.world.step(delta, correction);
    },

    /**
     * Checks if the vertices of the given body, or an array of bodies, contains the given point, or not.
     * 
     * You can pass in either a single body, or an array of bodies to be checked. This method will
     * return `true` if _any_ of the bodies in the array contain the point. See the `intersectPoint` method if you need
     * to get a list of intersecting bodies.
     * 
     * The point should be transformed into the Matter World coordinate system in advance. This happens by
     * default with Input Pointers, but if you wish to use points from another system you may need to
     * transform them before passing them.
     * 
     * @method Phaser.Physics.Matter.MatterPhysics#containsPoint
     * @since 3.22.0
     *
     * @param {(Phaser.Types.Physics.Matter.MatterBody|Phaser.Types.Physics.Matter.MatterBody[])} body - The body, or an array of bodies, to check against the point.
     * @param {number} x - The horizontal coordinate of the point.
     * @param {number} y - The vertical coordinate of the point.
     * 
     * @return {boolean} `true` if the point is within one of the bodies given, otherwise `false`.
     */
    containsPoint: function (body, x, y)
    {
        body = this.getMatterBodies(body);

        var position = Vector.create(x, y);

        var result = Query.point(body, position);

        return (result.length > 0) ? true : false;
    },

    /**
     * Checks the given coordinates to see if any vertices of the given bodies contain it.
     * 
     * If no bodies are provided it will search all bodies in the Matter World, including within Composites.
     * 
     * The coordinates should be transformed into the Matter World coordinate system in advance. This happens by
     * default with Input Pointers, but if you wish to use coordinates from another system you may need to
     * transform them before passing them.
     * 
     * @method Phaser.Physics.Matter.MatterPhysics#intersectPoint
     * @since 3.22.0
     *
     * @param {number} x - The horizontal coordinate of the point.
     * @param {number} y - The vertical coordinate of the point.
     * @param {Phaser.Types.Physics.Matter.MatterBody[]} [bodies] - An array of bodies to check. If not provided it will search all bodies in the world.
     * 
     * @return {Phaser.Types.Physics.Matter.MatterBody[]} An array of bodies which contain the given point.
     */
    intersectPoint: function (x, y, bodies)
    {
        bodies = this.getMatterBodies(bodies);

        var position = Vector.create(x, y);

        var output = [];

        var result = Query.point(bodies, position);

        result.forEach(function (body)
        {
            if (output.indexOf(body) === -1)
            {
                output.push(body);
            }
        });

        return output;
    },

    /**
     * Checks the given rectangular area to see if any vertices of the given bodies intersect with it.
     * Or, if the `outside` parameter is set to `true`, it checks to see which bodies do not
     * intersect with it.
     * 
     * If no bodies are provided it will search all bodies in the Matter World, including within Composites.
     * 
     * @method Phaser.Physics.Matter.MatterPhysics#intersectRect
     * @since 3.22.0
     *
     * @param {number} x - The horizontal coordinate of the top-left of the area.
     * @param {number} y - The vertical coordinate of the top-left of the area.
     * @param {number} width - The width of the area.
     * @param {number} height - The height of the area.
     * @param {boolean} [outside=false] - If `false` it checks for vertices inside the area, if `true` it checks for vertices outside the area.
     * @param {Phaser.Types.Physics.Matter.MatterBody[]} [bodies] - An array of bodies to check. If not provided it will search all bodies in the world.
     * 
     * @return {Phaser.Types.Physics.Matter.MatterBody[]} An array of bodies that intersect with the given area.
     */
    intersectRect: function (x, y, width, height, outside, bodies)
    {
        if (outside === undefined) { outside = false; }

        bodies = this.getMatterBodies(bodies);

        var bounds = {
            min: { x: x, y: y },
            max: { x: x + width, y: y + height }
        };

        var output = [];

        var result = Query.region(bodies, bounds, outside);

        result.forEach(function (body)
        {
            if (output.indexOf(body) === -1)
            {
                output.push(body);
            }
        });

        return output;
    },

    /**
     * Checks the given ray segment to see if any vertices of the given bodies intersect with it.
     * 
     * If no bodies are provided it will search all bodies in the Matter World.
     * 
     * The width of the ray can be specified via the `rayWidth` parameter.
     * 
     * @method Phaser.Physics.Matter.MatterPhysics#intersectRay
     * @since 3.22.0
     *
     * @param {number} x1 - The horizontal coordinate of the start of the ray segment.
     * @param {number} y1 - The vertical coordinate of the start of the ray segment.
     * @param {number} x2 - The horizontal coordinate of the end of the ray segment.
     * @param {number} y2 - The vertical coordinate of the end of the ray segment.
     * @param {number} [rayWidth=1] - The width of the ray segment.
     * @param {Phaser.Types.Physics.Matter.MatterBody[]} [bodies] - An array of bodies to check. If not provided it will search all bodies in the world.
     * 
     * @return {Phaser.Types.Physics.Matter.MatterBody[]} An array of bodies whos vertices intersect with the ray segment.
     */
    intersectRay: function (x1, y1, x2, y2, rayWidth, bodies)
    {
        if (rayWidth === undefined) { rayWidth = 1; }
        
        bodies = this.getMatterBodies(bodies);

        var result = [];
        var collisions = Query.ray(bodies, Vector.create(x1, y1), Vector.create(x2, y2), rayWidth);

        for (var i = 0; i < collisions.length; i++)
        {
            result.push(collisions[i].body);
        }

        return result;
    },

    /**
     * Checks the given Matter Body to see if it intersects with any of the given bodies.
     * 
     * If no bodies are provided it will check against all bodies in the Matter World.
     * 
     * @method Phaser.Physics.Matter.MatterPhysics#intersectBody
     * @since 3.22.0
     *
     * @param {Phaser.Types.Physics.Matter.MatterBody} body - The target body.
     * @param {Phaser.Types.Physics.Matter.MatterBody[]} [bodies] - An array of bodies to check the target body against. If not provided it will search all bodies in the world.
     * 
     * @return {Phaser.Types.Physics.Matter.MatterBody[]} An array of bodies whos vertices intersect with target body.
     */
    intersectBody: function (body, bodies)
    {
        bodies = this.getMatterBodies(bodies);

        var result = [];
        var collisions = Query.collides(body, bodies);

        for (var i = 0; i < collisions.length; i++)
        {
            var pair = collisions[i];

            if (pair.bodyA === body)
            {
                result.push(pair.bodyB);
            }
            else
            {
                result.push(pair.bodyA);
            }
        }

        return result;
    },

    /**
     * Checks to see if the target body, or an array of target bodies, intersects with any of the given bodies.
     * 
     * If intersection occurs this method will return `true` and, if provided, invoke the callbacks.
     * 
     * If no bodies are provided for the second parameter the target will check again all bodies in the Matter World.
     * 
     * Note that bodies can only overlap if they are in non-colliding collision groups or categories.
     * 
     * If you provide a `processCallback` then the two bodies that overlap are sent to it. This callback
     * must return a boolean and is used to allow you to perform additional processing tests before a final
     * outcome is decided. If it returns `true` then the bodies are finally passed to the `overlapCallback`, if set.
     * 
     * If you provide an `overlapCallback` then the matching pairs of overlapping bodies will be sent to it.
     * 
     * Both callbacks have the following signature: `function (bodyA, bodyB, collisionInfo)` where `bodyA` is always
     * the target body. The `collisionInfo` object contains additional data, such as the angle and depth of penetration.
     * 
     * @method Phaser.Physics.Matter.MatterPhysics#overlap
     * @since 3.22.0
     *
     * @param {(Phaser.Types.Physics.Matter.MatterBody|Phaser.Types.Physics.Matter.MatterBody[])} target - The target body, or array of target bodies, to check.
     * @param {Phaser.Types.Physics.Matter.MatterBody[]} [bodies] - The second body, or array of bodies, to check. If falsey it will check against all bodies in the world.
     * @param {ArcadePhysicsCallback} [overlapCallback] - An optional callback function that is called if the bodies overlap.
     * @param {ArcadePhysicsCallback} [processCallback] - An optional callback function that lets you perform additional checks against the two bodies if they overlap. If this is set then `overlapCallback` will only be invoked if this callback returns `true`.
     * @param {*} [callbackContext] - The context, or scope, in which to run the callbacks.
     * 
     * @return {boolean} `true` if the target body intersects with _any_ of the bodies given, otherwise `false`.
     */
    overlap: function (target, bodies, overlapCallback, processCallback, callbackContext)
    {
        if (overlapCallback === undefined) { overlapCallback = null; }
        if (processCallback === undefined) { processCallback = null; }
        if (callbackContext === undefined) { callbackContext = overlapCallback; }

        if (!Array.isArray(target))
        {
            target = [ target ];
        }

        target = this.getMatterBodies(target);
        bodies = this.getMatterBodies(bodies);

        var match = false;

        for (var i = 0; i < target.length; i++)
        {
            var entry = target[i];

            var collisions = Query.collides(entry, bodies);

            for (var c = 0; c < collisions.length; c++)
            {
                var info = collisions[c];
                var bodyB = (info.bodyA.id === entry.id) ? info.bodyB : info.bodyA;

                if (!processCallback || processCallback.call(callbackContext, entry, bodyB, info))
                {
                    match = true;

                    if (overlapCallback)
                    {
                        overlapCallback.call(callbackContext, entry, bodyB, info);
                    }
                    else if (!processCallback)
                    {
                        //  If there are no callbacks we don't need to test every body, just exit when the first is found
                        return true;
                    }
                }
            }
        }

        return match;
    },

    /**
     * Sets the collision filter category of all given Matter Bodies to the given value.
     * 
     * This number must be a power of two between 2^0 (= 1) and 2^31.
     * 
     * Bodies with different collision groups (see {@link #setCollisionGroup}) will only collide if their collision
     * categories are included in their collision masks (see {@link #setCollidesWith}).
     *
     * @method Phaser.Physics.Matter.MatterPhysics#setCollisionCategory
     * @since 3.22.0
     *
     * @param {Phaser.Types.Physics.Matter.MatterBody[]} bodies - An array of bodies to update. If falsey it will use all bodies in the world.
     * @param {number} value - Unique category bitfield.
     *
     * @return {this} This Matter Physics instance.
     */
    setCollisionCategory: function (bodies, value)
    {
        bodies = this.getMatterBodies(bodies);

        bodies.forEach(function (body)
        {
            body.collisionFilter.category = value;
        });

        return this;
    },

    /**
     * Sets the collision filter group of all given Matter Bodies to the given value.
     * 
     * If the group value is zero, or if two Matter Bodies have different group values,
     * they will collide according to the usual collision filter rules (see {@link #setCollisionCategory} and {@link #setCollisionGroup}).
     * 
     * If two Matter Bodies have the same positive group value, they will always collide;
     * if they have the same negative group value they will never collide.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#setCollisionGroup
     * @since 3.22.0
     *
     * @param {Phaser.Types.Physics.Matter.MatterBody[]} bodies - An array of bodies to update. If falsey it will use all bodies in the world.
     * @param {number} value - Unique group index.
     *
     * @return {this} This Matter Physics instance.
     */
    setCollisionGroup: function (bodies, value)
    {
        bodies = this.getMatterBodies(bodies);

        bodies.forEach(function (body)
        {
            body.collisionFilter.group = value;
        });

        return this;
    },

    /**
     * Sets the collision filter mask of all given Matter Bodies to the given value.
     * 
     * Two Matter Bodies with different collision groups will only collide if each one includes the others
     * category in its mask based on a bitwise AND operation: `(categoryA & maskB) !== 0` and 
     * `(categoryB & maskA) !== 0` are both true.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#setCollidesWith
     * @since 3.22.0
     *
     * @param {Phaser.Types.Physics.Matter.MatterBody[]} bodies - An array of bodies to update. If falsey it will use all bodies in the world.
     * @param {(number|number[])} categories - A unique category bitfield, or an array of them.
     *
     * @return {this} This Matter Physics instance.
     */
    setCollidesWith: function (bodies, categories)
    {
        bodies = this.getMatterBodies(bodies);

        var flags = 0;

        if (!Array.isArray(categories))
        {
            flags = categories;
        }
        else
        {
            for (var i = 0; i < categories.length; i++)
            {
                flags |= categories[i];
            }
        }

        bodies.forEach(function (body)
        {
            body.collisionFilter.mask = flags;
        });

        return this;
    },

    /**
     * Takes an array and returns a new array made from all of the Matter Bodies found in the original array.
     * 
     * For example, passing in Matter Game Objects, such as a bunch of Matter Sprites, to this method, would
     * return an array containing all of their native Matter Body objects.
     * 
     * If the `bodies` argument is falsey, it will return all bodies in the world.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#getMatterBodies
     * @since 3.22.0
     *
     * @param {array} [bodies] - An array of objects to extract the bodies from. If falsey, it will return all bodies in the world.
     *
     * @return {MatterJS.BodyType[]} An array of native Matter Body objects.
     */
    getMatterBodies: function (bodies)
    {
        if (!bodies)
        {
            return this.world.getAllBodies();
        }

        if (!Array.isArray(bodies))
        {
            bodies = [ bodies ];
        }

        var output = [];

        for (var i = 0; i < bodies.length; i++)
        {
            var body = (bodies[i].hasOwnProperty('body')) ? bodies[i].body : bodies[i];

            output.push(body);
        }

        return output;
    },

    /**
     * Sets both the horizontal and vertical linear velocity of the physics bodies.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#setVelocity
     * @since 3.22.0
     *
     * @param {(Phaser.Types.Physics.Matter.MatterBody|Phaser.Types.Physics.Matter.MatterBody[])} bodies - Either a single Body, or an array of bodies to update. If falsey it will use all bodies in the world.
     * @param {number} x - The horizontal linear velocity value.
     * @param {number} y - The vertical linear velocity value.
     *
     * @return {this} This Matter Physics instance.
     */
    setVelocity: function (bodies, x, y)
    {
        bodies = this.getMatterBodies(bodies);

        var vec2 = this._tempVec2;

        vec2.x = x;
        vec2.y = y;

        bodies.forEach(function (body)
        {
            Body.setVelocity(body, vec2);
        });

        return this;
    },

    /**
     * Sets just the horizontal linear velocity of the physics bodies.
     * The vertical velocity of the body is unchanged.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#setVelocityX
     * @since 3.22.0
     *
     * @param {(Phaser.Types.Physics.Matter.MatterBody|Phaser.Types.Physics.Matter.MatterBody[])} bodies - Either a single Body, or an array of bodies to update. If falsey it will use all bodies in the world.
     * @param {number} x - The horizontal linear velocity value.
     *
     * @return {this} This Matter Physics instance.
     */
    setVelocityX: function (bodies, x)
    {
        bodies = this.getMatterBodies(bodies);

        var vec2 = this._tempVec2;

        vec2.x = x;

        bodies.forEach(function (body)
        {
            vec2.y = body.velocity.y;
            Body.setVelocity(body, vec2);
        });

        return this;
    },

    /**
     * Sets just the vertical linear velocity of the physics bodies.
     * The horizontal velocity of the body is unchanged.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#setVelocityY
     * @since 3.22.0
     *
     * @param {(Phaser.Types.Physics.Matter.MatterBody|Phaser.Types.Physics.Matter.MatterBody[])} bodies - Either a single Body, or an array of bodies to update. If falsey it will use all bodies in the world.
     * @param {number} y - The vertical linear velocity value.
     *
     * @return {this} This Matter Physics instance.
     */
    setVelocityY: function (bodies, y)
    {
        bodies = this.getMatterBodies(bodies);

        var vec2 = this._tempVec2;

        vec2.y = y;

        bodies.forEach(function (body)
        {
            vec2.x = body.velocity.x;
            Body.setVelocity(body, vec2);
        });

        return this;
    },

    /**
     * Sets the angular velocity of the bodies instantly.
     * Position, angle, force etc. are unchanged.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#setAngularVelocity
     * @since 3.22.0
     *
     * @param {(Phaser.Types.Physics.Matter.MatterBody|Phaser.Types.Physics.Matter.MatterBody[])} bodies - Either a single Body, or an array of bodies to update. If falsey it will use all bodies in the world.
     * @param {number} value - The angular velocity.
     *
     * @return {this} This Matter Physics instance.
     */
    setAngularVelocity: function (bodies, value)
    {
        bodies = this.getMatterBodies(bodies);

        bodies.forEach(function (body)
        {
            Body.setAngularVelocity(body, value);
        });

        return this;
    },

    /**
     * Applies a force to a body, at the bodies current position, including resulting torque.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#applyForce
     * @since 3.22.0
     *
     * @param {(Phaser.Types.Physics.Matter.MatterBody|Phaser.Types.Physics.Matter.MatterBody[])} bodies - Either a single Body, or an array of bodies to update. If falsey it will use all bodies in the world.
     * @param {Phaser.Types.Math.Vector2Like} force - A Vector that specifies the force to apply.
     *
     * @return {this} This Matter Physics instance.
     */
    applyForce: function (bodies, force)
    {
        bodies = this.getMatterBodies(bodies);

        var vec2 = this._tempVec2;

        bodies.forEach(function (body)
        {
            vec2.x = body.position.x;
            vec2.y = body.position.y;

            Body.applyForce(body, vec2, force);
        });

        return this;
    },

    /**
     * Applies a force to a body, from the given world position, including resulting torque.
     * If no angle is given, the current body angle is used.
     * 
     * Use very small speed values, such as 0.1, depending on the mass and required velocity.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#applyForceFromPosition
     * @since 3.22.0
     *
     * @param {(Phaser.Types.Physics.Matter.MatterBody|Phaser.Types.Physics.Matter.MatterBody[])} bodies - Either a single Body, or an array of bodies to update. If falsey it will use all bodies in the world.
     * @param {Phaser.Types.Math.Vector2Like} position - A Vector that specifies the world-space position to apply the force at.
     * @param {number} speed - A speed value to be applied to a directional force.
     * @param {number} [angle] - The angle, in radians, to apply the force from. Leave undefined to use the current body angle.
     *
     * @return {this} This Matter Physics instance.
     */
    applyForceFromPosition: function (bodies, position, speed, angle)
    {
        bodies = this.getMatterBodies(bodies);

        var vec2 = this._tempVec2;

        bodies.forEach(function (body)
        {
            if (angle === undefined)
            {
                angle = body.angle;
            }

            vec2.x = speed * Math.cos(angle);
            vec2.y = speed * Math.sin(angle);

            Body.applyForce(body, position, vec2);
        });

        return this;
    },

    /**
     * Apply a force to a body based on the given angle and speed.
     * If no angle is given, the current body angle is used.
     * 
     * Use very small speed values, such as 0.1, depending on the mass and required velocity.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#applyForceFromAngle
     * @since 3.22.0
     *
     * @param {(Phaser.Types.Physics.Matter.MatterBody|Phaser.Types.Physics.Matter.MatterBody[])} bodies - Either a single Body, or an array of bodies to update. If falsey it will use all bodies in the world.
     * @param {number} speed - A speed value to be applied to a directional force.
     * @param {number} [angle] - The angle, in radians, to apply the force from. Leave undefined to use the current body angle.
     *
     * @return {this} This Matter Physics instance.
     */
    applyForceFromAngle: function (bodies, speed, angle)
    {
        bodies = this.getMatterBodies(bodies);

        var vec2 = this._tempVec2;

        bodies.forEach(function (body)
        {
            if (angle === undefined)
            {
                angle = body.angle;
            }

            vec2.x = speed * Math.cos(angle);
            vec2.y = speed * Math.sin(angle);

            Body.applyForce(body, { x: body.position.x, y: body.position.y }, vec2);
        });

        return this;
    },

    /**
     * Returns the length of the given constraint, which is the distance between the two points.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#getConstraintLength
     * @since 3.22.0
     *
     * @param {MatterJS.ConstraintType} constraint - The constraint to get the length from.
     *
     * @return {number} The length of the constraint.
     */
    getConstraintLength: function (constraint)
    {
        var aX = constraint.pointA.x;
        var aY = constraint.pointA.y;
        var bX = constraint.pointB.x;
        var bY = constraint.pointB.y;

        if (constraint.bodyA)
        {
            aX += constraint.bodyA.position.x;
            aY += constraint.bodyA.position.y;
        }

        if (constraint.bodyB)
        {
            bX += constraint.bodyB.position.x;
            bY += constraint.bodyB.position.y;
        }

        return DistanceBetween(aX, aY, bX, bY);
    },

    /**
     * Aligns a Body, or Matter Game Object, against the given coordinates.
     * 
     * The alignment takes place using the body bounds, which take into consideration things
     * like body scale and rotation.
     * 
     * Although a Body has a `position` property, it is based on the center of mass for the body,
     * not a dimension based center. This makes aligning bodies difficult, especially if they have
     * rotated or scaled. This method will derive the correct position based on the body bounds and
     * its center of mass offset, in order to align the body with the given coordinate.
     * 
     * For example, if you wanted to align a body so it sat in the bottom-center of the
     * Scene, and the world was 800 x 600 in size:
     * 
     * ```javascript
     * this.matter.alignBody(body, 400, 600, Phaser.Display.Align.BOTTOM_CENTER);
     * ```
     * 
     * You pass in 400 for the x coordinate, because that is the center of the world, and 600 for
     * the y coordinate, as that is the base of the world.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#alignBody
     * @since 3.22.0
     *
     * @param {Phaser.Types.Physics.Matter.MatterBody} body - The Body to align.
     * @param {number} x - The horizontal position to align the body to.
     * @param {number} y - The vertical position to align the body to.
     * @param {integer} align - One of the `Phaser.Display.Align` constants, such as `Phaser.Display.Align.TOP_LEFT`.
     *
     * @return {this} This Matter Physics instance.
     */
    alignBody: function (body, x, y, align)
    {
        body = (body.hasOwnProperty('body')) ? body.body : body;

        var pos;

        switch (align)
        {
            case ALIGN_CONST.TOP_LEFT:
            case ALIGN_CONST.LEFT_TOP:
                pos = this.bodyBounds.getTopLeft(body, x, y);
                break;

            case ALIGN_CONST.TOP_CENTER:
                pos = this.bodyBounds.getTopCenter(body, x, y);
                break;
    
            case ALIGN_CONST.TOP_RIGHT:
            case ALIGN_CONST.RIGHT_TOP:
                pos = this.bodyBounds.getTopRight(body, x, y);
                break;

            case ALIGN_CONST.LEFT_CENTER:
                pos = this.bodyBounds.getLeftCenter(body, x, y);
                break;

            case ALIGN_CONST.CENTER:
                pos = this.bodyBounds.getCenter(body, x, y);
                break;

            case ALIGN_CONST.RIGHT_CENTER:
                pos = this.bodyBounds.getRightCenter(body, x, y);
                break;

            case ALIGN_CONST.LEFT_BOTTOM:
            case ALIGN_CONST.BOTTOM_LEFT:
                pos = this.bodyBounds.getBottomLeft(body, x, y);
                break;

            case ALIGN_CONST.BOTTOM_CENTER:
                pos = this.bodyBounds.getBottomCenter(body, x, y);
                break;

            case ALIGN_CONST.BOTTOM_RIGHT:
            case ALIGN_CONST.RIGHT_BOTTOM:
                pos = this.bodyBounds.getBottomRight(body, x, y);
                break;
        }

        if (pos)
        {
            Body.setPosition(body, pos);
        }

        return this;
    },

    /**
     * The Scene that owns this plugin is shutting down.
     * We need to kill and reset all internal properties as well as stop listening to Scene events.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#shutdown
     * @private
     * @since 3.0.0
     */
    shutdown: function ()
    {
        var eventEmitter = this.systems.events;

        if (this.world)
        {
            eventEmitter.off(SceneEvents.UPDATE, this.world.update, this.world);
            eventEmitter.off(SceneEvents.POST_UPDATE, this.world.postUpdate, this.world);
        }

        eventEmitter.off(SceneEvents.SHUTDOWN, this.shutdown, this);

        if (this.add)
        {
            this.add.destroy();
        }

        if (this.world)
        {
            this.world.destroy();
        }

        this.add = null;
        this.world = null;
    },

    /**
     * The Scene that owns this plugin is being destroyed.
     * We need to shutdown and then kill off all external references.
     *
     * @method Phaser.Physics.Matter.MatterPhysics#destroy
     * @private
     * @since 3.0.0
     */
    destroy: function ()
    {
        this.shutdown();

        this.scene.sys.events.off(SceneEvents.START, this.start, this);

        this.scene = null;
        this.systems = null;
    }

});

PluginCache.register('MatterPhysics', MatterPhysics, 'matterPhysics');

module.exports = MatterPhysics;
