* Masks are positioned in global space and are not relative to the Game Object to which they

* are applied. The reason for this is that multiple Game Objects can all share the same mask.

* Masks have no impact on physics or input detection. They are purely a rendering component

* that allows you to limit what is visible during the render pass.

keep keep replace keep keep keep keep keep

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

var FillStyleCanvas = require('../FillStyleCanvas');
var LineStyleCanvas = require('../LineStyleCanvas');
var SetTransform = require('../../../renderer/canvas/utils/SetTransform');

/**
 * Renders this Game Object with the Canvas Renderer to the given Camera.
 * The object will not render if any of its renderFlags are set or it is being actively filtered out by the Camera.
 * This method should not be called directly. It is a utility function of the Render module.
 *
 * @method Phaser.GameObjects.Rectangle#renderCanvas
 * @since 3.13.0
 * @private
 *
 * @param {Phaser.Renderer.Canvas.CanvasRenderer} renderer - A reference to the current active Canvas renderer.
 * @param {Phaser.GameObjects.Rectangle} src - The Game Object being rendered in this call.
 * @param {Phaser.Cameras.Scene2D.Camera} camera - The Camera that is rendering the Game Object.
 * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - This transform matrix is defined if the game object is nested
 */
var RectangleCanvasRenderer = function (renderer, src, camera, parentMatrix)
{
    var ctx = renderer.currentContext;

    if (SetTransform(renderer, ctx, src, camera, parentMatrix))
    {
        var dx = src._displayOriginX;
        var dy = src._displayOriginY;

        if (src.isFilled)
        {
            FillStyleCanvas(ctx, src);

            ctx.fillRect(
                -dx,
                -dy,
                src.width,
                src.height
            );
        }

        if (src.isStroked)
        {
            LineStyleCanvas(ctx, src);

            ctx.beginPath();

            ctx.rect(
                -dx,
                -dy,
                src.width,
                src.height
            );

            ctx.stroke();
        }

        //  Restore the context saved in SetTransform
        ctx.restore();
    }
};

module.exports = RectangleCanvasRenderer;


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

/**
 * The Game Object Added to Scene Event.
 *
 * This event is dispatched when a Game Object is added to a Scene.
 *
 * Listen for it on a Game Object instance using `GameObject.on('addedtoscene', listener)`.
 *
 * @event Phaser.GameObjects.Events#ADDED_TO_SCENE
 * @since 3.50.0
 *
 * @param {Phaser.GameObjects.GameObject} gameObject - The Game Object that was added to the Scene.
 * @param {Phaser.Scene} scene - The Scene to which the Game Object was added.
 */
module.exports = 'addedtoscene';


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

var BitmapMask = require('../../display/mask/BitmapMask');
var GeometryMask = require('../../display/mask/GeometryMask');

/**
 * Provides methods used for getting and setting the mask of a Game Object.
 *
 * @namespace Phaser.GameObjects.Components.Mask
 * @since 3.0.0
 */

var Mask = {

    /**
     * The Mask this Game Object is using during render.
     *
     * @name Phaser.GameObjects.Components.Mask#mask
     * @type {Phaser.Display.Masks.BitmapMask|Phaser.Display.Masks.GeometryMask}
     * @since 3.0.0
     */
    mask: null,

    /**
     * Sets the mask that this Game Object will use to render with.
     *
     * The mask must have been previously created and can be either a GeometryMask or a BitmapMask.
     * Note: Bitmap Masks only work on WebGL. Geometry Masks work on both WebGL and Canvas.
     *
     * If a mask is already set on this Game Object it will be immediately replaced.
     *
     * Masks are positioned in global space and are not relative to the Game Object to which they
     * are applied. The reason for this is that multiple Game Objects can all share the same mask.
     *
     * Masks have no impact on physics or input detection. They are purely a rendering component
     * that allows you to limit what is visible during the render pass.
     *
     * @method Phaser.GameObjects.Components.Mask#setMask
     * @since 3.6.2
     *
     * @param {Phaser.Display.Masks.BitmapMask|Phaser.Display.Masks.GeometryMask} mask - The mask this Game Object will use when rendering.
     *
     * @return {this} This Game Object instance.
     */
    setMask: function (mask)
    {
        this.mask = mask;

        return this;
    },

    /**
     * Clears the mask that this Game Object was using.
     *
     * @method Phaser.GameObjects.Components.Mask#clearMask
     * @since 3.6.2
     *
     * @param {boolean} [destroyMask=false] - Destroy the mask before clearing it?
     *
     * @return {this} This Game Object instance.
     */
    clearMask: function (destroyMask)
    {
        if (destroyMask === undefined) { destroyMask = false; }

        if (destroyMask && this.mask)
        {
            this.mask.destroy();
        }

        this.mask = null;

        return this;
    },

    /**
     * Creates and returns a Bitmap Mask. This mask can be used by any Game Object,
     * including this one.
     *
     * To create the mask you need to pass in a reference to a renderable Game Object.
     * A renderable Game Object is one that uses a texture to render with, such as an
     * Image, Sprite, Render Texture or BitmapText.
     *
     * If you do not provide a renderable object, and this Game Object has a texture,
     * it will use itself as the object. This means you can call this method to create
     * a Bitmap Mask from any renderable Game Object.
     *
     * @method Phaser.GameObjects.Components.Mask#createBitmapMask
     * @since 3.6.2
     *
     * @param {Phaser.GameObjects.GameObject} [renderable] - A renderable Game Object that uses a texture, such as a Sprite.
     *
     * @return {Phaser.Display.Masks.BitmapMask} This Bitmap Mask that was created.
     */
    createBitmapMask: function (renderable)
    {
        if (renderable === undefined && (this.texture || this.shader))
        {
            // eslint-disable-next-line consistent-this
            renderable = this;
        }

        return new BitmapMask(this.scene, renderable);
    },

    /**
     * Creates and returns a Geometry Mask. This mask can be used by any Game Object,
     * including this one.
     *
     * To create the mask you need to pass in a reference to a Graphics Game Object.
     *
     * If you do not provide a graphics object, and this Game Object is an instance
     * of a Graphics object, then it will use itself to create the mask.
     *
     * This means you can call this method to create a Geometry Mask from any Graphics Game Object.
     *
     * @method Phaser.GameObjects.Components.Mask#createGeometryMask
     * @since 3.6.2
     *
     * @param {Phaser.GameObjects.Graphics} [graphics] - A Graphics Game Object. The geometry within it will be used as the mask.
     *
     * @return {Phaser.Display.Masks.GeometryMask} This Geometry Mask that was created.
     */
    createGeometryMask: function (graphics)
    {
        if (graphics === undefined && this.type === 'Graphics')
        {
            // eslint-disable-next-line consistent-this
            graphics = this;
        }

        return new GeometryMask(this.scene, graphics);
    }

};

module.exports = Mask;


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

/**
 * Returns the top coordinate from the bounds of the Game Object.
 *
 * @function Phaser.Display.Bounds.GetTop
 * @since 3.0.0
 *
 * @param {Phaser.GameObjects.GameObject} gameObject - The Game Object to get the bounds value from.
 *
 * @return {number} The top coordinate of the bounds of the Game Object.
 */
var GetTop = function (gameObject)
{
    return gameObject.y - (gameObject.height * gameObject.originY);
};

module.exports = GetTop;


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

var Class = require('../utils/Class');

/**
 * @classdesc
 * A single frame in an Animation sequence.
 *
 * An AnimationFrame consists of a reference to the Texture it uses for rendering, references to other
 * frames in the animation, and index data. It also has the ability to modify the animation timing.
 *
 * AnimationFrames are generated automatically by the Animation class.
 *
 * @class AnimationFrame
 * @memberof Phaser.Animations
 * @constructor
 * @since 3.0.0
 *
 * @param {string} textureKey - The key of the Texture this AnimationFrame uses.
 * @param {(string|integer)} textureFrame - The key of the Frame within the Texture that this AnimationFrame uses.
 * @param {integer} index - The index of this AnimationFrame within the Animation sequence.
 * @param {Phaser.Textures.Frame} frame - A reference to the Texture Frame this AnimationFrame uses for rendering.
 */
var AnimationFrame = new Class({

    initialize:

    function AnimationFrame (textureKey, textureFrame, index, frame)
    {
        /**
         * The key of the Texture this AnimationFrame uses.
         *
         * @name Phaser.Animations.AnimationFrame#textureKey
         * @type {string}
         * @since 3.0.0
         */
        this.textureKey = textureKey;

        /**
         * The key of the Frame within the Texture that this AnimationFrame uses.
         *
         * @name Phaser.Animations.AnimationFrame#textureFrame
         * @type {(string|integer)}
         * @since 3.0.0
         */
        this.textureFrame = textureFrame;

        /**
         * The index of this AnimationFrame within the Animation sequence.
         *
         * @name Phaser.Animations.AnimationFrame#index
         * @type {integer}
         * @since 3.0.0
         */
        this.index = index;

        /**
         * A reference to the Texture Frame this AnimationFrame uses for rendering.
         *
         * @name Phaser.Animations.AnimationFrame#frame
         * @type {Phaser.Textures.Frame}
         * @since 3.0.0
         */
        this.frame = frame;

        /**
         * Is this the first frame in an animation sequence?
         *
         * @name Phaser.Animations.AnimationFrame#isFirst
         * @type {boolean}
         * @default false
         * @readonly
         * @since 3.0.0
         */
        this.isFirst = false;

        /**
         * Is this the last frame in an animation sequence?
         *
         * @name Phaser.Animations.AnimationFrame#isLast
         * @type {boolean}
         * @default false
         * @readonly
         * @since 3.0.0
         */
        this.isLast = false;

        /**
         * A reference to the AnimationFrame that comes before this one in the animation, if any.
         *
         * @name Phaser.Animations.AnimationFrame#prevFrame
         * @type {?Phaser.Animations.AnimationFrame}
         * @default null
         * @readonly
         * @since 3.0.0
         */
        this.prevFrame = null;

        /**
         * A reference to the AnimationFrame that comes after this one in the animation, if any.
         *
         * @name Phaser.Animations.AnimationFrame#nextFrame
         * @type {?Phaser.Animations.AnimationFrame}
         * @default null
         * @readonly
         * @since 3.0.0
         */
        this.nextFrame = null;

        /**
         * Additional time (in ms) that this frame should appear for during playback.
         * The value is added onto the msPerFrame set by the animation.
         *
         * @name Phaser.Animations.AnimationFrame#duration
         * @type {number}
         * @default 0
         * @since 3.0.0
         */
        this.duration = 0;

        /**
         * What % through the animation does this frame come?
         * This value is generated when the animation is created and cached here.
         *
         * @name Phaser.Animations.AnimationFrame#progress
         * @type {number}
         * @default 0
         * @readonly
         * @since 3.0.0
         */
        this.progress = 0;
    },

    /**
     * Generates a JavaScript object suitable for converting to JSON.
     *
     * @method Phaser.Animations.AnimationFrame#toJSON
     * @since 3.0.0
     *
     * @return {Phaser.Types.Animations.JSONAnimationFrame} The AnimationFrame data.
     */
    toJSON: function ()
    {
        return {
            key: this.textureKey,
            frame: this.textureFrame,
            duration: this.duration
        };
    },

    /**
     * Destroys this object by removing references to external resources and callbacks.
     *
     * @method Phaser.Animations.AnimationFrame#destroy
     * @since 3.0.0
     */
    destroy: function ()
    {
        this.frame = undefined;
    }

});

