import { Noise } from "noisejs";
import { BC_CAMERA, BC_CHUNKS_SETTINGS, BC_SPRITES_SETTINGS, BC_TERRAIN_SETTINGS, PRNG } from "../GlobalVariables/GlobalVariables";
import { clampNumber, integerDivision, mapRange } from "../Utils/Math.utils";
import { addToViewport } from "../Utils/World.utils";
import { WorldChunk, createWorldChunkContainer } from "../WorldChunk/WorldChunk";
import { getSpriteFromAtlas } from "../Utils/Sprites.utils";
import * as PIXI from "../../pixi/pixi.mjs";
import { BuildingsObjectProps, TerrainObjectProps, VegetationObjectProps, VisibleChunkProps } from "./ObjectsPropsSchemes";
import { NumberCue, RGBColor, Vault } from "../Utils/DataTypes.utils";
import { addChunkToFillQueue } from "./ChunkFillQueue";
// import {WorldChunk} from "../Types/WorldGenerationTypes";

const terrainSpriteList = {
    0: { x: 1, y: 1 }, //water
    1: { x: 0, y: 1 }, //sand
    2: { x: 0, y: 0 }, //grass
    3: { x: 1, y: 0 }, //stone
};
const terrainTypeList = {
    0: "ter_water", //water
    1: "ter_sand", //sand
    2: "ter_grass", //grass
    3: "ter_stone", //stone
};
const grassVegetationSpriteList = {
    0: { x: 10, y: 11 },
    1: { x: 11, y: 11 },
    2: { x: 12, y: 11 },
    3: { x: 13, y: 11 },
    4: { x: 14, y: 11 },
    5: { x: 15, y: 11 },
    6: { x: 16, y: 11 },
    7: { x: 17, y: 11 },
    8: { x: 18, y: 11 },
    9: { x: 19, y: 11 },
    10: { x: 0, y: 12 },
    11: { x: 1, y: 12 },
    12: { x: 2, y: 12 },
    13: { x: 3, y: 12 },
    14: { x: 7, y: 13 },
    15: { x: 7, y: 12 },
    16: { x: 8, y: 12 },
    17: { x: 9, y: 12 },
};
const sandVegetationSpriteList = {
    0: { x: 14, y: 10 },
    1: { x: 7, y: 13 },
    2: { x: 6, y: 13 },
    3: { x: 5, y: 13 },
    4: { x: 4, y: 13 },
    5: { x: 3, y: 13 },
};
const stoneVegetationSpriteList = {
    0: { x: 5, y: 13 },
    1: { x: 4, y: 13 },
    2: { x: 3, y: 13 },
};
const grassVegResourcesList = {
    0: { type: "grass", num: 10 / 5 },
    1: { type: "grass", num: 9 / 5 },
    2: { type: "grass", num: 8 / 5 },
    3: { type: "grass", num: 7 / 5 },
    4: { type: "grass", num: 4 / 5 },
    5: { type: "grass", num: 4 / 5 },
    6: { type: "grass", num: 5 / 5 },
    7: { type: "grass", num: 3 / 5 },
    8: { type: "grass", num: 2 / 5 },
    9: { type: "grass", num: 1 / 5 },
    10: { type: "grass", num: 2 / 5 },
    11: { type: "grass", num: 2 / 5 },
    12: { type: "grass", num: 2 / 5 },
    13: { type: "grass", num: 2 / 5 },
    14: { type: "stone", num: 1 / 5 },
    15: { type: "wood", num: 2 / 5 },
    16: { type: "wood", num: 2 / 5 },
    17: { type: "wood", num: 2 / 5 },
};

const sandVegResourcesList = {
    0: { type: "grass", num: 4 / 5 },
    1: { type: "stone", num: 1 / 5 },
    2: { type: "stone", num: 3 / 5 },
    3: { type: "stone", num: 4 / 5 },
    4: { type: "stone", num: 5 / 5 },
    5: { type: "stone", num: 5 / 5 },
};

const stoneVegResourcesList = {
    0: { type: "stone", num: 4 / 5 },
    1: { type: "stone", num: 5 / 5 },
    2: { type: "stone", num: 5 / 5 },
};

const WORLD_CHUNKS = new Vault();
let VISIBLE_CHUNKS = new Vault();

