/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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
 *
 *      https://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.apache.ivy.core.cache;

import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.plugins.parser.ParserSettings;
import org.apache.ivy.util.Message;

import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.Iterator;
import java.util.LinkedHashMap;

/**
 * Cache ModuleDescriptors so that when the same module is used twice (in multi-module build for
 * instance), it is parsed only once. This cache is has a limited size, and keep the most recently
 * used entries. The entry in the cache are invalidated if there is a change to one variable used in
 * the module descriptor.
 */
class ModuleDescriptorMemoryCache {

    private final int maxSize;

    private final LinkedHashMap<File, CacheEntry> valueMap;

    /**
     * Create a cache of the given size
     *
     * @param size int
     */
    public ModuleDescriptorMemoryCache(int size) {
        this.maxSize = size;
        this.valueMap = new LinkedHashMap<>(size);
    }

    public ModuleDescriptor get(File ivyFile, ParserSettings ivySettings, boolean validated,
            ModuleDescriptorProvider mdProvider) throws ParseException, IOException {

        ModuleDescriptor descriptor = getFromCache(ivyFile, ivySettings, validated);
        if (descriptor == null) {
            descriptor = getStale(ivyFile, ivySettings, validated, mdProvider);
        }
        return descriptor;
    }

    /**
     * Get the module descriptor from the mdProvider and store it into the cache.
     */
    public ModuleDescriptor getStale(File ivyFile, ParserSettings ivySettings, boolean validated,
            ModuleDescriptorProvider mdProvider) throws ParseException, IOException {
        ParserSettingsMonitor settingsMonitor = new ParserSettingsMonitor(ivySettings);
        ModuleDescriptor descriptor = mdProvider.provideModule(
            settingsMonitor.getMonitoredSettings(), ivyFile, validated);
        putInCache(ivyFile, settingsMonitor, validated, descriptor);
        return descriptor;
    }

    ModuleDescriptor getFromCache(File ivyFile, ParserSettings ivySettings, boolean validated) {
        if (maxSize <= 0) {
            // cache is disabled
            return null;
        }
        synchronized (valueMap) {
            CacheEntry entry = valueMap.get(ivyFile);
            if (entry != null) {
                if (entry.isStale(ivyFile, validated, ivySettings)) {
                    Message.debug("Entry is found in the ModuleDescriptorCache but entry should be "
                            + "reevaluated : " + ivyFile);
                    valueMap.remove(ivyFile);
                    return null;
                } else {
                    // Move the entry at the end of the list
                    valueMap.remove(ivyFile);
                    valueMap.put(ivyFile, entry);
                    Message.debug("Entry is found in the ModuleDescriptorCache : " + ivyFile);
                    return entry.md;
                }
            } else {
                Message.debug("No entry is found in the ModuleDescriptorCache : " + ivyFile);
                return null;
            }
        }
    }

    void putInCache(File url, ParserSettingsMonitor ivySettingsMonitor, boolean validated,
            ModuleDescriptor descriptor) {
        if (maxSize <= 0) {
            // cache is disabled
            return;
        }
        synchronized (valueMap) {
            if (valueMap.size() >= maxSize) {
                Message.debug("ModuleDescriptorCache is full, remove one entry");
                Iterator<CacheEntry> it = valueMap.values().iterator();
                it.next();
                it.remove();
            }
            valueMap.put(url, new CacheEntry(descriptor, validated, ivySettingsMonitor));
        }
    }

    private static class CacheEntry {
        private final ModuleDescriptor md;

        private final boolean validated;

        private final ParserSettingsMonitor parserSettingsMonitor;

        CacheEntry(ModuleDescriptor md, boolean validated,
                ParserSettingsMonitor parserSettingsMonitor) {
            this.md = md;
            this.validated = validated;
            this.parserSettingsMonitor = parserSettingsMonitor;
        }

        boolean isStale(File ivyFile, boolean validated, ParserSettings newParserSettings) {
            return (validated && !this.validated)
                    || md.getLastModified() != ivyFile.lastModified()
                    || parserSettingsMonitor.hasChanged(newParserSettings);
        }
    }

}