module.exports = AnimationFrame;


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

var Bodies = require('../lib/factory/Bodies');
var Body = require('../lib/body/Body');
var FuzzyEquals = require('../../../math/fuzzy/Equal');
var GetFastValue = require('../../../utils/object/GetFastValue');
var PhysicsEditorParser = require('../PhysicsEditorParser');
var PhysicsJSONParser = require('../PhysicsJSONParser');
var Vertices = require('../lib/geometry/Vertices');

/**
 * Enables a Matter-enabled Game Object to set its Body. Should be used as a mixin and not directly.
 *
 * @namespace Phaser.Physics.Matter.Components.SetBody
 * @since 3.0.0
 */
var SetBody = {

    /**
     * Set the body on a Game Object to a rectangle.
     *
     * Calling this methods resets previous properties you may have set on the body, including
     * plugins, mass, friction, etc. So be sure to re-apply these in the options object if needed.
     *
     * @method Phaser.Physics.Matter.Components.SetBody#setRectangle
     * @since 3.0.0
     *
     * @param {number} width - Width of the rectangle.
     * @param {number} height - Height of the rectangle.
     * @param {Phaser.Types.Physics.Matter.MatterBodyConfig} [options] - An optional Body configuration object that is used to set initial Body properties on creation.
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    setRectangle: function (width, height, options)
    {
        return this.setBody({ type: 'rectangle', width: width, height: height }, options);
    },

    /**
     * Set the body on a Game Object to a circle.
     *
     * Calling this methods resets previous properties you may have set on the body, including
     * plugins, mass, friction, etc. So be sure to re-apply these in the options object if needed.
     *
     * @method Phaser.Physics.Matter.Components.SetBody#setCircle
     * @since 3.0.0
     *
     * @param {number} radius - The radius of the circle.
     * @param {Phaser.Types.Physics.Matter.MatterBodyConfig} [options] - An optional Body configuration object that is used to set initial Body properties on creation.
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    setCircle: function (radius, options)
    {
        return this.setBody({ type: 'circle', radius: radius }, options);
    },

    /**
     * Set the body on the Game Object to a polygon shape.
     *
     * Calling this methods resets previous properties you may have set on the body, including
     * plugins, mass, friction, etc. So be sure to re-apply these in the options object if needed.
     *
     * @method Phaser.Physics.Matter.Components.SetBody#setPolygon
     * @since 3.0.0
     *
     * @param {number} sides - The number of sides the polygon will have.
     * @param {number} radius - The "radius" of the polygon, i.e. the distance from its center to any vertex. This is also the radius of its circumcircle.
     * @param {Phaser.Types.Physics.Matter.MatterBodyConfig} [options] - An optional Body configuration object that is used to set initial Body properties on creation.
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    setPolygon: function (radius, sides, options)
    {
        return this.setBody({ type: 'polygon', sides: sides, radius: radius }, options);
    },

    /**
     * Set the body on the Game Object to a trapezoid shape.
     *
     * Calling this methods resets previous properties you may have set on the body, including
     * plugins, mass, friction, etc. So be sure to re-apply these in the options object if needed.
     *
     * @method Phaser.Physics.Matter.Components.SetBody#setTrapezoid
     * @since 3.0.0
     *
     * @param {number} width - The width of the trapezoid Body.
     * @param {number} height - The height of the trapezoid Body.
     * @param {number} slope - The slope of the trapezoid. 0 creates a rectangle, while 1 creates a triangle. Positive values make the top side shorter, while negative values make the bottom side shorter.
     * @param {Phaser.Types.Physics.Matter.MatterBodyConfig} [options] - An optional Body configuration object that is used to set initial Body properties on creation.
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    setTrapezoid: function (width, height, slope, options)
    {
        return this.setBody({ type: 'trapezoid', width: width, height: height, slope: slope }, options);
    },

    /**
     * Set this Game Object to use the given existing Matter Body.
     *
     * The body is first removed from the world before being added to this Game Object.
     *
     * @method Phaser.Physics.Matter.Components.SetBody#setExistingBody
     * @since 3.0.0
     *
     * @param {MatterJS.BodyType} body - The Body this Game Object should use.
     * @param {boolean} [addToWorld=true] - Should the body be immediately added to the World?
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    setExistingBody: function (body, addToWorld)
    {
        if (addToWorld === undefined) { addToWorld = true; }

        if (this.body)
        {
            this.world.remove(this.body, true);
        }

        this.body = body;

        for (var i = 0; i < body.parts.length; i++)
        {
            body.parts[i].gameObject = this;
        }

        var _this = this;

        body.destroy = function destroy ()
        {
            _this.world.remove(_this.body, true);
            _this.body.gameObject = null;
        };

        if (addToWorld)
        {
            if (this.world.has(body))
            {
                //  Because it could be part of another Composite
                this.world.remove(body, true);
            }

            this.world.add(body);
        }

        if (this._originComponent)
        {
            var rx = body.render.sprite.xOffset;
            var ry = body.render.sprite.yOffset;

            var comx = body.centerOfMass.x;
            var comy = body.centerOfMass.y;

            if (FuzzyEquals(comx, 0.5) && FuzzyEquals(comy, 0.5))
            {
                this.setOrigin(rx + 0.5, ry + 0.5);
            }
            else
            {
                var cx = body.centerOffset.x;
                var cy = body.centerOffset.y;

                this.setOrigin(rx + (cx / this.displayWidth), ry + (cy / this.displayHeight));
            }
        }

        return this;
    },

    /**
     * Set this Game Object to create and use a new Body based on the configuration object given.
     *
     * Calling this method resets previous properties you may have set on the body, including
     * plugins, mass, friction, etc. So be sure to re-apply these in the options object if needed.
     *
     * @method Phaser.Physics.Matter.Components.SetBody#setBody
     * @since 3.0.0
     *
     * @param {(string|Phaser.Types.Physics.Matter.MatterSetBodyConfig)} config - Either a string, such as `circle`, or a Matter Set Body Configuration object.
     * @param {Phaser.Types.Physics.Matter.MatterBodyConfig} [options] - An optional Body configuration object that is used to set initial Body properties on creation.
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    setBody: function (config, options)
    {
        if (!config)
        {
            return this;
        }

        var body;

        //  Allow them to do: shape: 'circle' instead of shape: { type: 'circle' }
        if (typeof config === 'string')
        {
            //  Using defaults
            config = { type: config };
        }

        var shapeType = GetFastValue(config, 'type', 'rectangle');
        var bodyX = GetFastValue(config, 'x', this._tempVec2.x);
        var bodyY = GetFastValue(config, 'y', this._tempVec2.y);
        var bodyWidth = GetFastValue(config, 'width', this.width);
        var bodyHeight = GetFastValue(config, 'height', this.height);

        switch (shapeType)
        {
            case 'rectangle':
                body = Bodies.rectangle(bodyX, bodyY, bodyWidth, bodyHeight, options);
                break;

            case 'circle':
                var radius = GetFastValue(config, 'radius', Math.max(bodyWidth, bodyHeight) / 2);
                var maxSides = GetFastValue(config, 'maxSides', 25);
                body = Bodies.circle(bodyX, bodyY, radius, options, maxSides);
                break;

            case 'trapezoid':
                var slope = GetFastValue(config, 'slope', 0.5);
                body = Bodies.trapezoid(bodyX, bodyY, bodyWidth, bodyHeight, slope, options);
                break;

            case 'polygon':
                var sides = GetFastValue(config, 'sides', 5);
                var pRadius = GetFastValue(config, 'radius', Math.max(bodyWidth, bodyHeight) / 2);
                body = Bodies.polygon(bodyX, bodyY, sides, pRadius, options);
                break;

            case 'fromVertices':
            case 'fromVerts':

                var verts = GetFastValue(config, 'verts', null);

                if (verts)
                {
                    //  Has the verts array come from Vertices.fromPath, or is it raw?
                    if (typeof verts === 'string')
                    {
                        verts = Vertices.fromPath(verts);
                    }

                    if (this.body && !this.body.hasOwnProperty('temp'))
                    {
                        Body.setVertices(this.body, verts);

                        body = this.body;
                    }
                    else
                    {
                        var flagInternal = GetFastValue(config, 'flagInternal', false);
                        var removeCollinear = GetFastValue(config, 'removeCollinear', 0.01);
                        var minimumArea = GetFastValue(config, 'minimumArea', 10);

                        body = Bodies.fromVertices(bodyX, bodyY, verts, options, flagInternal, removeCollinear, minimumArea);
                    }
                }

                break;

            case 'fromPhysicsEditor':
                body = PhysicsEditorParser.parseBody(bodyX, bodyY, config, options);
                break;

            case 'fromPhysicsTracer':
                body = PhysicsJSONParser.parseBody(bodyX, bodyY, config, options);
                break;
        }

        if (body)
        {
            this.setExistingBody(body, config.addToWorld);
        }

        return this;
    }

};

module.exports = SetBody;


* @param {Phaser.GameObjects.GameObject} [renderable] - A renderable Game Object that uses a texture, such as a Sprite.

* @return {Phaser.Display.Masks.BitmapMask} This Bitmap Mask that was created.

keep keep keep replace keep keep keep keep

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

var Class = require('../../../utils/Class');
var GetFastValue = require('../../../utils/object/GetFastValue');
var ShaderSourceFS = require('../shaders/BitmapMask-frag.js');
var ShaderSourceVS = require('../shaders/BitmapMask-vert.js');
var WebGLPipeline = require('../WebGLPipeline');

/**
 * @classdesc
 *
 * The Bitmap Mask Pipeline handles all of the bitmap mask rendering in WebGL for applying
 * alpha masks to Game Objects. It works by sampling two texture on the fragment shader and
 * using the fragments alpha to clip the region.
 *
 * The fragment shader it uses can be found in `shaders/src/BitmapMask.frag`.
 * The vertex shader it uses can be found in `shaders/src/BitmapMask.vert`.
 *
 * The default shader attributes for this pipeline are:
 *
 * `inPosition` (vec2, offset 0)
 *
 * The default shader uniforms for this pipeline are:
 *
 * `uResolution` (vec2)
 * `uMainSampler` (sampler2D)
 * `uMaskSampler` (sampler2D)
 * `uInvertMaskAlpha` (bool)
 *
 * @class BitmapMaskPipeline
 * @extends Phaser.Renderer.WebGL.WebGLPipeline
 * @memberof Phaser.Renderer.WebGL.Pipelines
 * @constructor
 * @since 3.0.0
 *
 * @param {Phaser.Types.Renderer.WebGL.WebGLPipelineConfig} config - The configuration options for this pipeline.
 */
var BitmapMaskPipeline = new Class({

    Extends: WebGLPipeline,

    initialize:

    function BitmapMaskPipeline (config)
    {
        config.fragShader = GetFastValue(config, 'fragShader', ShaderSourceFS),
        config.vertShader = GetFastValue(config, 'vertShader', ShaderSourceVS),
        config.vertexSize = GetFastValue(config, 'vertexSize', 8),
        config.vertexCapacity = GetFastValue(config, 'vertexCapacity', 3),
        config.vertices = GetFastValue(config, 'vertices', new Float32Array([ -1, 1, -1, -7, 7, 1 ]).buffer),
        config.attributes = GetFastValue(config, 'attributes', [
            {
                name: 'inPosition',
                size: 2,
                type: config.game.renderer.gl.FLOAT,
                normalized: false,
                offset: 0,
                enabled: false,
                location: -1
            }
        ]);
        config.uniforms = GetFastValue(config, 'uniforms', [
            'uResolution',
            'uMainSampler',
            'uMaskSampler',
            'uInvertMaskAlpha'
        ]);

        WebGLPipeline.call(this, config);

        // this.forceZero = true;
    },

    /**
     * Called every time the pipeline is bound by the renderer.
     * Sets the shader program, vertex buffer and other resources.
     * Should only be called when changing pipeline.
     *
     * @method Phaser.Renderer.WebGL.Pipelines.BitmapMaskPipeline#bind
     * @since 3.50.0
     *
     * @param {boolean} [reset=false] - Should the pipeline be fully re-bound after a renderer pipeline clear?
     *
     * @return {this} This WebGLPipeline instance.
     */
    bind: function (reset)
    {
        if (reset === undefined) { reset = false; }

        WebGLPipeline.prototype.bind.call(this, reset);

        this.set2f('uResolution', this.width, this.height);
        this.set1i('uMainSampler', 0);
        this.set1i('uMaskSampler', 1);

        return this;
    },

    /**
     * Binds necessary resources and renders the mask to a separated framebuffer.
     * The framebuffer for the masked object is also bound for further use.
     *
     * @method Phaser.Renderer.WebGL.Pipelines.BitmapMaskPipeline#beginMask
     * @since 3.0.0
     *
     * @param {Phaser.GameObjects.GameObject} mask - GameObject used as mask.
     * @param {Phaser.GameObjects.GameObject} maskedObject - GameObject masked by the mask GameObject.
     * @param {Phaser.Cameras.Scene2D.Camera} camera - The camera rendering the current mask.
     */
    beginMask: function (mask, maskedObject, camera)
    {
        var renderer = this.renderer;
        var gl = this.gl;

        //  The renderable Game Object that is being used for the bitmap mask
        var bitmapMask = mask.bitmapMask;

        if (bitmapMask && gl)
        {
            renderer.flush();

            mask.prevFramebuffer = renderer.currentFramebuffer;

            renderer.setFramebuffer(mask.mainFramebuffer);

            gl.disable(gl.STENCIL_TEST);
            gl.clearColor(0, 0, 0, 0);
            gl.clear(gl.COLOR_BUFFER_BIT);

            if (renderer.currentCameraMask.mask !== mask)
            {
                renderer.currentMask.mask = mask;
                renderer.currentMask.camera = camera;
            }
        }
    },

    /**
     * The masked game objects framebuffer is unbound and its texture
     * is bound together with the mask texture and the mask shader and
     * a draw call with a single quad is processed. Here is where the
     * masking effect is applied.
     *
     * @method Phaser.Renderer.WebGL.Pipelines.BitmapMaskPipeline#endMask
     * @since 3.0.0
     *
     * @param {Phaser.GameObjects.GameObject} mask - GameObject used as a mask.
     */
    endMask: function (mask, camera)
    {
        var gl = this.gl;
        var renderer = this.renderer;

        //  The renderable Game Object that is being used for the bitmap mask
        var bitmapMask = mask.bitmapMask;

        if (bitmapMask && gl)
        {
            renderer.flush();

            //  First we draw the mask to the mask fb
            renderer.setFramebuffer(mask.maskFramebuffer);

            gl.clearColor(0, 0, 0, 0);
            gl.clear(gl.COLOR_BUFFER_BIT);

            renderer.setBlendMode(0, true);

            bitmapMask.renderWebGL(renderer, bitmapMask, camera);

            renderer.flush();

            renderer.setFramebuffer(mask.prevFramebuffer);

            //  Is there a stencil further up the stack?
            var prev = renderer.getCurrentStencilMask();

            if (prev)
            {
                gl.enable(gl.STENCIL_TEST);

                prev.mask.applyStencil(renderer, prev.camera, true);
            }
            else
            {
                renderer.currentMask.mask = null;
            }

            //  Bind bitmap mask pipeline and draw
            renderer.pipelines.set(this);

            gl.activeTexture(gl.TEXTURE1);
            gl.bindTexture(gl.TEXTURE_2D, mask.maskTexture);

            gl.activeTexture(gl.TEXTURE0);
            gl.bindTexture(gl.TEXTURE_2D, mask.mainTexture);

            this.set1i('uInvertMaskAlpha', mask.invertAlpha);

            //  Finally, draw a triangle filling the whole screen
            gl.drawArrays(this.topology, 0, 3);
        }
    }

});

module.exports = BitmapMaskPipeline;


/**
 * @typedef {object} Phaser.Types.Tweens.TweenPropConfig
 * @since 3.18.0
 *
 * @property {(number|string|Phaser.Types.Tweens.GetEndCallback|Phaser.Types.Tweens.TweenPropConfig)} [value] - What the property will be at the END of the Tween.
 * @property {Phaser.Types.Tweens.GetActiveCallback} [getActive] - What the property will be set to immediately when this tween becomes active.
 * @property {Phaser.Types.Tweens.GetEndCallback} [getEnd] - What the property will be at the END of the Tween.
 * @property {Phaser.Types.Tweens.GetStartCallback} [getStart] - What the property will be at the START of the Tween.
 * @property {(string|function)} [ease] - The ease function this tween uses.
 * @property {number} [delay] - Time in ms/frames before tween will start.
 * @property {number} [duration] - Duration of the tween in ms/frames.
 * @property {boolean} [yoyo] - Determines whether the tween should return back to its start value after hold has expired.
 * @property {number} [hold] - Time in ms/frames the tween will pause before repeating or returning to its starting value if yoyo is set to true.
 * @property {number} [repeat] - Number of times to repeat the tween. The tween will always run once regardless, so a repeat value of '1' will play the tween twice.
 * @property {number} [repeatDelay] - Time in ms/frames before the repeat will start.
 * @property {boolean} [flipX] - Should toggleFlipX be called when yoyo or repeat happens?
 * @property {boolean} [flipY] - Should toggleFlipY be called when yoyo or repeat happens?
 */


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

var Class = require('../utils/Class');

/**
 * @classdesc
 * A Tileset is a combination of an image containing the tiles and a container for data about
 * each tile.
 *
 * @class Tileset
 * @memberof Phaser.Tilemaps
 * @constructor
 * @since 3.0.0
 *
 * @param {string} name - The name of the tileset in the map data.
 * @param {integer} firstgid - The first tile index this tileset contains.
 * @param {integer} [tileWidth=32] - Width of each tile (in pixels).
 * @param {integer} [tileHeight=32] - Height of each tile (in pixels).
 * @param {integer} [tileMargin=0] - The margin around all tiles in the sheet (in pixels).
 * @param {integer} [tileSpacing=0] - The spacing between each tile in the sheet (in pixels).
 * @param {object} [tileProperties={}] - Custom properties defined per tile in the Tileset.
 * These typically are custom properties created in Tiled when editing a tileset.
 * @param {object} [tileData={}] - Data stored per tile. These typically are created in Tiled
 * when editing a tileset, e.g. from Tiled's tile collision editor or terrain editor.
 */
var Tileset = new Class({

    initialize:

    function Tileset (name, firstgid, tileWidth, tileHeight, tileMargin, tileSpacing, tileProperties, tileData)
    {
        if (tileWidth === undefined || tileWidth <= 0) { tileWidth = 32; }
        if (tileHeight === undefined || tileHeight <= 0) { tileHeight = 32; }
        if (tileMargin === undefined) { tileMargin = 0; }
        if (tileSpacing === undefined) { tileSpacing = 0; }
        if (tileProperties === undefined) { tileProperties = {}; }
        if (tileData === undefined) { tileData = {}; }

        /**
         * The name of the Tileset.
         *
         * @name Phaser.Tilemaps.Tileset#name
         * @type {string}
         * @since 3.0.0
         */
        this.name = name;

        /**
         * The starting index of the first tile index this Tileset contains.
         *
         * @name Phaser.Tilemaps.Tileset#firstgid
         * @type {integer}
         * @since 3.0.0
         */
        this.firstgid = firstgid;

        /**
         * The width of each tile (in pixels). Use setTileSize to change.
         *
         * @name Phaser.Tilemaps.Tileset#tileWidth
         * @type {integer}
         * @readonly
         * @since 3.0.0
         */
        this.tileWidth = tileWidth;

        /**
         * The height of each tile (in pixels). Use setTileSize to change.
         *
         * @name Phaser.Tilemaps.Tileset#tileHeight
         * @type {integer}
         * @readonly
         * @since 3.0.0
         */
        this.tileHeight = tileHeight;

        /**
         * The margin around the tiles in the sheet (in pixels). Use `setSpacing` to change.
         *
         * @name Phaser.Tilemaps.Tileset#tileMargin
         * @type {integer}
         * @readonly
         * @since 3.0.0
         */
        this.tileMargin = tileMargin;

        /**
         * The spacing between each the tile in the sheet (in pixels). Use `setSpacing` to change.
         *
         * @name Phaser.Tilemaps.Tileset#tileSpacing
         * @type {integer}
         * @readonly
         * @since 3.0.0
         */
        this.tileSpacing = tileSpacing;

        /**
         * Tileset-specific properties per tile that are typically defined in the Tiled editor in the
         * Tileset editor.
         *
         * @name Phaser.Tilemaps.Tileset#tileProperties
         * @type {object}
         * @since 3.0.0
         */
        this.tileProperties = tileProperties;

        /**
         * Tileset-specific data per tile that are typically defined in the Tiled editor, e.g. within
         * the Tileset collision editor. This is where collision objects and terrain are stored.
         *
         * @name Phaser.Tilemaps.Tileset#tileData
         * @type {object}
         * @since 3.0.0
         */
        this.tileData = tileData;

        /**
         * The cached image that contains the individual tiles. Use setImage to set.
         *
         * @name Phaser.Tilemaps.Tileset#image
         * @type {?Phaser.Textures.Texture}
         * @readonly
         * @since 3.0.0
         */
        this.image = null;

        /**
         * The gl texture used by the WebGL renderer.
         *
         * @name Phaser.Tilemaps.Tileset#glTexture
         * @type {?WebGLTexture}
         * @readonly
         * @since 3.11.0
         */
        this.glTexture = null;

        /**
         * The number of tile rows in the the tileset.
         *
         * @name Phaser.Tilemaps.Tileset#rows
         * @type {integer}
         * @readonly
         * @since 3.0.0
         */
        this.rows = 0;

        /**
         * The number of tile columns in the tileset.
         *
         * @name Phaser.Tilemaps.Tileset#columns
         * @type {integer}
         * @readonly
         * @since 3.0.0
         */
        this.columns = 0;

        /**
         * The total number of tiles in the tileset.
         *
         * @name Phaser.Tilemaps.Tileset#total
         * @type {integer}
         * @readonly
         * @since 3.0.0
         */
        this.total = 0;

        /**
         * The look-up table to specific tile image texture coordinates (UV in pixels). Each element
         * contains the coordinates for a tile in an object of the form {x, y}.
         *
         * @name Phaser.Tilemaps.Tileset#texCoordinates
         * @type {object[]}
         * @readonly
         * @since 3.0.0
        */
        this.texCoordinates = [];
    },

    /**
     * Get a tiles properties that are stored in the Tileset. Returns null if tile index is not
     * contained in this Tileset. This is typically defined in Tiled under the Tileset editor.
     *
     * @method Phaser.Tilemaps.Tileset#getTileProperties
     * @since 3.0.0
     *
     * @param {integer} tileIndex - The unique id of the tile across all tilesets in the map.
     *
     * @return {?(object|undefined)}
     */
    getTileProperties: function (tileIndex)
    {
        if (!this.containsTileIndex(tileIndex)) { return null; }

        return this.tileProperties[tileIndex - this.firstgid];
    },

    /**
     * Get a tile's data that is stored in the Tileset. Returns null if tile index is not contained
     * in this Tileset. This is typically defined in Tiled and will contain both Tileset collision
     * info and terrain mapping.
     *
     * @method Phaser.Tilemaps.Tileset#getTileData
     * @since 3.0.0
     *
     * @param {integer} tileIndex - The unique id of the tile across all tilesets in the map.
     *
     * @return {?object|undefined}
     */
    getTileData: function (tileIndex)
    {
        if (!this.containsTileIndex(tileIndex)) { return null; }

        return this.tileData[tileIndex - this.firstgid];
    },

    /**
     * Get a tile's collision group that is stored in the Tileset. Returns null if tile index is not
     * contained in this Tileset. This is typically defined within Tiled's tileset collision editor.
     *
     * @method Phaser.Tilemaps.Tileset#getTileCollisionGroup
     * @since 3.0.0
     *
     * @param {integer} tileIndex - The unique id of the tile across all tilesets in the map.
     *
     * @return {?object}
     */
    getTileCollisionGroup: function (tileIndex)
    {
        var data = this.getTileData(tileIndex);

        return (data && data.objectgroup) ? data.objectgroup : null;
    },

    /**
     * Returns true if and only if this Tileset contains the given tile index.
     *
     * @method Phaser.Tilemaps.Tileset#containsTileIndex
     * @since 3.0.0
     *
     * @param {integer} tileIndex - The unique id of the tile across all tilesets in the map.
     *
     * @return {boolean}
     */
    containsTileIndex: function (tileIndex)
    {
        return (
            tileIndex >= this.firstgid &&
            tileIndex < (this.firstgid + this.total)
        );
    },

    /**
     * Returns the texture coordinates (UV in pixels) in the Tileset image for the given tile index.
     * Returns null if tile index is not contained in this Tileset.
     *
     * @method Phaser.Tilemaps.Tileset#getTileTextureCoordinates
     * @since 3.0.0
     *
     * @param {integer} tileIndex - The unique id of the tile across all tilesets in the map.
     *
     * @return {?object} Object in the form { x, y } representing the top-left UV coordinate
     * within the Tileset image.
     */
    getTileTextureCoordinates: function (tileIndex)
    {
        if (!this.containsTileIndex(tileIndex)) { return null; }

        return this.texCoordinates[tileIndex - this.firstgid];
    },

    /**
     * Sets the image associated with this Tileset and updates the tile data (rows, columns, etc.).
     *
     * @method Phaser.Tilemaps.Tileset#setImage
     * @since 3.0.0
     *
     * @param {Phaser.Textures.Texture} texture - The image that contains the tiles.
     *
     * @return {Phaser.Tilemaps.Tileset} This Tileset object.
     */
    setImage: function (texture)
    {
        this.image = texture;

        this.glTexture = texture.get().source.glTexture;

        this.updateTileData(this.image.source[0].width, this.image.source[0].height);

        return this;
    },

    /**
     * Sets the tile width & height and updates the tile data (rows, columns, etc.).
     *
     * @method Phaser.Tilemaps.Tileset#setTileSize
     * @since 3.0.0
     *
     * @param {integer} [tileWidth] - The width of a tile in pixels.
     * @param {integer} [tileHeight] - The height of a tile in pixels.
     *
     * @return {Phaser.Tilemaps.Tileset} This Tileset object.
     */
    setTileSize: function (tileWidth, tileHeight)
    {
        if (tileWidth !== undefined) { this.tileWidth = tileWidth; }
        if (tileHeight !== undefined) { this.tileHeight = tileHeight; }

        if (this.image)
        {
            this.updateTileData(this.image.source[0].width, this.image.source[0].height);
        }

        return this;
    },

    /**
     * Sets the tile margin & spacing and updates the tile data (rows, columns, etc.).
     *
     * @method Phaser.Tilemaps.Tileset#setSpacing
     * @since 3.0.0
     *
     * @param {integer} [margin] - The margin around the tiles in the sheet (in pixels).
     * @param {integer} [spacing] - The spacing between the tiles in the sheet (in pixels).
     *
     * @return {Phaser.Tilemaps.Tileset} This Tileset object.
     */
    setSpacing: function (margin, spacing)
    {
        if (margin !== undefined) { this.tileMargin = margin; }
        if (spacing !== undefined) { this.tileSpacing = spacing; }

        if (this.image)
        {
            this.updateTileData(this.image.source[0].width, this.image.source[0].height);
        }

        return this;
    },

    /**
     * Updates tile texture coordinates and tileset data.
     *
     * @method Phaser.Tilemaps.Tileset#updateTileData
     * @since 3.0.0
     *
     * @param {integer} imageWidth - The (expected) width of the image to slice.
     * @param {integer} imageHeight - The (expected) height of the image to slice.
     *
     * @return {Phaser.Tilemaps.Tileset} This Tileset object.
     */
    updateTileData: function (imageWidth, imageHeight)
    {
        var rowCount = (imageHeight - this.tileMargin * 2 + this.tileSpacing) / (this.tileHeight + this.tileSpacing);
        var colCount = (imageWidth - this.tileMargin * 2 + this.tileSpacing) / (this.tileWidth + this.tileSpacing);

        if (rowCount % 1 !== 0 || colCount % 1 !== 0)
        {
            console.warn('Image tile area not tile size multiple in: ' + this.name);
        }

        // In Tiled a tileset image that is not an even multiple of the tile dimensions is truncated
        // - hence the floor when calculating the rows/columns.
        rowCount = Math.floor(rowCount);
        colCount = Math.floor(colCount);

        this.rows = rowCount;
        this.columns = colCount;

        // In Tiled, "empty" spaces in a tileset count as tiles and hence count towards the gid
        this.total = rowCount * colCount;

        this.texCoordinates.length = 0;

        var tx = this.tileMargin;
        var ty = this.tileMargin;

        for (var y = 0; y < this.rows; y++)
        {
            for (var x = 0; x < this.columns; x++)
            {
                this.texCoordinates.push({ x: tx, y: ty });
                tx += this.tileWidth + this.tileSpacing;
            }

            tx = this.tileMargin;
            ty += this.tileHeight + this.tileSpacing;
        }

        return this;
    }

});

module.exports = Tileset;


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

var Class = require('../../utils/Class');
var CONST = require('../const');
var File = require('../File');
var FileTypesManager = require('../FileTypesManager');
var GetURL = require('../GetURL');
var GetFastValue = require('../../utils/object/GetFastValue');
var IsPlainObject = require('../../utils/object/IsPlainObject');

/**
 * @classdesc
 * A single Video File suitable for loading by the Loader.
 *
 * These are created when you use the Phaser.Loader.LoaderPlugin#video method and are not typically created directly.
 *
 * For documentation about what all the arguments and configuration options mean please see Phaser.Loader.LoaderPlugin#video.
 *
 * @class VideoFile
 * @extends Phaser.Loader.File
 * @memberof Phaser.Loader.FileTypes
 * @constructor
 * @since 3.20.0
 *
 * @param {Phaser.Loader.LoaderPlugin} loader - A reference to the Loader that is responsible for this file.
 * @param {(string|Phaser.Types.Loader.FileTypes.VideoFileConfig)} key - The key to use for this file, or a file configuration object.
 * @param {any} [urlConfig] - The absolute or relative URL to load this file from in a config object.
 * @param {string} [loadEvent] - The load event to listen for when _not_ loading as a blob. Either 'loadeddata', 'canplay' or 'canplaythrough'.
 * @param {boolean} [asBlob] - Load the video as a data blob, or via the Video element?
 * @param {boolean} [noAudio] - Does the video have an audio track? If not you can enable auto-playing on it.
 * @param {Phaser.Types.Loader.XHRSettingsObject} [xhrSettings] - Extra XHR Settings specifically for this file.
 */
var VideoFile = new Class({

    Extends: File,

    initialize:

    //  URL is an object created by VideoFile.getVideoURL
    function VideoFile (loader, key, urlConfig, loadEvent, asBlob, noAudio, xhrSettings)
    {
        if (loadEvent === undefined) { loadEvent = 'loadeddata'; }
        if (asBlob === undefined) { asBlob = false; }
        if (noAudio === undefined) { noAudio = false; }

        if (loadEvent !== 'loadeddata' && loadEvent !== 'canplay' && loadEvent !== 'canplaythrough')
        {
            loadEvent = 'loadeddata';
        }

        var fileConfig = {
            type: 'video',
            cache: loader.cacheManager.video,
            extension: urlConfig.type,
            responseType: 'blob',
            key: key,
            url: urlConfig.url,
            xhrSettings: xhrSettings,
            config: {
                loadEvent: loadEvent,
                asBlob: asBlob,
                noAudio: noAudio
            }
        };

        this.onLoadCallback = this.onVideoLoadHandler.bind(this);
        this.onErrorCallback = this.onVideoErrorHandler.bind(this);

        File.call(this, loader, fileConfig);
    },

    /**
     * Called automatically by Loader.nextFile.
     * This method controls what extra work this File does with its loaded data.
     *
     * @method Phaser.Loader.FileTypes.VideoFile#onProcess
     * @since 3.20.0
     */
    onProcess: function ()
    {
        this.state = CONST.FILE_PROCESSING;

        if (!this.config.asBlob)
        {
            this.onProcessComplete();

            return;
        }

        //  Load Video as blob

        var video = this.createVideoElement();

        this.data = video;

        var _this = this;

        this.data.onloadeddata = function ()
        {
            _this.onProcessComplete();
        };

        this.data.onerror = function ()
        {
            File.revokeObjectURL(_this.data);

            _this.onProcessError();
        };

        File.createObjectURL(video, this.xhrLoader.response, '');

        video.load();
    },

    /**
     * Creates a Video Element within the DOM.
     *
     * @method Phaser.Loader.FileTypes.VideoFile#createVideoElement
     * @private
     * @since 3.20.0
     *
     * @return {HTMLVideoElement} The newly created Video element.
     */
    createVideoElement: function ()
    {
        var video = document.createElement('video');

        video.controls = false;
        video.crossOrigin = this.loader.crossOrigin;

        if (this.config.noAudio)
        {
            video.muted = true;
            video.defaultMuted = true;

            video.setAttribute('autoplay', 'autoplay');
        }

        video.setAttribute('playsinline', 'playsinline');
        video.setAttribute('preload', 'auto');

        return video;
    },

    /**
     * Internal load event callback.
     *
     * @method Phaser.Loader.FileTypes.VideoFile#onVideoLoadHandler
     * @private
     * @since 3.20.0
     *
     * @param {ProgressEvent} event - The DOM ProgressEvent that resulted from this load.
     */
    onVideoLoadHandler: function (event)
    {
        var video = event.target;

        video.removeEventListener(this.config.loadEvent, this.onLoadCallback, true);
        video.removeEventListener('error', this.onErrorCallback, true);

        this.data = video;

        this.resetXHR();

        this.loader.nextFile(this, true);
    },

    /**
     * Internal load error event callback.
     *
     * @method Phaser.Loader.FileTypes.VideoFile#onVideoErrorHandler
     * @private
     * @since 3.20.0
     *
     * @param {ProgressEvent} event - The DOM ProgressEvent that resulted from this load.
     */
    onVideoErrorHandler: function (event)
    {
        var video = event.target;

        if (video)
        {
            video.removeEventListener(this.config.loadEvent, this.onLoadCallback, true);
            video.removeEventListener('error', this.onErrorCallback, true);
        }

        this.resetXHR();

        this.loader.nextFile(this, false);
    },

    /**
     * Called by the Loader, starts the actual file downloading.
     * During the load the methods onLoad, onError and onProgress are called, based on the XHR events.
     * You shouldn't normally call this method directly, it's meant to be invoked by the Loader.
     *
     * @method Phaser.Loader.FileTypes.VideoFile#load
     * @since 3.20.0
     */
    load: function ()
    {
        var loadEvent = this.config.loadEvent;

        if (this.config.asBlob)
        {
            File.prototype.load.call(this);
        }
        else
        {
            this.percentComplete = 0;

            var video = this.createVideoElement();

            video.addEventListener(loadEvent, this.onLoadCallback, true);
            video.addEventListener('error', this.onErrorCallback, true);

            video.src = GetURL(this, this.loader.baseURL);

            video.load();
        }
    }

});

VideoFile.create = function (loader, key, urls, loadEvent, asBlob, noAudio, xhrSettings)
{
    var game = loader.systems.game;

    //  url may be inside key, which may be an object
    if (IsPlainObject(key))
    {
        urls = GetFastValue(key, 'url', []);
        loadEvent = GetFastValue(key, 'loadEvent', 'loadeddata');
        asBlob = GetFastValue(key, 'asBlob', false);
        noAudio = GetFastValue(key, 'noAudio', false);
        xhrSettings = GetFastValue(key, 'xhrSettings');
    }

    var urlConfig = VideoFile.getVideoURL(game, urls);

    if (urlConfig)
    {
        return new VideoFile(loader, key, urlConfig, loadEvent, asBlob, noAudio, xhrSettings);
    }
};

VideoFile.getVideoURL = function (game, urls)
{
    if (!Array.isArray(urls))
    {
        urls = [ urls ];
    }

    for (var i = 0; i < urls.length; i++)
    {
        var url = GetFastValue(urls[i], 'url', urls[i]);

        if (url.indexOf('blob:') === 0)
        {
            return {
                url: url,
                type: ''
            };
        }

        var videoType;

        if (url.indexOf('data:') === 0)
        {
            videoType = url.split(',')[0].match(/\/(.*?);/);
        }
        else
        {
            videoType = url.match(/\.([a-zA-Z0-9]+)($|\?)/);
        }

        videoType = GetFastValue(urls[i], 'type', (videoType) ? videoType[1] : '').toLowerCase();

        if (game.device.video[videoType])
        {
            return {
                url: url,
                type: videoType
            };
        }
    }

    return null;
};

/**
 * Adds a Video file, or array of video files, to the current load queue.
 *
 * You can call this method from within your Scene's `preload`, along with any other files you wish to load:
 *
 * ```javascript
 * function preload ()
 * {
 *     this.load.video('intro', [ 'video/level1.mp4', 'video/level1.webm', 'video/level1.mov' ]);
 * }
 * ```
 *
 * The file is **not** loaded right away. It is added to a queue ready to be loaded either when the loader starts,
 * or if it's already running, when the next free load slot becomes available. This happens automatically if you
 * are calling this from within the Scene's `preload` method, or a related callback. Because the file is queued
 * it means you cannot use the file immediately after calling this method, but must wait for the file to complete.
 * The typical flow for a Phaser Scene is that you load assets in the Scene's `preload` method and then when the
 * Scene's `create` method is called you are guaranteed that all of those assets are ready for use and have been
 * loaded.
 *
 * The key must be a unique String. It is used to add the file to the global Video Cache upon a successful load.
 * The key should be unique both in terms of files being loaded and files already present in the Video Cache.
 * Loading a file using a key that is already taken will result in a warning. If you wish to replace an existing file
 * then remove it from the Video Cache first, before loading a new one.
  *
 * Instead of passing arguments you can pass a configuration object, such as:
 *
 * ```javascript
 * this.load.video({
 *     key: 'intro',
 *     url: [ 'video/level1.mp4', 'video/level1.webm', 'video/level1.mov' ],
 *     asBlob: false,
 *     noAudio: true
 * });
 * ```
 *
 * See the documentation for `Phaser.Types.Loader.FileTypes.VideoFileConfig` for more details.
 *
 * The URLs can be relative or absolute. If the URLs are relative the `Loader.baseURL` and `Loader.path` values will be prepended to them.
 *
 * Due to different browsers supporting different video file types you should usually provide your video files in a variety of formats.
 * mp4, mov and webm are the most common. If you provide an array of URLs then the Loader will determine which _one_ file to load based on
 * browser support, starting with the first in the array and progressing to the end.
 *
 * Unlike most asset-types, videos do not _need_ to be preloaded. You can create a Video Game Object and then call its `loadURL` method,
 * to load a video at run-time, rather than in advance.
 *
 * Note: The ability to load this type of file will only be available if the Video File type has been built into Phaser.
 * It is available in the default build but can be excluded from custom builds.
 *
 * @method Phaser.Loader.LoaderPlugin#video
 * @fires Phaser.Loader.LoaderPlugin#ADD
 * @since 3.20.0
 *
 * @param {(string|Phaser.Types.Loader.FileTypes.VideoFileConfig|Phaser.Types.Loader.FileTypes.VideoFileConfig[])} key - The key to use for this file, or a file configuration object, or array of them.
 * @param {(string|string[])} [urls] - The absolute or relative URL to load the video files from.
 * @param {string} [loadEvent='loadeddata'] - The load event to listen for when _not_ loading as a blob. Either `loadeddata`, `canplay` or `canplaythrough`.
 * @param {boolean} [asBlob=false] - Load the video as a data blob, or stream it via the Video element?
 * @param {boolean} [noAudio=false] - Does the video have an audio track? If not you can enable auto-playing on it.
 * @param {Phaser.Types.Loader.XHRSettingsObject} [xhrSettings] - An XHR Settings configuration object. Used in replacement of the Loaders default XHR Settings.
 *
 * @return {this} The Loader instance.
 */
FileTypesManager.register('video', function (key, urls, loadEvent, asBlob, noAudio, xhrSettings)
{
    var videoFile;

    if (Array.isArray(key))
    {
        for (var i = 0; i < key.length; i++)
        {
            //  If it's an array it has to be an array of Objects, so we get everything out of the 'key' object
            videoFile = VideoFile.create(this, key[i]);

            if (videoFile)
            {
                this.addFile(videoFile);
            }
        }
    }
    else
    {
        videoFile = VideoFile.create(this, key, urls, loadEvent, asBlob, noAudio, xhrSettings);

        if (videoFile)
        {
            this.addFile(videoFile);
        }
    }

    return this;
});

module.exports = VideoFile;


* To create the mask you need to pass in a reference to a Graphics Game Object.

* If you do not provide a graphics object, and this Game Object is an instance

* of a Graphics object, then it will use itself to create the mask.

* This means you can call this method to create a Geometry Mask from any Graphics Game Object.

keep keep keep keep keep replace keep keep keep


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

var Class = require('../../../utils/Class');
var Shape = require('../Shape');
var GeomTriangle = require('../../../geom/triangle/Triangle');
var TriangleRender = require('./TriangleRender');

/**
 * @classdesc
 * The Triangle Shape is a Game Object that can be added to a Scene, Group or Container. You can
 * treat it like any other Game Object in your game, such as tweening it, scaling it, or enabling
 * it for input or physics. It provides a quick and easy way for you to render this shape in your
 * game without using a texture, while still taking advantage of being fully batched in WebGL.
 *
 * This shape supports both fill and stroke colors.
 *
 * The Triangle consists of 3 lines, joining up to form a triangular shape. You can control the
 * position of each point of these lines. The triangle is always closed and cannot have an open
 * face. If you require that, consider using a Polygon instead.
 *
 * @class Triangle
 * @extends Phaser.GameObjects.Shape
 * @memberof Phaser.GameObjects
 * @constructor
 * @since 3.13.0
 *
 * @param {Phaser.Scene} scene - The Scene to which this Game Object belongs. A Game Object can only belong to one Scene at a time.
 * @param {number} [x=0] - The horizontal position of this Game Object in the world.
 * @param {number} [y=0] - The vertical position of this Game Object in the world.
 * @param {number} [x1=0] - The horizontal position of the first point in the triangle.
 * @param {number} [y1=128] - The vertical position of the first point in the triangle.
 * @param {number} [x2=64] - The horizontal position of the second point in the triangle.
 * @param {number} [y2=0] - The vertical position of the second point in the triangle.
 * @param {number} [x3=128] - The horizontal position of the third point in the triangle.
 * @param {number} [y3=128] - The vertical position of the third point in the triangle.
 * @param {number} [fillColor] - The color the triangle will be filled with, i.e. 0xff0000 for red.
 * @param {number} [fillAlpha] - The alpha the triangle will be filled with. You can also set the alpha of the overall Shape using its `alpha` property.
 */
var Triangle = new Class({

    Extends: Shape,

    Mixins: [
        TriangleRender
    ],

    initialize:

    function Triangle (scene, x, y, x1, y1, x2, y2, x3, y3, fillColor, fillAlpha)
    {
        if (x === undefined) { x = 0; }
        if (y === undefined) { y = 0; }
        if (x1 === undefined) { x1 = 0; }
        if (y1 === undefined) { y1 = 128; }
        if (x2 === undefined) { x2 = 64; }
        if (y2 === undefined) { y2 = 0; }
        if (x3 === undefined) { x3 = 128; }
        if (y3 === undefined) { y3 = 128; }

        Shape.call(this, scene, 'Triangle', new GeomTriangle(x1, y1, x2, y2, x3, y3));

        var width = this.geom.right - this.geom.left;
        var height = this.geom.bottom - this.geom.top;

        this.setPosition(x, y);
        this.setSize(width, height);

        if (fillColor !== undefined)
        {
            this.setFillStyle(fillColor, fillAlpha);
        }

        this.updateDisplayOrigin();
        this.updateData();
    },

    /**
     * Sets the data for the lines that make up this Triangle shape.
     *
     * @method Phaser.GameObjects.Triangle#setTo
     * @since 3.13.0
     *
     * @param {number} [x1=0] - The horizontal position of the first point in the triangle.
     * @param {number} [y1=0] - The vertical position of the first point in the triangle.
     * @param {number} [x2=0] - The horizontal position of the second point in the triangle.
     * @param {number} [y2=0] - The vertical position of the second point in the triangle.
     * @param {number} [x3=0] - The horizontal position of the third point in the triangle.
     * @param {number} [y3=0] - The vertical position of the third point in the triangle.
     *
     * @return {this} This Game Object instance.
     */
    setTo: function (x1, y1, x2, y2, x3, y3)
    {
        this.geom.setTo(x1, y1, x2, y2, x3, y3);

        return this.updateData();
    },

    /**
     * Internal method that updates the data and path values.
     *
     * @method Phaser.GameObjects.Triangle#updateData
     * @private
     * @since 3.13.0
     *
     * @return {this} This Game Object instance.
     */
    updateData: function ()
    {
        var path = [];
        var tri = this.geom;
        var line = this._tempLine;

        tri.getLineA(line);

        path.push(line.x1, line.y1, line.x2, line.y2);

        tri.getLineB(line);

        path.push(line.x2, line.y2);

        tri.getLineC(line);

        path.push(line.x2, line.y2);

        this.pathData = path;

        return this;
    }

});

module.exports = Triangle;


/**
 * @typedef {object} Phaser.Types.Input.Keyboard.CursorKeys
 * @since 3.0.0
 *
 * @property {Phaser.Input.Keyboard.Key} up - A Key object mapping to the UP arrow key.
 * @property {Phaser.Input.Keyboard.Key} down - A Key object mapping to the DOWN arrow key.
 * @property {Phaser.Input.Keyboard.Key} left - A Key object mapping to the LEFT arrow key.
 * @property {Phaser.Input.Keyboard.Key} right - A Key object mapping to the RIGHT arrow key.
 * @property {Phaser.Input.Keyboard.Key} space - A Key object mapping to the SPACE BAR key.
 * @property {Phaser.Input.Keyboard.Key} shift - A Key object mapping to the SHIFT key.
 */


/// <reference path="./spine.d.ts" />
/// <reference path="./SpinePlugin.d.ts" />

declare class SpineGameObject extends Phaser.GameObjects.GameObject implements Omit<Phaser.GameObjects.Components.ComputedSize, 'setSize'>, Phaser.GameObjects.Components.Depth, Phaser.GameObjects.Components.Flip, Phaser.GameObjects.Components.ScrollFactor, Phaser.GameObjects.Components.Transform, Phaser.GameObjects.Components.Visible {
    constructor(scene: Phaser.Scene, pluginManager: SpinePlugin, x: number, y: number, key?: string, animationName?: string, loop?: boolean);

    alpha: number;
    angle: number;

    readonly blendMode: number;

    blue: number;
    bounds: any;
    displayOriginX: number;
    displayOriginY: number;
    drawDebug: boolean;
    depth: number;
    displayWidth: number;
    displayHeight: number;
    flipX: boolean;
    flipY: boolean;
    green: number;
    height: number;
    plugin: SpinePlugin;
    preMultipliedAlpha: boolean;
    red: number;
    root: spine.Bone;
    rotation: number;
    scale: number;
    scaleX: number
    scaleY: number
    scrollFactorX: number;
    scrollFactorY: number;
    skeleton: spine.Skeleton;
    skeletonData: spine.SkeletonData;
    // @ts-ignore - spine.AnimationState significantly different than GameObject.state
    state: spine.AnimationState;
    stateData: spine.AnimationStateData;
    timeScale: number;
    visible: boolean;
    x: number;
    y: number;
    z: number;
    w: number;
    width: number;

    addAnimation(trackIndex: number, animationName: string, loop?: boolean, delay?: number): spine.TrackEntry;
    angleBoneToXY(bone: spine.Bone, worldX: number, worldY: number, offset?: number, minAngle?: number, maxAngle?: number): SpineGameObject;
    clearTrack(trackIndex: number): SpineGameObject;
    clearTracks(): SpineGameObject;
    findAnimation(animationName: string): spine.Animation;
    findBone(boneName: string): spine.Bone;
    findBoneIndex(boneName: string): number;
    findEvent(eventDataName: string): spine.EventData;
    findIkConstraint(constraintName: string): spine.IkConstraintData;
    findPathConstraint(constraintName: string): spine.PathConstraintData;
    findPathConstraintIndex(constraintName: string): number;
    findSkin(skinName: string): spine.Skin;
    findSlot(slotName: string): spine.Slot;
    findSlotIndex(slotName: string): number;
    findTransformConstraint(constraintName: string): spine.TransformConstraintData;
    getAnimationList(): string[];
    getAttachment(slotIndex: number, attachmentName: string): spine.Attachment;
    getAttachmentByName(slotName: string, attachmentName: string): spine.Attachment;
    getBoneList(): string[];
    getBounds(): any;
    getCurrentAnimation(trackIndex?: number): spine.Animation;
    getLocalTransformMatrix(tempMatrix?: Phaser.GameObjects.Components.TransformMatrix): Phaser.GameObjects.Components.TransformMatrix;
    getParentRotation(): number;
    getRootBone(): spine.Bone;
    getSkinList(): string[];
    getSlotList(): string[];
    getWorldTransformMatrix(tempMatrix?: Phaser.GameObjects.Components.TransformMatrix, parentMatrix?: Phaser.GameObjects.Components.TransformMatrix): Phaser.GameObjects.Components.TransformMatrix;
    play(animationName: string, loop?: boolean, ignoreIfPlaying?: boolean): SpineGameObject;

    protected preUpdate(time: number, delta: number): void;
    protected preDestroy(): void;

    refresh(): SpineGameObject;
    resetFlip(): this;
    setAlpha(value?: number): SpineGameObject;
    setAngle(degrees?: number): this;
    setAnimation(trackIndex: number, animationName: string, loop?: boolean, ignoreIfPlaying?: boolean): spine.TrackEntry;
    setAttachment(slotName: string, attachmentName: string): SpineGameObject;
    setBonesToSetupPose(): SpineGameObject;
    setColor(color?: number, slotName?: string): SpineGameObject;
    setDepth(value: number): this;
    setDisplaySize(width: number, height: number): this;
    setEmptyAnimation(trackIndex: number, mixDuration?: number): spine.TrackEntry;
    setFlipX(value: boolean): this;
    setFlipY(value: boolean): this;
    setFlip(x: boolean, y: boolean): this;
    setMix(fromName: string, toName: string, duration?: number): SpineGameObject;
    setOffset(offsetX?: number, offsetY?: number): SpineGameObject;
    setPosition(x?: number, y?: number, z?: number, w?: number): this;
    setRandomPosition(x?: number, y?: number, width?: number, height?: number): this;
    setRotation(radians?: number): this;
    setScale(x: number, y?: number): this;
    setScrollFactor(x: number, y?: number): this;
    setSize(width?: number, height?: number, offsetX?: number, offsetY?: number): SpineGameObject;
    setSkeleton(atlasDataKey: string, skeletonJSON: object, animationName?: string, loop?: boolean): SpineGameObject;
    setSkeletonFromJSON(atlasDataKey: string, skeletonJSON: object, animationName?: string, loop?: boolean): SpineGameObject;
    setSkin(newSkin: spine.Skin): SpineGameObject;
    setSkinByName(skinName: string): SpineGameObject;
    setSlotsToSetupPose(): SpineGameObject;
    setToSetupPose(): SpineGameObject;
    setVisible(value: boolean): this;
    setX(value?: number): this;
    setY(value?: number): this;
    setZ(value?: number): this;
    setW(value?: number): this;
    toggleFlipX(): this;
    toggleFlipY(): this;
    updateSize(): SpineGameObject;
    willRender(): boolean;
}

declare interface SpineGameObjectConfig extends Phaser.Types.GameObjects.GameObjectConfig {
    key?: string;
    animationName?: string;
    loop?: boolean;
    skinName?: string;
    slotName?: string;
    attachmentName?: string;
}


* @param {Phaser.GameObjects.Graphics} [graphics] - A Graphics Game Object. The geometry within it will be used as the mask.

* @return {Phaser.Display.Masks.GeometryMask} This Geometry Mask that was created.

keep keep keep replace keep keep keep keep keep


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

/**
 * The Loader Plugin Start Event.
 *
 * This event is dispatched when the Loader starts running. At this point load progress is zero.
 *
 * This event is dispatched even if there aren't any files in the load queue.
 *
 * Listen to it from a Scene using: `this.load.on('start', listener)`.
 *
 * @event Phaser.Loader.Events#START
 * @since 3.0.0
 *
 * @param {Phaser.Loader.LoaderPlugin} loader - A reference to the Loader Plugin that dispatched this event.
 */
module.exports = 'start';


/**
 * @callback Phaser.Types.Tweens.TweenOnUpdateCallback
 * @since 3.18.0
 *
 * @param {Phaser.Tweens.Tween} tween - The tween.
 * @param {any} target - The tween target.
 * @param {...any} param - Any value passed in `onUpdateParams`.
 */


/**
 * @typedef {object} Phaser.Types.GameObjects.Particles.ParticleEmitterDeathZoneConfig
 * @since 3.0.0
 *
 * @property {Phaser.Types.GameObjects.Particles.DeathZoneSource} source - A shape representing the zone. See {@link Phaser.GameObjects.Particles.Zones.DeathZone#source}.
 * @property {string} [type='onEnter'] - 'onEnter' or 'onLeave'.
 */


keep keep keep keep replace replace keep keep keep


/**
 * @callback Phaser.Types.Tweens.TimelineOnStartCallback
 * @since 3.18.0
 *
 * @param {Phaser.Tweens.Timeline} timeline - The timeline.
 * @param {...any} param - Any value passed in `onStartParams`.
 */


/**
 * @typedef {object} Phaser.Types.Math.Vector3Like
 * @since 3.50.0
 *
 * @property {number} [x] - The x component.
 * @property {number} [y] - The y component.
 * @property {number} [z] - The z component.
 */


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

/**
 * @typedef {object} Phaser.Physics.Matter.Events.CollisionStartEvent
 *
 * @property {Phaser.Types.Physics.Matter.MatterCollisionData[]} pairs - A list of all affected pairs in the collision.
 * @property {number} timestamp - The Matter Engine `timing.timestamp` value for the event.
 * @property {any} source - The source object of the event.
 * @property {string} name - The name of the event.
 */

/**
 * The Matter Physics Collision Start Event.
 *
 * This event is dispatched by a Matter Physics World instance after the engine has updated.
 * It provides a list of all pairs that have started to collide in the current tick (if any).
 *
 * Listen to it from a Scene using: `this.matter.world.on('collisionstart', listener)`.
 *
 * @event Phaser.Physics.Matter.Events#COLLISION_START
 * @since 3.0.0
 *
 * @param {Phaser.Physics.Matter.Events.CollisionStartEvent} event - The Collision Event object.
 * @param {MatterJS.BodyType} bodyA - The first body of the first colliding pair. The `event.pairs` array may contain more colliding bodies.
 * @param {MatterJS.BodyType} bodyB - The second body of the first colliding pair. The `event.pairs` array may contain more colliding bodies.
 */
module.exports = 'collisionstart';


keep keep keep keep keep replace keep keep keep keep keep

if (Number(process.version.match(/v(\d+)/)[1]) >= 6) {

'use strict'

const t = require('tap')
const test = t.test
const net = require('net')
const Fastify = require('..')
const statusCodes = require('http').STATUS_CODES

const codes = Object.keys(statusCodes)
codes.forEach(code => {
  if (Number(code) >= 400) helper(code)
})

function helper (code) {
  test('Reply error handling - code: ' + code, t => {
    t.plan(4)
    const fastify = Fastify()
    const err = new Error('winter is coming')

    fastify.get('/', (req, reply) => {
      reply
        .code(Number(code))
        .send(err)
    })

    fastify.inject({
      method: 'GET',
      url: '/'
    }, (error, res) => {
      t.error(error)
      t.strictEqual(res.statusCode, Number(code))
      t.equal(res.headers['content-type'], 'application/json')
      t.deepEqual(
        {
          error: statusCodes[code],
          message: err.message,
          statusCode: Number(code)
        },
        JSON.parse(res.payload)
      )
    })
  })
}

test('preHandler hook error handling with external code', t => {
  t.plan(3)
  const fastify = Fastify()
  const err = new Error('winter is coming')

  fastify.addHook('preHandler', (req, reply, done) => {
    reply.code(400)
    done(err)
  })

  fastify.get('/', () => {})

  fastify.inject({
    method: 'GET',
    url: '/'
  }, (error, res) => {
    t.error(error)
    t.strictEqual(res.statusCode, 400)
    t.deepEqual(
      {
        error: statusCodes['400'],
        message: err.message,
        statusCode: 400
      },
      JSON.parse(res.payload)
    )
  })
})

test('onRequest hook error handling with external done', t => {
  t.plan(3)
  const fastify = Fastify()
  const err = new Error('winter is coming')

  fastify.addHook('onRequest', (req, res, done) => {
    res.statusCode = 400
    done(err)
  })

  fastify.get('/', () => {})

  fastify.inject({
    method: 'GET',
    url: '/'
  }, (error, res) => {
    t.error(error)
    t.strictEqual(res.statusCode, 400)
    t.deepEqual(
      {
        error: statusCodes['400'],
        message: err.message,
        statusCode: 400
      },
      JSON.parse(res.payload)
    )
  })
})

if (Number(process.versions.node[0]) >= 6) {
  test('Should reply 400 on client error', t => {
    t.plan(2)

    const fastify = Fastify()
    fastify.listen(0, err => {
      t.error(err)

      const client = net.connect(fastify.server.address().port)
      client.end('oooops!')

      var chunks = ''
      client.on('data', chunk => {
        chunks += chunk
      })

      client.once('end', () => {
        const body = JSON.stringify({
          error: 'Bad Request',
          message: 'Client Error',
          statusCode: 400
        })
        t.equal(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: 'application/json'\r\n\r\n${body}`, chunks)
        fastify.close()
      })
    })
  })
}

test('Error instance sets HTTP status code', t => {
  t.plan(3)
  const fastify = Fastify()
  const err = new Error('winter is coming')
  err.statusCode = 418

  fastify.get('/', () => {
    return Promise.reject(err)
  })

  fastify.inject({
    method: 'GET',
    url: '/'
  }, (error, res) => {
    t.error(error)
    t.strictEqual(res.statusCode, 418)
    t.deepEqual(
      {
        error: statusCodes['418'],
        message: err.message,
        statusCode: 418
      },
      JSON.parse(res.payload)
    )
  })
})

test('Error status code below 400 defaults to 500', t => {
  t.plan(3)
  const fastify = Fastify()
  const err = new Error('winter is coming')
  err.statusCode = 399

  fastify.get('/', () => {
    return Promise.reject(err)
  })

  fastify.inject({
    method: 'GET',
    url: '/'
  }, (error, res) => {
    t.error(error)
    t.strictEqual(res.statusCode, 500)
    t.deepEqual(
      {
        error: statusCodes['500'],
        message: err.message,
        statusCode: 500
      },
      JSON.parse(res.payload)
    )
  })
})

test('Error.status property support', t => {
  t.plan(3)
  const fastify = Fastify()
  const err = new Error('winter is coming')
  err.status = 418

  fastify.get('/', () => {
    return Promise.reject(err)
  })

  fastify.inject({
    method: 'GET',
    url: '/'
  }, (error, res) => {
    t.error(error)
    t.strictEqual(res.statusCode, 418)
    t.deepEqual(
      {
        error: statusCodes['418'],
        message: err.message,
        statusCode: 418
      },
      JSON.parse(res.payload)
    )
  })
})

test('Support rejection with values that are not Error instances', t => {
  const objs = [
    0,
    '',
    [],
    {},
    null,
    undefined,
    123,
    'abc',
    new RegExp(),
    new Date(),
    new Uint8Array()
  ]
  t.plan(objs.length)
  for (const nonErr of objs) {
    t.test('Type: ' + typeof nonErr, t => {
      t.plan(4)
      const fastify = Fastify()

      fastify.get('/', () => {
        return Promise.reject(nonErr)
      })

      fastify.setErrorHandler((err, request, reply) => {
        if (typeof err === 'object') {
          t.deepEqual(err, nonErr)
        } else {
          t.strictEqual(err, nonErr)
        }
        reply.send('error')
      })

      fastify.inject({
        method: 'GET',
        url: '/'
      }, (error, res) => {
        t.error(error)
        t.strictEqual(res.statusCode, 500)
        t.strictEqual(res.payload, 'error')
      })
    })
  }
})

test('invalid schema - ajv', t => {
  t.plan(4)

  const fastify = Fastify()
  fastify.get('/', {
    schema: {
      querystring: {
        type: 'object',
        properties: {
          id: { type: 'number' }
        }
      }
    }
  }, (req, reply) => {
    t.fail('we should not be here')
  })

  fastify.setErrorHandler((err, request, reply) => {
    t.ok(Array.isArray(err.validation))
    reply.send('error')
  })

  fastify.inject({
    url: '/?id=abc',
    method: 'GET'
  }, (err, res) => {
    t.error(err)
    t.strictEqual(res.statusCode, 400)
    t.strictEqual(res.payload, 'error')
  })
})

test('should set the status code and the headers from the error object (from route handler)', t => {
  t.plan(4)
  const fastify = Fastify()

  fastify.get('/', (req, reply) => {
    const error = new Error('kaboom')
    error.headers = { hello: 'world' }
    error.statusCode = 400
    reply.send(error)
  })

  fastify.inject({
    url: '/',
    method: 'GET'
  }, (err, res) => {
    t.error(err)
    t.strictEqual(res.statusCode, 400)
    t.strictEqual(res.headers.hello, 'world')
    t.deepEqual(JSON.parse(res.payload), {
      error: 'Bad Request',
      message: 'kaboom',
      statusCode: 400
    })
  })
})

test('should set the status code and the headers from the error object (from custom error handler)', t => {
  t.plan(6)
  const fastify = Fastify()

  fastify.get('/', (req, reply) => {
    const error = new Error('ouch')
    error.statusCode = 401
    reply.send(error)
  })

  fastify.setErrorHandler((err, request, reply) => {
    t.is(err.message, 'ouch')
    t.is(reply.res.statusCode, 401)
    const error = new Error('kaboom')
    error.headers = { hello: 'world' }
    error.statusCode = 400
    reply.send(error)
  })

  fastify.inject({
    url: '/',
    method: 'GET'
  }, (err, res) => {
    t.error(err)
    t.strictEqual(res.statusCode, 400)
    t.strictEqual(res.headers.hello, 'world')
    t.deepEqual(JSON.parse(res.payload), {
      error: 'Bad Request',
      message: 'kaboom',
      statusCode: 400
    })
  })
})

// Issue 595 https://github.com/fastify/fastify/issues/595
test('\'*\' should throw an error due to serializer can not handle the payload type', t => {
  t.plan(2)
  const fastify = Fastify()

  fastify.get('/', (req, reply) => {
    reply.type('text/html')
    try {
      reply.send({})
    } catch (err) {
      t.type(err, TypeError)
      t.strictEqual(err.message, "Attempted to send payload of invalid type 'object'. Expected a string or Buffer.")
    }
  })

  fastify.inject({
    url: '/',
    method: 'GET'
  }, (e, res) => {
    t.fail('should not be called')
  })
})

test('should throw an error if the custom serializer does not serialize the payload to a valid type', t => {
  t.plan(2)
  const fastify = Fastify()

  fastify.get('/', (req, reply) => {
    try {
      reply
        .type('text/html')
        .serializer(payload => payload)
        .send({})
    } catch (err) {
      t.type(err, TypeError)
      t.strictEqual(err.message, "Attempted to send payload of invalid type 'object'. Expected a string or Buffer.")
    }
  })

  fastify.inject({
    url: '/',
    method: 'GET'
  }, (e, res) => {
    t.fail('should not be called')
  })
})


'use strict'

const os = require('os')
const path = require('path')
const fs = require('fs')
const test = require('tap').test
const Fastify = require('..')

test('listen accepts a port and a callback', t => {
  t.plan(3)
  const fastify = Fastify()
  fastify.listen(0, (err) => {
    fastify.server.unref()
    t.is(fastify.server.address().address, '127.0.0.1')
    t.error(err)
    t.pass()
    fastify.close()
  })
})

test('listen accepts a port, address, and callback', t => {
  t.plan(2)
  const fastify = Fastify()
  fastify.listen(0, '127.0.0.1', (err) => {
    fastify.server.unref()
    t.error(err)
    t.pass()
    fastify.close()
  })
})

test('listen accepts a port, address, backlog and callback', t => {
  t.plan(2)
  const fastify = Fastify()
  fastify.listen(0, '127.0.0.1', 511, (err) => {
    fastify.server.unref()
    t.error(err)
    t.pass()
    fastify.close()
  })
})

test('listen after Promise.resolve()', t => {
  t.plan(2)
  const f = Fastify()
  Promise.resolve()
    .then(() => {
      f.listen(0, (err) => {
        f.server.unref()
        t.error(err)
        t.pass()
        f.close()
      })
    })
})

test('register after listen using Promise.resolve()', t => {
  t.plan(1)
  const f = Fastify()

  const handler = (req, res) => res.send({})
  Promise.resolve()
    .then(() => {
      f.get('/', handler)
      f.register((f2, options, done) => {
        f2.get('/plugin', handler)
        done()
      })
      return f.ready()
    })
    .catch(t.error)
    .then(() => t.pass('resolved'))
})

test('double listen errors', t => {
  t.plan(2)
  const fastify = Fastify()
  fastify.listen(0, (err) => {
    t.error(err)
    fastify.listen(fastify.server.address().port, (err) => {
      t.ok(err)
      fastify.close()
    })
  })
})

test('listen twice on the same port', t => {
  t.plan(2)
  const fastify = Fastify()
  fastify.listen(0, (err) => {
    t.error(err)
    const s2 = Fastify()
    s2.listen(fastify.server.address().port, (err) => {
      fastify.close()
      t.ok(err)
    })
  })
})

// https://nodejs.org/api/net.html#net_ipc_support
if (os.platform() !== 'win32') {
  test('listen on socket', t => {
    t.plan(2)
    const fastify = Fastify()
    const sockFile = path.join(os.tmpdir(), 'server.sock')
    try {
      fs.unlinkSync(sockFile)
    } catch (e) { }
    fastify.listen(sockFile, (err) => {
      t.error(err)
      t.equal(sockFile, fastify.server.address())
      fastify.close()
    })
  })
}

test('listen without callback', t => {
  t.plan(1)
  const fastify = Fastify()
  fastify.listen(0)
    .then(() => {
      t.is(fastify.server.address().address, '127.0.0.1')
      fastify.close()
      t.end()
    })
})

test('double listen without callback rejects', t => {
  t.plan(1)
  const fastify = Fastify()
  fastify.listen(0)
    .then(() => {
      fastify.listen(0)
        .then(() => {
          t.error(new Error('second call to fastify.listen resolved'))
          fastify.close()
        })
        .catch(err => {
          t.ok(err)
          fastify.close()
        })
    })
    .catch(err => t.error(err))
})

test('listen twice on the same port without callback rejects', t => {
  t.plan(1)
  const fastify = Fastify()

  fastify.listen(0)
    .then(() => {
      const s2 = Fastify()
      s2.listen(fastify.server.address().port)
        .then(() => {
          t.error(new Error('listen on port already in use resolved'))
          fastify.close()
          s2.close()
        })
        .catch(err => {
          t.ok(err)
          fastify.close()
          s2.close()
        })
    })
    .catch(err => t.error(err))
})

test('listen logs the port as info', t => {
  t.plan(1)
  const fastify = Fastify()

  t.teardown(() => fastify.close())

  const msgs = []
  fastify.log.info = function (msg) {
    msgs.push(msg)
  }

  fastify.listen(0)
    .then(() => {
      t.ok(/http:\/\//.test(msgs[0]))
    })
})


'use strict'

const t = require('tap')
const test = t.test
const Fastify = require('..')
const sget = require('simple-get').concat
const fp = require('fastify-plugin')

test('require a plugin', t => {
  t.plan(1)
  const fastify = Fastify()
  fastify.register(require('./plugin.helper'))
  fastify.ready(() => {
    t.ok(fastify.test)
  })
})

test('fastify.register with fastify-plugin should not incapsulate his code', t => {
  t.plan(10)
  const fastify = Fastify()

  fastify.register((instance, opts, next) => {
    instance.register(fp((i, o, n) => {
      i.decorate('test', () => {})
      t.ok(i.test)
      n()
    }))

    t.notOk(instance.test)

    // the decoration is added at the end
    instance.after(() => {
      t.ok(instance.test)
    })

    instance.get('/', (req, reply) => {
      t.ok(instance.test)
      reply.send({ hello: 'world' })
    })

    next()
  })

  fastify.ready(() => {
    t.notOk(fastify.test)
  })

  fastify.listen(0, err => {
    t.error(err)
    fastify.server.unref()

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.deepEqual(JSON.parse(body), { hello: 'world' })
    })
  })
})

test('fastify.register with fastify-plugin registers root level plugins', t => {
  t.plan(15)
  const fastify = Fastify()

  function rootPlugin (instance, opts, next) {
    instance.decorate('test', 'first')
    t.ok(instance.test)
    next()
  }

  function innerPlugin (instance, opts, next) {
    instance.decorate('test2', 'second')
    next()
  }

  fastify.register(fp(rootPlugin))

  fastify.register((instance, opts, next) => {
    t.ok(instance.test)
    instance.register(fp(innerPlugin))

    instance.get('/test2', (req, reply) => {
      t.ok(instance.test2)
      reply.send({ test2: instance.test2 })
    })

    next()
  })

  fastify.ready(() => {
    t.ok(fastify.test)
    t.notOk(fastify.test2)
  })

  fastify.get('/', (req, reply) => {
    t.ok(fastify.test)
    reply.send({ test: fastify.test })
  })

  fastify.listen(0, err => {
    t.error(err)
    fastify.server.unref()

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.deepEqual(JSON.parse(body), { test: 'first' })
    })

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/test2'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.deepEqual(JSON.parse(body), { test2: 'second' })
    })
  })
})

test('check dependencies - should not throw', t => {
  t.plan(12)
  const fastify = Fastify()

  fastify.register((instance, opts, next) => {
    instance.register(fp((i, o, n) => {
      i.decorate('test', () => {})
      t.ok(i.test)
      n()
    }))

    instance.register(fp((i, o, n) => {
      try {
        i.decorate('otherTest', () => {}, ['test'])
        t.ok(i.test)
        t.ok(i.otherTest)
        n()
      } catch (e) {
        t.fail()
      }
    }))

    instance.get('/', (req, reply) => {
      t.ok(instance.test)
      t.ok(instance.otherTest)
      reply.send({ hello: 'world' })
    })

    next()
  })

  fastify.ready(() => {
    t.notOk(fastify.test)
    t.notOk(fastify.otherTest)
  })

  fastify.listen(0, err => {
    t.error(err)
    fastify.server.unref()

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.deepEqual(JSON.parse(body), { hello: 'world' })
    })
  })
})

test('check dependencies - should throw', t => {
  t.plan(11)
  const fastify = Fastify()

  fastify.register((instance, opts, next) => {
    instance.register(fp((i, o, n) => {
      try {
        i.decorate('otherTest', () => {}, ['test'])
        t.fail()
      } catch (e) {
        t.is(e.message, 'Fastify decorator: missing dependency: \'test\'.')
      }
      n()
    }))

    instance.register(fp((i, o, n) => {
      i.decorate('test', () => {})
      t.ok(i.test)
      t.notOk(i.otherTest)
      n()
    }))

    instance.get('/', (req, reply) => {
      t.ok(instance.test)
      t.notOk(instance.otherTest)
      reply.send({ hello: 'world' })
    })

    next()
  })

  fastify.ready(() => {
    t.notOk(fastify.test)
  })

  fastify.listen(0, err => {
    t.error(err)
    fastify.server.unref()

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.deepEqual(JSON.parse(body), { hello: 'world' })
    })
  })
})

test('plugin incapsulation', t => {
  t.plan(10)
  const fastify = Fastify()

  fastify.register((instance, opts, next) => {
    instance.register(fp((i, o, n) => {
      i.decorate('test', 'first')
      n()
    }))

    instance.get('/first', (req, reply) => {
      reply.send({ plugin: instance.test })
    })

    next()
  })

  fastify.register((instance, opts, next) => {
    instance.register(fp((i, o, n) => {
      i.decorate('test', 'second')
      n()
    }))

    instance.get('/second', (req, reply) => {
      reply.send({ plugin: instance.test })
    })

    next()
  })

  fastify.ready(() => {
    t.notOk(fastify.test)
  })

  fastify.listen(0, err => {
    t.error(err)
    fastify.server.unref()

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/first'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.deepEqual(JSON.parse(body), { plugin: 'first' })
    })

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/second'
    }, (err, response, body) => {
      t.error(err)
      t.strictEqual(response.statusCode, 200)
      t.strictEqual(response.headers['content-length'], '' + body.length)
      t.deepEqual(JSON.parse(body), { plugin: 'second' })
    })
  })
})

test('if a plugin raises an error and there is not a callback to handle it, the server must not start', t => {
  t.plan(2)
  const fastify = Fastify()

  fastify.register((instance, opts, next) => {
    next(new Error('err'))
  })

  fastify.listen(0, err => {
    t.ok(err instanceof Error)
    t.is(err.message, 'err')
  })
})

test('add hooks after route declaration', t => {
  t.plan(3)
  const fastify = Fastify()

  function plugin (instance, opts, next) {
    instance.decorateRequest('check', {})
    setImmediate(next)
  }
  fastify.register(fp(plugin))

  fastify.register((instance, options, next) => {
    instance.addHook('preHandler', function b (req, res, next) {
      req.check.hook2 = true
      next()
    })

    instance.get('/', (req, reply) => {
      reply.send(req.check)
    })

    instance.addHook('preHandler', function c (req, res, next) {
      req.check.hook3 = true
      next()
    })

    next()
  })

  fastify.addHook('preHandler', function a (req, res, next) {
    req.check.hook1 = true
    next()
  })

  fastify.listen(0, err => {
    t.error(err)

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port
    }, (err, response, body) => {
      t.error(err)
      t.deepEqual(JSON.parse(body), { hook1: true, hook2: true, hook3: true })
      fastify.close()
    })
  })
})

test('nested plugins', t => {
  t.plan(5)

  const fastify = Fastify()

  t.tearDown(fastify.close.bind(fastify))

  fastify.register(function (fastify, opts, next) {
    fastify.register((fastify, opts, next) => {
      fastify.get('/', function (req, reply) {
        reply.send('I am child 1')
      })
      next()
    }, { prefix: '/child1' })

    fastify.register((fastify, opts, next) => {
      fastify.get('/', function (req, reply) {
        reply.send('I am child 2')
      })
      next()
    }, { prefix: '/child2' })

    next()
  }, { prefix: '/parent' })

  fastify.listen(0, err => {
    t.error(err)

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/parent/child1'
    }, (err, response, body) => {
      t.error(err)
      t.deepEqual(body.toString(), 'I am child 1')
    })

    sget({
      method: 'GET',
      url: 'http://localhost:' + fastify.server.address().port + '/parent/child2'
    }, (err, response, body) => {
      t.error(err)
      t.deepEqual(body.toString(), 'I am child 2')
    })
  })
})

test('plugin metadata - decorators', t => {
  t.plan(1)
  const fastify = Fastify()

  fastify.decorate('plugin1', true)
  fastify.decorateReply('plugin1', true)
  fastify.decorateRequest('plugin1', true)

  plugin[Symbol.for('skip-override')] = true
  plugin[Symbol.for('plugin-meta')] = {
    decorators: {
      fastify: ['plugin1'],
      reply: ['plugin1'],
      request: ['plugin1']
    }
  }

  fastify.register(plugin)

  fastify.ready(() => {
    t.ok(fastify.plugin)
  })

  function plugin (instance, opts, next) {
    instance.decorate('plugin', true)
    next()
  }
})

test('plugin metadata - dependencies', t => {
  t.plan(1)
  const fastify = Fastify()

  dependency[Symbol.for('skip-override')] = true
  dependency[Symbol.for('plugin-meta')] = {
    name: 'plugin'
  }

  plugin[Symbol.for('skip-override')] = true
  plugin[Symbol.for('plugin-meta')] = {
    dependencies: ['plugin']
  }

  fastify.register(dependency)
  fastify.register(plugin)

  fastify.ready(() => {
    t.pass('everything right')
  })

  function dependency (instance, opts, next) {
    next()
  }

  function plugin (instance, opts, next) {
    next()
  }
})

test('plugin metadata - dependencies (nested)', t => {
  t.plan(1)
  const fastify = Fastify()

  dependency[Symbol.for('skip-override')] = true
  dependency[Symbol.for('plugin-meta')] = {
    name: 'plugin'
  }

  nested[Symbol.for('skip-override')] = true
  nested[Symbol.for('plugin-meta')] = {
    dependencies: ['plugin']
  }

  fastify.register(dependency)
  fastify.register(plugin)

  fastify.ready(() => {
    t.pass('everything right')
  })

  function dependency (instance, opts, next) {
    next()
  }

  function plugin (instance, opts, next) {
    instance.register(nested)
    next()
  }

  function nested (instance, opts, next) {
    next()
  }
})


#### You have already researched for similiar issues?
It's not uncommon that somebody already opened an issue or in best case it's already fixed but not merged. That's the reason why you should [search](https://github.com/fastify/fastify/issues) at first before submitting a new one.

#### Are you sure this is an issue with the fastify or are you just looking for some help?

Issues should only be posted in this repository after you have been able to reproduce
them and confirm that they are a bug or incorrect/missing information in the [docs](https://github.com/fastify/fastify/docs).

For all other questions, requests, help resolving an issue, or if you are not sure if this is
the right place, please do not open an issue here. Instead, ask a question in our [help](https://github.com/fastify/help) repository or try to find a solution in the community [Gitter Chat](https://gitter.im/fastify).

If you have issues with the [fastify.io](https://www.fastify.io) site, please open an issue [here](https://github.com/fastify/website/issues).

#### Is this a security related issue?

Do not open issues that might have security implications. It is critical that security related issues
are reported privately so we have time to address them before they become public knowledge. Please
email all security related issues to the [Fastify Team](https://github.com/fastify/fastify#team).

#### What are you trying to achieve or the steps to reproduce?

Describe your issue here, include as much detail as neccessary to reproduce the issue
or implement a missing functionality. If you are reporting a bug, you will get faster response
if you submit a pull request with a failing test.

```js
// Wrap code in markdown source tags
```

#### What was the result you received?

#### What did you expect?

#### Context

* *node version*: 4,5,6,7,8,9
* *fastify version*: >=0.37.0
* *os*: Mac, Windows
* *any other relevant information*:

**Please read this entire template before posting any issue. If you ignore these instructions
and post an issue here that does not follow the instructions, your issue might be closed,
locked, and assigned the `missing discussion` label.**


const sget = require('simple-get').concat