package com.atlassian.pageobjects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;

/**
 * Constructs a {@link TestedProduct}.  The {@link TestedProduct} instance is created by calling the constructor
 * with the following method signature:
 * <pre>
 *   TestedProduct(TesterFactory, ProductInstance)
 * </pre>
 */
public class TestedProductFactory
{
    private static final Logger LOG = LoggerFactory.getLogger(TestedProductFactory.class);

    /**
     * A factory for {@link Tester} instances
     * @param <T> The tester type
     */
    public static interface TesterFactory<T>
    {
        /**
         * @return The tester instance to be used through the tested product
         */
        T create();
    }

    /**
     * A factory that always returns the same {@link Tester}
     * @param <T> The tester type
     */
    public static class SingletonTesterFactory<T extends Tester> implements TesterFactory<T>
    {
        private final T tester;

        public SingletonTesterFactory(T tester)
        {
            this.tester = tester;
        }

        public T create()
        {
            return tester;
        }
    }

    /**
     * Creates a tested product, allowing the instance to choose its own default {@link Tester} and instance id
     * @param testedProductClass The tested product class
     * @param <P> The tested product type
     * @return The created tested product
     */
    public static <P extends TestedProduct<?>> P create(Class<P> testedProductClass)
    {
        return create(testedProductClass, getDefaultInstanceId(testedProductClass), null);
    }

    /**
     * Creates a tested product using the passed tester factory and instance id.
     * @param testedProductClass The tested product class
     * @param instanceId The instance id
     * @param testerFactory The tester factory to use to pass to the tested product
     * @param <P> The tested product type
     * @return The created tested product
     */
    public static <P extends TestedProduct<?>> P create(Class<P> testedProductClass, String instanceId, TesterFactory<?> testerFactory)
    {
        final String contextPath, baseUrl;
        final int httpPort;

        final String ampsBaseUrl = System.getProperty("baseurl." + instanceId);
        final ProductInstance instance;
        if (ampsBaseUrl != null)    // running within an AMPS IntegrationTestMojo invocation - read sys props for env vars
        {
            httpPort = Integer.getInteger("http." + instanceId + ".port");
            contextPath = System.getProperty("context." + instanceId + ".path");
            baseUrl = ampsBaseUrl;
        }
        else
        {
            Defaults defaults = getDefaultsAnnotation(testedProductClass);
            httpPort = defaults.httpPort();
            contextPath = defaults.contextPath();
            baseUrl = "http://" + getLocalHostName() + ":" + httpPort + contextPath;
        }
        instance = new DefaultProductInstance(instanceId, httpPort, contextPath, baseUrl);
        return create(testedProductClass, instance, testerFactory);
    }

    private static String getDefaultInstanceId(Class<?> testedProductClass)
    {
        Defaults annotation = getDefaultsAnnotation(testedProductClass);
        return annotation.instanceId();
    }

    private static Defaults getDefaultsAnnotation(Class<?> testedProductClass)
    {
        Defaults annotation = testedProductClass.getAnnotation(Defaults.class);
        if (annotation == null)
        {
            throw new IllegalArgumentException("The tested product class '" + testedProductClass.getName() + "' is missing the @Defaults annotation");
        }
        return annotation;
    }

    private static <P extends TestedProduct<?>> P create(Class<P> testedProductClass, ProductInstance instance, TesterFactory<?> testerFactory) {
        try
        {
            Constructor<P> c = testedProductClass.getConstructor(TesterFactory.class, ProductInstance.class);
            return c.newInstance(testerFactory, instance);
        }
        catch (NoSuchMethodException e)
        {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        catch (InvocationTargetException e)
        {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        catch (InstantiationException e)
        {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        catch (IllegalAccessException e)
        {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        throw new RuntimeException();
    }

    private static String getLocalHostName()
    {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Information representing the product instance being tested
     */
    private static class DefaultProductInstance implements ProductInstance
    {
        private final String baseUrl;
        private final String instanceId;
        private final int httpPort;
        private final String contextPath;


        public DefaultProductInstance(final String instanceId, final int httpPort, final String contextPath, final String baseUrl)
        {
            this.instanceId = instanceId;
            this.httpPort = httpPort;
            this.contextPath = contextPath;
            this.baseUrl = baseUrl;
        }

        public String getBaseUrl()
        {
            return baseUrl.toLowerCase();
        }

        public int getHttpPort()
        {
            return httpPort;
        }

        public String getContextPath()
        {
            return contextPath;
        }

        public String getInstanceId() {
            return instanceId;
        }
    }
}
@@ -1259,32 +1259,772 @@
        %7D%0A    %7D%0A%0A
+    /**%0A     * Creates a tested product, allowing the instance to choose its own default %7B@link Tester%7D and instance id%0A     * @param testedProductClass The tested product class name%0A     * @return The created tested product%0A     */%0A    @SuppressWarnings(%22unchecked%22)%0A    public static TestedProduct%3C?%3E create(String testedProductClass)%0A    %7B%0A        Class%3CTestedProduct%3C?%3E%3E clazz;%0A        try%0A        %7B%0A            clazz = (Class%3CTestedProduct%3C?%3E%3E) TestedProductFactory.class.getClassLoader().loadClass(testedProductClass);%0A        %7D%0A        catch (ClassNotFoundException e)%0A        %7B%0A            throw new IllegalArgumentException(%22Cannot find tested product class: %22 + testedProductClass);%0A        %7D%0A        return create(clazz);%0A    %7D%0A%0A
     /**%0A     * C

Add constructor to make it easier to take a system property as a tested product class