package com.netflix.karyon.jersey.blocking;

import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.api.core.ScanningResourceConfig;
import com.sun.jersey.core.spi.scanning.PackageNamesScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.core.MediaType;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import static com.netflix.config.ConfigurationManager.getConfigInstance;

/**
 * An implementation of {@link ResourceConfig} that enables users to define all jersey properties in a property file
 * loaded by karyon via archaius. <br/>
 * This supports scanning of classpath (using {@link ScanningResourceConfig}) to discover provider and other resource
 * classes. The scanning of classpath is done lazily, at the first call to {@link #getClasses()} in order to make sure
 * that we do not do scanning too early, even before all properties are loaded.
 *
 * @author Nitesh Kant
 */
public class PropertiesBasedResourceConfig extends ScanningResourceConfig {

    private static final Logger logger = LoggerFactory.getLogger(PropertiesBasedResourceConfig.class);

    private volatile boolean initialized;

    @Override
    public Set<Class<?>> getClasses() {
        initIfRequired();
        return super.getClasses();
    }

    @Override
    public Set<Object> getSingletons() {
        initIfRequired();
        return super.getSingletons();
    }

    @Override
    public Map<String, MediaType> getMediaTypeMappings() {
        initIfRequired();
        return super.getMediaTypeMappings();
    }

    @Override
    public Map<String, String> getLanguageMappings() {
        initIfRequired();
        return super.getLanguageMappings();
    }

    @Override
    public Map<String, Object> getExplicitRootResources() {
        initIfRequired();
        return super.getExplicitRootResources();
    }

    @Override
    public Map<String, Boolean> getFeatures() {
        initIfRequired();
        return super.getFeatures();
    }

    @Override
    public boolean getFeature(String featureName) {
        initIfRequired();
        return super.getFeature(featureName);
    }

    @Override
    public Map<String, Object> getProperties() {
        initIfRequired();
        return super.getProperties();
    }

    @Override
    public Object getProperty(String propertyName) {
        initIfRequired();
        return super.getProperty(propertyName);
    }

    private synchronized void initIfRequired() {
        if (initialized) {
            return;
        }
        initialized = true;
        String pkgNamesStr = getConfigInstance().getString(PackagesResourceConfig.PROPERTY_PACKAGES, null);
        if (null == pkgNamesStr) {
            logger.warn("No property defined with name: " + PackagesResourceConfig.PROPERTY_PACKAGES +
                        ", this means that jersey can not find any of your resource/provider classes.");
        } else {
            String[] pkgNames = getElements(new String[]{pkgNamesStr}, ResourceConfig.COMMON_DELIMITERS);
            logger.info("Packages to scan by jersey {}", Arrays.toString(pkgNames));
            init(new PackageNamesScanner(pkgNames));
        }
        Map<String, Object> jerseyProperties = createPropertiesMap();
        setPropertiesAndFeatures(jerseyProperties);
    }

    private static Map<String, Object> createPropertiesMap() {
        Properties properties = getConfigInstance().getProperties("com.sun.jersey");
        return new TypeSafePropertiesDelegate(properties);
    }

    private static class TypeSafePropertiesDelegate implements Map<String, Object> {

        private final Properties properties;
        // This intends to not make a copy of the properties but just refer to the property name & delegate to the
        // properties instance for values.
        private final Set<Entry<String, Object>> entrySet;

        public TypeSafePropertiesDelegate(Properties properties) {
            this.properties = properties;
            entrySet = new HashSet<Entry<String, Object>>(properties.size());
            for (final String propName : properties.stringPropertyNames()) {
                entrySet.add(new Entry<String, Object>() {
                    @Override
                    public String getKey() {
                        return propName;
                    }

                    @Override
                    public Object getValue() {
                        return TypeSafePropertiesDelegate.this.properties.getProperty(propName);
                    }

                    @Override
                    public Object setValue(Object value) {
                        throw new UnsupportedOperationException("Writes are not supported on jersey features and properties map.");
                    }
                });
            }
        }

        @Override
        public int size() {
            return properties.size();
        }

        @Override
        public boolean isEmpty() {
            return properties.isEmpty();
        }

        @Override
        public boolean containsKey(Object key) {
            return properties.contains(key);
        }

        @Override
        public boolean containsValue(Object value) {
            return properties.containsValue(value);
        }

        @Override
        public Object get(Object key) {
            return properties.getProperty(String.valueOf(key));
        }

        @Override
        public Object put(String key, Object value) {
            throw new UnsupportedOperationException("Writes are not supported on jersey features and properties map.");
        }

        @Override
        public Object remove(Object key) {
            throw new UnsupportedOperationException("Writes are not supported on jersey features and properties map.");
        }

        @Override
        public void putAll(Map<? extends String, ?> m) {
            throw new UnsupportedOperationException("Writes are not supported on jersey features and properties map.");
        }

        @Override
        public void clear() {
            throw new UnsupportedOperationException("Writes are not supported on jersey features and properties map.");
        }

        @Override
        public Set<String> keySet() {
            return properties.stringPropertyNames();
        }

        @Override
        public Collection<Object> values() {
            return properties.values();
        }

        @Override
        public Set<Entry<String, Object>> entrySet() {
            return entrySet;
        }
    }
}

@@ -429,16 +429,43 @@
 ashSet;%0A
+import java.util.Iterator;%0A
 import j
@@ -1292,16 +1292,92 @@
 class);%0A
+    private static final String JERSEY_ROOT_PACKAGE = %22com.sun.jersey%22;%0A    
 %0A    pri
@@ -3655,61 +3655,267 @@
 s = 
-getConfigInstance().getProperties(%22com.sun.jersey%22
+new Properties();%0A        Iterator%3CString%3E iter = getConfigInstance().getKeys(JERSEY_ROOT_PACKAGE);%0A        while (iter.hasNext()) %7B%0A            String key = iter.next();%0A            properties.setProperty(key, getConfigInstance().getString(key)
 );%0A
+        %7D%0A%0A
     

Fix for https://github.com/Netflix/karyon/issues/79