/**
 * Copyright 2016 Palantir Technologies
 *
 * Licensed under the BSD-3 License (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://opensource.org/licenses/BSD-3-Clause
 *
 * 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.palantir.atlasdb.factory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ServiceLoader;
import java.util.function.Predicate;
import java.util.stream.StreamSupport;

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

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.palantir.atlasdb.config.LeaderConfig;
import com.palantir.atlasdb.keyvalue.api.KeyValueService;
import com.palantir.atlasdb.spi.AtlasDbFactory;
import com.palantir.atlasdb.spi.KeyValueServiceConfig;
import com.palantir.timestamp.TimestampService;
import com.palantir.util.debug.ThreadDumps;

public class ServiceDiscoveringAtlasSupplier {
    private static final Logger log = LoggerFactory.getLogger(ServiceDiscoveringAtlasSupplier.class);
    private static final ServiceLoader<AtlasDbFactory> loader = ServiceLoader.load(AtlasDbFactory.class);

    private static String timestampServiceCreationInfo = null;

    private final KeyValueServiceConfig config;
    private final Optional<LeaderConfig> leaderConfig;
    private final Supplier<KeyValueService> keyValueService;
    private final Supplier<TimestampService> timestampService;

    public ServiceDiscoveringAtlasSupplier(KeyValueServiceConfig config, Optional<LeaderConfig> leaderConfig) {
        this.config = config;
        this.leaderConfig = leaderConfig;

        AtlasDbFactory atlasFactory = StreamSupport.stream(loader.spliterator(), false)
                .filter(producesCorrectType())
                .findFirst()
                .orElseThrow(() -> new IllegalStateException(
                        "No atlas provider for KeyValueService type " + config.type() + " could be found."
                        + " Have you annotated it with @AutoService(AtlasDbFactory.class)?"
                ));
        keyValueService = Suppliers.memoize(() -> atlasFactory.createRawKeyValueService(config, leaderConfig));
        timestampService = () -> atlasFactory.createTimestampService(getKeyValueService());
    }

    public KeyValueService getKeyValueService() {
        return keyValueService.get();
    }

    public synchronized TimestampService getTimestampService() {
        log.info("Fetching timestamp service from thread {}. This should only happen once.",
                Thread.currentThread().getName());

        if (timestampServiceCreationInfo == null) {
            timestampServiceCreationInfo = ThreadDumps.programmaticThreadDump();
        } else {
            handleMultipleTimestampFetch();
        }

        return timestampService.get();
    }

    private void handleMultipleTimestampFetch() {
        try {
            String threadDumpFile = saveThreadDumps();
            reportMultipleTimestampFetch(threadDumpFile);
        } catch (IOException e) {
            log.error("The timestamp service was fetched for a second time. We tried to output thread "
                    + "dumps to a temporary file, but encountered an error.", e);
        }
    }

    @VisibleForTesting
    String saveThreadDumps() throws IOException {
        File file = getTempFile();
        return saveThreadDumpsToFile(file);
    }

    private static File getTempFile() throws IOException {
        String tempDir = System.getProperty("java.io.tmpdir");
        Path path = Paths.get(tempDir, "atlas-timestamp-service-creation.log");
        if (!Files.exists(path)) {
            Files.createFile(path);
        }
        return path.toFile();
    }

    private String saveThreadDumpsToFile(File file) throws IOException {
        try (FileOutputStream outputStream = new FileOutputStream(file)) {
            writeStringToStream(outputStream,
                    "This file contains thread dumps that will be useful for the AtlasDB Dev team, in case you hit a "
                            + "MultipleRunningTimestampServices error. In this case, please send this file to them.\n");
            writeStringToStream(outputStream, "First thread dump: " + timestampServiceCreationInfo + "\n");
            writeStringToStream(outputStream, "Second thread dump: " + ThreadDumps.programmaticThreadDump() + "\n");
            return file.getPath();
        }
    }

    private void reportMultipleTimestampFetch(String path) {
        if (!leaderConfig.isPresent()) {
            log.warn("Timestamp service fetched for a second time, and there is no leader config. "
                    + "This means that you may soon encounter the MultipleRunningTimestampServices error. "
                    + "Thread dumps from both fetches of the timestamp service have been outputted to {}. "
                    + "If you encounter a MultipleRunningTimestampServices error, please send this file to "
                    + "support.", path);
        } else {
            log.warn("Timestamp service fetched for a second time. This is only OK if you are "
                    + "running in an HA configuration and have just had a leadership election. "
                    + "You do have a leader config, but we're outputting thread dumps from both fetches of the "
                    + "timestamp service, in case this second service was created in error. "
                    + "Thread dumps from both fetches of the timestamp service have been outputted to {}. "
                    + "If you encounter a MultipleRunningTimestampServices error, please send this file to "
                    + "support.", path);
        }
    }

    private void writeStringToStream(FileOutputStream outputStream, String stringToWrite) throws IOException {
        outputStream.write(stringToWrite.getBytes(StandardCharsets.UTF_8));
    }

    private Predicate<AtlasDbFactory> producesCorrectType() {
        return factory -> config.type().equalsIgnoreCase(factory.getType());
    }
}

@@ -3042,16 +3042,45 @@
 g.info(%22
+%5Btimestamp-service-creation%5D 
 Fetching
@@ -3103,16 +3103,45 @@
 ce from 
+%22%0A                        + %22
 thread %7B
@@ -3174,32 +3174,16 @@
  once.%22,
-%0A               
  Thread.
@@ -3696,16 +3696,45 @@
 .error(%22
+%5Btimestamp-service-creation%5D 
 The time
@@ -3778,16 +3778,41 @@
 d time. 
+%22%0A                    + %22
 We tried
@@ -3833,33 +3833,8 @@
 ead 
-%22%0A                    + %22
 dump
@@ -5211,32 +5211,61 @@
       log.warn(%22
+%5Btimestamp-service-creation%5D 
 Timestamp servic
@@ -5315,24 +5315,16 @@
 o leader
- config.
  %22%0A     
@@ -5341,16 +5341,24 @@
      + %22
+config. 
 This mea
@@ -5731,16 +5731,45 @@
 g.warn(%22
+%5Btimestamp-service-creation%5D 
 Timestam
@@ -5828,16 +5828,8 @@
  if 
-you are 
 %22%0A  
@@ -5849,16 +5849,24 @@
      + %22
+you are 
 running 

Update logging to include grepable keyword