package jetbrains.mps.baseLanguage.javastub;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import jetbrains.mps.logging.Logger;
import jetbrains.mps.vfs.IFile;
import org.jetbrains.org.objectweb.asm.ClassReader;
import org.jetbrains.org.objectweb.asm.tree.InnerClassNode;
import org.jetbrains.mps.openapi.model.SNodeId;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.stubs.javastub.classpath.ClassifierKind;
import jetbrains.mps.util.NameUtil;
import jetbrains.mps.smodel.LazySNode;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SPropertyOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import org.jetbrains.org.objectweb.asm.Opcodes;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SLinkOperations;
import jetbrains.mps.baseLanguage.javastub.asm.ASMClass;
import java.util.List;
import java.util.Collections;
import java.util.ArrayList;
import jetbrains.mps.java.stub.ReferenceFactory;
import java.util.function.Function;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import java.io.InputStream;
import jetbrains.mps.util.ReadUtil;
import java.io.IOException;
import jetbrains.mps.smodel.builder.SNodeBuilder;
import org.jetbrains.mps.openapi.language.SConcept;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.language.SContainmentLink;

@GeneratedClass(node = "r:aa7e8178-3b66-4295-bcce-165c85d78006(jetbrains.mps.baseLanguage.javastub)/4558608847800740951", model = "r:aa7e8178-3b66-4295-bcce-165c85d78006(jetbrains.mps.baseLanguage.javastub)")
public class ClassifierLoader {
  private static final Logger LOG = Logger.getLogger(ClassifierLoader.class);

  private final boolean mySkipPrivate;
  private final boolean myOnlyPublic;
  protected final IFile myFile;
  protected ClassReader myClassReader;
  protected InnerClassNode myInnerClassDescriptor;

  public ClassifierLoader(IFile file, boolean onlyPublic, boolean skipPrivate) {
    mySkipPrivate = skipPrivate;
    myOnlyPublic = onlyPublic;
    myFile = file;
    byte[] code = readClass(file);
    myClassReader = null;
    if (code != null) {
      try {
        myClassReader = new ClassReader(code);
      } catch (ArrayIndexOutOfBoundsException ex) {
        if (LOG.isWarningLevel()) {
          LOG.warning("File " + file.getPath() + " seems to be corrupted. Empty model loaded until file refresh.");
        }
      }
    }
  }

  protected ClassifierLoader(IFile file, ClassifierLoader outer, InnerClassNode innerClassStruct) {
    this(file, outer.myOnlyPublic, outer.mySkipPrivate);
    myInnerClassDescriptor = innerClassStruct;
  }

  public SNodeId createNodeId() {
    return ASMNodeId.createId(getClassName(myFile));
  }

  public SNode createClassifier() {
    if (myClassReader == null) {
      return null;
    }
    if (isIgnored()) {
      return null;
    }
    final ClassifierKind kind = ClassifierKind.getClassifierKind(myClassReader);
    final String className = getClassName(myFile);
    final SNodeId nodeId = ASMNodeId.createId(className);
    final String shortName = NameUtil.shortNameFromLongName(className.replace('$', '.'));
    SNode lazyRoot;
    switch (kind) {
      case CLASS:
        lazyRoot = new LazySNode(CONCEPTS.ClassConcept$bK, nodeId);

        SPropertyOperations.assign(SNodeOperations.cast(lazyRoot, CONCEPTS.ClassConcept$bK), PROPS.abstractClass$Ta1X, (myClassReader.getAccess() & Opcodes.ACC_ABSTRACT) != 0);
        SPropertyOperations.assign(SNodeOperations.cast(lazyRoot, CONCEPTS.ClassConcept$bK), PROPS.isFinal$f7C_, (myClassReader.getAccess() & Opcodes.ACC_FINAL) != 0);
        break;
      case INTERFACE:
        lazyRoot = new LazySNode(CONCEPTS.Interface$db, nodeId);
        break;
      case ENUM:
        lazyRoot = new LazySNode(CONCEPTS.EnumClass$Vk, nodeId);
        break;
      case ANNOTATIONS:
        lazyRoot = new LazySNode(CONCEPTS.Annotation$he, nodeId);
        break;
      default:
        return null;

    }
    SNode rv = SNodeOperations.as(lazyRoot, CONCEPTS.Classifier$Ix);
    SPropertyOperations.assign(rv, PROPS.name$MnvL, shortName);

    if (myInnerClassDescriptor != null) {
      // static, protected, private are accessible from inner class structure only,
      // JLS 4.7.6, table 4.8
      boolean isStatic = (myInnerClassDescriptor.access & Opcodes.ACC_STATIC) != 0;
      boolean isProtected = (myInnerClassDescriptor.access & Opcodes.ACC_PROTECTED) != 0;
      boolean isPrivate = (myInnerClassDescriptor.access & Opcodes.ACC_PRIVATE) != 0;
      // public, final, abstract are taken from the class, JLS 4.1, table 4.1
      SPropertyOperations.assign(rv, PROPS.nonStatic$aWW8, !(isStatic));
      if (isProtected) {
        SLinkOperations.setTarget(rv, LINKS.visibility$Yyua, createProtectedVisibility_eoyrbu_a0a0h0l0n());
      } else if (isPrivate) {
        SLinkOperations.setTarget(rv, LINKS.visibility$Yyua, createPrivateVisibility_eoyrbu_a0a0a7a11a31());
      }
    }
    return rv;
  }

