import { KillEvent, PreUpdateEvent, PostUpdateEvent, PostKillEvent, PreKillEvent } from './Events';
import { Logger } from './Util/Log';
import { Vector, vec } from './Math/vector';
import { BodyComponent } from './Collision/BodyComponent';
import { CollisionType } from './Collision/CollisionType';
import { Entity } from './EntityComponentSystem/Entity';
import { TransformComponent } from './EntityComponentSystem/Components/TransformComponent';
import { MotionComponent } from './EntityComponentSystem/Components/MotionComponent';
import { GraphicsComponent } from './Graphics/GraphicsComponent';
import { Rectangle } from './Graphics/Rectangle';
import { ColliderComponent } from './Collision/ColliderComponent';
import { Shape } from './Collision/Colliders/Shape';
import { watch } from './Util/Watch';
import { Circle } from './Graphics/Circle';
import { PointerComponent } from './Input/PointerComponent';
import { ActionsComponent } from './Actions/ActionsComponent';
import { Raster } from './Graphics/Raster';
import { Text } from './Graphics/Text';
import { CoordPlane } from './Math/coord-plane';
import { EventEmitter } from './EventEmitter';
/**
 * Type guard for checking if something is an Actor
 * @param x
 */
export function isActor(x) {
    return x instanceof Actor;
}
export const ActorEvents = {
    CollisionStart: 'collisionstart',
    CollisionEnd: 'collisionend',
    PreCollision: 'precollision',
    PostCollision: 'postcollision',
    Kill: 'kill',
    PreKill: 'prekill',
    PostKill: 'postkill',
    PreDraw: 'predraw',
    PostDraw: 'postdraw',
    PreTransformDraw: 'pretransformdraw',
    PostTransformDraw: 'posttransformdraw',
    PreDebugDraw: 'predebugdraw',
    PostDebugDraw: 'postdebugdraw',
    PointerUp: 'pointerup',
    PointerDown: 'pointerdown',
    PointerEnter: 'pointerenter',
    PointerLeave: 'pointerleave',
    PointerMove: 'pointermove',
    PointerCancel: 'pointercancel',
    Wheel: 'pointerwheel',
    PointerDrag: 'pointerdragstart',
    PointerDragEnd: 'pointerdragend',
    PointerDragEnter: 'pointerdragenter',
    PointerDragLeave: 'pointerdragleave',
    PointerDragMove: 'pointerdragmove',
    EnterViewPort: 'enterviewport',
    ExitViewPort: 'exitviewport',
    ActionStart: 'actionstart',
    ActionComplete: 'actioncomplete'
};
/**
 * The most important primitive in Excalibur is an `Actor`. Anything that
 * can move on the screen, collide with another `Actor`, respond to events,
 * or interact with the current scene, must be an actor. An `Actor` **must**
 * be part of a [[Scene]] for it to be drawn to the screen.
 */
