function clampColorChannel(value) {
    return Math.max(0, Math.min(255, Math.round(value)));
}

function createNoise(seedX, seedY) {
    const value = Math.sin(seedX * 127.1 + seedY * 311.7) * 43758.5453123;
    return value - Math.floor(value);
}

function buildColor([red, green, blue], delta) {
    return `rgb(${clampColorChannel(red + delta)}, ${clampColorChannel(green + delta)}, ${clampColorChannel(blue + delta)})`;
}

export class Background {
    constructor(options = {}) {
        this.type = options.type || "grass";
        this.tileCache = {};
        this.palette = {
            grass: {
                baseColor: [88, 134, 54],
                bladeColor: [69, 117, 38],
                varianceRange: 10,
                bladeSpacing: 6,
                tileSize: 200,
                bladePlacementChance: 0.72,
                bladeJitter: 2
            }
        };
    }

    setType(type) {
        if (this.palette[type]) {
            this.type = type;
        }
    }

    draw(ctx, cam, viewport) {
        if (this.type === "grass") {
            this.drawGrass(ctx, cam, viewport);
        }
    }

    drawGrass(ctx, cam, viewport) {
        const grass = this.palette.grass;
        const viewWidth = viewport.width;
        const viewHeight = viewport.height;
        const worldLeft = -cam.x;
        const worldTop = -cam.y;
        const worldRight = worldLeft + viewWidth / cam.zoom;
        const worldBottom = worldTop + viewHeight / cam.zoom;
        const tileSize = grass.tileSize;
        const grassTile = this.getGrassTile();
        const tileStartX = Math.floor(worldLeft / tileSize) * tileSize;
        const tileStartY = Math.floor(worldTop / tileSize) * tileSize;
        const tileEndX = Math.ceil(worldRight / tileSize) * tileSize;
        const tileEndY = Math.ceil(worldBottom / tileSize) * tileSize;

        for (let tileY = tileStartY; tileY < tileEndY; tileY += tileSize) {
            for (let tileX = tileStartX; tileX < tileEndX; tileX += tileSize) {
                const screenX = Math.round((tileX + cam.x) * cam.zoom);
                const screenY = Math.round((tileY + cam.y) * cam.zoom);
                const tileScreenSize = Math.ceil(tileSize * cam.zoom);

                ctx.drawImage(grassTile, screenX, screenY, tileScreenSize, tileScreenSize);
            }
        }
    }

    getGrassTile() {
        if (this.tileCache.grass) {
            return this.tileCache.grass;
        }

        const grass = this.palette.grass;
        const tileCanvas = document.createElement("canvas");
        tileCanvas.width = grass.tileSize;
        tileCanvas.height = grass.tileSize;

        const tileContext = tileCanvas.getContext("2d");
        tileContext.imageSmoothingEnabled = false;
        tileContext.fillStyle = buildColor(grass.baseColor, 0);
        tileContext.fillRect(0, 0, grass.tileSize, grass.tileSize);

        for (let bladeY = 0; bladeY < grass.tileSize; bladeY += grass.bladeSpacing) {
            for (let bladeX = 0; bladeX < grass.tileSize; bladeX += grass.bladeSpacing) {
                if (!this.shouldDrawGrassBlade(bladeX, bladeY, grass)) {
                    continue;
                }

                const bladeNoise = createNoise(bladeX / grass.bladeSpacing, bladeY / grass.bladeSpacing);
                const colorDelta = Math.round((bladeNoise * 2 - 1) * grass.varianceRange);
                const offsetX = Math.round((createNoise(bladeX + 17, bladeY + 23) * (grass.bladeJitter * 2)) - grass.bladeJitter);
                const offsetY = Math.round((createNoise(bladeX + 43, bladeY + 11) * (grass.bladeJitter * 2)) - grass.bladeJitter);

                tileContext.fillStyle = buildColor(grass.bladeColor, colorDelta);
                this.drawGrassBlade(tileContext, bladeX + offsetX, bladeY + offsetY, grass.tileSize);
            }
        }

        this.tileCache.grass = tileCanvas;
        return tileCanvas;
    }

    shouldDrawGrassBlade(x, y, grass) {
        const densityNoise = createNoise(x + 101, y + 59);
        return densityNoise <= grass.bladePlacementChance;
    }

    drawGrassBlade(ctx, x, y, tileSize) {
        this.drawTilePixel(ctx, x, y, tileSize);
        this.drawTilePixel(ctx, x, y - 1, tileSize);
        this.drawTilePixel(ctx, x + 1, y - 2, tileSize);
        this.drawTilePixel(ctx, x + 1, y - 3, tileSize);
        this.drawTilePixel(ctx, x + 2, y - 4, tileSize);
        this.drawTilePixel(ctx, x + 2, y - 5, tileSize);
    }

    drawTilePixel(ctx, x, y, tileSize) {
        const wrappedX = ((x % tileSize) + tileSize) % tileSize;
        const wrappedY = ((y % tileSize) + tileSize) % tileSize;

        ctx.fillRect(wrappedX, wrappedY, 1, 1);
    }
}

export const background = new Background({ type: "grass" });