package com.capgemini.framework.common;

import lombok.extern.slf4j.Slf4j;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map.Entry;
import java.util.Properties;

/**
 * Property manager pattern for managing settings. <br>
 * All methods are protected, please extend this class to present a specific getter method of the right type for each parameter.
 **/
@Slf4j
public abstract class AbstractPropertiesManager {

    private static final String UNEXPECTED_FORMAT_OF_PROPERTY = "Unexpected format of property: ";

    /** File path to the property file. **/
    final String propertyFilePath;

    /** Prefix that should be appended at the beginning of all properties. **/
    private final String propertyCommonPrefix;

    /** Properties resource bundle corresponding to the propertyFilePath. **/
    private final Properties propertyFile;

    /**
     * Constructor : load the properties from the property file.
     * 
     * @param aPropertyFilePath File path to the property file
     **/
    AbstractPropertiesManager(final String aPropertyFilePath) {
        propertyFilePath = aPropertyFilePath;
        propertyCommonPrefix = getPropPrefix();
        propertyFile = new Properties();
        // load file
        try {
            loadServerPopertiesFile();
        } catch (IOException e) {
            log.error("Unable to load properties from file: " + propertyFilePath, e);
        }
        // log properties reado
        if (log.isDebugEnabled()) {
            log.debug("-- First startup of Properties Manager --");
            for (Entry<Object, Object> entry : propertyFile.entrySet()) {
                log.debug(String.format(" + Key: %-70s Value: %s", entry.getKey(), entry.getValue()));
            }
        }
    }

    /**
     * Get Boolean value from settings.
     * 
     * @param aKey Searched setting key
     * @return boolean value matching with searched setting key
     */
    protected boolean getBooleanValue(final String aKey) {
        boolean theBoolValue = false;
        String theValue = getStringValue(aKey);
        if ((theValue != null) && (isTrue(theValue))) {
            theBoolValue = true;
        }
        return theBoolValue;
    }

    /**
     * Check if current value match with a value considered as "true".
     * 
     * @param aValue String value which should be analyzed
     * @return Boolean interpretation of current String value
     **/
    private static boolean isTrue(final String aValue) {
        boolean theStatus = false;
        final String[] trueValueArray = {"true", "TRUE", "True", "1"};
        for (String aCurrentValue : trueValueArray) {
            if (aCurrentValue.equals(aValue)) {
                theStatus = true;
                break;
            }
        }
        return theStatus;
    }

    /**
     * Get Integer value from settings.
     * 
     * @param aKey Searched setting key
     * @return integer value matching with searched setting key
     */
    int getIntegerValue(final String aKey) {
        int theIntValue = 0;
        try {
            String theValue = getStringValue(aKey);
            if (theValue != null) {
                theIntValue = Integer.valueOf(theValue);
            }
        } catch (NumberFormatException e) {
            log.error(UNEXPECTED_FORMAT_OF_PROPERTY + aKey + " (it should be an integer)", e);
        }
        return theIntValue;
    }

    /**
     * Get a string value from settings.
     * 
     * @param aKey Searched setting key
     * @return String value matching with searched setting key
     */
    String getStringValue(final String aKey) {
        // Read property value
        String thePropertyValue = getProperty(aKey);

        // Log warning when a property has not been found
        if (thePropertyValue == null) {
            log.error("Setting reference: \"" + aKey + "\" has not been found");
        }

        return thePropertyValue;
    }

    /**
     * Get a property from the reference parameter and set a default value if the reference is not found.
     * 
     * @param aRef Setting key
     * @param aDefaultValue Default returned value when setting is not defined
     * @return string value matching with searched setting key or default value when key has not been found
     */
    String getPropertyWithDefaultValueImpl(final String aRef, final String aDefaultValue) {
        // Read property value
        String thePropertyValue = getProperty(aRef);

        if (thePropertyValue == null) {
            thePropertyValue = aDefaultValue;
        }
        return thePropertyValue;
    }

    /**
     * Get a property from the reference parameter and set a default value if the reference is not found.
     * 
     * @param aRef Setting key
     * @param aDefaultValue Default returned boolean when setting is not defined
     * @return string value matching with searched setting key or default value when key has not been found
     */
    boolean getBooleanWithDefaultValueImpl(final String aRef, final boolean aDefaultValue) {
        // Read property value
        String thePropertyValue = getProperty(aRef);
        if (thePropertyValue == null) {
            return aDefaultValue;
        } else {
            return isTrue(thePropertyValue);
        }
    }

    /**
     * Attempt to load server properties. If failed, loads internal EAR properties.
     * 
     * @throws IOException if even the internal file couldn't be read
     */
    private void loadServerPopertiesFile() throws IOException {
        String theJbossConfPath = System.getenv("JBOSS_HOME") + "/standalone/configuration/" + propertyFilePath;
        boolean isConfFile = FileHelper.fileExist(theJbossConfPath);
        if (isConfFile) {
            log.debug("JBOSS configuration file has been found in: " + theJbossConfPath);
            try (FileInputStream thePropStream = new FileInputStream(theJbossConfPath)) {
                readPropertiesFile(thePropStream);
            } catch (IOException e) {
                log.error("JBOSS configuration file has been found but is not readable : " + theJbossConfPath
                        + " defaulting to internal porperties file", e);
                loadinternalPropertiesFile();
            }
        } else {
            log.info("EAR file will be used, no configuration file found in  " + theJbossConfPath);
            loadinternalPropertiesFile();
        }
    }

    /**
     * Loads the internal EAR file.
     * @throws IOException if unable to read the file
     */
    private void loadinternalPropertiesFile() throws IOException {
        try (InputStream stream = AbstractPropertiesManager.class.getResourceAsStream(propertyFilePath)) {
            readPropertiesFile(stream);
        }
    }

    /**
     * Reads any {@link InputStream} into a {@link Properties} file using the correct {@link Charset}.
     * @param stream the stream to the file
     * @throws IOException if unable to load the file
     */
    private void readPropertiesFile(InputStream stream) throws IOException {
        Reader reader = new InputStreamReader(stream, StandardCharsets.ISO_8859_1);
        propertyFile.load(reader);
    }

    /**
     * Insert a required prefix at the beginning of property reference. Then obtains the value associated with it. Return may be null.
     * 
     * @param aPropRef Property reference to find
     * @return String content of searched property, null if no such key found
     */
    private String getProperty(final String aPropRef) {
        // filter invalid characters in the key
        String filteredKey = aPropRef.replaceAll(StringHelper.REGEXP_ALPHANUM, "");

        // Insert common prefix if not present
        String thePropRefWithPrefix;
        if ((propertyCommonPrefix != null) && !filteredKey.startsWith(propertyCommonPrefix)) {
            thePropRefWithPrefix = propertyCommonPrefix + filteredKey;
        } else {
            thePropRefWithPrefix = filteredKey;
        }

        // Get property content
        String propertyValue = propertyFile.getProperty(thePropRefWithPrefix);
        if (propertyValue == null) {
            return null;
        } else {
            return new String(propertyValue.getBytes(), StandardCharsets.ISO_8859_1);
        }
    }

    Properties getPropertyFile() {
        return propertyFile;
    }

    /**
     * Getter for prefix that should be appended at the beginning of all properties.
     * 
     * @return The common prefix value
     **/
    protected abstract String getPropPrefix();
}
