package eu.drus.jpa.unit.mongodb.ext;

import static com.google.common.base.Preconditions.checkArgument;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;

import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
import com.mongodb.ReadPreference;
import com.mongodb.ServerAddress;
import com.mongodb.WriteConcern;

import eu.drus.jpa.unit.spi.PersistenceUnitDescriptor;

public class EclipseLinkConfiguration extends AbstractConfiguration {

    private static final String ECLIPSELINK_NOSQL_PROPERTY_MONGO_READ_PREFERENCE = "eclipselink.nosql.property.mongo.read-preference";
    private static final String ECLIPSELINK_NOSQL_PROPERTY_MONGO_WRITE_CONCERN = "eclipselink.nosql.property.mongo.write-concern";
    private static final String ECLIPSELINK_NOSQL_PROPERTY_PASSWORD = "eclipselink.nosql.property.password";
    private static final String ECLIPSELINK_NOSQL_PROPERTY_USER = "eclipselink.nosql.property.user";
    private static final String ECLIPSELINK_NOSQL_PROPERTY_MONGO_DB = "eclipselink.nosql.property.mongo.db";
    private static final String ECLIPSELINK_NOSQL_PROPERTY_MONGO_HOST = "eclipselink.nosql.property.mongo.host";
    private static final String ECLIPSELINK_NOSQL_PROPERTY_MONGO_PORT = "eclipselink.nosql.property.mongo.port";
    private static final String ECLIPSELINK_TARGET_DATABASE = "eclipselink.target-database";

    public static class ConfigurationFactoryImpl implements ConfigurationFactory {

        @Override
        public boolean isSupported(final PersistenceUnitDescriptor descriptor) {
            final Map<String, Object> properties = descriptor.getProperties();

            final String tgtDataBase = (String) properties.get(ECLIPSELINK_TARGET_DATABASE);
            return tgtDataBase != null && tgtDataBase.contains("mongo");
        }

        @Override
        public Configuration createConfiguration(final PersistenceUnitDescriptor descriptor) {
            return new EclipseLinkConfiguration(descriptor);
        }
    }

    private EclipseLinkConfiguration(final PersistenceUnitDescriptor descriptor) {
        final Map<String, Object> properties = descriptor.getProperties();

        configureServerAddresses(properties);
        configureDatabaseName(properties);
        configureCredentials(properties);
        configureClientOptions(properties);
    }

    private void configureClientOptions(final Map<String, Object> properties) {
        final MongoClientOptions.Builder builder = MongoClientOptions.builder();
        final String writeConcern = (String) properties.get(ECLIPSELINK_NOSQL_PROPERTY_MONGO_WRITE_CONCERN);
        final String readPreference = (String) properties.get(ECLIPSELINK_NOSQL_PROPERTY_MONGO_READ_PREFERENCE);

        if (writeConcern != null) {
            builder.writeConcern(WriteConcern.valueOf(writeConcern));
        }
        if (readPreference != null) {
            builder.readPreference(ReadPreference.valueOf(readPreference));
        }

        mongoClientOptions = builder.build();
    }

    private void configureCredentials(final Map<String, Object> properties) {
        final String userName = (String) properties.get(ECLIPSELINK_NOSQL_PROPERTY_USER);
        final String password = (String) properties.get(ECLIPSELINK_NOSQL_PROPERTY_PASSWORD);
        if (userName != null) {
            checkArgument(password != null, ECLIPSELINK_NOSQL_PROPERTY_PASSWORD + " was not configured, but required");
            mongoCredentialList = Collections
                    .singletonList(MongoCredential.createPlainCredential(userName, "admin", password.toCharArray()));
        } else {
            mongoCredentialList = Collections.emptyList();
        }
    }

    private void configureDatabaseName(final Map<String, Object> properties) {
        databaseName = (String) properties.get(ECLIPSELINK_NOSQL_PROPERTY_MONGO_DB);
        checkArgument(databaseName != null, ECLIPSELINK_NOSQL_PROPERTY_MONGO_DB + " was not configured, but required");
    }

    private void configureServerAddresses(final Map<String, Object> properties) {
        final String ports = (String) properties.get(ECLIPSELINK_NOSQL_PROPERTY_MONGO_PORT);
        final String hosts = (String) properties.get(ECLIPSELINK_NOSQL_PROPERTY_MONGO_HOST);
        final String[] hostList = hosts != null ? hosts.split(",") : new String[] {};
        final String[] portList = ports != null ? ports.split(",") : new String[] {};

        serverAddresses = new ArrayList<>();
        for (int i = 0; i < hostList.length; i++) {
            int port;
            if (i >= portList.length) {
                port = ServerAddress.defaultPort();
            } else {
                port = Integer.valueOf(portList[i].trim());
            }
            serverAddresses.add(new ServerAddress(hostList[i].trim(), port));
        }
        if (serverAddresses.isEmpty()) {
            serverAddresses.add(new ServerAddress());
        }
    }
}