package edu.pdx.cs410J.grader;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.ByteStreams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

public class GwtZipFixer {

  private static final Logger logger = LoggerFactory.getLogger("edu.pdx.cs410J.grader");

  public static void main(String[] args) {
    String outputDirectoryName = null;
    List<String> zipFileNames = new ArrayList<>();

    for (String arg : args) {
      if (outputDirectoryName == null) {
        outputDirectoryName = arg;

      } else {
        zipFileNames.add(arg);
      }
    }

    if (outputDirectoryName == null) {
      usage("Missing output directory");
      return;
    }

    if (zipFileNames.isEmpty()) {
      usage("Missing zip file");
      return;
    }

    File outputDirectory = new File(outputDirectoryName);

    for (String zipFileName : zipFileNames) {
      File zipFile = new File(zipFileName);
      try {
        fixZipFile(zipFile, outputDirectory);

      } catch (IOException e) {
        usage("While fixing zip file " + zipFile + ": " + e);
      }

    }
  }

  private static void fixZipFile(File zipFile, File outputDirectory) throws IOException {
    File fixedZipFile = getFixedZipFile(zipFile, outputDirectory);

    try (
      ZipInputStream input = new ZipInputStream(new FileInputStream(zipFile));
      ZipOutputStream output = new ZipOutputStream(new FileOutputStream(fixedZipFile))
    ) {
      for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) {
        String entryName = entry.getName();
        String fixedEntryName = getFixedEntryName(entryName);

        if (fixedEntryName != null) {
          logger.debug(entryName + " fixed to " + fixedEntryName);

          ZipEntry fixedEntry = new ZipEntry(fixedEntryName);
          output.putNextEntry(fixedEntry);

          ByteStreams.copy(input, output);

        } else {
          logger.debug(entryName + " ignored");
        }

        output.flush();
      }
    }
  }

  @VisibleForTesting
  static String getFixedEntryName(String entryName) {
    Stream<String> ignore = Stream.of("__MACOSX", "/test", "/it", "/target/", ".DS_Store");
    if (ignore.anyMatch(entryName::contains)) {
      return null;
    }

    if (entryName.contains("pom.xml")) {
      return "pom.xml";
    }

    String fixedName = replaceRegexWithPrefix(entryName, ".*/main/(.*)", "src/main/");

    if (fixedName == null) {
      fixedName = replaceRegexWithPrefix(entryName, ".*/java/(.*)", "src/main/java/");
    }

    if (fixedName == null) {
      fixedName = replaceRegexWithPrefix(entryName, ".*/resources/(.*)", "src/main/resources/");
    }

    if (fixedName == null) {
      fixedName = replaceRegexWithPrefix(entryName, ".*/webapp/(.*)", "src/main/webapp/");
    }

    if (fixedName == null) {
      fixedName = replaceRegexWithPrefix(entryName, "^edu/(.*)", "src/main/java/edu/");
    }

    return fixedName;
  }

  private static String replaceRegexWithPrefix(String entryName, String regex, String replaceWithPrefix) {
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(entryName);

    if (matcher.matches()) {
      String portionUnderMain = matcher.group(1);
      if ("".equals(portionUnderMain)) {
        return null;

      } else {
        return replaceWithPrefix + portionUnderMain;
      }

    } else {
      return null;
    }
  }

  private static File getFixedZipFile(File zipFile, File outputDirectory) {
    return new File(outputDirectory, zipFile.getName());
  }

  private static void usage(String message) {
    PrintStream err = System.err;

    err.println("+++ " + message);
    err.println();
    err.println("usage: java GwtZipFixer outputDirectory zipFile+");
    err.println("    outputDirectory     Name of direct into which fixed zip files should be written");
    err.println("    zipFile             Name of zip file to be fixed");
    err.println();
    err.println("Fixes the contents of a zip file submitted for the GWT ");
    err.println("project so that it will work with the grading scripts");
    err.println();

    System.exit(1);
  }
}

@@ -85,49 +85,8 @@
 ng;%0A
-import com.google.common.io.ByteStreams;%0A
 impo
@@ -181,24 +181,50 @@
 .ArrayList;%0A
+import java.util.HashMap;%0A
 import java.
@@ -230,24 +230,46 @@
 .util.List;%0A
+import java.util.Map;%0A
 import java.
@@ -423,46 +423,8 @@
 eam;
-%0Aimport java.util.zip.ZipOutputStream;
 %0A%0Apu
@@ -1504,24 +1504,98 @@
 tDirectory);
+%0A    ZipFileMaker maker = new ZipFileMaker(fixedZipFile, new HashMap%3C%3E());
 %0A%0A    try (%0A
@@ -1681,98 +1681,79 @@
     
-  ZipOutputStream output = new ZipOutputStream(new FileOutputStream(fixedZipFile))%0A    ) %7B
+) %7B%0A      Map%3CZipEntry, InputStream%3E zipFileEntries = new HashMap%3C%3E();%0A
 %0A   
@@ -2135,79 +2135,211 @@
     
-output.putNextEntry(fixedEntry);%0A%0A          ByteStreams.copy(input, out
+fixedEntry.setLastModifiedTime(entry.getLastModifiedTime());%0A          fixedEntry.setSize(entry.getSize());%0A          fixedEntry.setMethod(ZipEntry.DEFLATED);%0A%0A          zipFileEntries.put(fixedEntry, in
 put)
@@ -2420,46 +2420,64 @@
   %7D%0A
-%0A        output.flush(
+      %7D%0A%0A      maker.makeZipFile(zipFileEntries
 );%0A    
- 
- 
 %7D%0A
-    %7D
 %0A  %7D

Refactor GwtZipFixer to use the ZipFileMaker. This will help us address issue #246.