package com.cosium.code.format.executable;

import static java.util.Objects.requireNonNull;

import com.cosium.code.format.MavenGitCodeFormatException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.logging.Log;

/**
 * Created on 08/11/17.
 *
 * @author Reda.Housni-Alaoui
 */
class DefaulExecutable implements Executable {

  private static final String SHIBANG = "#!/bin/bash";

  private final Supplier<Log> log;
  private final Path file;

  DefaulExecutable(Supplier<Log> log, Path file) throws IOException {
    requireNonNull(log);
    requireNonNull(file);
    this.log = log;

    this.file = file;

    if (!Files.exists(file)) {
      this.log.get().debug("Creating " + file);
      Files.createFile(file);
      truncate();
    } else {
      this.log.get().debug(file + " already exists");
    }

    this.log.get().debug("Marking '" + file + "' as executable");
    Set<PosixFilePermission> permissions;
    try {
      permissions = Files.getPosixFilePermissions(file);
    } catch (UnsupportedOperationException ignored) {
      return;
    }

    permissions.add(PosixFilePermission.OWNER_EXECUTE);
    permissions.add(PosixFilePermission.GROUP_EXECUTE);
    permissions.add(PosixFilePermission.OTHERS_EXECUTE);

    Files.setPosixFilePermissions(file, permissions);
  }

  @Override
  public Executable truncate() throws IOException {
    log.get().debug("Truncating '" + file + "'");
    Files.write(file, Collections.singleton(SHIBANG), StandardOpenOption.TRUNCATE_EXISTING);
    return this;
  }

  @Override
  public Executable truncateWithTemplate(
      Supplier<InputStream> template, String sourceEncoding, Object... values) throws IOException {
    try (InputStream inputStream = template.get()) {
      String rawContent = IOUtils.toString(inputStream, sourceEncoding);
      Object[] refinedValues = Stream.of(values).map(this::unixifyPath).toArray();
      String content = String.format(rawContent, refinedValues);
      Files.write(file, content.getBytes(), StandardOpenOption.TRUNCATE_EXISTING);
    }
    return this;
  }

  @Override
  public Executable appendCommandCall(String commandCall) throws IOException {
    String unixCommandCall = unixifyPath(commandCall, true);
    boolean callExists =
        Files.readAllLines(file).stream().anyMatch(s -> s.contains(unixCommandCall));
    if (callExists) {
      log.get().debug("Command call already exists in " + file);
    } else {
      log.get().debug("No command call found in " + file);
      log.get().debug("Appending the command call to " + file);
      Files.write(file, Collections.singletonList(unixCommandCall), StandardOpenOption.APPEND);
      log.get().debug("Appended the command call to " + file);
    }
    return this;
  }

  @Override
  public Executable removeCommandCall(String commandCall) {
    String unixCommandCall = unixifyPath(commandCall, true);
    try {
      List<String> linesToKeep =
          Files.readAllLines(file).stream()
              .filter(line -> !unixCommandCall.equals(line))
              .collect(Collectors.toList());
      Files.write(file, linesToKeep, StandardOpenOption.TRUNCATE_EXISTING);
    } catch (IOException e) {
      throw new MavenGitCodeFormatException(e);
    }
    return this;
  }

  private String unixifyPath(Object o) {
    return unixifyPath(o, false);
  }

  private String unixifyPath(Object o, boolean force) {
    if (!force && !(o instanceof Path)) {
      return String.valueOf(o);
    }

    String result;
    if (o instanceof Path) {
      Path path = (Path) o;
      result = path.toAbsolutePath().toString();
    } else {
      result = String.valueOf(o);
    }
    return "\"" + StringUtils.replace(result, "\\", "/") + "\"";
  }

  @Override
  public String toString() {
    return file.toString();
  }
}