/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.ignite.tests.utils;

import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.Statement;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import java.util.ResourceBundle;

import org.apache.ignite.cache.store.cassandra.datasource.DataSource;
import org.apache.ignite.cache.store.cassandra.session.pool.SessionPool;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lifecycle.LifecycleEventType;
import org.apache.ignite.logger.log4j.Log4JLogger;
import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Helper class providing bunch of utility methods to work with Cassandra
 */
public class CassandraHelper {
    /** */
    private static final ResourceBundle CREDENTIALS = ResourceBundle.getBundle("org/apache/ignite/tests/cassandra/credentials");

    /** */
    private static final ResourceBundle CONNECTION = ResourceBundle.getBundle("org/apache/ignite/tests/cassandra/connection");

    /** */
    private static final ResourceBundle KEYSPACES = ResourceBundle.getBundle("org/apache/ignite/tests/cassandra/keyspaces");

    /** */
    private static final String EMBEDDED_CASSANDRA_YAML = "org/apache/ignite/tests/cassandra/embedded-cassandra.yaml";

    /** */
    private static final ApplicationContext connectionContext = new ClassPathXmlApplicationContext("org/apache/ignite/tests/cassandra/connection-settings.xml");

    /** */
    private static DataSource adminDataSrc;

    /** */
    private static DataSource regularDataSrc;

    /** */
    private static Cluster adminCluster;

    /** */
    private static Cluster regularCluster;

    /** */
    private static Session adminSes;

    /** */
    private static Session regularSes;

    /** */
    private static CassandraLifeCycleBean embeddedCassandraBean;

    /** */
    public static String getAdminUser() {
        return CREDENTIALS.getString("admin.user");
    }

    /** */
    public static String getAdminPassword() {
        return CREDENTIALS.getString("admin.password");
    }

    /** */
    public static String getRegularUser() {
        return CREDENTIALS.getString("regular.user");
    }

    /** */
    public static String getRegularPassword() {
        return CREDENTIALS.getString("regular.password");
    }

    /** */
    public static String[] getTestKeyspaces() {
        return KEYSPACES.getString("keyspaces").split(",");
    }

    /** */
    public static String[] getContactPointsArray() {
        String[] points = CONNECTION.getString("contact.points").split(",");

        if (points.length == 0)
            throw new RuntimeException("No Cassandra contact points specified");

        for (int i = 0; i < points.length; i++)
            points[i] = points[i].trim();

        return points;
    }

    /** */
    public static List<InetAddress> getContactPoints() {
        String[] points = getContactPointsArray();

        List<InetAddress> contactPoints = new LinkedList<>();

        for (String point : points) {
            if (point.contains(":"))
                continue;

            try {
                contactPoints.add(InetAddress.getByName(point));
            }
            catch (Throwable e) {
                throw new IllegalArgumentException("Incorrect contact point '" + point +
                    "' specified for Cassandra cache storage", e);
            }
        }

        return contactPoints;
    }

    /** */
    public static List<InetSocketAddress> getContactPointsWithPorts() {
        String[] points = getContactPointsArray();

        List<InetSocketAddress> contactPoints = new LinkedList<>();

        for (String point : points) {
            if (!point.contains(":"))
                continue;

            String[] chunks = point.split(":");

            try {
                contactPoints.add(InetSocketAddress.createUnresolved(chunks[0].trim(), Integer.parseInt(chunks[1].trim())));
            }
            catch (Throwable e) {
                throw new IllegalArgumentException("Incorrect contact point '" + point +
                    "' specified for Cassandra cache storage", e);
            }
        }

        return contactPoints;
    }

    /**
     * Checks if embedded Cassandra should be used for unit tests
     * @return true if embedded Cassandra should be used
     */
    public static boolean useEmbeddedCassandra() {
        String[] contactPoints = getContactPointsArray();

        return contactPoints != null && contactPoints.length == 1 && contactPoints[0].trim().startsWith("127.0.0.1");
    }

    /** */
    public static void dropTestKeyspaces() {
        String[] keyspaces = getTestKeyspaces();

        for (String keyspace : keyspaces) {
            try {
                executeWithAdminCredentials("DROP KEYSPACE IF EXISTS " + keyspace + ";");
            }
            catch (Throwable e) {
                throw new RuntimeException("Failed to drop keyspace: " + keyspace, e);
            }
        }
    }

    /** */
    public static ResultSet executeWithAdminCredentials(String statement, Object... args) {
        if (args == null || args.length == 0)
            return adminSession().execute(statement);

        PreparedStatement ps = adminSession().prepare(statement);
        return adminSession().execute(ps.bind(args));
    }

