/*
 * Licensed to The OpenNMS Group, Inc (TOG) under one or more
 * contributor license agreements.  See the LICENSE.md file
 * distributed with this work for additional information
 * regarding copyright ownership.
 *
 * TOG licenses this file to You under the GNU Affero General
 * Public License Version 3 (the "License") or (at your option)
 * any later version.  You may not use this file except in
 * compliance with the License.  You may obtain a copy of the
 * License at:
 *
 *      https://www.gnu.org/licenses/agpl-3.0.txt
 *
 * 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.opennms.core.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Convenience class for looking up string and integer values in a parameter
 * map.
 */
public abstract class ParameterMap {
	
	private static final Logger LOG = LoggerFactory.getLogger(ParameterMap.class);
	
    	/**
    	 * This method is used to lookup a specific key in the map. If the mapped
    	 * value is a string it is converted to a long and the original string
    	 * value is replaced in the map. The converted value is returned to the
    	 * caller. If the value cannot be converted then the default value is stored
    	 * in the map. If the specified key does not exist in the map then the
    	 * default value is returned.
    	 *
    	 * @return The long value associated with the key.
    	 * @param map a {@link java.util.Map} object.
    	 * @param key a {@link java.lang.String} object.
    	 * @param defValue a long.
    	 */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static long getKeyedLong(final Map map, final String key, final long defValue) {
	    
	    if (map == null) return defValue;
	    
        long value = defValue;
        Object oValue = map.get(key);

        if (oValue != null && oValue instanceof String) {
            try {
                value = Long.parseLong((String) oValue);
            } catch (NumberFormatException ne) {
                value = defValue;
                LOG.info("getKeyedLong: Failed to convert value {} for key {}", oValue , key, ne);
            }
            map.put(key, new Long(value));
        } else if (oValue != null) {
            value = ((Number) oValue).longValue();
        }
        return value;
    }

    /**
     * Same as getKeyedLong, but does not mutate the map
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static long getKeyedLongImmutable(final Map map, final String key, final long defValue) {

        if (map == null) return defValue;

        long value = defValue;
        Object oValue = map.get(key);

        if (oValue != null && oValue instanceof String) {
            try {
                value = Long.parseLong((String) oValue);
            } catch (NumberFormatException ne) {
                value = defValue;
                LOG.info("getKeyedLong: Failed to convert value {} for key {}", oValue , key, ne);
            }
        } else if (oValue != null) {
            value = ((Number) oValue).longValue();
        }
        return value;
    }
	
    /**
     * This method is used to lookup a specific key in the map. If the mapped
     * value is a string it is converted to a long and the original string
     * value is replaced in the map. The converted value is returned to the
     * caller. If the value cannot be converted then the default value is stored
     * in the map. If the specified key does not exist in the map then the
     * default value is returned.
     *
     * @return The long value associated with the key.
     * @param map a {@link java.util.Map} object.
     * @param key a {@link java.lang.String} object.
     * @param defValue a long.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static long getKeyedDecodedLong(final Map map, final String key, final long defValue) {
            
            if (map == null) return defValue;
            
        long value = defValue;
        Object oValue = map.get(key);
    
        if (oValue != null && oValue instanceof String) {
            try {
                value = Long.decode((String) oValue);
            } catch (final NumberFormatException ne) {
                value = defValue;
                LOG.info("getKeyedDecodedLong: Failed to convert value {} for key {}", oValue , key, ne);
            }
            map.put(key, new Long(value));
        } else if (oValue != null) {
            value = ((Number) oValue).longValue();
        }
        return value;
    }
        
    /**
     * This method is used to lookup a specific key in the map. If the mapped
     * value is a string it is converted to an integer and the original string
     * value is replaced in the map. The converted value is returned to the
     * caller. If the value cannot be converted then the default value is stored
     * in the map. If the specified key does not exist in the map then the
     * default value is returned.
     *
     * @return The int value associated with the key.
     * @param map a {@link java.util.Map} object.
     * @param key a {@link java.lang.String} object.
     * @param defValue a int.
     */
    public static int getKeyedInteger(@SuppressWarnings("rawtypes") final Map map, final String key, final int defValue) {
        return new Long(ParameterMap.getKeyedLong(map, key, defValue)).intValue();
    }

