/*
 * FXGL - JavaFX Game Library. The MIT License (MIT).
 * Copyright (c) AlmasB (almaslvl@gmail.com).
 * See LICENSE for details.
 */

package sandbox.ai.pathfinding;

import com.almasb.fxgl.animation.AnimatedColor;
import com.almasb.fxgl.animation.Interpolators;
import com.almasb.fxgl.app.GameApplication;
import com.almasb.fxgl.app.GameSettings;
import com.almasb.fxgl.app.scene.GameView;
import com.almasb.fxgl.entity.Entity;
import com.almasb.fxgl.core.collection.grid.Grid;
import com.almasb.fxgl.procedural.BiomeMapGenerator;
import com.almasb.fxgl.procedural.HeightMapGenerator;
import com.almasb.fxgl.texture.ColoredTexture;
import com.almasb.fxgl.texture.Texture;
import javafx.collections.FXCollections;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;

import static com.almasb.fxgl.core.math.FXGLMath.map;
import static com.almasb.fxgl.core.math.FXGLMath.noise2D;
import static com.almasb.fxgl.dsl.FXGL.*;

/**
 * Shows how to use PCG maps.
 * Some concepts in this file are adapted from https://www.redblobgames.com/maps/terrain-from-noise/
 *
 * @author Almas Baimagambetov (AlmasB) (almaslvl@gmail.com)
 */
public class MapGenerationSample extends GameApplication {

    @Override
    protected void initSettings(GameSettings settings) {
        settings.setWidth(800 + 200);
        settings.setHeight(800);
        settings.setTitle("MapGenerationSample");
        settings.setVersion("0.1");
    }

    @Override
    protected void initGame() {

        // these use various settings, comment out one and uncomment the other
        // to play around with values and see which values work

        coloredMap();

        //plain();

        //variety();
    }

    private void plain() {
        // tile size
        int size = 5;

        int W = (getAppWidth() - 200) / size;
        int H = getAppHeight() / size;

        Grid<BiomeMapGenerator.BiomeData> map = new Grid<>(BiomeMapGenerator.BiomeData.class, W, H, new BiomeMapGenerator(W, H, 2.4));

        map.forEach(cell -> {
            Texture texture;

            if (cell.getElevation() < 0.2) {
                // water
                texture = new ColoredTexture(size, size, Color.BLUE);
            } else if (cell.getElevation() < 0.8) {
                // grass
                texture = texture("grass.png", size, size);
            } else {
                // in-land grass / mud?
                texture = texture("grass.png", size, size).desaturate().brighter().desaturate();
            }

            entityBuilder()
                    .at(cell.getX()*size, cell.getY()*size)
                    .view(texture)
                    .buildAndAttach();
        });
    }

    private static class CustomHeightMapGenerator extends HeightMapGenerator {

        public CustomHeightMapGenerator(int width, int height) {
            super(width, height);
        }

        @Override
        public HeightData apply(int x, int y) {
            var nx = x * 1.0 / getWidth() - 0.5;
            var ny = y * 1.0 / getHeight() - 0.5;

            double noiseValue = noise2D(nx, ny)
                    + 0.5 * noise2D(getAppWidth() / 400.0 * nx, getAppHeight() / 400.0 * ny)
                    + 0.2 * noise2D(7 * nx, 7 * ny);

            noiseValue *= noiseValue;

            if (noiseValue < 0) {
                noiseValue = 0;
            }

            if (noiseValue > 1) {
                noiseValue = 1;
            }

            return new HeightData(x, y, noiseValue);
        }
    }

    private void variety() {
        // tile size
        int size = 5;

        int W = (getAppWidth() - 200) / size;
        int H = getAppHeight() / size;

        Grid<HeightMapGenerator.HeightData> map = new Grid<>(HeightMapGenerator.HeightData.class, W, H, new CustomHeightMapGenerator(W, H));

        map.forEach(cell -> {
            Texture texture;

            if (cell.getHeight() < 0.2) {
                // water
                texture = new ColoredTexture(size, size, Color.BLUE);
            } else if (cell.getHeight() < 0.8) {
                // grass
                texture = texture("grass.png", size, size);
            } else {
                // in-land grass / mud?
                texture = texture("grass.png", size, size).desaturate().brighter().desaturate();
            }

            entityBuilder()
                    .at(cell.getX()*size, cell.getY()*size)
                    .view(texture)
                    .buildAndAttach();
        });
    }

    private Interpolators interpolator = Interpolators.LINEAR;

    private void coloredMap() {
        // tile size
        int size = 1;

        int W = (getAppWidth() + 200) / size;
        int H = (getAppHeight() + 200) / size;

        Grid<HeightMapGenerator.HeightData> map = new Grid<>(HeightMapGenerator.HeightData.class, W, H, new CustomHeightMapGenerator(W, H));

        AnimatedColor water = new AnimatedColor(Color.DARKBLUE, Color.AQUA);
        AnimatedColor land = new AnimatedColor(Color.DARKGREEN, Color.LIGHTGREEN);
        AnimatedColor desert = new AnimatedColor(Color.GREENYELLOW, Color.LIGHTYELLOW);
        AnimatedColor snow = new AnimatedColor(Color.GREEN, Color.WHITESMOKE);

        WritableImage image = new WritableImage(W, H);

        map.forEach(cell -> {

            AnimatedColor anim;

            double adjustedValue;

            if (cell.getHeight() < 0.2) {
                anim = water;
                adjustedValue = map(cell.getHeight(), 0.0, 0.2, 0.0, 1.0);
            } else if (cell.getHeight() < 0.4) {
                anim = land;
                adjustedValue = map(cell.getHeight(), 0.2, 0.4, 0.0, 1.0);
            } else if (cell.getHeight() < 0.9) {
                anim = desert;
                adjustedValue = map(cell.getHeight(), 0.4, 0.9, 0.0, 1.0);
            } else {
                anim = snow;
                adjustedValue = map(cell.getHeight(), 0.9, 1.0, 0.0, 1.0);
            }

            image.getPixelWriter().setColor(cell.getX(), cell.getY(), anim.getValue(adjustedValue, interpolator.EASE_OUT()));
        });

        getGameScene().getViewport().bindToEntity(new Entity(), 0, 0);

        var t = new Texture(image);
        t.setTranslateX(-100);
        t.setTranslateY(-100);

        getGameScene().addGameView(new GameView(t, 0));
    }

    @Override
    protected void initUI() {
        var choiceBox = new ChoiceBox<Interpolators>();
        choiceBox.setItems(FXCollections.observableArrayList(Interpolators.values()));
        choiceBox.setValue(Interpolators.LINEAR);

        var btn = new Button("Generate");
        btn.setOnAction(e -> {
            interpolator = choiceBox.getValue();
            getGameController().startNewGame();
        });

        addUINode(new VBox(btn, choiceBox), getAppWidth() - 200, 0);
    }

    public static void main(String[] args) {
        launch(args);
    }
}
