/*
 * 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 org.jetbrains.java.decompiler.struct;

import org.jetbrains.java.decompiler.code.CodeConstants;
import org.jetbrains.java.decompiler.struct.consts.ConstantPool;
import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant;
import org.jetbrains.java.decompiler.struct.lazy.LazyLoader;
import org.jetbrains.java.decompiler.util.DataInputFullStream;
import org.jetbrains.java.decompiler.util.InterpreterUtil;
import org.jetbrains.java.decompiler.util.VBStyleCollection;

import java.io.IOException;

/*
  class_file {
    u4 magic;
    u2 minor_version;
    u2 major_version;
    u2 constant_pool_count;
    cp_info constant_pool[constant_pool_count-1];
    u2 access_flags;
    u2 this_class;
    u2 super_class;
    u2 interfaces_count;
    u2 interfaces[interfaces_count];
    u2 fields_count;
    field_info fields[fields_count];
    u2 methods_count;
    method_info methods[methods_count];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
  }
*/
public class StructClass extends StructMember {

  public final String qualifiedName;
  public final PrimitiveConstant superClass;

  private final boolean own;
  private final LazyLoader loader;
  private final int minorVersion;
  private final int majorVersion;
  private final int[] interfaces;
  private final String[] interfaceNames;
  private final VBStyleCollection<StructField, String> fields;
  private final VBStyleCollection<StructMethod, String> methods;

  private ConstantPool pool;

  public StructClass(byte[] bytes, boolean own, LazyLoader loader) throws IOException {
    this(new DataInputFullStream(bytes), own, loader);
  }

  public StructClass(DataInputFullStream in, boolean own, LazyLoader loader) throws IOException {
    this.own = own;
    this.loader = loader;

    in.discard(4);

    minorVersion = in.readUnsignedShort();
    majorVersion = in.readUnsignedShort();

    pool = new ConstantPool(in);

    accessFlags = in.readUnsignedShort();
    int thisClassIdx = in.readUnsignedShort();
    int superClassIdx = in.readUnsignedShort();
    qualifiedName = pool.getPrimitiveConstant(thisClassIdx).getString();
    superClass = pool.getPrimitiveConstant(superClassIdx);

    // interfaces
    int length = in.readUnsignedShort();
    interfaces = new int[length];
    interfaceNames = new String[length];
    for (int i = 0; i < length; i++) {
      interfaces[i] = in.readUnsignedShort();
      interfaceNames[i] = pool.getPrimitiveConstant(interfaces[i]).getString();
    }

    // fields
    length = in.readUnsignedShort();
    fields = new VBStyleCollection<StructField, String>();
    for (int i = 0; i < length; i++) {
      StructField field = new StructField(in, this);
      fields.addWithKey(field, InterpreterUtil.makeUniqueKey(field.getName(), field.getDescriptor()));
    }

    // methods
    length = in.readUnsignedShort();
    methods = new VBStyleCollection<StructMethod, String>();
    for (int i = 0; i < length; i++) {
      StructMethod method = new StructMethod(in, this);
      methods.addWithKey(method, InterpreterUtil.makeUniqueKey(method.getName(), method.getDescriptor()));
    }

    // attributes
    attributes = readAttributes(in, pool);

    releaseResources();
  }

  public boolean hasField(String name, String descriptor) {
    return getField(name, descriptor) != null;
  }

  public StructField getField(String name, String descriptor) {
    return fields.getWithKey(InterpreterUtil.makeUniqueKey(name, descriptor));
  }

  public StructMethod getMethod(String key) {
    return methods.getWithKey(key);
  }

  public StructMethod getMethod(String name, String descriptor) {
    return methods.getWithKey(InterpreterUtil.makeUniqueKey(name, descriptor));
  }

  public String getInterface(int i) {
    return interfaceNames[i];
  }

  public void releaseResources() {
    if (loader != null) {
      pool = null;
    }
  }

  public ConstantPool getPool() {
    if (pool == null && loader != null) {
      pool = loader.loadPool(qualifiedName);
    }
    return pool;
  }

  public int[] getInterfaces() {
    return interfaces;
  }

  public String[] getInterfaceNames() {
    return interfaceNames;
  }

  public VBStyleCollection<StructMethod, String> getMethods() {
    return methods;
  }

  public VBStyleCollection<StructField, String> getFields() {
    return fields;
  }

  public boolean isOwn() {
    return own;
  }

  public LazyLoader getLoader() {
    return loader;
  }

  public boolean isVersionGE_1_5() {
    return (majorVersion > 48 || (majorVersion == 48 && minorVersion > 0)); // FIXME: check second condition
  }

  public boolean isVersionGE_1_7() {
    return (majorVersion >= 51);
  }

  public int getBytecodeVersion() {
    switch (majorVersion) {
      case 52:
        return CodeConstants.BYTECODE_JAVA_8;
      case 51:
        return CodeConstants.BYTECODE_JAVA_7;
      case 50:
        return CodeConstants.BYTECODE_JAVA_6;
      case 49:
        return CodeConstants.BYTECODE_JAVA_5;
    }

    return CodeConstants.BYTECODE_JAVA_LE_4;
  }
}
