content: /**
   * Copied and adapted from plugin
   * <a href="https://github.com/neueda/jetbrains-plugin-graph-database-support">Graph Database Support</a>
   * by Neueda Technologies, Ltd.
   * Modified by Alberto Venturini, 2022
   */
  package com.albertoventurini.graphdbplugin.database.neo4j.bolt;
  
  import com.albertoventurini.graphdbplugin.database.api.GraphDatabaseApi;
  import com.albertoventurini.graphdbplugin.database.api.data.GraphDatabaseVersion;
  import com.albertoventurini.graphdbplugin.database.api.data.GraphMetadata;
  import com.albertoventurini.graphdbplugin.database.api.query.GraphQueryResult;
  import com.albertoventurini.graphdbplugin.database.neo4j.bolt.data.Neo4jGraphDatabaseVersion;
  import com.albertoventurini.graphdbplugin.database.neo4j.bolt.query.Neo4jBoltQueryResult;
  import com.albertoventurini.graphdbplugin.database.neo4j.bolt.query.Neo4jBoltQueryResultRow;
  import org.neo4j.driver.AuthToken;
  import org.neo4j.driver.AuthTokens;
  import org.neo4j.driver.Driver;
  import org.neo4j.driver.GraphDatabase;
  import org.neo4j.driver.Record;
  import org.neo4j.driver.Result;
  import org.neo4j.driver.Session;
  import org.neo4j.driver.SessionConfig;
  import org.neo4j.driver.exceptions.ClientException;
  
  import java.nio.channels.UnresolvedAddressException;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.Map;
  import java.util.stream.IntStream;
  
  /**
   * Communicates with Neo4j 3.0+ database using Bolt driver.
   */
  public class Neo4jBoltDatabase implements GraphDatabaseApi {
  
      private static final String VERSION_QUERY = """
              CALL dbms.components() YIELD versions
              RETURN versions[0] AS version
              """;
  
      private final String url;
      private final AuthToken auth;
      private final SessionConfig dbConfig;
  
      public Neo4jBoltDatabase(Map<String, String> configuration) {
          this(new Neo4jBoltConfiguration(configuration));
      }
  
      public Neo4jBoltDatabase(Neo4jBoltConfiguration configuration) {
          final String host = configuration.getHost();
          final int port = configuration.getPort();
          final String username = configuration.getUser();
          final String password = configuration.getPassword();
  
          final String authType = configuration.getAuthType();
          final String protocol = configuration.getProtocol();
          final String database = configuration.getDatabase();
  
          if (protocol == null) {
              if (host.startsWith("bolt://") || host.startsWith("bolt+routing://")) {
                  url = String.format("%s:%s", host, port);
              } else {
                  url = String.format("bolt://%s:%s", host, port);
              }
          } else {
              url = "%s://%s:%s".formatted(protocol, host, port);
          }
  
          if (username != null && password != null) {
              auth = AuthTokens.basic(username, password);
          } else {
              auth = AuthTokens.none();
          }
  
          if (database != null && !database.trim().isEmpty()) {
            dbConfig = SessionConfig.forDatabase(database);
          } else {
            dbConfig = SessionConfig.defaultConfig();
          }
      }
  
      @Override
      public GraphQueryResult execute(String query) {
          return execute(query, Collections.emptyMap());
      }
  
      @Override
      public GraphQueryResult execute(String query, Map<String, Object> statementParameters) {
          try {
              // TODO: driver might be instantiated when this object is constructed. No need to create a new driver every time a query is executed.
              Driver driver = GraphDatabase.driver(url, auth);
              try {
                  try (Session session = driver.session(dbConfig)) {
  
                      Neo4jBoltBuffer buffer = new Neo4jBoltBuffer();
  
                      long startTime = System.currentTimeMillis();
                      Result statementResult = session.run(query, statementParameters);
                      buffer.addColumns(statementResult.keys());
  
                      for (Record record : statementResult.list()) {
                          // Add row
                          buffer.addRow(record);
                      }
                      buffer.addResultSummary(statementResult.consume());
                      long endTime = System.currentTimeMillis();
  
                      return new Neo4jBoltQueryResult(endTime - startTime, buffer);
                  }
              } finally {
                  driver.closeAsync();
              }
          } catch (UnresolvedAddressException e) {
              throw new ClientException(e.getMessage());
          }
      }
  
      @Override
      public GraphMetadata metadata() {
          throw new IllegalStateException("Not implemented");
      }
  
      @Override
      public GraphDatabaseVersion getVersion() {
          var result = execute(VERSION_QUERY);
          var row = result.getRows().get(0);
          var neo4jRow = (Neo4jBoltQueryResultRow) row;
          var rawVersion = neo4jRow.getValue("version").asString();
          var parsedVersion = IntStream.concat(
                  Arrays.stream(rawVersion.split("\\.", 4))
                          .limit(3)
                          .mapToInt(Integer::parseInt),
                  IntStream.generate(() -> 0)
          ).limit(3).toArray();
          return new Neo4jGraphDatabaseVersion(parsedVersion[0], parsedVersion[1], parsedVersion[2]);
      }
  }
  