export function worldCoordinatesToChunkIndex(x, y) {
    let w = BC_CHUNKS_SETTINGS.width * BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let h = BC_CHUNKS_SETTINGS.height * BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    return { x: Math.floor(x / w), y: Math.floor(y / h) };
}
export function worldCoordinatesToChunkIndexesCoordinates(x, y) {
    let ws = BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let hs = BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    return { x: Math.floor(x / ws), y: Math.floor(y / hs) };
}
export function worldCoordinatesToChunkLocalCoordinates(x, y) {
    let ws = BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let hs = BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    if (x < 0 && y >= 0)
        return {
            x: BC_CHUNKS_SETTINGS.width - (Math.floor(Math.abs(x) / ws) % BC_CHUNKS_SETTINGS.width) - 1,
            y: Math.floor(Math.abs(y) / hs) % BC_CHUNKS_SETTINGS.height,
        };
    else if (x < 0 && y < 0)
        return {
            x: BC_CHUNKS_SETTINGS.width - (Math.floor(Math.abs(x) / ws) % BC_CHUNKS_SETTINGS.width) - 1,
            y: BC_CHUNKS_SETTINGS.height - (Math.floor(Math.abs(y) / hs) % BC_CHUNKS_SETTINGS.height) - 1,
        };
    else if (x > 0 && y < 0)
        return {
            x: Math.floor(Math.abs(x) / ws) % BC_CHUNKS_SETTINGS.width,
            y: BC_CHUNKS_SETTINGS.height - (Math.floor(Math.abs(y) / hs) % BC_CHUNKS_SETTINGS.height) - 1,
        };
    else
        return {
            x: Math.floor(Math.abs(x) / ws) % BC_CHUNKS_SETTINGS.width,
            y: Math.floor(Math.abs(y) / hs) % BC_CHUNKS_SETTINGS.height,
        };
}

/**
 *
 * @param {Number} x world coordinates
 * @param {Number} y world coordinates
 */
export function getChunk(x, y) {
    let t = worldCoordinatesToChunkIndex(x, y);
    if (WORLD_CHUNKS.existsKey(t.x + "_" + t.y)) return WORLD_CHUNKS.get(t.x + "_" + t.y);
    else return undefined;
}

let enableAutoGeneration = false;

export function updateChunksVisibility() {
    let w = BC_CHUNKS_SETTINGS.width * BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let h = BC_CHUNKS_SETTINGS.height * BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let cx = Math.floor(BC_CAMERA.position.x / w);
    let cy = Math.floor(BC_CAMERA.position.y / h);
    let chunkId = cx + "_" + cy;
    if ((VISIBLE_CHUNKS.existsKey(chunkId) && !VISIBLE_CHUNKS.get(chunkId).central) || !VISIBLE_CHUNKS.existsKey(chunkId)) {
        VISIBLE_CHUNKS.forAll((vis_chunk) => {
            vis_chunk.chunkRef.chunk.visible = false;
        });
        VISIBLE_CHUNKS.clear();
        for (let i = -2; i < 3; i++) {
            for (let j = -2; j < 3; j++) {
                let t_chunkId = cx + i + "_" + (cy + j);
                if (WORLD_CHUNKS.existsKey(t_chunkId)) {
                    VISIBLE_CHUNKS.set(t_chunkId, new VisibleChunkProps(WORLD_CHUNKS.get(t_chunkId), i === 0 && j === 0));
                    VISIBLE_CHUNKS.get(t_chunkId).chunkRef.chunk.visible = true;
                } else if (enableAutoGeneration) {
                    // console.log(t_chunkId);
                    let newChunk = createWorldChunkContainer();
                    // chunkRef.isRenderGroup = true;
                    newChunk.position.set(w * (cx + i), h * (cy + j));
                    // console.log(w * chunkXCeiled, h * chunkYCeiled);
                    let chunk0 = new WorldChunk(
                        newChunk,
                        newChunk.getChildAt(0),
                        newChunk.getChildAt(1),
                        newChunk.getChildAt(2),
                        new Vault("terrain"),
                        new Vault("vegetation"),
                        new Vault("buildings")
                    );
                    // fillChunk(chunk0, cx + i, cy + j);
                    addChunkToFillQueue(chunk0, cx + i, cy + j);
                    WORLD_CHUNKS.set(t_chunkId, chunk0);
                    addToViewport(newChunk);
                    VISIBLE_CHUNKS.set(t_chunkId, new VisibleChunkProps(WORLD_CHUNKS.get(t_chunkId), i === 0 && j === 0));
                    VISIBLE_CHUNKS.get(t_chunkId).chunkRef.chunk.visible = true;
                }
            }
        }
    }
}

/**
 *
 * @param {WorldChunk} chunk from WORLD_CHUNKS
 * @param {PIXI.Container} object
 * @param {TerrainObjectProps} props
 * @param {Number} ceiledX
 * @param {Number} ceiledY
 */
