/*
* Copyright 2005,2006 WSO2, Inc. http://www.wso2.org
*
* 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.synapse.commons.throttle.core.impl.ipbase;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.commons.throttle.core.CallerConfiguration;
import org.apache.synapse.commons.throttle.core.ThrottleConfiguration;
import org.apache.synapse.commons.throttle.core.ThrottleConstants;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class IPBaseThrottleConfiguration implements ThrottleConfiguration {

    private static Log log = LogFactory.getLog(IPBaseThrottleConfiguration.class.getName());

    /* The key for "Other" configuration */
    private String keyOfOther;

    /* The default configuration for a throttle and this will apply to callersMap
    that have not a custom configuration */
    private CallerConfiguration defaultCallerConfiguration;

    /* To hold configurations */
    private Map configurationsMap;

    public IPBaseThrottleConfiguration() {
        this.configurationsMap = new HashMap();
    }

    /**
     * To get a IPBaseCallerConfiguration - if a configuration for given key found ,it returns ,
     * other wise , the default configuration will return.
     *
     * @param ID - The Remote caller id (IP)
     * @return Returns the corresponding configuration for the caller with given ID
     */
    public CallerConfiguration getCallerConfiguration(String ID) {

        if (ID.equals(ThrottleConstants.KEY_OF_DEFAULT_CONFIGURATION_FOR_OTHER)) {
            return defaultCallerConfiguration;
        } else {
            String key = getConfigurationKeyOfCaller(ID);
            if (key != null) {
                if (key.equals(ThrottleConstants.KEY_OF_DEFAULT_CONFIGURATION_FOR_OTHER)) {
                    return defaultCallerConfiguration;
                }
                return (CallerConfiguration) configurationsMap.get(key);
            }
        }
        return null;
    }

    /**
     * To add a IPBaseCallerConfiguration
     *
     * @param configuration The configuration for a caller
     */
    public void addCallerConfiguration(CallerConfiguration configuration) {
        //TODO need to allow overlapping of ip with FirstPartOfIPRange
        IPBaseCallerConfiguration ipBaseCallerConfiguration =
                (IPBaseCallerConfiguration) configuration;
        String key = ipBaseCallerConfiguration.getFirstPartOfIPRange();
        if (key == null) {
            return;
        }
        key = key.trim();
        if (key.equals(ThrottleConstants.KEY_OF_DEFAULT_CONFIGURATION_FOR_OTHER)) {
            keyOfOther = ThrottleConstants.KEY_OF_DEFAULT_CONFIGURATION_FOR_OTHER;
            defaultCallerConfiguration = ipBaseCallerConfiguration;
        } else {
            configurationsMap.put(key, ipBaseCallerConfiguration);
        }
    }

    /**
     * To get key for access configuration
     *
     * @param callerID The remote caller id (ip)
     * @return Object-String representation of  corrected epr-key for get configuration
     */
    public String getConfigurationKeyOfCaller(String callerID) {

        if (callerID != null) {
            callerID = callerID.trim();
            //if there is a unique IP
            if (configurationsMap.containsKey(callerID)) {
                return callerID;
            } else {
                int index = callerID.lastIndexOf(".");
                if (index > 0) {
                    String net = callerID.substring(0, index);     // get the network portion
                    String host = callerID.substring(index + 1, callerID.length());
                    //get the host portion
                    if (net != null && host != null) {
                        Set keys = configurationsMap.keySet();
                        if (keys != null && !keys.isEmpty()) {
                            for (Iterator it = keys.iterator(); it.hasNext(); ) {
                                String key = (String) it.next();
                                if (key != null && key.startsWith(net) && isAfter(key, host)) {
                                    // all ips with in same network
                                    IPBaseCallerConfiguration con =
                                            (IPBaseCallerConfiguration) configurationsMap.get(key);
                                    if (con != null) {
                                        String secondPart = con.getSecondPartOfIPRange();
                                        if (secondPart != null && isBefore(secondPart, host)) {
                                            return key;
                                        }
                                    }
                                }
                            }
                            String all = net + ".*";
                            if (configurationsMap.containsKey(all)) {
                                return all;
                            }
                        }
                    }
                }
            }
        }
        return keyOfOther;
    }

    /**
     * Helper method to check the ip with the given host is exists  after the given  ip
     *
     * @param ip   The lower IP
     * @param host host
     * @return true if the ip with host is higher than given ip ,ow. false
     */
    private boolean isAfter(String ip, String host) {
        int index = ip.lastIndexOf(".");
        if (index > 0) {
            // host of first part of ip
            String hostfromip = ip.substring(index + 1, ip.length());
            if (hostfromip != null && host != null) {

                return toInt(hostfromip) <= toInt(host);
            }
        }
        return false;
    }

    /**
     * Helper method to check the ip with the given host is exists  before the given  ip
     *
     * @param ip   the higher ip
     * @param host host
     * @return true if the ip with host is lower than given ip ,ow. false
     */
    private boolean isBefore(String ip, String host) {
        int index = ip.lastIndexOf(".");
        if (index > 0) {
            // host of second part of ip
            String hostfromip = ip.substring(index + 1, ip.length());
            if (hostfromip != null && host != null) {

                return toInt(hostfromip) >= toInt(host);

            }
        }
        return false;
    }

    public int getType() {
        return ThrottleConstants.IP_BASE;
    }

    private int toInt(String str) {

        if (str == null || "".equals(str)) {
            handleException("Invalid string - null value");
        }

        try {
            return Integer.parseInt(str.trim());
        } catch (NumberFormatException e) {
            handleException("Invalid string '" + str + "' , except an integer value ");
        }
        return -1;
    }

    private void handleException(String message) {
        String msg = "Error was occurred during ip(ip-range) processing  " + message;
        log.error(msg);
        throw new IllegalArgumentException(msg);
    }
}
