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 WaterSystem from "../../src/engine/graphics/ecs/water/WaterSystem.js";
import Water from "../../src/engine/graphics/ecs/water/Water.js";
import { ThemeEngine } from "../../src/generation/theme/ThemeEngine.js";
import { Theme } from "../../src/generation/theme/Theme.js";
import { TerrainTheme } from "../../src/generation/theme/TerrainTheme.js";
import { AreaTheme } from "../../src/generation/theme/AreaTheme.js";
import { GridData } from "../../src/generation/grid/GridData.js";
import { TerrainLayerRule } from "../../src/generation/theme/TerrainLayerRule.js";
import { TerrainLayerDescription } from "../../src/generation/theme/TerrainLayerDescription.js";
import Vector2 from "../../src/core/geom/Vector2.js";
import { CellFilterLiteralFloat } from "../../src/generation/filtering/numeric/CellFilterLiteralFloat.js";
import { GridDataLayer } from "../../src/generation/grid/layers/GridDataLayer.js";
import {
    CellFilterSampleLayerLinear
} from "../../src/generation/filtering/numeric/sampling/CellFilterSampleLayerLinear.js";
import { CellFilterSmoothStep } from "../../src/generation/filtering/numeric/math/CellFilterSmoothStep.js";
import { CellFilterOneMinus } from "../../src/generation/filtering/numeric/math/CellFilterOneMinus.js";
import { CellFilterMultiply } from "../../src/generation/filtering/numeric/math/algebra/CellFilterMultiply.js";
import { CellFilterCurvature } from "../../src/generation/filtering/numeric/complex/CellFilterCurvature.js";
import { CellFilterMax2 } from "../../src/generation/filtering/numeric/math/CellFilterMax2.js";
import { CellFilterMin2 } from "../../src/generation/filtering/numeric/math/CellFilterMin2.js";
import {
    AmbientOcclusionPostProcessEffect
} from "../../src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js";
import { CellFilterAngleToNormal } from "../../src/generation/filtering/numeric/complex/CellFilterAngleToNormal.js";
import { CellFilterCache } from "../../src/generation/filtering/numeric/CellFilterCache.js";
import { TaskLoadingScreen } from "../../src/view/task/TaskLoadingScreen.js";

const HEIGHT_RANGE = 100;

const eh = new EngineHarness();