export function addToTerrain(chunk, object, props, ceiledX, ceiledY) {
    chunk.terrainLayer.addChild(object);
    chunk.terrainObjectsVault.set(ceiledX + "_" + ceiledY, new TerrainObjectProps(object, { ...props }));
}

/**
 *
 * @param {WorldChunk} chunk from WORLD_CHUNKS
 * @param {PIXI.Container} object
 * @param {VegetationObjectProps} props
 * @param {Number} ceiledX
 * @param {Number} ceiledY
 */
export function addToVegetation(chunk, object, props, ceiledX, ceiledY) {
    chunk.vegetationLayer.addChild(object);
    chunk.vegetationObjectsVault.set(ceiledX + "_" + ceiledY, new VegetationObjectProps(object, { ...props }));
}

/**
 *
 * @param {WorldChunk} chunk from WORLD_CHUNKS
 * @param {PIXI.Container} object
 * @param {BuildingsObjectProps} props
 * @param {Number} ceiledX
 * @param {Number} ceiledY
 */
export function addToBuildings(chunk, object, props, ceiledX, ceiledY) {
    chunk.buildingsLayer.addChild(object);
    chunk.buildingsObjectsVault.set(ceiledX + "_" + ceiledY, new BuildingsObjectProps(object, { ...props }));
}

/**
 *
 * @param {Number} x world coordinates
 * @param {Number} y world coordinates
 * @param {PIXI.Container} object object to remove
 */
export function removeFromTerrain(x, y, object) {
    let chunkIndex = worldCoordinatesToChunkIndex(x, y);
    let chunkId = chunkIndex.x + "_" + chunkIndex.y;
    if (WORLD_CHUNKS.existsKey(chunkId)) {
        let objLocalPos = worldCoordinatesToChunkIndexesCoordinates(x, y);
        let objId = objLocalPos.x + "_" + objLocalPos.y;
        WORLD_CHUNKS.get(chunkId).terrainLayer.removeChild(object);
        WORLD_CHUNKS.get(chunkId).terrainObjectsVault.del(objId);
        object.destroy();
    }
}
/**
 *
 * @param {Number} x world coordinates
 * @param {Number} y world coordinates
 * @param {PIXI.Container} object object to remove
 */
export function removeFromVegetation(x, y, object) {
    let chunkIndex = worldCoordinatesToChunkIndex(x, y);
    let chunkId = chunkIndex.x + "_" + chunkIndex.y;
    if (WORLD_CHUNKS.existsKey(chunkId)) {
        let objLocalPos = worldCoordinatesToChunkIndexesCoordinates(x, y);
        let objId = objLocalPos.x + "_" + objLocalPos.y;
        WORLD_CHUNKS.get(chunkId).vegetationLayer.removeChild(object);
        WORLD_CHUNKS.get(chunkId).vegetationObjectsVault.del(objId);
        object.destroy();
    }
}

/**
 *
 * @param {Number} x world coordinates
 * @param {Number} y world coordinates
 * @param {PIXI.Container} object object to remove
 */
export function removeFromBuildings(x, y, object) {
    let chunkIndex = worldCoordinatesToChunkIndex(x, y);
    let chunkId = chunkIndex.x + "_" + chunkIndex.y;
    if (WORLD_CHUNKS.existsKey(chunkId)) {
        let objLocalPos = worldCoordinatesToChunkIndexesCoordinates(x, y);
        let objId = objLocalPos.x + "_" + objLocalPos.y;
        WORLD_CHUNKS.get(chunkId).buildingsLayer.removeChild(object);
        WORLD_CHUNKS.get(chunkId).buildingsObjectsVault.del(objId);
        object.destroy();
    }
}

/**
 *
 * @param {Number} x world coordinates
 * @param {Number} y world coordinates
 */
export function getObjectFromTerrainLayer(x, y) {
    // let t_ceiled = {x: Math.floor(t.x / (BC_TERRAIN_SETTINGS.tileSize*BC_TERRAIN_SETTINGS.scale)), y: Math.floor(t.y / (BC_TERRAIN_SETTINGS.tileSize*BC_TERRAIN_SETTINGS.scale))};
    let w = BC_CHUNKS_SETTINGS.width * BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let h = BC_CHUNKS_SETTINGS.height * BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let ws = BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let hs = BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let chunkId = Math.floor(x / w) + "_" + Math.floor(y / h);
    let objectId = Math.floor(x / ws) + "_" + Math.floor(y / hs);
    if (WORLD_CHUNKS.existsKey(chunkId)) return WORLD_CHUNKS.get(chunkId).terrainObjectsVault.get(objectId);
}
/**
 *
 * @param {Number} x world coordinates
 * @param {Number} y world coordinates
 */
