/*
 * Copyright 2013-present Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package com.facebook.buck.dalvik;

import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * Helper to write a Zip file used by {@link DalvikAwareZipSplitter}.
 */
public class DalvikAwareOutputStreamHelper implements ZipOutputStreamHelper {

  private static final int MAX_METHOD_REFERENCES = 64 * 1024;

  private final ZipOutputStream outStream;
  private final Set<String> entryNames = Sets.newHashSet();
  private final long linearAllocLimit;
  private final File reportFile;
  private final DalvikStatsCache dalvikStatsCache;

  private final Set<DalvikStatsTool.MethodReference> currentMethodReferences = Sets.newHashSet();
  private long currentLinearAllocSize;

  DalvikAwareOutputStreamHelper(
      File outputFile,
      long linearAllocLimit,
      File reportDir,
      DalvikStatsCache dalvikStatsCache)
      throws FileNotFoundException {
    this.outStream = new ZipOutputStream(
      new BufferedOutputStream(
          new FileOutputStream(outputFile)));
    this.linearAllocLimit = linearAllocLimit;
    this.reportFile = new File(reportDir, outputFile.getName() + ".txt");
    this.dalvikStatsCache = dalvikStatsCache;
  }

  private boolean isEntryTooBig(FileLike entry) {
    DalvikStatsTool.Stats stats = dalvikStatsCache.getStats(entry);
    if (currentLinearAllocSize + stats.estimatedLinearAllocSize > linearAllocLimit) {
      return true;
    }
    int newReferences = Sets.difference(stats.methodReferences, currentMethodReferences).size();
    if (currentMethodReferences.size() + newReferences > MAX_METHOD_REFERENCES) {
      return true;
    }
    return false;
  }

  @Override
  public boolean canPutEntry(FileLike fileLike) {
    return !isEntryTooBig(fileLike);
  }

  @Override
  public boolean containsEntry(FileLike fileLike) {
    return entryNames.contains(fileLike.getRelativePath());
  }

  @Override
  public void putEntry(FileLike fileLike) throws IOException {
    String name = fileLike.getRelativePath();
    // Tracks unique entry names and avoids duplicates.  This is, believe it or not, how
    // proguard seems to handle merging multiple -injars into a single -outjar.
    if (!containsEntry(fileLike)) {
      entryNames.add(name);
      outStream.putNextEntry(new ZipEntry(name));
      try (InputStream in = fileLike.getInput()) {
        ByteStreams.copy(in, outStream);
      }

      // Make sure FileLike#getSize didn't lie (or we forgot to call canPutEntry).
      DalvikStatsTool.Stats stats = dalvikStatsCache.getStats(fileLike);
      Preconditions.checkState(!isEntryTooBig(fileLike),
          "Putting entry %s (%s) exceeded maximum size of %s",
          name, stats.estimatedLinearAllocSize, linearAllocLimit);
      currentLinearAllocSize += stats.estimatedLinearAllocSize;
      currentMethodReferences.addAll(stats.methodReferences);
      String report = String.format(
          "%d %d %s\n",
          stats.estimatedLinearAllocSize, stats.methodReferences.size(), name
      );
      Files.append(report, reportFile, Charsets.UTF_8);
    }
  }

  private DalvikStatsTool.Stats getStats(FileLike entry) {
    String name = entry.getRelativePath();
    if (!name.endsWith(".class")) {
      // Probably something like a pom.properties file in a JAR: this does not contribute
      // to the linear alloc size, so return zero.
      return DalvikStatsTool.Stats.ZERO;
    }

    try {
      return DalvikStatsTool.getEstimate(entry.getInput());
    } catch (IOException | RuntimeException e) {
      throw new RuntimeException(String.format("Error calculating size for %s.", name), e);
    }
  }

  @Override
  public void close() throws IOException {
    outStream.close();
  }
}

@@ -4056,557 +4056,8 @@
  %7D%0A%0A
-  private DalvikStatsTool.Stats getStats(FileLike entry) %7B%0A    String name = entry.getRelativePath();%0A    if (!name.endsWith(%22.class%22)) %7B%0A      // Probably something like a pom.properties file in a JAR: this does not contribute%0A      // to the linear alloc size, so return zero.%0A      return DalvikStatsTool.Stats.ZERO;%0A    %7D%0A%0A    try %7B%0A      return DalvikStatsTool.getEstimate(entry.getInput());%0A    %7D catch (IOException %7C RuntimeException e) %7B%0A      throw new RuntimeException(String.format(%22Error calculating size for %25s.%22, name), e);%0A    %7D%0A  %7D%0A%0A
   @O

Remove unused method in DalvikAwareOutputStreamHelper.