export class Actor extends Entity {
    /**
     * Gets the position vector of the actor in pixels
     */
    get pos() {
        return this.transform.pos;
    }
    /**
     * Sets the position vector of the actor in pixels
     */
    set pos(thePos) {
        this.transform.pos = thePos.clone();
    }
    /**
     * Gets the position vector of the actor from the last frame
     */
    get oldPos() {
        return this.body.oldPos;
    }
    /**
     * Sets the position vector of the actor in the last frame
     */
    set oldPos(thePos) {
        this.body.oldPos.setTo(thePos.x, thePos.y);
    }
    /**
     * Gets the velocity vector of the actor in pixels/sec
     */
    get vel() {
        return this.motion.vel;
    }
    /**
     * Sets the velocity vector of the actor in pixels/sec
     */
    set vel(theVel) {
        this.motion.vel = theVel.clone();
    }
    /**
     * Gets the velocity vector of the actor from the last frame
     */
    get oldVel() {
        return this.body.oldVel;
    }
    /**
     * Sets the velocity vector of the actor from the last frame
     */
    set oldVel(theVel) {
        this.body.oldVel.setTo(theVel.x, theVel.y);
    }
    /**
     * Gets the acceleration vector of the actor in pixels/second/second. An acceleration pointing down such as (0, 100) may be
     * useful to simulate a gravitational effect.
     */
    get acc() {
        return this.motion.acc;
    }
    /**
     * Sets the acceleration vector of teh actor in pixels/second/second
     */
    set acc(theAcc) {
        this.motion.acc = theAcc.clone();
    }
    /**
     * Sets the acceleration of the actor from the last frame. This does not include the global acc [[Physics.acc]].
     */
    set oldAcc(theAcc) {
        this.body.oldAcc.setTo(theAcc.x, theAcc.y);
    }
    /**
     * Gets the acceleration of the actor from the last frame. This does not include the global acc [[Physics.acc]].
     */
    get oldAcc() {
        return this.body.oldAcc;
    }
    /**
     * Gets the rotation of the actor in radians. 1 radian = 180/PI Degrees.
     */
    get rotation() {
        return this.transform.rotation;
    }
    /**
     * Sets the rotation of the actor in radians. 1 radian = 180/PI Degrees.
     */
    set rotation(theAngle) {
        this.transform.rotation = theAngle;
    }
    /**
     * Gets the rotational velocity of the actor in radians/second
     */
    get angularVelocity() {
        return this.motion.angularVelocity;
    }
    /**
     * Sets the rotational velocity of the actor in radians/sec
     */
    set angularVelocity(angularVelocity) {
        this.motion.angularVelocity = angularVelocity;
    }
    get scale() {
        return this.get(TransformComponent).scale;
    }
    set scale(scale) {
        this.get(TransformComponent).scale = scale;
    }
    /**
     * The anchor to apply all actor related transformations like rotation,
     * translation, and scaling. By default the anchor is in the center of
     * the actor. By default it is set to the center of the actor (.5, .5)
     *
     * An anchor of (.5, .5) will ensure that drawings are centered.
     *
     * Use `anchor.setTo` to set the anchor to a different point using
     * values between 0 and 1. For example, anchoring to the top-left would be
     * `Actor.anchor.setTo(0, 0)` and top-right would be `Actor.anchor.setTo(0, 1)`.
     */
    get anchor() {
        return this._anchor;
    }
    set anchor(vec) {
        this._anchor = watch(vec, (v) => this._handleAnchorChange(v));
        this._handleAnchorChange(vec);
    }
    _handleAnchorChange(v) {
        if (this.graphics) {
            this.graphics.anchor = v;
        }
    }
    /**
     * The offset in pixels to apply to all actor graphics
     *
     * Default offset of (0, 0)
     */
    get offset() {
        return this._offset;
    }
    set offset(vec) {
        this._offset = watch(vec, (v) => this._handleOffsetChange(v));
        this._handleOffsetChange(vec);
    }
    _handleOffsetChange(v) {
        if (this.graphics) {
            this.graphics.offset = v;
        }
    }
    /**
     * Indicates whether the actor is physically in the viewport
     */
    get isOffScreen() {
        return this.hasTag('ex.offscreen');
    }
    get draggable() {
        return this._draggable;
    }
    set draggable(isDraggable) {
        if (isDraggable) {
            if (isDraggable && !this._draggable) {
                this.events.on('pointerdragstart', this._pointerDragStartHandler);
                this.events.on('pointerdragend', this._pointerDragEndHandler);
                this.events.on('pointerdragmove', this._pointerDragMoveHandler);
                this.events.on('pointerdragleave', this._pointerDragLeaveHandler);
            }
            else if (!isDraggable && this._draggable) {
                this.events.off('pointerdragstart', this._pointerDragStartHandler);
                this.events.off('pointerdragend', this._pointerDragEndHandler);
                this.events.off('pointerdragmove', this._pointerDragMoveHandler);
                this.events.off('pointerdragleave', this._pointerDragLeaveHandler);
            }
            this._draggable = isDraggable;
        }
    }
    /**
     * Sets the color of the actor's current graphic
     */
    get color() {
        return this._color;
    }
    set color(v) {
        this._color = v.clone();
        const currentGraphic = this.graphics.current;
        if (currentGraphic instanceof Raster || currentGraphic instanceof Text) {
            currentGraphic.color = this._color;
        }
    }
    // #endregion
    /**
     *
     * @param config
     */
    constructor(config) {
        super();
        this.events = new EventEmitter();
        this._anchor = watch(Vector.Half, (v) => this._handleAnchorChange(v));
        this._offset = watch(Vector.Zero, (v) => this._handleOffsetChange(v));
        /**
         * Convenience reference to the global logger
         */
        this.logger = Logger.getInstance();
        /**
         * Draggable helper
         */
        this._draggable = false;
        this._dragging = false;
        this._pointerDragStartHandler = () => {
            this._dragging = true;
        };
        this._pointerDragEndHandler = () => {
            this._dragging = false;
        };
        this._pointerDragMoveHandler = (pe) => {
            if (this._dragging) {
                this.pos = pe.worldPos;
            }
        };
        this._pointerDragLeaveHandler = (pe) => {
            if (this._dragging) {
                this.pos = pe.worldPos;
            }
        };
        const { name, x, y, pos, coordPlane, scale, width, height, radius, collider, vel, acc, rotation, angularVelocity, z, color, visible, opacity, anchor, offset, collisionType, collisionGroup } = {
            ...config
        };
        this.name = name !== null && name !== void 0 ? name : this.name;
        this.anchor = anchor !== null && anchor !== void 0 ? anchor : Actor.defaults.anchor.clone();
        this.offset = offset !== null && offset !== void 0 ? offset : Vector.Zero;
        this.transform = new TransformComponent();
        this.addComponent(this.transform);
        this.pos = pos !== null && pos !== void 0 ? pos : vec(x !== null && x !== void 0 ? x : 0, y !== null && y !== void 0 ? y : 0);
        this.rotation = rotation !== null && rotation !== void 0 ? rotation : 0;
        this.scale = scale !== null && scale !== void 0 ? scale : vec(1, 1);
        this.z = z !== null && z !== void 0 ? z : 0;
        this.transform.coordPlane = coordPlane !== null && coordPlane !== void 0 ? coordPlane : CoordPlane.World;
        this.pointer = new PointerComponent();
        this.addComponent(this.pointer);
        this.graphics = new GraphicsComponent({
            anchor: this.anchor,
            offset: this.offset,
            opacity: opacity
        });
        this.addComponent(this.graphics);
        this.motion = new MotionComponent();
        this.addComponent(this.motion);
        this.vel = vel !== null && vel !== void 0 ? vel : Vector.Zero;
        this.acc = acc !== null && acc !== void 0 ? acc : Vector.Zero;
        this.angularVelocity = angularVelocity !== null && angularVelocity !== void 0 ? angularVelocity : 0;
        this.actions = new ActionsComponent();
        this.addComponent(this.actions);
        this.body = new BodyComponent();
        this.addComponent(this.body);
        this.body.collisionType = collisionType !== null && collisionType !== void 0 ? collisionType : CollisionType.Passive;
        if (collisionGroup) {
            this.body.group = collisionGroup;
        }
        if (collider) {
            this.collider = new ColliderComponent(collider);
            this.addComponent(this.collider);
        }
        else if (radius) {
            this.collider = new ColliderComponent(Shape.Circle(radius));
            this.addComponent(this.collider);
        }
        else {
            if (width > 0 && height > 0) {
                this.collider = new ColliderComponent(Shape.Box(width, height, this.anchor));
                this.addComponent(this.collider);
            }
            else {
                this.collider = new ColliderComponent();
                this.addComponent(this.collider); // no collider
            }
        }
        this.graphics.visible = visible !== null && visible !== void 0 ? visible : true;
        if (color) {
            this.color = color;
            if (width && height) {
                this.graphics.add(new Rectangle({
                    color: color,
                    width,
                    height
                }));
            }
            else if (radius) {
                this.graphics.add(new Circle({
                    color: color,
                    radius
                }));
            }
        }
    }
    clone() {
        const clone = new Actor({
            color: this.color.clone(),
            anchor: this.anchor.clone(),
            offset: this.offset.clone()
        });
        clone.clearComponents();
        clone.processComponentRemoval();
        // Clone builtins, order is important, same as ctor
        clone.addComponent((clone.transform = this.transform.clone()), true);
        clone.addComponent((clone.pointer = this.pointer.clone()), true);
        clone.addComponent((clone.graphics = this.graphics.clone()), true);
        clone.addComponent((clone.motion = this.motion.clone()), true);
        clone.addComponent((clone.actions = this.actions.clone()), true);
        clone.addComponent((clone.body = this.body.clone()), true);
        clone.addComponent((clone.collider = this.collider.clone()), true);
        const builtInComponents = [
            this.transform,
            this.pointer,
            this.graphics,
            this.motion,
            this.actions,
            this.body,
            this.collider
        ];
        // Clone non-builtin the current actors components
        const components = this.getComponents();
        for (const c of components) {
            if (!builtInComponents.includes(c)) {
                clone.addComponent(c.clone(), true);
            }
        }
        return clone;
    }
    /**
     * `onInitialize` is called before the first update of the actor. This method is meant to be
     * overridden. This is where initialization of child actors should take place.
     *
     * Synonymous with the event handler `.on('initialize', (evt) => {...})`
     */
    onInitialize(engine) {
        // Override me
    }
    /**
     * Initializes this actor and all it's child actors, meant to be called by the Scene before first update not by users of Excalibur.
     *
     * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
     * @internal
     */
    _initialize(engine) {
        super._initialize(engine);
        for (const child of this.children) {
            child._initialize(engine);
        }
    }
    emit(eventName, event) {
        this.events.emit(eventName, event);
    }
    on(eventName, handler) {
        return this.events.on(eventName, handler);
    }
    once(eventName, handler) {
        return this.events.once(eventName, handler);
    }
    off(eventName, handler) {
        this.events.off(eventName, handler);
    }
    // #endregion
    /**
     * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
     *
     * Internal _prekill handler for [[onPreKill]] lifecycle event
     * @internal
     */
    _prekill(scene) {
        this.events.emit('prekill', new PreKillEvent(this));
        this.onPreKill(scene);
    }
    /**
     * Safe to override onPreKill lifecycle event handler. Synonymous with `.on('prekill', (evt) =>{...})`
     *
     * `onPreKill` is called directly before an actor is killed and removed from its current [[Scene]].
     */
    onPreKill(scene) {
        // Override me
    }
    /**
     * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
     *
     * Internal _prekill handler for [[onPostKill]] lifecycle event
     * @internal
     */
    _postkill(scene) {
        this.events.emit('postkill', new PostKillEvent(this));
        this.onPostKill(scene);
    }
    /**
     * Safe to override onPostKill lifecycle event handler. Synonymous with `.on('postkill', (evt) => {...})`
     *
     * `onPostKill` is called directly after an actor is killed and remove from its current [[Scene]].
     */
    onPostKill(scene) {
        // Override me
    }
    /**
     * If the current actor is a member of the scene, this will remove
     * it from the scene graph. It will no longer be drawn or updated.
     */
    kill() {
        if (this.scene) {
            this._prekill(this.scene);
            this.events.emit('kill', new KillEvent(this));
            super.kill();
            this._postkill(this.scene);
        }
        else {
            this.logger.warn(`Cannot kill actor named "${this.name}", it was never added to the Scene`);
        }
    }
    /**
     * If the current actor is killed, it will now not be killed.
     */
    unkill() {
        this.active = true;
    }
    /**
     * Indicates wether the actor has been killed.
     */
    isKilled() {
        return !this.active;
    }
    /**
     * Gets the z-index of an actor. The z-index determines the relative order an actor is drawn in.
     * Actors with a higher z-index are drawn on top of actors with a lower z-index
     */
    get z() {
        return this.get(TransformComponent).z;
    }
    /**
     * Sets the z-index of an actor and updates it in the drawing list for the scene.
     * The z-index determines the relative order an actor is drawn in.
     * Actors with a higher z-index are drawn on top of actors with a lower z-index
     * @param newZ new z-index to assign
     */
    set z(newZ) {
        this.get(TransformComponent).z = newZ;
    }
    /**
     * Get the center point of an actor (global position)
     */
    get center() {
        const globalPos = this.getGlobalPos();
        return new Vector(globalPos.x + this.width / 2 - this.anchor.x * this.width, globalPos.y + this.height / 2 - this.anchor.y * this.height);
    }
    /**
     * Get the local center point of an actor
     */
    get localCenter() {
        return new Vector(this.pos.x + this.width / 2 - this.anchor.x * this.width, this.pos.y + this.height / 2 - this.anchor.y * this.height);
    }
    get width() {
        return this.collider.localBounds.width * this.getGlobalScale().x;
    }
    get height() {
        return this.collider.localBounds.height * this.getGlobalScale().y;
    }
    /**
     * Gets this actor's rotation taking into account any parent relationships
     * @returns Rotation angle in radians
     */
    getGlobalRotation() {
        return this.get(TransformComponent).globalRotation;
    }
    /**
     * Gets an actor's world position taking into account parent relationships, scaling, rotation, and translation
     * @returns Position in world coordinates
     */
    getGlobalPos() {
        return this.get(TransformComponent).globalPos;
    }
    /**
     * Gets the global scale of the Actor
     */
    getGlobalScale() {
        return this.get(TransformComponent).globalScale;
    }
    // #region Collision
    /**
     * Tests whether the x/y specified are contained in the actor
     * @param x  X coordinate to test (in world coordinates)
     * @param y  Y coordinate to test (in world coordinates)
     * @param recurse checks whether the x/y are contained in any child actors (if they exist).
     */
    contains(x, y, recurse = false) {
        const point = vec(x, y);
        const collider = this.get(ColliderComponent);
        collider.update();
        const geom = collider.get();
        if (!geom) {
            return false;
        }
        const containment = geom.contains(point);
        if (recurse) {
            return (containment ||
                this.children.some((child) => {
                    return child.contains(x, y, true);
                }));
        }
        return containment;
    }
    /**
     * Returns true if the two actor.collider's surfaces are less than or equal to the distance specified from each other
     * @param actor     Actor to test
     * @param distance  Distance in pixels to test
     */
    within(actor, distance) {
        const collider = this.get(ColliderComponent);
        const otherCollider = actor.get(ColliderComponent);
        const me = collider.get();
        const other = otherCollider.get();
        if (me && other) {
            return me.getClosestLineBetween(other).getLength() <= distance;
        }
        return false;
    }
    // #endregion
    // #region Update
    /**
     * Called by the Engine, updates the state of the actor
     * @internal
     * @param engine The reference to the current game engine
     * @param delta  The time elapsed since the last update in milliseconds
     */
    update(engine, delta) {
        this._initialize(engine);
        this._preupdate(engine, delta);
        this._postupdate(engine, delta);
    }
    /**
     * Safe to override onPreUpdate lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
     *
     * `onPreUpdate` is called directly before an actor is updated.
     */
    onPreUpdate(engine, delta) {
        // Override me
    }
    /**
     * Safe to override onPostUpdate lifecycle event handler. Synonymous with `.on('postupdate', (evt) =>{...})`
     *
     * `onPostUpdate` is called directly after an actor is updated.
     */
    onPostUpdate(engine, delta) {
        // Override me
    }
    /**
     * Fires before every collision resolution for a confirmed contact
     * @param self
     * @param other
     * @param side
     * @param contact
     */
    onPreCollisionResolve(self, other, side, contact) {
        // Override me
    }
    /**
     * Fires after every resolution for a confirmed contact.
     * @param self
     * @param other
     * @param side
     * @param contact
     */
    onPostCollisionResolve(self, other, side, contact) {
        // Override me
    }
    /**
     * Fires once when 2 entities with a ColliderComponent first start colliding or touching, if the Colliders stay in contact this
     * does not continue firing until they separate and re-collide.
     * @param self
     * @param other
     * @param side
     * @param contact
     */
    onCollisionStart(self, other, side, contact) {
        // Override me
    }
    /**
     * Fires once when 2 entities with a ColliderComponent separate after having been in contact.
     * @param self
     * @param other
     * @param side
     * @param lastContact
     */
    onCollisionEnd(self, other, side, lastContact) {
        // Override me
    }
    /**
     * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
     *
     * Internal _preupdate handler for [[onPreUpdate]] lifecycle event
     * @internal
     */
    _preupdate(engine, delta) {
        this.events.emit('preupdate', new PreUpdateEvent(engine, delta, this));
        this.onPreUpdate(engine, delta);
    }
    /**
     * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
     *
     * Internal _preupdate handler for [[onPostUpdate]] lifecycle event
     * @internal
     */
    _postupdate(engine, delta) {
        this.events.emit('postupdate', new PostUpdateEvent(engine, delta, this));
        this.onPostUpdate(engine, delta);
    }
}
// #region Properties
/**
 * Set defaults for all Actors
 */
Actor.defaults = {
    anchor: Vector.Half
};
//# sourceMappingURL=Actor.js.map