
interface Size {
    width: number;
    height: number;
}

export class HdpiManager {
    private _zoomModifier: number = 1;

    /**
     *
     * @param minRecommendedGamePixelsNumber The minimum number of pixels we want to display "by default" to the user
     * @param absoluteMinPixelNumber The very minimum of game pixels to display. Below, we forbid zooming more
     */
    public constructor(private minRecommendedGamePixelsNumber: number, private absoluteMinPixelNumber: number) {}

    /**
     * Returns the optimal size in "game pixels" based on the screen size in "real pixels".
     *
     * Note: the function is returning the optimal size in "game pixels" in the "game" property,
     * but also recommends resizing the "real" pixel screen size of the canvas.
     * The proposed new real size is a few pixels bigger than the real size available (if the size is not a multiple of the pixel size) and should overflow.
     *
     * @param realPixelScreenSize
     */
    public getOptimalGameSize(realPixelScreenSize: Size): { game: Size, real: Size } {
        const realPixelNumber = realPixelScreenSize.width * realPixelScreenSize.height;
        // If the screen has not a definition small enough to match the minimum number of pixels we want to display,
        // let's make the canvas the size of the screen (in real pixels)
        if (realPixelNumber <= this.minRecommendedGamePixelsNumber) {
            return {
                game: realPixelScreenSize,
                real: realPixelScreenSize
            };
        }

        const optimalZoomLevel = this.getOptimalZoomLevel(realPixelNumber);

        // Has the canvas more pixels than the screen? This is forbidden
        if (optimalZoomLevel * this._zoomModifier < 1) {
            // Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
            this._zoomModifier = 1 / optimalZoomLevel;

            return {
                game: {
                    width: realPixelScreenSize.width,
                    height: realPixelScreenSize.height,
                },
                real: {
                    width: realPixelScreenSize.width,
                    height: realPixelScreenSize.height,
                }
            }
        }

        const gameWidth = Math.ceil(realPixelScreenSize.width / optimalZoomLevel / this._zoomModifier);
        const gameHeight = Math.ceil(realPixelScreenSize.height / optimalZoomLevel / this._zoomModifier);

        // Let's ensure we display a minimum of pixels, even if crazily zoomed in.
        if (gameWidth * gameHeight < this.absoluteMinPixelNumber) {
            const minGameHeight = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.height / realPixelScreenSize.width);
            const minGameWidth = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.width / realPixelScreenSize.height);

            // Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
            this._zoomModifier = realPixelScreenSize.width / minGameWidth / optimalZoomLevel;

            return {
                game: {
                    width: minGameWidth,
                    height: minGameHeight,
                },
                real: {
                    width: realPixelScreenSize.width,
                    height: realPixelScreenSize.height,
                }
            }

        }

        return {
            game: {
                width: gameWidth,
                height: gameHeight,
            },
            real: {
                width: Math.ceil(realPixelScreenSize.width / optimalZoomLevel) * optimalZoomLevel,
                height: Math.ceil(realPixelScreenSize.height / optimalZoomLevel) * optimalZoomLevel,
            }
        }
    }

    /**
     * We only accept integer but we make an exception for 1.5
     */
    private getOptimalZoomLevel(realPixelNumber: number): number {
        const result = Math.sqrt(realPixelNumber / this.minRecommendedGamePixelsNumber);
        if (1.5 <= result && result < 2) {
            return 1.5
        } else {
            return Math.floor(result);
        }
    }

    public get zoomModifier(): number {
        return this._zoomModifier;
    }

    public set zoomModifier(zoomModifier: number) {
        this._zoomModifier = zoomModifier;
    }
}
