package com.github.ruediste1.btrbck;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ProcessBuilder.Redirect;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.LinkedList;

import javax.inject.Singleton;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import com.github.ruediste1.btrbck.SyncService.SendFileSpec;
import com.github.ruediste1.btrbck.dom.Snapshot;

/**
 * Encapsulates BTRFS operations
 */
@Singleton
public class BtrfsService {
    Logger log = LoggerFactory.getLogger(BtrfsService.class);

    private ThreadLocal<Boolean> useSudo = new ThreadLocal<>();

    public void setUseSudo(boolean value) {
        useSudo.set(value);
    }

    private boolean useSudo() {
        return Boolean.TRUE.equals(useSudo.get());
    }

    private ThreadLocal<Boolean> useStrace = new ThreadLocal<>();

    public void setUseStrace(boolean value) {
        useStrace.set(value);
    }

    private boolean useStrace() {
        return Boolean.TRUE.equals(useStrace.get());
    }

    public void createSubVolume(Path subVolumeDir) {
        String path = subVolumeDir.toAbsolutePath().toString();
        try {
            {
                int exitValue = processBuilder("btrfs", "subvolume", "create",
                        path).start().waitFor();
                if (exitValue != 0) {
                    throw new IOException("exit code: " + exitValue);
                }
            }

            // determine the current user
            String userName;
            {
                Process process = new ProcessBuilder("whoami").redirectError(
                        Redirect.INHERIT).start();
                int exitValue = process.waitFor();
                if (exitValue != 0) {
                    throw new RuntimeException("whoami exited with "
                            + exitValue);
                }
                userName = Util.readFully(process.getInputStream());
                if (userName.endsWith("\n")) {
                    userName = userName.substring(0,
                            userName.length() - "\n".length());
                }
            }

            // change the owner of the subvolume
            {
                int exitValue = processBuilder("chown", userName + ":", path)
                        .start().waitFor();
                if (exitValue != 0) {
                    throw new RuntimeException("chown exited with " + exitValue);
                }
            }
        }
        catch (IOException | InterruptedException e) {
            throw new RuntimeException("Error while creating sub volume in "
                    + path, e);
        }
    }

    private ProcessBuilder processBuilder(String... commands) {
        LinkedList<String> list = new LinkedList<String>(
                Arrays.asList(commands));
        return processBuilder(list);
    }

    private ProcessBuilder processBuilder(LinkedList<String> list) {
        if (useStrace()) {
            list.addFirst("-ff");
            list.addFirst("strace." + MDC.get("id") + ".log");
            list.addFirst("-o");
            list.addFirst("strace");
        }
        if (useSudo()) {
            list.addFirst("sudo");
        }
        log.debug("created process builder: " + list);
        return new ProcessBuilder().redirectError(Redirect.INHERIT)
                .redirectOutput(Redirect.INHERIT).command(list);
    }

    public void deleteSubVolume(Path subVolumeDir) {
        String path = subVolumeDir.toAbsolutePath().toString();
        try {
            int exitValue = processBuilder("btrfs", "subvolume", "delete", path)
                    .start().waitFor();
            if (exitValue != 0) {
                throw new IOException("exit code: " + exitValue);
            }
        }
        catch (IOException | InterruptedException e) {
            throw new RuntimeException("Error while deleting sub volume in "
                    + path, e);
        }

    }

    public void receive(Path destinationPath, Consumer<OutputStream> callback) {
        try {
            Process process = processBuilder("btrfs", "receive", "-e",
                    destinationPath.toAbsolutePath().toString())
                    .redirectOutput(Redirect.PIPE).start();
            callback.consume(process.getOutputStream());
            process.getOutputStream().close();
            int exitValue = process.waitFor();
            if (exitValue != 0) {
                throw new IOException("exit code: " + exitValue);
            }
        }
        catch (IOException | InterruptedException e) {
            throw new RuntimeException(
                    "Error while receiving snapshot sub volume in "
                            + destinationPath, e);
        }
    }

    public void send(SendFileSpec sendFile, Consumer<InputStream> callback) {
        try {
            LinkedList<String> args = new LinkedList<>();
            args.addAll(Arrays.asList("btrfs", "send"));
            {
                boolean firstSource = true;
                for (Snapshot s : sendFile.cloneSources) {
                    if (firstSource) {
                        args.add("-p");
                        firstSource = false;
                    } else {
                        args.add("-c");
                    }
                    args.add(s.getSnapshotDir().toAbsolutePath().toString());
                }
            }
            args.add(sendFile.target.getSnapshotDir().toAbsolutePath()
                    .toString());
            Process process = processBuilder(args)
                    .redirectOutput(Redirect.PIPE).start();
            callback.consume(process.getInputStream());
            int exitValue = process.waitFor();
            if (exitValue != 0) {
                throw new IOException("exit code: " + exitValue);
            }
        }
        catch (IOException | InterruptedException e) {
            throw new RuntimeException(
                    "Error while sending snapshot sub volume in " + sendFile, e);
        }
    }

    /**
     * Takes a read only snapshot of the source an puts it to the target
     * 
     * @param readonly
     *            TODO
     */
    public void takeSnapshot(Path sourceVolume, Path target, boolean readonly) {
        try {
            {
                LinkedList<String> list = new LinkedList<String>();
                list.addAll(Arrays.asList("btrfs", "subvolume", "snapshot"));
                if (readonly) {
                    list.add("-r");
                }
                list.add(sourceVolume.toAbsolutePath().toString());
                list.add(target.toAbsolutePath().toString());

                int exitValue = processBuilder(list).start().waitFor();
                if (exitValue != 0) {
                    throw new IOException("exit code: " + exitValue);
                }
            }

        }
        catch (IOException | InterruptedException e) {
            throw new RuntimeException("Error while taking snapshot of  "
                    + sourceVolume.toAbsolutePath() + " to "
                    + target.toAbsolutePath(), e);
        }
    }
}