package be.iminds.ilabt.jfed.log.cache;

import be.iminds.ilabt.jfed.call_log_output.SerializableApiCallDetails;
import be.iminds.ilabt.jfed.call_log_output.SerializableApiCallDetailsFactory;
import be.iminds.ilabt.jfed.log.ApiCallDetails;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

import javax.inject.Inject;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

/**
 * User: twalcari
 * Date: 4/15/2014
 * Time: 15:56
 */
public class DiskApiCallDetailsCache extends ApiCallDetailsCache {

    private static final Logger LOG = LoggerFactory.getLogger(DiskApiCallDetailsCache.class);
    private static final Marker CLEAR_MARKER = MarkerFactory.getMarker("CLEAR");
    private final Path tempDir;
    private final SerializableApiCallDetailsFactory serializableApiCallDetailsFactory;

    @Inject
    DiskApiCallDetailsCache(SerializableApiCallDetailsFactory serializableApiCallDetailsFactory) {
        this.serializableApiCallDetailsFactory = serializableApiCallDetailsFactory;
        Path tempDir;

        try {
            tempDir = Files.createTempDirectory("jFedCalls");
            tempDir.toFile().deleteOnExit();
            LOG.debug("Created a tempdir for logging calls at {}", tempDir);
        } catch (IOException e) {
            LOG.error("Could not create temporary directory to store jFed calls history!", e);
            tempDir = null;
        }
        this.tempDir = tempDir;
    }

    @Override
    protected SerializableApiCallDetails getFullApiDetails(ApiCallDetailsRef ref) {
        Path path = tempDir.resolve(Integer.toString(ref.getId()));

        try {
            byte[] compressedDetails = Files.readAllBytes(path);
            return decompressFullApiDetails(compressedDetails);
        } catch (IOException e) {
            LOG.error("Could not read ApiCallDetails with ref " + ref);
            return null;
        }
    }

    @Override
    public void clear() {
        try {
            Files.walkFileTree(tempDir, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
                    LOG.debug(CLEAR_MARKER, "Skipping {}", dir.toString());
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    try {
                        Files.delete(file);
                        LOG.debug(CLEAR_MARKER, "Successfully deleted file {}", file.toString());
                    } catch (IOException ex) {
                        LOG.warn(CLEAR_MARKER, "Could not delete file {}", file.toString());
                        throw ex;
                    }


                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                    try {
                        Files.delete(file);
                        LOG.debug("Successfully deleted FAILING file {}", file.toString());
                    } catch (IOException ex) {
                        LOG.warn("Could not delete FAILING file {}", file.toString());
                        throw ex;
                    }

                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
                    return FileVisitResult.CONTINUE;
                }
            });
        } catch (IOException e) {
            LOG.error("An error occurred while clearing the calls cache", e);
        }
    }


    @Override
    public ApiCallDetailsRef registerApiCallDetails(ApiCallDetails apiCallDetails) {
        ApiCallDetailsRef ref = new ApiCallDetailsRef(apiCallDetails);

        Path path = tempDir.resolve(Integer.toString(ref.getId()));

        if (!path.toFile().exists()) {
            try {
                Path newFile =
                        Files.write(path, compressFullApiDetails(
                                serializableApiCallDetailsFactory.createFromApiCallDetails(apiCallDetails)
                        ));
                path.toFile().deleteOnExit();
            } catch (IOException e) {
                LOG.error("Error while writing ApiCallDetails to disk", e);
            }
        } else {
            LOG.warn("Detected that ApiCallDetails #{} already exists on disk. Ignoring new ApiCallDetails", apiCallDetails.getId());
        }

        return ref;
    }

    @Override
    public ApiCallDetailsRef registerApiCallDetails(SerializableApiCallDetails apiCallDetails) {
        //first create a copy of the received SerializableApiCallDetails
        SerializableApiCallDetails copy = new SerializableApiCallDetails(apiCallDetails);

        ApiCallDetailsRef ref = new ApiCallDetailsRef(copy);

        Path path = tempDir.resolve(Integer.toString(ref.getId()));

        if (!path.toFile().exists()) {
            try {
                Path newFile =
                        Files.write(path, compressFullApiDetails(new SerializableApiCallDetails(apiCallDetails)));
                path.toFile().deleteOnExit();
            } catch (IOException e) {
                LOG.error("Error while writing ApiCallDetails to disk", e);
            }
        } else {
            LOG.warn("Detected that ApiCallDetails #{} already exists on disk. Ignoring new ApiCallDetails", apiCallDetails.getId());
        }


        return ref;
    }
}
