class PythonArchiveAnalyzer implements FileAnalyzer {

  @Override
  public Map<ConstructId, Construct> getConstructs() throws FileAnalysisException {
    if (this.constructs.isEmpty()) {

      // list of __init__.py files (to become packages)
      final List<String> inits = new ArrayList<String>();
      boolean pre_processed = false;
      try (InputStream is = this.getArchiveInputStream()) {

        // Read archive to look for __init__.py
        if (is instanceof ZipInputStream) {
          ZipEntry en = null;
          while ((en = ((ZipInputStream) is).getNextEntry()) != null) {
            // loop over the archive to understand which files has an __init__.py
            if (!en.isDirectory()) {
              if (this.isPackage(en.getName())) {
                inits.add(en.getName());
              }
            }
          }
        } else if (is instanceof TarArchiveInputStream) {
          ArchiveEntry en = null;
          while ((en = ((TarArchiveInputStream) is).getNextEntry()) != null) {
            // Loop over the archive to understand which files has an __init__.py
            if (!en.isDirectory()) {
              if (this.isPackage(en.getName())) {
                inits.add(en.getName());
              }
            }
          }
        }
        pre_processed = true;
      } catch (IOException e) {
        log.error(
            "IOException analyzing Python archive [" + this.archive.getAbsolutePath() + "]", e);
      }

      // Re-read archive to create constructs
      try (InputStream is = this.getArchiveInputStream()) {

        if (is instanceof ZipInputStream && pre_processed) {
          ZipEntry en = null;
          while ((en = ((ZipInputStream) is).getNextEntry()) != null) {

            if (!en.isDirectory()) {
              // check if it is a tar (wheel egg), then extract and analyze it (here or where??) !!!
              if (en.getName().endsWith(".whl")
                  || en.getName().endsWith(".egg")
                  || en.getName().endsWith(".gz")) {
                final FileAnalyzer fa = DirAnalyzer.createAnalyzerForArchiveEntry(is, en.getName());
                if (fa != null) this.nestedAnalyzers.add(fa);
              } else if (en.getName().endsWith(".py")) {

                // Create the package (if any)
                final List<String> package_name = this.getPackageName(en.getName(), inits);
                PythonId pack = null;
                if (!package_name.isEmpty())
                  pack =
                      new PythonId(null, PythonId.Type.PACKAGE, StringUtil.join(package_name, "."));

                // Create module
                final String module_name = this.getModuleName(en.getName());
                final PythonId module = new PythonId(pack, PythonId.Type.MODULE, module_name);

                // We put everything into a byte[] to avoid that the stream gets closed when read by
                // antlr
                final byte[] py_bytes = FileUtil.readInputStream(is);

                final FileAnalyzer fa =
                    PythonFileAnalyzer.createAnalyzer(new ByteArrayInputStream(py_bytes));
                if (fa instanceof Python3FileAnalyzer) {
                  ((Python3FileAnalyzer) fa).setContext(module, pack);
                  this.constructs.putAll(
                      ((Python3FileAnalyzer) fa).getConstructs(new ByteArrayInputStream(py_bytes)));
                } else if (fa instanceof Python335FileAnalyzer) {
                  ((Python335FileAnalyzer) fa).setContext(module, pack);
                  this.constructs.putAll(
                      ((Python335FileAnalyzer) fa)
                          .getConstructs(new ByteArrayInputStream(py_bytes)));
                }
              }
            }
          }
        }
        // almost duplicated code from the alternative above as we have another type for the entry
        // (ArchiveEntry from commons compress instead of ZipEntry from java.util.zip)
        else if (is instanceof TarArchiveInputStream) {
          ArchiveEntry en = null;
          while ((en = ((TarArchiveInputStream) is).getNextEntry()) != null) {
            if (!en.isDirectory()) {
              // check if it is a tar (wheel egg), then extract and analyze it (here or where??) !!!
              if (en.getName().endsWith(".whl")
                  || en.getName().endsWith(".egg")
                  || en.getName().endsWith(".gz")) {
                final FileAnalyzer fa = DirAnalyzer.createAnalyzerForArchiveEntry(is, en.getName());
                if (fa != null) this.nestedAnalyzers.add(fa);
              } else if (en.getName().endsWith(".py")) {

                // Create the package (if any)
                final List<String> package_name = this.getPackageName(en.getName(), inits);
                PythonId pack = null;
                if (!package_name.isEmpty())
                  pack =
                      new PythonId(null, PythonId.Type.PACKAGE, StringUtil.join(package_name, "."));

                // Create module
                final String module_name = this.getModuleName(en.getName());
                final PythonId module = new PythonId(pack, PythonId.Type.MODULE, module_name);

                // We put everything into a byte[] to avoid that the stream gets closed when read by
                // antlr
                final byte[] py_bytes = FileUtil.readInputStream(is);

                final FileAnalyzer fa =
                    PythonFileAnalyzer.createAnalyzer(new ByteArrayInputStream(py_bytes));
                if (fa instanceof Python3FileAnalyzer) {
                  ((Python3FileAnalyzer) fa).setContext(module, pack);
                  this.constructs.putAll(
                      ((Python3FileAnalyzer) fa).getConstructs(new ByteArrayInputStream(py_bytes)));
                } else if (fa instanceof Python335FileAnalyzer) {
                  ((Python335FileAnalyzer) fa).setContext(module, pack);
                  this.constructs.putAll(
                      ((Python335FileAnalyzer) fa)
                          .getConstructs(new ByteArrayInputStream(py_bytes)));
                }
              }
            }
          }
        }
      } catch (IOException e) {
        log.error(
            "IOException analyzing Python archive [" + this.archive.getAbsolutePath() + "]", e);
      }
    }

    return constructs;
  }

}