/**
 *
 * @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 WaterSystem(engine.graphics));

    r.addPlugin(AmbientOcclusionPostProcessEffect);

    return r;
}

function makeTheme() {
    const theme = new Theme();


    const terrainTheme = new TerrainTheme();

    const height = CellFilterSampleLayerLinear.from('height');
    const curvature = CellFilterCurvature.from(height);

    const slope = CellFilterAngleToNormal.from(height, Vector3.forward);

    const filterNotVerySharpSlope = CellFilterCache.from(
        CellFilterOneMinus.from(
            CellFilterSmoothStep.from(
                CellFilterLiteralFloat.from(Math.PI / 9),
                CellFilterLiteralFloat.from(Math.PI / 5),
                slope
            )
        )
    );

    const main_dress_weights = CellFilterMin2.from(
        CellFilterSmoothStep.from(
            CellFilterLiteralFloat.from(0.125 * HEIGHT_RANGE),
            CellFilterLiteralFloat.from(0.175 * HEIGHT_RANGE),
            height
        ),
        CellFilterOneMinus.from(curvature)
    );

    terrainTheme.rules.push(
        // RIVER
        TerrainLayerRule.from(
            CellFilterOneMinus.from(
                CellFilterSmoothStep.from(
                    CellFilterLiteralFloat.from(0.115 * HEIGHT_RANGE),
                    CellFilterLiteralFloat.from(0.125 * HEIGHT_RANGE),
                    height
                )
            ),
            TerrainLayerDescription.from({
                diffuse: "data/textures/materials/water-pool-texture-seamless-and-free.jpg",
                size: new Vector2(100, 100)
            })
        ),
        // WET AREAS
        TerrainLayerRule.from(
            CellFilterMax2.from(
                CellFilterMultiply.from(
                    CellFilterSmoothStep.from(
                        CellFilterLiteralFloat.from(0.125 * HEIGHT_RANGE),
                        CellFilterLiteralFloat.from(0.15 * HEIGHT_RANGE),
                        height
                    ),
                    CellFilterOneMinus.from(
                        CellFilterSmoothStep.from(
                            CellFilterLiteralFloat.from(0.175 * HEIGHT_RANGE),
                            CellFilterLiteralFloat.from(0.2 * HEIGHT_RANGE),
                            height
                        )
                    )
                ),
                CellFilterSmoothStep.from(
                    CellFilterLiteralFloat.from(0.2),
                    CellFilterLiteralFloat.from(0.5),
                    curvature
                )
            ),
            TerrainLayerDescription.from({
                diffuse: "data/textures/materials/terrain_township_set/512/Ground_1_dark.png",
                size: new Vector2(40, 40)
            })
        ),
        // TOP SOIL
        TerrainLayerRule.from(
            CellFilterMultiply.from(
                main_dress_weights,
                filterNotVerySharpSlope
            ),
            TerrainLayerDescription.from({
                diffuse: "data/textures/materials/terrain_township_set/512/Grass_1.png",
                size: new Vector2(40, 40)
            })
        ),
        // SLOPE SOIL
        TerrainLayerRule.from(
            CellFilterMultiply.from(
                main_dress_weights,
                CellFilterOneMinus.from(filterNotVerySharpSlope)
            ),
            TerrainLayerDescription.from({
                diffuse: "data/textures/materials/terrain_township_set/512/Grass_3.png",
                size: new Vector2(40, 40)
            })
        )
    );

    theme.terrain = terrainTheme;

    return theme;
}

/**
 *
 * @param {Engine} engine
 */
async function main(engine) {
    const TERRAIN_SIZE = 256;

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

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


    const terrain = new Terrain();
    terrain.size.set(TERRAIN_SIZE, TERRAIN_SIZE);
    terrain.resolution = 2;
    terrain.gridScale = 2;
    terrain.splat.resize(TERRAIN_SIZE, TERRAIN_SIZE, 1);
    terrain.splat.fillLayerWeights(0, 255);

    // load height map
    const asset = await engine.assetManager.promise('moicon/heightmap.png', 'image');

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

    terrain.samplerHeight.resize(sampler.width, sampler.height);
    const source_height_data = sampler.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 = 65536 - 1;
    }

    for (let i = 0; i < sampler_pixel_count; i++) {
        const source_v = source_height_data[i];

        const b0 = source_v & 0xFF;
        const b1 = (source_v >> 8) & 0xFF;

        let rotated_v;
        rotated_v = b1 | (b0 << 8);

        if (rotated_v <= 7000) {
            rotated_v = 0;
        }

        terrain_height_data[i] = (rotated_v / divisor) * HEIGHT_RANGE
    }

    const grid = new GridData();
    grid.resize(TERRAIN_SIZE, TERRAIN_SIZE);

    //inject height map into grid layer
    const layer = new GridDataLayer();
    layer.id = 'height';

    grid.addLayer(layer);

    layer.sampler = terrain.samplerHeight;

    const theme_engine = new ThemeEngine();

    const areaTheme = new AreaTheme();
    areaTheme.theme = makeTheme();
    areaTheme.mask.resize(grid.width, grid.height);
    areaTheme.mask.mask.data.fill(255);
    areaTheme.mask.distanceField.data.fill(0);
    areaTheme.mask.bounds.set(0, 0, grid.width, grid.height);

    theme_engine.add(areaTheme);


    terrain.build(engine.assetManager);

    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);

    const task = theme_engine.apply(grid, ecd);

    const promise_load = TaskLoadingScreen.load(engine, task);

    engine.executor.runGroup(task);

    await promise_load;

    await terrain.buildLightMap(1);
}

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

    await makeConfig(engine).apply(engine);

    await eh.initialize();

    main(engine);
}

init(eh);
