/*
 *  @author Ranides Atterwim <ranides@gmail.com>
 *  @copyright Ranides Atterwim
 *  @license WTFPL
 *  @url http://ranides.net/projects/assira
 */

package net.ranides.assira.config;

import java.util.Map;
import java.util.ServiceLoader;
import net.ranides.assira.collection.map.Cache;

/**
 * Klasa zarządzająca cyklem wyszukiwania, ładowania i dostępem do poszczególnych
 * konfiguracji. Dzięki menagerowi nie musimy w aplikacji tworzyć globalnie
 * dostępnych instancji - każdy moduł może zażądać udostępnienia odpowiedniej
 * konfiguracji na podstawie typu oraz abstrakcyjnej nazwy.
 *
 * <p>
 * Klasa zarządza również zarejestrowanymi fabrykami, obsługującymi różne formaty
 * konfiguracyjne.
 * </p>
 * @author ranides
 */
public final class ConfigService {

    private ConfigService() { /* utility class */ }

    private static final Cache<String, ConfigProvider> FACTORIES = new Cache<>();

    private static final Map<String, Configuration> CONFIGS = new Cache<>(Integer.MAX_VALUE, 8);

    /**
     * <p>
     * Zwraca konfigurację o podanej nazwie oraz typie.
     * Zwrócona konfiguracja jest załadowana. Jeśli wskazana konfiguracja została już
     * załadowana do pamięci wcześnej, operacja nie jest powtarzana. Metoda może być
     * wywoływana wielokrotnie bez negatywnego wpływu na wydajność.
     * </p><p>
     * Uwaga! Wielokrotne wywołanie metody gwarantuje, że zwróci poprzednią
     * instancję tak długo, jak gdziekolwiek w programie będzie istnieć referencja
     * do niej. Z tego względu każda niezależna część aplikacji, która chce mieć
     * gwarancję, że wprowadzane przez nią zmiany będą globalnie widoczne, musi
     * przechowywać referencję do wyniku. Innymi słowy, z {@link Configuration} należy
     * postępować identycznie jak z {@link java.util.logging.Logger}.
     * </p>
     * @param path "provider:path" co najczęściej przekłada się na "provider:protocol:location"
     * @return
     */
    public static Configuration getInstance(String path) {
        if( CONFIGS.containsKey(path) ) {
            return CONFIGS.get(path);
        }
        String[] params = path.split(":", 2);
        if(params.length != 2) {
            throw new IllegalArgumentException("Illegal name of configuration: " + path);
        }
        Configuration result = getProvider(params[0]).getInstance(params[1]);
        CONFIGS.put(path, result);
        return result.load();
    }

    /**
     * Zwraca fabrykę o określonej nazwie. Wyniki zwracane przez metodę są buforowane,
     * ale nie jest gwarantowane, że wywołanie metody z tą samą nazwą zwraci te
     * same obiekty. Jeśli aplikacja musi cały czas pracować z tym samym providerem,
     * to musi samodzielnie przechowywać referencję do wyniku uzyskanego po pierwszym
     * wywołaniu.
     * @param name
     * @return
     */
    public static ConfigProvider getProvider(String name) {
        if( FACTORIES.containsKey(name) ) {
            return FACTORIES.get(name);
        }
        for(ConfigProvider item : ServiceLoader.load(ConfigProvider.class) ) {
            if( name.equals(item.getName()) ) {
                FACTORIES.put(name, item);
                return item;
            }
        }
        throw new ConfigException("ConfigFactory not found: " + name);
    }

    public static void register(ConfigProvider provider) {
        FACTORIES.put(provider.getName(), provider);
    }

}
