package be.iminds.ilabt.jfed.util;

import be.iminds.ilabt.jfed.log.ApiCallDetails;
import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.log.ResultListener;
import javafx.application.Platform;

import javax.annotation.Nonnull;
import javax.inject.Singleton;

/**
 * JavaFXLogger exactly the same as Logger, but always handles events in JavaFX thread
 */
@Singleton
public class JavaFXLogger extends Logger {
    private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(JavaFXLogger.class);

    private final boolean synchronous;

    /**
     * Creates a synchronous JavaFXLogger
     */
    public JavaFXLogger() {
        this(true);
    }


    /**
     * @param synchronous should fireResult be synchronous, or should it return after all results have been processed
     */
    protected JavaFXLogger(boolean synchronous) {
        this.synchronous = synchronous;
    }

    public boolean isSynchronous() {
        return this.synchronous;
    }

    @Override
    public synchronized void fireResult(final ApiCallDetails reply) {
//        if (reply.getServer() == null) {
//            try {
//                throw new RuntimeException("reply authority is null");
//            } catch (Exception e) {
//                LOG.error("Saw a reply with a null authority. Will fire event, but this is a bug!\n\nReply:\n {}",
//                        reply.toString(), e);
//            }
//        }

        Runnable fireListeners = () -> {
            for (ResultListener l : resultListeners) {
                try {
                    l.onResult(reply);
                } catch (Exception | AssertionError t) {
                    LOG.error("Error while processing Logger ApiCallDetails reply on JavaFX thread. (will be ignored)", t);
                }
            }
        };


        if (!synchronous) {
            //asynchronous event processing
            Platform.runLater(fireListeners);
        } else {
            FXPlatformUtil.runAndWait(fireListeners);
        }
    }

    private static class MyWrappingJavaFXLogger extends JavaFXLogger {
        private final JavaFXLogger parentLogger;

        public MyWrappingJavaFXLogger(boolean synchronous, JavaFXLogger parentLogger) {
            super(synchronous);
            this.parentLogger = parentLogger;
        }

        @Override
        public synchronized void fireResult(ApiCallDetails reply) {
            //fire for all parentLogger listeners, even if they changed
            parentLogger.fireResult(reply);

            //fire for any listener added to this new wrapping logger
            super.fireResult(reply);
        }
    }

    @Override
    public JavaFXLogger getWrappingLogger() {
        return new MyWrappingJavaFXLogger(synchronous, this);
    }

    public JavaFXLogger getWrappingLogger(boolean synchronous) {
        return new MyWrappingJavaFXLogger(synchronous, this);
    }

    public static JavaFXLogger wrap(@Nonnull final JavaFXLogger logger, ResultListener... resultListeners) {
        JavaFXLogger res = new MyWrappingJavaFXLogger(logger.synchronous, logger);
        for (ResultListener resultListener : resultListeners)
            res.addResultListener(resultListener);
        return res;
    }

    public static JavaFXLogger wrap(boolean synchronous, @Nonnull final JavaFXLogger logger, ResultListener... resultListeners) {
        JavaFXLogger res = new MyWrappingJavaFXLogger(synchronous, logger);
        for (ResultListener resultListener : resultListeners)
            res.addResultListener(resultListener);
        return res;
    }
}