export function getObjectFromVegetationLayer(x, y) {
    // let t_ceiled = {x: Math.floor(t.x / (BC_TERRAIN_SETTINGS.tileSize*BC_TERRAIN_SETTINGS.scale)), y: Math.floor(t.y / (BC_TERRAIN_SETTINGS.tileSize*BC_TERRAIN_SETTINGS.scale))};
    let w = BC_CHUNKS_SETTINGS.width * BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let h = BC_CHUNKS_SETTINGS.height * BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let ws = BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let hs = BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let chunkId = Math.floor(x / w) + "_" + Math.floor(y / h);
    let objectId = Math.floor(x / ws) + "_" + Math.floor(y / hs);
    if (WORLD_CHUNKS.existsKey(chunkId)) return WORLD_CHUNKS.get(chunkId).vegetationObjectsVault.get(objectId);
}

/**
 *
 * @param {Number} x world coordinates
 * @param {Number} y world coordinates
 */
export function getObjectFromBuildingsLayer(x, y) {
    // let t_ceiled = {x: Math.floor(t.x / (BC_TERRAIN_SETTINGS.tileSize*BC_TERRAIN_SETTINGS.scale)), y: Math.floor(t.y / (BC_TERRAIN_SETTINGS.tileSize*BC_TERRAIN_SETTINGS.scale))};
    let w = BC_CHUNKS_SETTINGS.width * BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let h = BC_CHUNKS_SETTINGS.height * BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let ws = BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let hs = BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let chunkId = Math.floor(x / w) + "_" + Math.floor(y / h);
    let objectId = Math.floor(x / ws) + "_" + Math.floor(y / hs);
    if (WORLD_CHUNKS.existsKey(chunkId)) return WORLD_CHUNKS.get(chunkId).buildingsObjectsVault.get(objectId);
}

export function createFirstWorldChunks() {
    let w = BC_CHUNKS_SETTINGS.width * BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    let h = BC_CHUNKS_SETTINGS.height * BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale;
    for (let i = -1; i < 2; i++) {
        for (let j = -1; j < 2; j++) {
            let chunkXCeiled = Math.floor((BC_CAMERA.position.x + w * i) / w);
            let chunkYCeiled = Math.floor((BC_CAMERA.position.y + h * j) / h);
            let chunkId = chunkXCeiled + "_" + chunkYCeiled;
            // console.log(chunkId);
            let chunkRef = createWorldChunkContainer();
            // chunkRef.isRenderGroup = true;
            chunkRef.position.set(w * chunkXCeiled, h * chunkYCeiled);
            // console.log(w * chunkXCeiled, h * chunkYCeiled);
            let chunk0 = new WorldChunk(
                chunkRef,
                chunkRef.getChildAt(0),
                chunkRef.getChildAt(1),
                chunkRef.getChildAt(2),
                new Vault("terrain"),
                new Vault("vegetation"),
                new Vault("buildings")
            );

            fillChunk(chunk0, chunkXCeiled, chunkYCeiled);
            WORLD_CHUNKS.set(chunkId, chunk0);
            chunkRef.visible = false;
            addToViewport(chunkRef);
            // console.log(WORLD_CHUNKS)
            // console.log(chunkXCeiled, chunkYCeiled);
        }
    }
    enableAutoGeneration = true;
}

let noise = new Noise(Math.floor(PRNG() * 188822321));
let noiseErosion = new Noise(Math.floor(PRNG() * 327749029));
let noiseBiomes = new Noise(Math.floor(PRNG() * 927472011));

let terrainCue = new NumberCue([0, 1, 2, 3, 3], [0.0, 0.45, 0.5, 0.9, 1.0]);
let terrainTintCue = new NumberCue([0.9, 1, 1, 0.95, 0.9, 1, 0.93, 1], [0.0, 0.45, 0.45, 0.5, 0.5, 0.9, 0.9, 1.0]);

