package com.dynamicwallpaper.core;

import com.dynamicwallpaper.model.WallpaperImage;
import com.dynamicwallpaper.util.Json;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Drives the native helper to assemble a finished .heic Dynamic Desktop
 * file. This class only knows how to describe the job as JSON and run a
 * process - the HEIC/metadata work itself happens in HeicBuilder.swift (see
 * docs/FILE_FORMAT.md).
 */
public final class WallpaperBuilder {

    /** Receives one callback per line of progress output from the native helper. */
    @FunctionalInterface
    public interface ProgressListener {
        void onLine(String line);
    }

    private final NativeHelper nativeHelper;

    public WallpaperBuilder(NativeHelper nativeHelper) {
        this.nativeHelper = nativeHelper;
    }

    public void buildAppearance(File light, File dark, File output, double quality, ProgressListener listener)
            throws BuildException {
        Map<String, Object> job = Json.object();
        job.put("mode", "appearance");
        job.put("output", output.getAbsolutePath());
        job.put("quality", quality);
        job.put("light", light.getAbsolutePath());
        job.put("dark", dark.getAbsolutePath());
        run(job, listener);
    }

    public void buildSolar(List<WallpaperImage> images, File output, double quality, ProgressListener listener)
            throws BuildException {
        Map<String, Object> job = Json.object();
        job.put("mode", "solar");
        job.put("output", output.getAbsolutePath());
        job.put("quality", quality);

        List<Object> imageArray = Json.array();
        for (WallpaperImage image : images) {
            Map<String, Object> entry = Json.object();
            entry.put("path", image.file().getAbsolutePath());
            entry.put("altitude", image.altitude());
            entry.put("azimuth", image.azimuth());
            entry.put("lightReference", image.isLightReference());
            entry.put("darkReference", image.isDarkReference());
            imageArray.add(entry);
        }
        job.put("images", imageArray);
        run(job, listener);
    }

    private void run(Map<String, Object> job, ProgressListener listener) throws BuildException {
        Path binary;
        try {
            binary = nativeHelper.ensureBuilt();
        } catch (NativeHelper.NativeHelperException e) {
            throw new BuildException(e.getMessage(), e);
        }

        Path jobFile;
        try {
            jobFile = Files.createTempFile("dynamic-wallpaper-job", ".json");
            Files.writeString(jobFile, Json.write(job), StandardCharsets.UTF_8);
            jobFile.toFile().deleteOnExit();
        } catch (IOException e) {
            throw new BuildException("Could not write job file: " + e.getMessage(), e);
        }

        try {
            ProcessBuilder pb = new ProcessBuilder(binary.toString(), "build", jobFile.toString());
            pb.redirectErrorStream(true);
            Process process = pb.start();

            StringBuilder allOutput = new StringBuilder();
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    allOutput.append(line).append('\n');
                    if (listener != null) {
                        listener.onLine(line);
                    }
                }
            }

            boolean finished = process.waitFor(5, TimeUnit.MINUTES);
            if (!finished) {
                process.destroyForcibly();
                throw new BuildException("Building the wallpaper timed out.");
            }
            if (process.exitValue() != 0) {
                throw new BuildException("Native helper failed:\n" + allOutput);
            }
        } catch (IOException | InterruptedException e) {
            throw new BuildException("Failed to run native helper: " + e.getMessage(), e);
        } finally {
            try {
                Files.deleteIfExists(jobFile);
            } catch (IOException ignored) {
                // best effort cleanup
            }
        }
    }

    /** Thrown when building a wallpaper fails for any reason. */
    public static final class BuildException extends Exception {
        public BuildException(String message) {
            super(message);
        }

        public BuildException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}
