package ru.yandex.webmaster3.core.util;

import org.slf4j.MDC;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Интерфейс для переноса thread-local контекста. В обычном синхронном мире можно засунуть что-нибудь в ThreadLocal,
 * а потом использовать это где-нибудь глубже по стеку. Так работает MDC (в try-with-resources мы записываем, например,
 * request id - и во время всего исполнения action'а этот request id попадает во все log statement'ы), так же работает
 * RequestTracer, который у нас собирает статистику по работе с бд во время вызова action'а.
 * Как только action начинает фигачить в разных потоках - этот контекст начинает теряться. И мы, например, не запишем
 * все вызовы в БД или потеряем request id при логировании - потому что код может исполниться в другом потоке.
 * Конкретно с ForkJoin - совсем адъ, потому что мало того, что все работает на разных потоках - так еще и таска исполняясь
 * может уйти в join, и в нем исполнить другую таску.
 * Оставить мусорный контекст - тоже плохо, потому что тогда мы можем, например, посчитать action'у запросы - которые на
 * самом деле были не его.
 *
 * В общем ContextTracker обозначает контракт - имплементация корректно сохраняет, восстанавливает и очищает свои ThreadLocal
 * А любой код, который занимается переносом контекста (наша реализация AsyncPool, например) - вызывает эти методы в нужных
 * местах
 * @author avhaliullin
 */
public interface ContextTracker {
    Context dumpContext();

    interface Context {
        void restoreContext();

        void clearContext();
    }

    ContextTracker MDC_TRACKER = () -> {
        Map<String, String> mdc = MDC.getCopyOfContextMap();
        return new Context() {
            @Override
            public void restoreContext() {
                MDC.setContextMap(mdc);
            }

            @Override
            public void clearContext() {
                MDC.clear();
            }
        };
    };

    static ContextTracker aggregate(ContextTracker ct, ContextTracker... cts) {
        List<ContextTracker> ctList = W3CollectionUtils.varargToList(ct, cts);
        return () -> {
            List<Context> contexts = ctList.stream().map(ContextTracker::dumpContext).collect(Collectors.toList());
            return new Context() {
                @Override
                public void restoreContext() {
                    contexts.forEach(Context::restoreContext);
                }

                @Override
                public void clearContext() {
                    contexts.forEach(Context::clearContext);
                }
            };
        };
    }
}