export function fillChunk(chunk, x, y) {
    let ii = 0;
    let jj = 0;
    for (let i = BC_CHUNKS_SETTINGS.width * x; i < BC_CHUNKS_SETTINGS.width * (x + 1); i++) {
        jj = 0;
        for (let j = BC_CHUNKS_SETTINGS.height * y; j < BC_CHUNKS_SETTINGS.height * (y + 1); j++) {
            let res = (noise.simplex2(i * 0.025, j * 0.025) + 1) / 2;
            let resR = (noiseErosion.simplex2(i * 0.3, j * 0.3) + 1) / 2;
            let resB = (noiseBiomes.simplex2(i * 0.01, j * 0.01) + 1) / 2;
            if (resB > 0.7) {
                res = clampNumber(res - resR / 4, 0.0, 0.99);
            }
            if (resB > 0.5 && res < 0.9 && res >= 0.5) {
                res = clampNumber(res + resR / 4, 0.0, 0.99);
            }
            // res = Math.pow(res, 2);
            // console.log(resR);
            // let resB = (noiseBiomes.perlin2(i * 0.01, j * 0.01) + 1) / 2;
            // console.log(Math.pow(resB, 0.5));
            // console.log(res);
            // res = clampNumber(res * resB, 0.0, 0.99);
            // if(resB < 0.5)
            // {
            //     res = clampNumber(res / 2 , 0.0, 0.99);
            // }
            // else if(resB < 0.9)
            // {
            //     res = clampNumber(res * 2, 0.0, 0.99);
            // }
            // res = Math.pow(res, 0.9);
            let sTint = new RGBColor(255, 255, 255).multiplyByNumber(terrainTintCue.getValueAt(res)).toNumber();
            res = Math.floor(terrainCue.getValueAt(res));
            let sprite = getSpriteFromAtlas(
                "assets/images/world/world_terrain_atlas.png",
                new PIXI.Rectangle(16 * terrainSpriteList[res].x, 16 * terrainSpriteList[res].y, 16, 16)
            );
            sprite.position.set(16 * BC_SPRITES_SETTINGS.scale * ii, 16 * BC_SPRITES_SETTINGS.scale * jj);
            sprite.scale.set(BC_SPRITES_SETTINGS.scale, BC_SPRITES_SETTINGS.scale);
            sprite.tint = sTint;
            addToTerrain(chunk, sprite, { type: terrainTypeList[res] }, i, j);
            // console.log(ii, jj);
            // addToTerrain(sprite, {x: i, y: j}, {type: terrainTypeList[res]});
            if (res === 2 && PRNG() > 0.9) {
                let rv = Math.floor(PRNG() * 18);

                let veg = getSpriteFromAtlas(
                    "assets/images/world/vegetation_ts.png",
                    new PIXI.Rectangle(16 * grassVegetationSpriteList[rv].x, 16 * grassVegetationSpriteList[rv].y, 16, 16)
                );
                veg.position.set(16 * BC_SPRITES_SETTINGS.scale * ii, 16 * BC_SPRITES_SETTINGS.scale * jj);
                veg.scale.set(BC_SPRITES_SETTINGS.scale, BC_SPRITES_SETTINGS.scale);
                veg.tint = sTint;
                addToVegetation(chunk, veg, { ...grassVegResourcesList[rv] }, i, j);
                jj++;
                continue;
            }
            if (res === 1 && PRNG() > 0.99) {
                let rv = Math.floor(PRNG() * 6);

                let veg = getSpriteFromAtlas(
                    "assets/images/world/vegetation_ts.png",
                    new PIXI.Rectangle(16 * sandVegetationSpriteList[rv].x, 16 * sandVegetationSpriteList[rv].y, 16, 16)
                );
                veg.position.set(16 * BC_SPRITES_SETTINGS.scale * ii, 16 * BC_SPRITES_SETTINGS.scale * jj);
                veg.scale.set(BC_SPRITES_SETTINGS.scale, BC_SPRITES_SETTINGS.scale);
                veg.tint = sTint;
                addToVegetation(chunk, veg, { ...sandVegResourcesList[rv] }, i, j);
                jj++;
                continue;
            }
            if (res === 3 && PRNG() > 0.9) {
                let rv = Math.floor(PRNG() * 3);

                let veg = getSpriteFromAtlas(
                    "assets/images/world/vegetation_ts.png",
                    new PIXI.Rectangle(16 * stoneVegetationSpriteList[rv].x, 16 * stoneVegetationSpriteList[rv].y, 16, 16)
                );
                veg.position.set(16 * BC_SPRITES_SETTINGS.scale * ii, 16 * BC_SPRITES_SETTINGS.scale * jj);
                veg.scale.set(BC_SPRITES_SETTINGS.scale, BC_SPRITES_SETTINGS.scale);
                veg.tint = sTint;
                addToVegetation(chunk, veg, { ...stoneVegResourcesList[rv] }, i, j);
                jj++;
                continue;
            }
            jj++;
        }
        ii++;
    }
}