    /**
     * This method is used to lookup a specific key in the map. If the mapped
     * value is a string is is converted to an integer and the original string
     * value is replaced in the map. The converted value is returned to the
     * caller. If the value cannot be converted then the default value is used.
     *
     * @return The array of integer values associated with the key.
     * @param map a {@link java.util.Map} object.
     * @param key a {@link java.lang.String} object.
     * @param defValues an array of int.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static final int[] getKeyedIntegerArray(final Map map, final String key, final int[] defValues) {
        
        if (map == null) return defValues;
        
        int[] result = defValues;
        Object oValue = map.get(key);

        if (oValue != null && oValue instanceof int[]) {
            result = (int[]) oValue;
        } else if (oValue != null) {
            List<Integer> tmpList = new ArrayList<Integer>(5);

            // Split on spaces, commas, colons, or semicolons
            //
            StringTokenizer ints = new StringTokenizer(oValue.toString(), " ;:,");
            while (ints.hasMoreElements()) {
                String token = ints.nextToken();
                try {
                    int x = Integer.parseInt(token);
                    tmpList.add(Integer.valueOf(x));
                } catch (NumberFormatException e) {
                	LOG.warn("getKeyedIntegerArray: failed to convert value {} to int array for key {} due to value {}", oValue, key, token, e);
                }
            }
            result = new int[tmpList.size()];

            for (int x = 0; x < result.length; x++)
                result[x] = ((Integer) tmpList.get(x)).intValue();

            map.put(key, result);
        }
        return result;
    }

    /**
     * This method is used to lookup a specific key in the map. If the mapped
     * value is a string it is converted to an integer and the original string
     * value is replaced in the map. The converted value is returned to the
     * caller. If the value cannot be converted then the default value is stored
     * in the map. If the specified key does not exist in the map then the
     * default value is returned.
     *
     * @return The int value associated with the key.
     * @param map a {@link java.util.Map} object.
     * @param key a {@link java.lang.String} object.
     * @param defValue a int.
     */
    public static int getKeyedDecodedInteger(@SuppressWarnings("rawtypes") final Map map, final String key, final int defValue) {
        return new Long(ParameterMap.getKeyedDecodedLong(map, key, defValue)).intValue();
    }

    /**
     * This method is used to lookup a specific key in the map. If the mapped
     * value is not a String it is converted to a String and the original value
     * is replaced in the map. The converted value is returned to the caller. If
     * the specified key does not exist in the map then the default value is
     * returned.
     *
     * @return The String value associated with the key.
     * @param map a {@link java.util.Map} object.
     * @param key a {@link java.lang.String} object.
     * @param defValue a {@link java.lang.String} object.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static String getKeyedString(final Map map, final String key, final String defValue) {
        
        if (map == null) return defValue;

        String value = defValue;
        Object oValue = map.get(key);

        if (oValue != null && oValue instanceof String) {
            value = (String) oValue;
        } else if (oValue != null) {
            value = oValue.toString();
            map.put(key, value);
        }
        return value;
    }

    /**
     * This method is used to lookup a specific key in the map. If the mapped
     * value is a string it is converted to a boolean and the original string
     * value is replaced in the map. The converted value is returned to the
     * caller. If the value cannot be converted then the default value is stored
     * in the map. If the specified key does not exist in the map then the
     * default value is returned.
     *
     * @return The bool value associated with the key.
     * @param map a {@link java.util.Map} object.
     * @param key a {@link java.lang.String} object.
     * @param defValue a boolean.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static boolean getKeyedBoolean(final Map map, final String key, final boolean defValue) {
        
        if (map == null) return defValue;
        
        boolean value = defValue;
        Object oValue = map.get(key);

               if (oValue != null && oValue instanceof String) {
                       oValue = Boolean.valueOf((String) oValue);
               }

        if (oValue != null && oValue instanceof Boolean) {
            try {
                value = ((Boolean) oValue).booleanValue();
            } catch (NumberFormatException ne) {
                value = defValue;
                LOG.info("getKeyedBoolean: Failed to convert value {} for key {}", oValue, key, ne);
            }
            map.put(key, Boolean.valueOf(value));
        } else if (oValue != null) {
            value = ((Boolean) oValue).booleanValue();
        }
        return value;
    }

    /**
     * This method is used to lookup a specific key in the map. If the value 
     * cannot be found in the map then the default value is stored
     * in the map. If the specified key does not exist in the map then the
     * default value is returned.
     *
     */
    public static <T> T getKeyedValue(final Map<String,T> map, final String key, final T defValue) {

        if (map == null) return defValue;

        T oValue = map.get(key);

        if (oValue != null) {
            return oValue;
        } else {
            map.put(key, defValue);
            return defValue;
        }
    }

    public static Long getLongValue(String key, Object value, Long defaultValue) {
        if (value != null && value instanceof String) {
            try {
                String valueAsString = (String) value;
                return Long.parseLong(valueAsString);
            } catch (NumberFormatException e) {
                LOG.warn("The value = {} associated with key = {}  is not a number", value, key, e);
            }
        }
        return defaultValue;
    }

    public static Integer getIntValue(String key, Object value, Integer defaultValue) {
        if (value != null && value instanceof String) {
            try {
                String valueAsString = (String) value;
                return Integer.parseInt(valueAsString);
            } catch (NumberFormatException e) {
                LOG.warn("The value = {} associated with key = {}  is not a number", value, key, e);
            }
        }
        return defaultValue;
    }


}