    /** */
    @SuppressWarnings("UnusedDeclaration")
    public static ResultSet executeWithRegularCredentials(String statement, Object... args) {
        if (args == null || args.length == 0)
            return regularSession().execute(statement);

        PreparedStatement ps = regularSession().prepare(statement);
        return regularSession().execute(ps.bind(args));
    }

    /** */
    @SuppressWarnings("UnusedDeclaration")
    public static ResultSet executeWithAdminCredentials(Statement statement) {
        return adminSession().execute(statement);
    }

    /** */
    @SuppressWarnings("UnusedDeclaration")
    public static ResultSet executeWithRegularCredentials(Statement statement) {
        return regularSession().execute(statement);
    }

    /** */
    public static synchronized DataSource getAdminDataSrc() {
        if (adminDataSrc != null)
            return adminDataSrc;

        return adminDataSrc = (DataSource)connectionContext.getBean("cassandraAdminDataSource");
    }

    /** */
    @SuppressWarnings("UnusedDeclaration")
    public static synchronized DataSource getRegularDataSrc() {
        if (regularDataSrc != null)
            return regularDataSrc;

        return regularDataSrc = (DataSource)connectionContext.getBean("cassandraRegularDataSource");
    }

    /** */
    public static void testAdminConnection() {
        try {
            adminSession();
        }
        catch (Throwable e) {
            throw new RuntimeException("Failed to check admin connection to Cassandra", e);
        }
    }

    /** */
    public static void testRegularConnection() {
        try {
            regularSession();
        }
        catch (Throwable e) {
            throw new RuntimeException("Failed to check regular connection to Cassandra", e);
        }
    }

    /** */
    public static synchronized void releaseCassandraResources() {
        try {
            if (adminSes != null && !adminSes.isClosed())
                U.closeQuiet(adminSes);
        }
        finally {
            adminSes = null;
        }

        try {
            if (adminCluster != null && !adminCluster.isClosed())
                U.closeQuiet(adminCluster);
        }
        finally {
            adminCluster = null;
        }

        try {
            if (regularSes != null && !regularSes.isClosed())
                U.closeQuiet(regularSes);
        }
        finally {
            regularSes = null;
        }

        try {
            if (regularCluster != null && !regularCluster.isClosed())
                U.closeQuiet(regularCluster);
        }
        finally {
            regularCluster = null;
        }

        SessionPool.release();
    }

    /** */
    private static synchronized Session adminSession() {
        if (adminSes != null)
            return adminSes;

        try {
            Cluster.Builder builder = Cluster.builder();
            builder = builder.withCredentials(getAdminUser(), getAdminPassword());
            builder.addContactPoints(getContactPoints());
            builder.addContactPointsWithPorts(getContactPointsWithPorts());

            adminCluster = builder.build();
            return adminSes = adminCluster.connect();
        }
        catch (Throwable e) {
            throw new RuntimeException("Failed to create admin session to Cassandra database", e);
        }
    }

    /** */
    private static synchronized Session regularSession() {
        if (regularSes != null)
            return regularSes;

        try {
            Cluster.Builder builder = Cluster.builder();
            builder = builder.withCredentials(getRegularUser(), getRegularPassword());
            builder.addContactPoints(getContactPoints());
            builder.addContactPointsWithPorts(getContactPointsWithPorts());

            regularCluster = builder.build();
            return regularSes = regularCluster.connect();
        }
        catch (Throwable e) {
            throw new RuntimeException("Failed to create regular session to Cassandra database", e);
        }
    }

    /** */
    public static void startEmbeddedCassandra(Logger log) {
        ClassLoader clsLdr = CassandraHelper.class.getClassLoader();
        URL url = clsLdr.getResource(EMBEDDED_CASSANDRA_YAML);

        embeddedCassandraBean = new CassandraLifeCycleBean();
        embeddedCassandraBean.setCassandraConfigFile(url.getFile());

        try {
            Field logField = CassandraLifeCycleBean.class.getDeclaredField("log");
            logField.setAccessible(true);
            logField.set(embeddedCassandraBean, new Log4JLogger(log));
        }
        catch (Throwable e) {
            throw new RuntimeException("Failed to initialize logger for CassandraLifeCycleBean", e);
        }

        embeddedCassandraBean.onLifecycleEvent(LifecycleEventType.BEFORE_NODE_START);
    }

    /** */
    public static void stopEmbeddedCassandra() {
        if (embeddedCassandraBean != null)
            embeddedCassandraBean.onLifecycleEvent(LifecycleEventType.BEFORE_NODE_STOP);
    }
}
