package eu.jonahbauer.raytracing;

import eu.jonahbauer.raytracing.render.ImageFormat;
import eu.jonahbauer.raytracing.render.canvas.Canvas;
import eu.jonahbauer.raytracing.render.canvas.Image;
import eu.jonahbauer.raytracing.render.canvas.LiveCanvas;
import eu.jonahbauer.raytracing.render.renderer.SimpleRenderer;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.function.IntFunction;

public class Main {
    public static void main(String[] args) throws IOException {
        var config = Config.parse(args);
        var example = config.example;
        var scene = example.scene();
        var camera = example.camera();

        var renderer = SimpleRenderer.builder()
                .withSamplesPerPixel(config.samples)
                .withMaxDepth(config.depth)
                .withIterative(config.iterative)
                .withParallel(config.parallel)
                .build();

        Canvas canvas;
        if (config.preview) {
            var image = new LiveCanvas(new Image(camera.getWidth(), camera.getHeight()));
            image.preview();
            canvas = image;
        } else {
            canvas = new Image(camera.getWidth(), camera.getHeight());
        }

        long time = System.nanoTime();
        renderer.render(camera, scene, canvas);
        System.out.printf("rendering finished after %dms", (System.nanoTime() - time) / 1_000_000);

        ImageFormat.PNG.write(canvas, config.path);
    }

    private record Config(@NotNull Example example, @NotNull Path path, boolean preview, boolean iterative, boolean parallel, int samples, int depth) {
        public static @NotNull Config parse(@NotNull String @NotNull[] args) {
            IntFunction<Example> example = null;
            Path path = null;
            boolean preview = true;
            boolean iterative = false;
            boolean parallel = false;
            int samples = 1000;
            int depth = 50;
            int height = -1;

            for (int i = 0; i < args.length; i++) {
                switch (args[i]) {
                    case "--output" -> {
                        if (i + 1 == args.length) throw fail("missing value for parameter --output");
                        try {
                            path = Path.of(args[++i]);
                        } catch (InvalidPathException ex) {
                            throw fail("value " + args[i] + " is not a valid path");
                        }
                    }
                    case "--preview" -> preview = true;
                    case "--no-preview" -> preview = false;
                    case "--iterative" -> iterative = true;
                    case "--no-iterative" -> iterative = false;
                    case "--parallel" -> parallel = true;
                    case "--no-parallel" -> parallel = false;
                    case "--samples" -> {
                        if (i + 1 == args.length) throw fail("missing value for parameter --samples");
                        try {
                            samples = Integer.parseInt(args[++i]);
                            if (samples <= 0) throw fail("samples must be positive");
                        } catch (NumberFormatException ex) {
                            throw fail("value " + args[i] + " is not a valid integer");
                        }
                    }
                    case "--depth" -> {
                        if (i + 1 == args.length) throw fail("missing value for parameter --depth");
                        try {
                            depth = Integer.parseInt(args[++i]);
                            if (depth <= 0) throw fail("depth must be positive");
                        } catch (NumberFormatException ex) {
                            throw fail("value " + args[i] + " is not a valid integer");
                        }
                    }
                    case "--height" -> {
                        if (i + 1 == args.length) throw fail("missing value for parameter --height");
                        try {
                            height = Integer.parseInt(args[++i]);
                            if (height <= 0) throw fail("height must be positive");
                        } catch (NumberFormatException ex) {
                            throw fail("value " + args[i] + " is not a valid integer");
                        }
                    }
                    case String str when !str.startsWith("-") -> {
                        try {
                            example = Examples.getByName(str);
                        } catch (IllegalArgumentException ex) {
                            throw fail(ex.getMessage());
                        }
                    }
                    default -> throw fail("unknown option " + args[i]);
                }
            }

            if (example == null) example = Examples::getCornellBoxSmoke;
            if (path == null) path = Path.of("scene-" + System.currentTimeMillis() + ".png");
            return new Config(example.apply(height), path, preview, iterative, parallel, samples, depth);
        }

        private static @NotNull RuntimeException fail(@NotNull String message) {
            System.err.println(message);
            System.exit(1);
            return new RuntimeException();
        }
    }
}