/*
 * Copyright 2000-2014 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.
 */
package com.intellij.codeInspection.bytecodeAnalysis;

import com.intellij.ide.highlighter.JavaClassFileType;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.SystemProperties;
import com.intellij.util.indexing.*;
import com.intellij.util.io.DataExternalizer;
import com.intellij.util.io.DataInputOutputUtil;
import com.intellij.util.io.DifferentSerializableBytesImplyNonEqualityPolicy;
import com.intellij.util.io.KeyDescriptor;
import org.jetbrains.annotations.NotNull;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * @author lambdamix
 */
public class BytecodeAnalysisIndex extends FileBasedIndexExtension<Bytes, HEquations> {
  public static final ID<Bytes, HEquations> NAME = ID.create("bytecodeAnalysis");
  private final HEquationsExternalizer myExternalizer = new HEquationsExternalizer();
  private static final ClassDataIndexer INDEXER = new ClassDataIndexer();
  private static final HKeyDescriptor KEY_DESCRIPTOR = new HKeyDescriptor();

  private static final int ourInternalVersion = 0;
  private static boolean ourEnabled = SystemProperties.getBooleanProperty("idea.enable.bytecode.contract.inference", isEnabledByDefault());

  private static boolean isEnabledByDefault() {
    Application application = ApplicationManager.getApplication();
    return application.isInternal() || application.isUnitTestMode();
  }

  @NotNull
  @Override
  public ID<Bytes, HEquations> getName() {
    return NAME;
  }

  @NotNull
  @Override
  public DataIndexer<Bytes, HEquations, FileContent> getIndexer() {
    return INDEXER;
  }

  @NotNull
  @Override
  public KeyDescriptor<Bytes> getKeyDescriptor() {
    return KEY_DESCRIPTOR;
  }

  @NotNull
  @Override
  public DataExternalizer<HEquations> getValueExternalizer() {
    return myExternalizer;
  }

  @NotNull
  @Override
  public FileBasedIndex.InputFilter getInputFilter() {
    return new DefaultFileTypeSpecificInputFilter(JavaClassFileType.INSTANCE) {
      @Override
      public boolean acceptInput(@NotNull VirtualFile file) {
        return ourEnabled && super.acceptInput(file);
      }
    };
  }

  @Override
  public boolean dependsOnFileContent() {
    return true;
  }

  @Override
  public int getVersion() {
    return ourInternalVersion + (ourEnabled ? 0xFF : 0);
  }

  private static class HKeyDescriptor implements KeyDescriptor<Bytes>, DifferentSerializableBytesImplyNonEqualityPolicy {

    @Override
    public void save(@NotNull DataOutput out, Bytes value) throws IOException {
      out.write(value.bytes);
    }

    @Override
    public Bytes read(@NotNull DataInput in) throws IOException {
      byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE];
      for (int i = 0; i < bytes.length; i++) {
        bytes[i] = in.readByte();
      }
      return new Bytes(bytes);
    }

    @Override
    public int getHashCode(Bytes value) {
      return Arrays.hashCode(value.bytes);
    }

    @Override
    public boolean isEqual(Bytes val1, Bytes val2) {
      return Arrays.equals(val1.bytes, val2.bytes);
    }
  }

  public static class HEquationsExternalizer implements DataExternalizer<HEquations>, DifferentSerializableBytesImplyNonEqualityPolicy {
    @Override
    public void save(@NotNull DataOutput out, HEquations eqs) throws IOException {
      out.writeBoolean(eqs.stable);
      DataInputOutputUtil.writeINT(out, eqs.results.size());
      for (DirectionResultPair pair : eqs.results) {
        DataInputOutputUtil.writeINT(out, pair.directionKey);
        HResult rhs = pair.hResult;
        if (rhs instanceof HFinal) {
          HFinal finalResult = (HFinal)rhs;
          out.writeBoolean(true); // final flag
          DataInputOutputUtil.writeINT(out, finalResult.value.ordinal());
        }
        else {
          HPending pendResult = (HPending)rhs;
          out.writeBoolean(false); // pending flag
          DataInputOutputUtil.writeINT(out, pendResult.delta.length);

          for (HComponent component : pendResult.delta) {
            DataInputOutputUtil.writeINT(out, component.value.ordinal());
            HKey[] ids = component.ids;
            DataInputOutputUtil.writeINT(out, ids.length);
            for (HKey hKey : ids) {
              out.write(hKey.key);
              DataInputOutputUtil.writeINT(out, hKey.dirKey);
              out.writeBoolean(hKey.stable);
            }
          }
        }
      }
    }

    @Override
    public HEquations read(@NotNull DataInput in) throws IOException {
      boolean stable = in.readBoolean();
      int size = DataInputOutputUtil.readINT(in);
      ArrayList<DirectionResultPair> results = new ArrayList<DirectionResultPair>(size);
      for (int k = 0; k < size; k++) {
        int directionKey = DataInputOutputUtil.readINT(in);
        boolean isFinal = in.readBoolean(); // flag
        if (isFinal) {
          int ordinal = DataInputOutputUtil.readINT(in);
          Value value = Value.values()[ordinal];
          results.add(new DirectionResultPair(directionKey, new HFinal(value)));
        }
        else {
          int sumLength = DataInputOutputUtil.readINT(in);
          HComponent[] components = new HComponent[sumLength];

          for (int i = 0; i < sumLength; i++) {
            int ordinal = DataInputOutputUtil.readINT(in);
            Value value = Value.values()[ordinal];
            int componentSize = DataInputOutputUtil.readINT(in);
            HKey[] ids = new HKey[componentSize];
            for (int j = 0; j < componentSize; j++) {
              byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE];
              for (int bi = 0; bi < bytes.length; bi++) {
                bytes[bi] = in.readByte();
              }
              ids[j] = new HKey(bytes, DataInputOutputUtil.readINT(in), in.readBoolean());
            }
            components[i] = new HComponent(value, ids);
          }
          results.add(new DirectionResultPair(directionKey, new HPending(components)));
        }
      }
      return new HEquations(results, stable);
    }
  }
}

@@ -708,121 +708,8 @@
 pe;%0A
-import com.intellij.openapi.application.Application;%0Aimport com.intellij.openapi.application.ApplicationManager;%0A
 impo
@@ -1832,219 +1832,14 @@
 e%22, 
-isEnabledByDefault());%0A%0A  private static boolean isEnabledByDefault() %7B%0A    Application application = ApplicationManager.getApplication();%0A    return application.isInternal() %7C%7C application.isUnitTestMode();%0A  %7D
+true);
 %0A%0A  

enable bytecode analysis