/*
 * Copyright (c) 2020 by Joel Uckelman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available
 * at http://www.opensource.org.
 */
package VASSAL.tools.io;

import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FilterOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * A class for writing ZIP archives.
 *
 * @author Joel Uckelman
 * @since 3.5.0
 */
public class ZipWriter implements Closeable {
  private final Path full;
  private final Path part;
  private final ZipOutputStream zout;

  public ZipWriter(File f) throws IOException {
    this(Objects.requireNonNull(f).toPath());
  }

  public ZipWriter(Path p) throws IOException {
    full = Objects.requireNonNull(p);

    if (!Files.exists(full)) {
      Files.createFile(full);
    }

    part = full.resolveSibling(full.getFileName() + ".part");
    zout = new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(part)));
  }

  public void write(File src, String dst) throws IOException {
    write(src.toPath(), dst);
  }

  public void write(Path src, String dst) throws IOException {
    final ZipEntry e = makeEntry(dst);
    e.setTime(Files.getLastModifiedTime(src).toMillis());
    zout.putNextEntry(e);

    try (InputStream in = Files.newInputStream(src)) {
      in.transferTo(zout);
    }
  }

  public void write(InputStream src, String dst) throws IOException {
    zout.putNextEntry(makeEntry(dst));
    src.transferTo(zout);
  }

  public void write(byte[] src, String dst) throws IOException {
    zout.putNextEntry(makeEntry(dst));
    zout.write(src, 0, src.length);
  }

  public OutputStream write(String dst) throws IOException {
    zout.putNextEntry(makeEntry(dst));
    return new FilterOutputStream(zout) {
      // Override the write methods to pass directly to the inner stream
      @Override
      public void write(int b) throws IOException {
        out.write(b);
      }

      @Override
      public void write(byte[] b) throws IOException {
        out.write(b);
      }

      @Override
      public void write(byte[] b, int off, int len) throws IOException {
        out.write(b, off, len);
      }

      @Override
      public void close() throws IOException {
        // Prevent zout from being closed; there may be entries yet to write
      }
    };
  }

  @Override
  public void close() throws IOException {
    zout.close();
    Files.move(part, full, StandardCopyOption.REPLACE_EXISTING);
  }

  private static ZipEntry makeEntry(String path) {
    final ZipEntry e = new ZipEntry(path);
    e.setMethod(ZipEntry.DEFLATED);
    return e;
  }
}

@@ -883,32 +883,140 @@
 o.OutputStream;%0A
+import java.nio.channels.Channels;%0Aimport java.nio.channels.FileChannel;%0Aimport java.nio.channels.FileLock;%0A
 import java.nio.
@@ -1079,28 +1079,28 @@
 ile.Standard
-Copy
+Open
 Option;%0Aimpo
@@ -1347,20 +1347,20 @@
 al Path 
-full
+path
 ;%0A  priv
@@ -1373,55 +1373,91 @@
 nal 
-Path part;%0A  private final ZipOutputStream zout
+ZipOutputStream zout;%0A  private final FileChannel fc;%0A  private final FileLock lock
 ;%0A%0A 
@@ -1606,20 +1606,20 @@
 n %7B%0A    
-full
+path
  = Objec
@@ -1649,131 +1649,178 @@
     
-if (!Files.exists(full)) %7B%0A      Files.createFile(full);%0A    %7D%0A%0A    part = full.resolveSibling(full.getFileName() + %22.part%22
+fc = FileChannel.open(%0A      path,%0A      StandardOpenOption.WRITE,%0A      StandardOpenOption.CREATE,%0A      StandardOpenOption.TRUNCATE_EXISTING%0A    );%0A%0A    lock = fc.lock(
 );%0A 
@@ -1874,20 +1874,23 @@
 tStream(
-File
+Channel
 s.newOut
@@ -1899,20 +1899,18 @@
 tStream(
-part
+fc
 )));%0A  %7D
@@ -3343,86 +3343,225 @@
     
-zout.close();%0A    Files.move(part, full, StandardCopyOption.REPLACE_EXISTING);
+try %7B%0A      try %7B%0A        try %7B%0A          zout.flush();%0A        %7D%0A        finally %7B%0A          lock.release();%0A        %7D%0A      %7D%0A      finally %7B%0A        zout.close();%0A      %7D%0A    %7D%0A    finally %7B%0A      fc.close();%0A    %7D
 %0A  %7D

Write directily to the output file and lock it.