import { WebAudio } from '../Util/WebAudio';
import { Canvas } from '../Graphics/Canvas';
import { ImageFiltering } from '../Graphics/Filtering';
import { clamp } from '../Math/util';
import { Sound } from '../Resources/Sound/Sound';
import { Future } from '../Util/Future';
import { EventEmitter } from '../EventEmitter';
import { Color } from '../Color';
import { delay } from '../Util/Util';
export const LoaderEvents = {
    // Add event types here
    BeforeLoad: 'beforeload',
    AfterLoad: 'afterload',
    UserAction: 'useraction',
    LoadResourceStart: 'loadresourcestart',
    LoadResourceEnd: 'loadresourceend'
};
/**
 * Returns true if the constructor is for an Excalibur Loader
 */
export function isLoaderConstructor(x) {
    var _a, _b;
    return !!(x === null || x === void 0 ? void 0 : x.prototype) && !!((_b = (_a = x === null || x === void 0 ? void 0 : x.prototype) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name);
}
export class DefaultLoader {
    get resources() {
        return this._resources;
    }
    /**
     * @param options Optionally provide the list of resources you want to load at constructor time
     */
    constructor(options) {
        var _a;
        this.events = new EventEmitter();
        this.canvas = new Canvas({
            filtering: ImageFiltering.Blended,
            smoothing: true,
            cache: false,
            draw: this.onDraw.bind(this)
        });
        this._resources = [];
        this._numLoaded = 0;
        this._totalTimeMs = 0;
        this._loadingFuture = new Future();
        if (options && ((_a = options.loadables) === null || _a === void 0 ? void 0 : _a.length)) {
            this.addResources(options.loadables);
        }
    }
    /**
     * Called by the engine before loading
     * @param engine
     */
    onInitialize(engine) {
        this.engine = engine;
        this.canvas.width = this.engine.screen.resolution.width;
        this.canvas.height = this.engine.screen.resolution.height;
    }
    /**
     * Return a promise that resolves when the user interacts with the loading screen in some way, usually a click.
     *
     * It's important to implement this in order to unlock the audio context in the browser. Browsers automatically prevent
     * audio from playing until the user performs an action.
     *
     */
    async onUserAction() {
        return await Promise.resolve();
    }
    /**
     * Overridable lifecycle method, called directly before loading starts
     */
    async onBeforeLoad() {
        // override me
    }
    /**
     * Overridable lifecycle method, called after loading has completed
     */
    async onAfterLoad() {
        // override me
        await delay(500, this.engine.clock); // avoid a flicker
    }
    /**
     * Add a resource to the loader to load
     * @param loadable  Resource to add
     */
    addResource(loadable) {
        this._resources.push(loadable);
    }
    /**
     * Add a list of resources to the loader to load
     * @param loadables  The list of resources to load
     */
    addResources(loadables) {
        let i = 0;
        const len = loadables.length;
        for (i; i < len; i++) {
            this.addResource(loadables[i]);
        }
    }
    markResourceComplete() {
        this._numLoaded++;
    }
    /**
     * Returns the progress of the loader as a number between [0, 1] inclusive.
     */
    get progress() {
        const total = this._resources.length;
        return total > 0 ? clamp(this._numLoaded, 0, total) / total : 1;
    }
    /**
     * Returns true if the loader has completely loaded all resources
     */
    isLoaded() {
        return this._numLoaded === this._resources.length;
    }
    /**
     * Optionally override the onUpdate
     * @param engine
     * @param elapsedMilliseconds
     */
    onUpdate(engine, elapsedMilliseconds) {
        this._totalTimeMs += elapsedMilliseconds;
        // override me
    }
    /**
     * Optionally override the onDraw
     */
    onDraw(ctx) {
        const seconds = this._totalTimeMs / 1000;
        ctx.fillStyle = Color.Black.toRGBA();
        ctx.fillRect(0, 0, this.engine.screen.resolution.width, this.engine.screen.resolution.height);
        ctx.save();
        ctx.translate(this.engine.screen.resolution.width / 2, this.engine.screen.resolution.height / 2);
        const speed = seconds * 10;
        ctx.strokeStyle = 'white';
        ctx.lineWidth = 10;
        ctx.lineCap = 'round';
        ctx.arc(0, 0, 40, speed, speed + (Math.PI * 3) / 2);
        ctx.stroke();
        ctx.fillStyle = 'white';
        ctx.font = '16px sans-serif';
        const text = (this.progress * 100).toFixed(0) + '%';
        const textbox = ctx.measureText(text);
        const width = Math.abs(textbox.actualBoundingBoxLeft) + Math.abs(textbox.actualBoundingBoxRight);
        const height = Math.abs(textbox.actualBoundingBoxAscent) + Math.abs(textbox.actualBoundingBoxDescent);
        ctx.fillText(text, -width / 2, height / 2); // center
        ctx.restore();
    }
    areResourcesLoaded() {
        return this._loadingFuture.promise;
    }
    /**
     * Not meant to be overridden
     *
     * Begin loading all of the supplied resources, returning a promise
     * that resolves when loading of all is complete AND the user has interacted with the loading screen
     */
    async load() {
        await this.onBeforeLoad();
        this.events.emit('beforeload');
        this.canvas.flagDirty();
        await Promise.all(this._resources.map(async (r) => {
            this.events.emit('loadresourcestart', r);
            await r.load().finally(() => {
                // capture progress
                this._numLoaded++;
                this.canvas.flagDirty();
                this.events.emit('loadresourceend', r);
            });
        }));
        // Wire all sound to the engine
        for (const resource of this._resources) {
            if (resource instanceof Sound) {
                resource.wireEngine(this.engine);
            }
        }
        this._loadingFuture.resolve();
        this.canvas.flagDirty();
        // Unlock browser AudioContext in after user gesture
        // See: https://github.com/excaliburjs/Excalibur/issues/262
        // See: https://github.com/excaliburjs/Excalibur/issues/1031
        await this.onUserAction();
        this.events.emit('useraction');
        await WebAudio.unlock();
        await this.onAfterLoad();
        this.events.emit('afterload');
        return (this.data = this._resources);
    }
    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);
    }
}
//# sourceMappingURL=DefaultLoader.js.map