  protected Iterable<ClassifierLoader> getInnerClassifiers(ASMClass ac) {
    List<InnerClassNode> innerClasses = ac.getInnerClasses();
    if (innerClasses.isEmpty()) {
      return Collections.emptyList();
    }
    String outerName = myClassReader.getClassName();
    IFile parent = myFile.getParent();
    ArrayList<ClassifierLoader> rv = new ArrayList<ClassifierLoader>(innerClasses.size());
    for (InnerClassNode cn : innerClasses) {
      String name = cn.name;
      if (name == null) {
        // I doubt this could ever happen
        continue;
      }
      if (cn.innerName == null) {
        // JVM spec, 4.7.6, inner_name_index - anonymous classes have no inner name
        continue;
      }
      if ((cn.access & Opcodes.ACC_SYNTHETIC) != 0) {
        continue;
      }

      boolean isPrivate = (cn.access & Opcodes.ACC_PRIVATE) != 0;
      if (isPrivate && mySkipPrivate) {
        continue;
      }

      if (!(outerName.equals(cn.outerName))) {
        continue;
      }

      int index = name.lastIndexOf('/');
      if (index != -1) {
        name = name.substring(index + 1);
      }

      ClassifierLoader inner = createChildClassifierLoader(parent, name, cn);
      rv.add(inner);
    }
    return rv;
  }

  protected ClassifierLoader createChildClassifierLoader(IFile parent, String name, InnerClassNode cn) {
    return new ClassifierLoader(parent.findChild(name + ".class"), this, cn);
  }

  public void updateClassifier(SNode classifier, ReferenceFactory refFactory, Function<ASMClass, Documentation> docSupplier) {
    assert myClassReader != null;
    ASMClass ac = new ASMClass(myClassReader, true);
    Documentation doc = docSupplier.apply(ac);
    new ClassifierUpdater(ac, mySkipPrivate, refFactory, doc).update(classifier);
    for (ClassifierLoader innerLoader : getInnerClassifiers(ac)) {
      SNode inner = innerLoader.createClassifier();
      if (inner != null) {
        ListSequence.fromList(SLinkOperations.getChildren(classifier, LINKS.member$L_2d)).addElement(inner);
        innerLoader.updateClassifier(inner, refFactory, docSupplier);
      }
    }
  }

  private boolean isIgnored() {
    return myOnlyPublic && (myClassReader.getAccess() & Opcodes.ACC_PUBLIC) == 0;
  }

  private static byte[] readClass(IFile file) {
    try (InputStream inp = file.openInputStream()) {
      return ReadUtil.read(inp);
    } catch (IOException e) {
      return null;
    }
  }

  public static String getClassName(IFile file) {
    String name = file.getName();
    return name.substring(0, name.indexOf('.'));
  }

  public ClassReader getClassReader() {
    return this.myClassReader;
  }
  private static SNode createProtectedVisibility_eoyrbu_a0a0h0l0n() {
    SNodeBuilder n0 = new SNodeBuilder().init(CONCEPTS.ProtectedVisibility$hr);
    return n0.getResult();
  }
  private static SNode createPrivateVisibility_eoyrbu_a0a0a7a11a31() {
    SNodeBuilder n0 = new SNodeBuilder().init(CONCEPTS.PrivateVisibility$l0);
    return n0.getResult();
  }

  private static final class CONCEPTS {
    /*package*/ static final SConcept ClassConcept$bK = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c108ca66L, "jetbrains.mps.baseLanguage.structure.ClassConcept");
    /*package*/ static final SConcept Interface$db = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101edd46144L, "jetbrains.mps.baseLanguage.structure.Interface");
    /*package*/ static final SConcept EnumClass$Vk = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xfc367070a5L, "jetbrains.mps.baseLanguage.structure.EnumClass");
    /*package*/ static final SConcept Annotation$he = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x114a69dc80cL, "jetbrains.mps.baseLanguage.structure.Annotation");
    /*package*/ static final SConcept Classifier$Ix = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101d9d3ca30L, "jetbrains.mps.baseLanguage.structure.Classifier");
    /*package*/ static final SConcept ProtectedVisibility$hr = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x10af958b686L, "jetbrains.mps.baseLanguage.structure.ProtectedVisibility");
    /*package*/ static final SConcept PrivateVisibility$l0 = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x10af9586f0cL, "jetbrains.mps.baseLanguage.structure.PrivateVisibility");
  }

  private static final class PROPS {
    /*package*/ static final SProperty abstractClass$Ta1X = MetaAdapterFactory.getProperty(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c108ca66L, 0xfa5cee6dfaL, "abstractClass");
    /*package*/ static final SProperty isFinal$f7C_ = MetaAdapterFactory.getProperty(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c108ca66L, 0x11c6af4b284L, "isFinal");
    /*package*/ static final SProperty name$MnvL = MetaAdapterFactory.getProperty(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x110396eaaa4L, 0x110396ec041L, "name");
    /*package*/ static final SProperty nonStatic$aWW8 = MetaAdapterFactory.getProperty(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101d9d3ca30L, 0x73c6d8a8c021f99L, "nonStatic");
  }

  private static final class LINKS {
    /*package*/ static final SContainmentLink visibility$Yyua = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x112670d273fL, 0x112670d886aL, "visibility");
    /*package*/ static final SContainmentLink member$L_2d = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101d9d3ca30L, 0x4a9a46de59132803L, "member");
  }
}
