/*
 * 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 com.datastax.driver.osgi;

import static org.ops4j.pax.exam.CoreOptions.bootDelegationPackages;
import static org.ops4j.pax.exam.CoreOptions.bundle;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemPackages;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;

import com.datastax.driver.core.CCMBridge;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.ProtocolOptions;
import com.datastax.driver.core.TestUtils;
import com.google.common.collect.Lists;
import java.util.List;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.options.CompositeOption;
import org.ops4j.pax.exam.options.MavenArtifactProvisionOption;
import org.ops4j.pax.exam.options.UrlProvisionOption;
import org.ops4j.pax.exam.util.PathUtils;

/**
 * To check that all driver bundles are correctly provisioned, or to debug provisioning problems,
 * run the Maven Pax Runner plugin:
 *
 * <pre>
 * mvn pax:run
 * </pre>
 *
 * The plugin will start a Felix Gogo interactive shell and attempt to provision the driver bundles.
 *
 * <p>Note: you MUST run 'mvn install' on the entire project before!
 *
 * @see <a href="http://felix.apache.org/documentation/subprojects/apache-felix-gogo.html">Apache
 *     Felix Gogo Documentation</a>
 */
public class BundleOptions {

  public static UrlProvisionOption driverBundle() {
    return driverBundle(false);
  }

  public static UrlProvisionOption driverBundle(boolean useShaded) {
    String classifier = useShaded ? "-shaded" : "";
    return bundle(
        "reference:file:"
            + PathUtils.getBaseDir()
            + "/../../../driver-core/target/cassandra-driver-core-"
            + Cluster.getDriverVersion()
            + classifier
            + ".jar");
  }

  public static UrlProvisionOption mappingBundle() {
    return bundle(
        "reference:file:"
            + PathUtils.getBaseDir()
            + "/../../../driver-mapping/target/cassandra-driver-mapping-"
            + Cluster.getDriverVersion()
            + ".jar");
  }

  public static UrlProvisionOption extrasBundle() {
    return bundle(
        "reference:file:"
            + PathUtils.getBaseDir()
            + "/../../../driver-extras/target/cassandra-driver-extras-"
            + Cluster.getDriverVersion()
            + ".jar");
  }

  public static MavenArtifactProvisionOption guavaBundle() {
    return mavenBundle("com.google.guava", "guava", getVersion("guava.version"));
  }

  public static CompositeOption lz4Bundle() {
    return new CompositeOption() {

      @Override
      public Option[] getOptions() {
        return options(
            systemProperty("cassandra.compression").value(ProtocolOptions.Compression.LZ4.name()),
            mavenBundle("org.lz4", "lz4-java", getVersion("lz4.version")));
      }
    };
  }

  public static CompositeOption snappyBundle() {
    return new CompositeOption() {

      @Override
      public Option[] getOptions() {
        return options(
            systemProperty("cassandra.compression")
                .value(ProtocolOptions.Compression.SNAPPY.name()),
            mavenBundle("org.xerial.snappy", "snappy-java", getVersion("snappy.version")));
      }
    };
  }

  public static CompositeOption hdrHistogramBundle() {
    return new CompositeOption() {

      @Override
      public Option[] getOptions() {
        return options(
            systemProperty("cassandra.usePercentileSpeculativeExecutionPolicy").value("true"),
            mavenBundle("org.hdrhistogram", "HdrHistogram", getVersion("hdr.version")));
      }
    };
  }

  public static CompositeOption nettyBundles() {
    final String nettyVersion = getVersion("netty.version");
    return new CompositeOption() {

      @Override
      public Option[] getOptions() {
        return options(
            mavenBundle("io.netty", "netty-buffer", nettyVersion),
            mavenBundle("io.netty", "netty-codec", nettyVersion),
            mavenBundle("io.netty", "netty-common", nettyVersion),
            mavenBundle("io.netty", "netty-handler", nettyVersion),
            mavenBundle("io.netty", "netty-transport", nettyVersion),
            mavenBundle("io.netty", "netty-transport-native-unix-common", nettyVersion),
            mavenBundle("io.netty", "netty-resolver", nettyVersion));
      }
    };
  }

  public static CompositeOption dropwizardMetricsBundle() {
    return new CompositeOption() {

      @Override
      public Option[] getOptions() {
        return options(
            mavenBundle("io.dropwizard.metrics", "metrics-core", getVersion("metrics.version")));
      }
    };
  }

  public static UrlProvisionOption mailboxBundle() {
    return bundle("reference:file:" + PathUtils.getBaseDir() + "/target/classes");
  }

  public static CompositeOption defaultOptions() {
    return new CompositeOption() {

      @Override
      public Option[] getOptions() {
        List<Option> options =
            Lists.newArrayList(
                // Delegate javax.security.cert to the parent classloader.
                // javax.security.cert.X509Certificate is used in
                // io.netty.util.internal.EmptyArrays, but not directly by the driver.
                bootDelegationPackages("javax.security.cert", "javax.management"),
                systemProperty("cassandra.version")
                    .value(CCMBridge.getGlobalCassandraVersion().toString()),
                systemProperty("cassandra.contactpoints").value(TestUtils.IP_PREFIX + 1),
                systemProperty("logback.configurationFile")
                    .value("file:" + PathUtils.getBaseDir() + "/src/test/resources/logback.xml"),
                mavenBundle("org.slf4j", "slf4j-api", getVersion("slf4j.version")),
                mavenBundle("ch.qos.logback", "logback-classic", getVersion("logback.version")),
                mavenBundle("ch.qos.logback", "logback-core", getVersion("logback.version")),
                mavenBundle(
                    "com.fasterxml.jackson.core",
                    "jackson-databind",
                    getVersion("jackson-databind.version")),
                mavenBundle(
                    "com.fasterxml.jackson.core", "jackson-core", getVersion("jackson.version")),
                mavenBundle(
                    "com.fasterxml.jackson.core",
                    "jackson-annotations",
                    getVersion("jackson.version")),
                mavenBundle("org.testng", "testng", getVersion("testng.version")),
                systemPackages(
                    "org.testng",
                    "org.junit",
                    "org.junit.runner",
                    "org.junit.runner.manipulation",
                    "org.junit.runner.notification",
                    "com.jcabi.manifests"));
        if (CCMBridge.isWindows()) {
          // Workaround for Felix + Windows Server 2012.   Felix does not properly alias
          // 'windowsserver2012'
          // to 'win32', because of this some native libraries may fail to load.  To work around
          // this, force
          // the os.name property to win32 if on a windows platform.
          // See: https://issues.apache.org/jira/browse/FELIX-5184
          options.add(systemProperty("os.name").value("win32"));
        }

        return options.toArray(new Option[options.size()]);
      }
    };
  }

  private static String getVersion(String propertyName) {
    String value = System.getProperty(propertyName);
    if (value == null) {
      throw new IllegalArgumentException(propertyName + " system property is not set.");
    }
    return value;
  }
}
