/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.cryptolib.commons.configuration;

import static java.nio.file.Files.exists;
import static java.text.MessageFormat.format;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.commons.utils.system.OperatingSystem;

/**
 * This class helps {@code Policy} from properties builders to retrieve not
 * blank properties.
 */
public class PolicyFromPropertiesHelper {
    private static final String OS_DEPENDENCY_PROPERTY_FORMAT = "%s.%s";

    private final Properties properties;

    /**
     * Creates a helper that will get properties from the given properties file.
     * 
     * @param path
     *            The path of the properties file.
     * @throws CryptoLibException
     *             if the file loading failed.
     */
    public PolicyFromPropertiesHelper(final String path)
            throws CryptoLibException {
        properties = loadProperties(findProperties(path));
    }

    /*
     * Finds the URL of the policy properties. To preserve the backward 
     * compatibility the implementation repeats the following steps from 
     * Apache Commons Configuration:
     * 
     * 1. Try to use the path as a valid URL.
     * 
     * 2. Try to treat the path as an absolute file. If the file exists, 
     * then transform it to URL.
     * 
     * 3. Try to resolve the path against the user home folder. If the file
     * exists, then transform it to URL.
     * 
     * 4. Try to find the resource in the context class loader.
     * 
     * 5. Try to find the resource in the system class loader.
     */
    private static URL findProperties(String path)
            throws CryptoLibException {
        if (path == null) {
            throw new CryptoLibException("Path is null.");
        }
        
        try {
            return new URL(path);
        } catch (MalformedURLException e) {
            // nothing to do
        }

        Path file = Paths.get(path);
        if (file.isAbsolute() && exists(file)) {
            try {
                return file.toUri().toURL();
            } catch (MalformedURLException e) {
                throw new IllegalStateException(e);
            }
        }

        file = Paths.get(System.getProperty("user.home"), path);
        if (exists(file)) {
            try {
                return file.toUri().toURL();
            } catch (MalformedURLException e) {
                throw new IllegalStateException(e);
            }
        }

        URL url = Thread.currentThread().getContextClassLoader()
            .getResource(path);
        if (url != null) {
            return url;
        }

        url = ClassLoader.getSystemResource(path);
        if (url != null) {
            return url;
        }

        throw new CryptoLibException(
            format("Failed to find properties at ''{0}''.", path));
    }

    private static Properties loadProperties(URL url)
            throws CryptoLibException {
        Properties properties = new Properties();
        try (InputStream stream = url.openStream()) {
            properties.load(stream);
        } catch (IOException e) {
            throw new CryptoLibException(
                format("Failed to load properties from ''{0}''.", url), e);
        }
        return properties;
    }

    private static void validateNotBlank(final String propertyName,
            final String propertyValue)
            throws CryptoLibException {
        if (propertyValue == null || propertyValue.isEmpty()) {
            throw new CryptoLibException(String
                .format("property:'%s' cannot be blank.", propertyName));
        }
        for (int i = 0; i < propertyValue.length(); i++) {
            if (!Character.isWhitespace(propertyValue.charAt(i))) {
                return;
            }
        }
        throw new CryptoLibException(String
            .format("property:'%s' cannot be blank.", propertyName));
    }

    private String getNotBlankProperty(final String key) {
        String value = properties.getProperty(key);
        validateNotBlank(key, value);
        return value;
    }

    private String getOSName() {
        return OperatingSystem.current().name().toLowerCase();
    }

    /**
     * Retrieves the value of the given key. The method also checks if the
     * returned value is blank.
     * 
     * @param key
     *            The requested key.
     * @return The value assigned to the key.
     */
    public String getNotBlankPropertyValue(final String key) {

        return getNotBlankProperty(key);

    }

    /**
     * Retrieves the value of the given key adapted to the operating system. The
     * method also checks if the returned value is blank.
     * 
     * @param key
     *            The requested key.
     * @return The value assigned to the key.
     * @throws CryptoLibException
     *             in case of returning a blank value.
     */
    public String getNotBlankOSDependentPropertyValue(final String key) {

        return getNotBlankProperty(String
            .format(OS_DEPENDENCY_PROPERTY_FORMAT, key, getOSName()));

    }
    
    /**
     * Retrieves the value of the given key.
     * 
     * @param key the key
     * @return the value or {@code null} if the key is not defined.
     */
    public String getPropertyValue(String key) {
        return properties.getProperty(key);
    }
}
