/*
 * Copyright 2013 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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 org.gradle.api.internal.artifacts.ivyservice.resolveengine.store;

import org.gradle.api.artifacts.result.ResolvedComponentResult;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.TransientConfigurationResults;
import org.gradle.api.internal.cache.Store;
import org.gradle.api.internal.file.TemporaryFileProvider;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.internal.concurrent.CompositeStoppable;
import org.gradle.util.Clock;

import java.io.Closeable;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

public class ResolutionResultsStoreFactory implements Closeable {
    private final static Logger LOG = Logging.getLogger(ResolutionResultsStoreFactory.class);
    private static final int DEFAULT_MAX_SIZE = 2000000000; //2 gigs

    private final TemporaryFileProvider temp;
    private int maxSize;

    private CachedStoreFactory<TransientConfigurationResults> oldModelCache;
    private CachedStoreFactory<ResolvedComponentResult> newModelCache;

    private AtomicInteger storeSetBaseId = new AtomicInteger(0);

    public ResolutionResultsStoreFactory(TemporaryFileProvider temp) {
        this(temp, DEFAULT_MAX_SIZE);
    }

    /**
     * @param temp - Provider of temporary files.
     * @param maxSize - indicates the approx. maximum size of the binary store that will trigger rolling of the file
     */
    ResolutionResultsStoreFactory(TemporaryFileProvider temp, int maxSize) {
        this.temp = temp;
        this.maxSize = maxSize;
    }

    private final Map<String, DefaultBinaryStore> stores = new HashMap<String, DefaultBinaryStore>();
    private final CompositeStoppable cleanUpLater = new CompositeStoppable();

    private synchronized DefaultBinaryStore createBinaryStore(String storeKey) {
        DefaultBinaryStore store = stores.get(storeKey);
        if (store == null || isFull(store)) {
            File storeFile = temp.createTemporaryFile("gradle", ".bin");
            storeFile.deleteOnExit();
            store = new DefaultBinaryStore(storeFile);
            stores.put(storeKey, store);
            cleanUpLater.add(store);
        }
        return store;
    }

    private synchronized CachedStoreFactory<TransientConfigurationResults> getOldModelCache() {
        if (oldModelCache == null) {
            oldModelCache = new CachedStoreFactory<TransientConfigurationResults>("Resolution result");
            cleanUpLater.add(oldModelCache);
        }
        return oldModelCache;
    }

    private synchronized CachedStoreFactory<ResolvedComponentResult> getNewModelCache() {
        if (newModelCache == null) {
            newModelCache = new CachedStoreFactory<ResolvedComponentResult>("Resolution result");
            cleanUpLater.add(newModelCache);
        }
        return newModelCache;
    }

    public StoreSet createStoreSet() {
        return new StoreSet() {
            int storeSetId = storeSetBaseId.getAndIncrement();
            int binaryStoreId;
            public DefaultBinaryStore nextBinaryStore() {
                //one binary store per id+threadId
                String storeKey = Thread.currentThread().getId() + "-" + binaryStoreId++;
                return createBinaryStore(storeKey);
            }

            public Store<ResolvedComponentResult> newModelCache() {
                return getNewModelCache().createCachedStore(storeSetId);
            }

            public Store<TransientConfigurationResults> oldModelCache() {
                return getOldModelCache().createCachedStore(storeSetId);
            }
        };
    }

    //offset based implementation is only safe up to certain figure
    //because of the int max value
    //for large streams/files (huge builds), we need to roll the file
    //otherwise the stream.size() returns max integer and the offset is no longer correct
    private boolean isFull(DefaultBinaryStore store) {
        return store.getSize() > maxSize;
    }

    public void close() {
        try {
            Clock clock = new Clock();
            cleanUpLater.stop();
            LOG.debug("Deleted {} resolution results binary files in {}", stores.size(), clock.getTime());
        } finally {
            oldModelCache = null;
            newModelCache = null;
            stores.clear();
        }
    }
}