package ru.yandex.qatools.allure.experimental;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.yandex.qatools.allure.events.ClearStepStorageEvent;
import ru.yandex.qatools.allure.events.ClearTestStorageEvent;
import ru.yandex.qatools.allure.events.StepEvent;
import ru.yandex.qatools.allure.events.StepFinishedEvent;
import ru.yandex.qatools.allure.events.StepStartedEvent;
import ru.yandex.qatools.allure.events.TestCaseEvent;
import ru.yandex.qatools.allure.events.TestCaseFinishedEvent;
import ru.yandex.qatools.allure.events.TestCaseStartedEvent;
import ru.yandex.qatools.allure.events.TestSuiteEvent;
import ru.yandex.qatools.allure.events.TestSuiteFinishedEvent;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;

/**
 * This is an internal Allure component.
 * <p/>
 * {@link ru.yandex.qatools.allure.experimental.ListenersNotifier} used for notify
 * listeners {@link ru.yandex.qatools.allure.experimental.LifecycleListener} of
 * Allure events.
 * <p/>
 *
 * @author Dmitry Baev [email protected]
 *         Date: 26.05.14
 * @see ru.yandex.qatools.allure.Allure
 */
public class ListenersNotifier extends LifecycleListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(ListenersNotifier.class);

    private List<LifecycleListener> listeners = new ArrayList<>();

    /**
     * Create instance of {@link ru.yandex.qatools.allure.experimental.ListenersNotifier}
     * using current context class loader.
     */
    public ListenersNotifier() {
        this(Thread.currentThread().getContextClassLoader());
    }

    /**
     * Create instance of {@link ru.yandex.qatools.allure.experimental.ListenersNotifier}
     * using specified class loader. This class loader will be used to find listeners
     * via Java SPI.
     *
     * @param classLoader given class loader to find {@link ru.yandex.qatools.allure.experimental.LifecycleListener}
     *                    services
     */
    public ListenersNotifier(ClassLoader classLoader) {
        loadListeners(ServiceLoader.load(
                LifecycleListener.class,
                classLoader
        ));
    }

    /**
     * Safely find all {@link ru.yandex.qatools.allure.experimental.LifecycleListener}
     * services.
     *
     * @param loader specified {@link java.util.ServiceLoader} to find our services
     */
    private void loadListeners(ServiceLoader<LifecycleListener> loader) {
        Iterator<LifecycleListener> iterator = loader.iterator();
        while (hasNextSafely(iterator)) {
            try {
                LifecycleListener listener = iterator.next();
                listeners.add(listener);
                LOGGER.info(String.format("Found %s: %s", LifecycleListener.class, listener.getClass()));
            } catch (ServiceConfigurationError e) {
                LOGGER.error("iterator.next() failed", e);
            }
        }
    }

    /**
     * Safely check for <pre>iterator.hasNext()</pre>.
     *
     * @param iterator specified iterator to check he presence of next element
     * @return {@code true} if the iteration has more elements, false otherwise
     */
    private boolean hasNextSafely(Iterator iterator) {
        try {
            /* Throw a ServiceConfigurationError if a provider-configuration file violates the specified format,
+            or if it names a provider class that cannot be found and instantiated, or if the result of
+            instantiating the class is not assignable to the service type, or if any other kind of exception
+            or error is thrown as the next provider is located and instantiated.
+            @see http://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html#iterator()
+            */
            return iterator.hasNext();
        } catch (Exception e) {
            LOGGER.error("iterator.hasNext() failed", e);
            return false;
        }
    }

    /**
     * Invoke to tell listeners that an step started event processed
     */
    @Override
    public void fire(StepStartedEvent event) {
        for (LifecycleListener listener : listeners) {
            try {
                listener.fire(event);
            } catch (Exception e) {
                logError(listener, e);
            }
        }
    }

    /**
     * Invoke to tell listeners that an custom step event processed
     */
    @Override
    public void fire(StepEvent event) {
        for (LifecycleListener listener : listeners) {
            try {
                listener.fire(event);
            } catch (Exception e) {
                logError(listener, e);
            }
        }
    }

    /**
     * Invoke to tell listeners that an step finished event processed
     */
    @Override
    public void fire(StepFinishedEvent event) {
        for (LifecycleListener listener : listeners) {
            try {
                listener.fire(event);
            } catch (Exception e) {
                logError(listener, e);
            }
        }
    }

    /**
     * Invoke to tell listeners that an test case started event processed
     */
    @Override
    public void fire(TestCaseStartedEvent event) {
        for (LifecycleListener listener : listeners) {
            try {
                listener.fire(event);
            } catch (Exception e) {
                logError(listener, e);
            }
        }
    }

    /**
     * Invoke to tell listeners that an custom test case event processed
     */
    @Override
    public void fire(TestCaseEvent event) {
        for (LifecycleListener listener : listeners) {
            try {
                listener.fire(event);
            } catch (Exception e) {
                logError(listener, e);
            }
        }
    }

    /**
     * Invoke to tell listeners that an test case finished event processed
     */
    @Override
    public void fire(TestCaseFinishedEvent event) {
        for (LifecycleListener listener : listeners) {
            try {
                listener.fire(event);
            } catch (Exception e) {
                logError(listener, e);
            }
        }
    }

    /**
     * Invoke to tell listeners that an custom test suite event processed
     */
    @Override
    public void fire(TestSuiteEvent event) {
        for (LifecycleListener listener : listeners) {
            try {
                listener.fire(event);
            } catch (Exception e) {
                logError(listener, e);
            }
        }
    }

    /**
     * Invoke to tell listeners that an test suite finished event processed
     */
    @Override
    public void fire(TestSuiteFinishedEvent event) {
        for (LifecycleListener listener : listeners) {
            try {
                listener.fire(event);
            } catch (Exception e) {
                logError(listener, e);
            }
        }
    }

    /**
     * Invoke to tell listeners that an clear step storage event processed
     */
    @Override
    public void fire(ClearStepStorageEvent event) {
        for (LifecycleListener listener : listeners) {
            try {
                listener.fire(event);
            } catch (Exception e) {
                logError(listener, e);
            }
        }
    }

    /**
     * Invoke to tell listeners that an clear test case storage event processed
     */
    @Override
    public void fire(ClearTestStorageEvent event) {
        for (LifecycleListener listener : listeners) {
            try {
                listener.fire(event);
            } catch (Exception e) {
                logError(listener, e);
            }
        }
    }

    /**
     * This method log given exception in specified listener
     */
    private void logError(LifecycleListener listener, Exception e) {
        LOGGER.error("Error for listener " + listener.getClass(), e);
    }

    /**
     * You can use this method to add listeners to this notifier.
     */
    public void addListener(LifecycleListener listener) {
        listeners.add(listener);
    }

    /**
     * Getter for {@link #listeners}
     */
    public List<LifecycleListener> getListeners() {
        return listeners;
    }
}