package jetbrains.buildServer.buildTriggers.url;

import jetbrains.buildServer.log.Loggers;
import jetbrains.buildServer.serverSide.CustomDataStorage;
import jetbrains.buildServer.util.FileUtil;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URL;
import java.net.URLConnection;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Map;

/**
 * User: vbedrosova
 * Date: 06.12.10
 * Time: 20:10
 */
public class UrlChangesDetector {
  @NotNull
  private static final Logger LOG = Logger.getLogger(Loggers.VCS_CATEGORY + UrlChangesDetector.class);

  @NotNull
  private static final String DEBUG_RESOURCE_PATH_PARAM = "url.build.trigger.debug.resource.path";

  public boolean isChanged(@NotNull final String urlStr,
                           @NotNull final CustomDataStorage dataStorage,
                           @Nullable final Map<String, String> additionalParams) throws Exception {
    final URL url = new URL(urlStr);

    final String digest = getDigest(url.openStream(), getDebugResourceWriter(url, additionalParams));
    LOG.debug("Calculated md5 digest for " + urlStr + ": " + digest);

    final String storedDigest = getStoredDigest(urlStr, dataStorage);
    LOG.debug("Retrieved md5 digest for " + urlStr + " from data storage: " + storedDigest);

    if (!digest.equals(storedDigest)) {
      LOG.debug("Detected md5 digest changed for " + urlStr + "new:" + digest + " old:" + storedDigest);
      storeDigest(urlStr, digest, dataStorage);
      return true;
    }

    return false;
  }

  private static void storeDigest(@NotNull String url,
                                  @NotNull final String digest,
                                  @NotNull CustomDataStorage dataStorage) {
    dataStorage.putValue(url, digest);
  }

  @Nullable
  private static String getStoredDigest(@NotNull String url, @NotNull CustomDataStorage dataStorage) {
    return dataStorage.getValue(url);
  }

  @NotNull
  private static String getDigest(@NotNull final InputStream source, @Nullable final FileWriter fw) throws IOException {
    try {
      final MessageDigest digest = MessageDigest.getInstance("MD5");
      final DigestInputStream dis = new DigestInputStream(source, digest);
      final byte[] bytes = new byte[32768];
      try {
        while (dis.read(bytes) > 0) {
          if (fw != null) fw.write(new String(bytes));
        }
      } finally {
        dis.close();
        close(fw);
      }
      return toHex(digest.digest());
    } catch (NoSuchAlgorithmException e) {
      throw new IOException("MD5 not installed", e);
    }
  }

  private static void close(FileWriter fw) throws IOException {
    if (fw != null) {
      fw.close();
    }
  }

  private static String toHex(byte[] arg) throws IOException {
    return String.format("%x", new BigInteger(arg));
  }

  @Nullable
  private static FileWriter getDebugResourceWriter(@NotNull URL url,
                                                   @Nullable final Map<String, String> additionalParams) throws  IOException{
    if (additionalParams != null && additionalParams.containsKey(DEBUG_RESOURCE_PATH_PARAM)) {
      final File file = new File(additionalParams.get(DEBUG_RESOURCE_PATH_PARAM), url.toString().hashCode() + url.getFile());
      FileUtil.delete(file);
      file.getParentFile().mkdirs();
      return new FileWriter(file);
    }
    return null;
  }
}
