import { EngineConfiguration } from "../../src/engine/EngineConfiguration.js";
import { EngineHarness } from "../../src/engine/EngineHarness.js";
import { GameAssetType } from "../../src/engine/asset/GameAssetType.js";
import { TextureAssetLoader } from "../../src/engine/asset/loaders/texture/TextureAssetLoader.js";
import Vector3 from "../../src/core/geom/Vector3.js";
import Entity from "../../src/engine/ecs/Entity.js";
import Terrain from "../../src/engine/ecs/terrain/ecs/Terrain.js";
import { ImageRGBADataLoader } from "../../src/engine/asset/loaders/image/ImageRGBADataLoader.js";
import TerrainSystem from "../../src/engine/ecs/terrain/ecs/TerrainSystem.js";
import Water from "../../src/engine/graphics/ecs/water/Water.js";
import {
    AmbientOcclusionPostProcessEffect
} from "../../src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js";
import { TerrainLayer } from "../../src/engine/ecs/terrain/ecs/layers/TerrainLayer.js";
import Mesh from "../../src/engine/graphics/ecs/mesh/Mesh.js";
import { Transform } from "../../src/engine/ecs/transform/Transform.js";
import { MeshSystem } from "../../src/engine/graphics/ecs/mesh/MeshSystem.js";
import { url_to_data_url } from "../../src/core/binary/url_to_data_url.js";
import ButtonView from "../../src/view/elements/button/ButtonView.js";
import {downloadAsFile} from "../../src/core/binary/downloadAsFile.js";

const HEIGHT_RANGE = 64;

const eh = new EngineHarness();


/**
 *
 * @param {Sampler2D} source
 * @param {Terrain} terrain
 */
function scale_terrain_heights(source, terrain) {
    const source_height_data = source.data;
    const sampler_pixel_count = source_height_data.length;
    const terrain_height_data = terrain.samplerHeight.data;

    let divisor;
    if (source_height_data instanceof Uint8Array) {
        divisor = 255;
    } else if (source_height_data instanceof Uint16Array) {
        divisor = 65535;
    }

    for (let i = 0; i < sampler_pixel_count; i++) {
        terrain_height_data[i] = (source_height_data[i] / divisor) * HEIGHT_RANGE;
    }
}

/**
 *
 * @param {Engine} engine
 */
async function main(engine) {
    const TERRAIN_SIZE_X = 1024;
    const TERRAIN_SIZE_Y = 512;

    await EngineHarness.buildBasics({
        engine,
        enableWater: false,
        enableTerrain: false,
        focus: new Vector3(250, 40, 128),
        yaw: -Math.PI,
        pitch: 0.91,
        distance: 100
    });

    /**
     *
     * @type {EntityComponentDataset}
     */
    const ecd = engine.entityManager.dataset;


    const terrain = new Terrain();
    terrain.size.set(TERRAIN_SIZE_X / 4, TERRAIN_SIZE_Y / 4);
    terrain.resolution = 4;
    terrain.gridScale = 2;
    terrain.splat.resize(TERRAIN_SIZE_X, TERRAIN_SIZE_Y, 3);


    // load height map
    const am = engine.assetManager;

    /**
     *
     * @type {Sampler2D}
     */
    const sampler = await load_sampler('moicon/23_Nov_21_Skogplanter/01/splat_height_2.png', am);


    async function load_splat_layer(path, index) {

        const sampler = await load_sampler(path, am);

        terrain.splat.writeLayerFromSampler(sampler, index, 0);
    }

    await load_splat_layer("moicon/23_Nov_21_Skogplanter/01/splat_green.png", 0);
    await load_splat_layer("moicon/23_Nov_21_Skogplanter/01/splat_dirt.png", 1);
    await load_splat_layer("moicon/23_Nov_21_Skogplanter/01/splat_stone.png", 2);

    terrain.samplerHeight.resize(sampler.width, sampler.height);
    scale_terrain_heights(sampler, terrain);

    terrain.layers.resolution.set(1024, 1024);

    terrain.layers.addLayer(TerrainLayer.from(
        "moicon/23_Nov_21_Skogplanter/01/textures/Grass_2.png",
        20,
        20
    ));
    terrain.layers.addLayer(TerrainLayer.from(
        "moicon/23_Nov_21_Skogplanter/01/textures/Ground_1_dark.png",
        20,
        20
    ));
    terrain.layers.addLayer(TerrainLayer.from(
        "moicon/23_Nov_21_Skogplanter/01/textures/Sand_01.png",
        20,
        20
    ));

    terrain.build(am);

    const water = new Water();

    water.level.set(5);
    water.shoreDepthTransition.set(0.1, 0.3);
    water.scattering.set(3);

    new Entity()
        .add(terrain)
        // .add(water)
        .build(ecd);


    await terrain.buildLightMap(1);

    const transform = Transform.fromJSON({
        position: new Vector3(256, 45, 128),
        scale: new Vector3(0.54, 0.54, 0.54)
    });

    load_gltf("moicon/23_Nov_21_Skogplanter/02/model.gltf", engine, transform);
    // load_gltf("moicon/23_Nov_21_Skogplanter/03/model.gltf", engine, transform);
    // load_gltf("moicon/23_Nov_21_Skogplanter/04/model.gltf", engine, transform);

    engine.gui.view.addChild(new ButtonView({
        name: 'Download',
        async action() {
            for (const layer of terrain.layers.layers.data) {
                // embed textures
                layer.textureDiffuseURL = await url_to_data_url(layer.textureDiffuseURL);
            }

            // we wrap the terrain data into "component json" format that meep editor recognizes to make this compatible with the standalone terrain editor
            const json_payload = {
                type: "Terrain",
                data: terrain.toJSON()
            };

            downloadAsFile(
                JSON.stringify(json_payload),
                'terrain.json'
            );
        },
        css: {
            bottom: '4px',
            left: '4px',
            position: 'absolute',
            background: 'white',
            border: '1px solid black',
            padding: '2px',
            pointerEvents: 'auto'
        }
    }));
}

/**
 *
 * @param {EngineHarness} harness
 */
async function init(harness) {
    const engine = eh.engine;

    await makeConfig(engine).apply(engine);

    await eh.initialize();

    main(engine);
}

init(eh);

/**
 *
 * @param {Engine} engine
 * @return {EngineConfiguration}
 */
function makeConfig(engine) {
    const r = new EngineConfiguration();

    // configure engine here, add systems, loaders etc.
    r.addLoader(GameAssetType.Texture, new TextureAssetLoader());
    r.addLoader(GameAssetType.Image, new ImageRGBADataLoader());


    r.addSystem(new TerrainSystem(engine.graphics, engine.assetManager));
    r.addSystem(new MeshSystem(engine));

    r.addPlugin(AmbientOcclusionPostProcessEffect);

    return r;
}

/**
 *
 * @param {string} path
 * @param {AssetManager} am
 */
async function load_sampler(path, am) {

    const asset = await am.promise(path, 'image');

    /**
     *
     * @type {Sampler2D}
     */
    const sampler = asset.create();

    return sampler;
}

/**
 *
 * @param {string} path
 * @param {Engine} engine
 * @param {Transform} transform
 */
async function load_gltf(path, engine, transform) {
    const em = engine.entityManager;

    const ecd = em.dataset;

    new Entity()
        .add(Mesh.fromJSON({
            url: path
        }))
        .add(transform)
        .build(ecd);
}
