/*
 * Copyright 2000-2013 JetBrains s.r.o.
 *
 * 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.
 */

/*
 * @author max
 */
package com.intellij.util.io;

import org.jetbrains.annotations.NonNls;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

public class SourceCodeCompressor {
  private static final VaultOutputStream OUTPUT;
  private static final byte[] PRESET_BUF;
  private static final Deflater DEFLATER;
  private static final Inflater INFLATER;
  private static final byte[] INFLATE_BUFFER;

  private SourceCodeCompressor() {
  }

  static {
    @NonNls final String preset_buf_string =
      "                   ;\r\n\r\n\r\n\r\n\n\n\n { {\r\n }\r\n = == != < > >= <= ? : ++ += -- -= [] [i] () ()) ())) (); ()); ())); () {" +
      "// /* /** */ * opyright (c)package com.import java.utilimport javax.swingimport java.awt" +
      "import com.intellijimport org.import gnu.*;new super(this(public interface extends implements " +
      "public abstract class public class private final static final protected synchronized my our " +
      "instanceof throws return return;if (else {for (while (do {break;continue;throw try {catch (finally {" +
      "null;true;false;void byte short int long boolean float double Object String Class System.Exception Throwable" +
      "getsetputcontainsrunashCodeequalslengthsizeremoveaddclearwritereadopenclosename=\"getNamerray" +
      "istollectionHashMapSetnpututputtreamhildrenarentrootitemctionefaultrojectomponentpplicationerializ" +
      "Created by IntelliJ IDEA.@author Logger ettingsFontialog JPanel JLabel JCheckBox JComboBox JList JSpinner " +
      "<html>/>\r\n<head</head><body bgcolor=</body>table<?xml version=\"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML" +
      "titleframecaret<a href=\"http://</a><div </div><td </td><tr </tr><p </p><hscripttext/css<img src=" +
      "<!--><link rel=width=height=align=span=centerrightleftstyle=celljsp:rootxmlns:avascript";
    PRESET_BUF = preset_buf_string.getBytes();
    OUTPUT = new VaultOutputStream();
    DEFLATER = new Deflater(Deflater.BEST_COMPRESSION);
    INFLATER = new Inflater();
    INFLATE_BUFFER = new byte[4096];
  }

  public static synchronized byte[] compress(byte[] source, int off, int len) {
    try {
      DEFLATER.reset();
      DEFLATER.setDictionary(PRESET_BUF);
      try {
        DeflaterOutputStream output = null;
        try {
          output = new DeflaterOutputStream(OUTPUT, DEFLATER);
          output.write(source, off, len);
        }
        finally {
          if (output != null) {
            output.close();
          }
        }
      }
      catch (IOException e) {
        return source;
      }
      return OUTPUT.toByteArray();
    }
    finally {
      OUTPUT.reset();
    }

  }

  public static synchronized byte[] compress(byte[] source) {
    return compress(source, 0, source.length);
  }

  public static synchronized byte[] decompress(byte[] compressed) throws IOException {
    final int len = compressed.length;
    final int off = 0;
    return decompress(compressed, len, off);
  }

  public static byte[] decompress(final byte[] compressed, final int len, final int off) throws IOException {
    INFLATER.reset();
    InflaterInputStream input = null;
    try {
      input = new InflaterInputStream(new ByteArrayInputStream(compressed, off, len), INFLATER);
      final int b = input.read();
      if (b == -1) {
        INFLATER.setDictionary(PRESET_BUF);
      }
      else {
        OUTPUT.write(b);
      }
      int readBytes;
      while ((readBytes = input.read(INFLATE_BUFFER)) > 0) {
        OUTPUT.write(INFLATE_BUFFER, 0, readBytes);
      }
      return OUTPUT.toByteArray();
    }
    finally {
      if (input != null) {
        input.close();
      }
      OUTPUT.reset();
    }
  }

  private static class VaultOutputStream extends ByteArrayOutputStream {

    private static final int MIN_BUF_SIZE = 0x10000;
    private final byte[] MIN_BUFFER;

    public VaultOutputStream() {
      super(MIN_BUF_SIZE);
      MIN_BUFFER = buf;
    }

    @Override
    @SuppressWarnings({"NonSynchronizedMethodOverridesSynchronizedMethod"})
    public void reset() {
      count = 0;
      buf = MIN_BUFFER;
    }
  }
}
