/*
 * 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.apache.karaf.config.core.impl;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import javax.management.MBeanException;
import javax.management.NotCompliantMBeanException;
import javax.management.StandardMBean;

import org.apache.karaf.config.core.ConfigMBean;
import org.apache.karaf.config.core.ConfigRepository;
import org.apache.karaf.util.StreamUtils;
import org.osgi.service.cm.Configuration;

/**
 * Implementation of the ConfigMBean.
 */
public class ConfigMBeanImpl extends StandardMBean implements ConfigMBean {

    private ConfigRepository configRepo;

    public ConfigMBeanImpl() throws NotCompliantMBeanException {
        super(ConfigMBean.class);
    }

    private Configuration getConfiguration(String pid) throws IOException {
        Configuration configuration = configRepo.getConfigAdmin().getConfiguration(pid, null);
        if (configuration == null) {
            throw new IllegalArgumentException("Configuration PID " + pid + " doesn't exist");
        }
        return configuration;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private Dictionary<String, Object> getConfigProperties(String pid) throws IOException {
        Configuration configuration = getConfiguration(pid);

        Dictionary<String, Object> dictionary = configuration.getProperties();
        if (dictionary == null) {
            dictionary = new Hashtable(new java.util.Properties());
        }
        return dictionary;
    }

    /**
     * Get all config pids
     */
    @Override
    public List<String> getConfigs() throws MBeanException {
        try {
            Configuration[] configurations = this.configRepo.getConfigAdmin().listConfigurations(null);
            List<String> pids = new ArrayList<>();
            for (int i = 0; i < configurations.length; i++) {
                pids.add(configurations[i].getPid());
            }
            return pids;
        } catch (Exception e) {
            throw new MBeanException(null, e.toString());
        }
    }

    @Override
    public void create(String pid) throws MBeanException {
        try {
            configRepo.update(pid, new Hashtable<>());
        } catch (Exception e) {
            throw new MBeanException(null, e.toString());
        }
    }

    @Override
    public void install(String url, String finalname, boolean override) throws MBeanException {
        try {
            File etcFolder = new File(System.getProperty("karaf.etc"));
            File file = new File(etcFolder, finalname);
            if (file.exists()) {
                if (!override) {
                    throw new IllegalArgumentException("Configuration file {} already exists " + finalname);
                }
            }

            try (InputStream is = new BufferedInputStream(new URL(url).openStream())) {
                if (!file.exists()) {
                    File parentFile = file.getParentFile();
                    if (parentFile != null) {
                        parentFile.mkdirs();
                    }
                    file.createNewFile();
                }
                try (FileOutputStream fop = new FileOutputStream(file)) {
                    StreamUtils.copy(is, fop);
                }
            } catch (RuntimeException | MalformedURLException e) {
                throw e;
            }
        } catch (Exception e) {
            throw new MBeanException(null, e.toString());
        }
    }

    @Override
    public void delete(String pid) throws MBeanException {
        try {
            this.configRepo.delete(pid);
        } catch (Exception e) {
            throw new MBeanException(null, e.toString());
        }
    }

    @Override
    @SuppressWarnings("rawtypes")
    public Map<String, String> listProperties(String pid) throws MBeanException {
        try {
            Dictionary dictionary = getConfigProperties(pid);

            Map<String, String> propertiesMap = new HashMap<>();
            for (Enumeration e = dictionary.keys(); e.hasMoreElements(); ) {
                Object key = e.nextElement();
                Object value = dictionary.get(key);
                propertiesMap.put(key.toString(), value.toString());
            }
            return propertiesMap;
        } catch (Exception e) {
            throw new MBeanException(null, e.toString());
        }
    }

    @Override
    public void deleteProperty(String pid, String key) throws MBeanException {
        try {
            Dictionary<String, Object> dictionary = getConfigProperties(pid);
            dictionary.remove(key);
            configRepo.update(pid, dictionary);
        } catch (Exception e) {
            throw new MBeanException(null, e.toString());
        }
    }

    @Override
    public void appendProperty(String pid, String key, String value) throws MBeanException {
        try {
            Dictionary<String, Object> dictionary = getConfigProperties(pid);
            Object currentValue = dictionary.get(key);
            if (currentValue == null) {
                dictionary.put(key, value);
            } else if (currentValue instanceof String) {
                dictionary.put(key, currentValue + value);
            } else {
                throw new IllegalStateException("Current value is not a String");
            }
            configRepo.update(pid, dictionary);
        } catch (Exception e) {
            throw new MBeanException(null, e.toString());
        }
    }

    @Override
    public void setProperty(String pid, String key, String value) throws MBeanException {
        try {
            Dictionary<String, Object> dictionary = getConfigProperties(pid);
            dictionary.put(key, value);
            configRepo.update(pid, dictionary);
        } catch (Exception e) {
            throw new MBeanException(null, e.toString());
        }
    }

    @Override
    public String getProperty(String pid, String key) throws MBeanException {
        try {
            Dictionary<String, Object> dictionary = getConfigProperties(pid);
            Object value = dictionary.get(key);
            if (value != null) {
                return value.toString();
            }
            return null;
        } catch (Exception e) {
            throw new MBeanException(null, e.toString());
        }
    }

    @Override
    public void update(String pid, Map<String, String> properties) throws MBeanException {
        try {
            if (properties == null) {
                properties = new HashMap<>();
            }
            Dictionary<String, Object> dictionary = toDictionary(properties);
            configRepo.update(pid, dictionary);
        } catch (Exception e) {
            throw new MBeanException(null, e.toString());
        }
    }

	private Dictionary<String, Object> toDictionary(
			Map<String, String> properties) {
		Dictionary<String, Object> dictionary = new Hashtable<>();
		for (String key : properties.keySet()) {
		    dictionary.put(key, properties.get(key));
		}
		return dictionary;
	}


    public void setConfigRepo(ConfigRepository configRepo) {
        this.configRepo = configRepo;
    }

	@Override
	public String createFactoryConfiguration(String factoryPid,
			Map<String, String> properties) throws MBeanException {
		Dictionary<String, Object> dict = toDictionary(properties);
		return configRepo.createFactoryConfiguration(factoryPid, dict);
	}

}
