package com.sematext.solr.redis;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.search.QParser;
import org.apache.solr.search.QParserPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Protocol;

/**
 * ParserPlugin which builds a query parser basing on data stored in Redis.
 * <p> RedisQParserPlugin initiates connection with Redis and pass the connection
 * object to RedisQParser which is responsible for fetching data and building a
 * query.
 *
 * @author prog
 * @author lstrojny
 */
public class RedisQParserPlugin extends QParserPlugin {

  /**
   * Redis parameter name constant.
   */
  public static final String NAME = "redis";

  /**
   * Logger
   */
  private static final Logger log = LoggerFactory.getLogger(RedisQParserPlugin.class);

  /**
   * Host name parameter name constant
   */
  private static final String HOST_FIELD = "host";

  /**
   * Maximum number of connections to redis parameter name constant
   */
  private static final String MAX_CONNECTIONS_FIELD = "maxConnections";

  /**
   * Number of retries parameter name constant
   */
  private static final String RETRIES_FIELD = "retries";

  /**
   * Database name parameter name constant
   */
  private static final String DATABASE_FIELD = "database";

  /**
   * Password parameter name constant
   */
  private static final String PASSWORD_FIELD = "password";

  /**
   * Redis parameter name constant
   */
  private static final String TIMEOUT_FIELD = "timeout";

  /**
   * Default number of connections limit
   */
  private static final int DEFAULT_MAX_CONNECTIONS = 5;

  /**
   * Default number of operation retries
   */
  private static final int DEFAULT_RETRIES = 1;

  /**
   * Jedis connection handler
   */
  private CommandHandler connectionHandler;

  @Override
  public QParser createParser(final String qstr, final SolrParams localParams, final SolrParams params,
    final SolrQueryRequest req) {
    return new RedisQParser(qstr, localParams, params, req, connectionHandler);
  }

  @Override
  public void init(final NamedList args) {
    final GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
    poolConfig.setMaxTotal(getInt(args, MAX_CONNECTIONS_FIELD, DEFAULT_MAX_CONNECTIONS));

    final String host = getString(args, HOST_FIELD, HostAndPort.LOCALHOST_STR);
    final int timeout = getInt(args, TIMEOUT_FIELD, Protocol.DEFAULT_TIMEOUT);
    final String password = getString(args, PASSWORD_FIELD, null);
    final int database = getInt(args, DATABASE_FIELD, Protocol.DEFAULT_DATABASE);
    final int retries = getInt(args, RETRIES_FIELD, DEFAULT_RETRIES);

    final String[] hostAndPort = host.split(":");
    final JedisPool jedisConnectionPool = createPool(poolConfig, hostAndPort[0],
        hostAndPort.length == 2 ? Integer.parseInt(hostAndPort[1]) : Protocol.DEFAULT_PORT, timeout, password,
        database);

    connectionHandler = createCommandHandler(jedisConnectionPool, retries);

    log.info("Initialized RedisQParserPlugin with host: " + host);
  }

  /**
   * Creates redis connection pool.
   *
   * @param poolConfig Pool configuration
   * @param host Hostname
   * @param port Port
   * @param timeout Timeout value optional
   * @param password Password optional
   * @param database Database name optional
   * @return Prepared connection pool
   */
  JedisPool createPool(final GenericObjectPoolConfig poolConfig, final String host, final int port, final int timeout,
    final String password, final int database) {
    return new JedisPool(poolConfig, host, port, timeout, password, database);
  }

  /**
   * Create a new command handler
   *
   * @param connectionPool Redis connection pool
   * @param retries How often should a failed operation be retried
   * @return Relevant command handler
   */
  CommandHandler createCommandHandler(final JedisPool connectionPool, final int retries) {
    return new RetryingCommandHandler(connectionPool, retries);
  }

  /**
   * Extract integer value from parameters list.
   *
   * @param args Arguments list
   * @param key Name of field
   * @param def Default value
   * @return Integer value of parameter with given name
   */
  private int getInt(final NamedList args, final String key, final int def) {
    final Object value = args != null ? args.get(key) : null;
    return value instanceof String ? Integer.parseInt((String) value) : def;
  }

  /**
   * Extract string  value from parameters list.
   *
   * @param args Arguments list
   * @param key Name of field
   * @param def Default value
   * @return String value of parameter with given name
   */
  private String getString(final NamedList args, final String key, final String def) {
    final Object value = args != null ? args.get(key) : null;
    return value instanceof String ? (String) value : def;
  }
}