/*
* Copyright 2005,2006 WSO2, Inc. http://wso2.com
*
* 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;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.commons.throttle.core.factory.CallerContextFactory;

/**
 * Controls the access of remote callers according to the controlling  policy .
 * This provides abstraction that need to control access based on caller IP or caller domain name.
 * This implementation is thread safe.
 */

public class AccessRateController {

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

    private static final String ACCESS_DENIED_TEMPORALLY =
            "You cannot access this service since you have exceeded the allocated quota.";

    private static final String ACCESS_DENIED =
            "You cannot access this service since you have been prohibited permanently.";

    /* The Object for used to lock in synchronizing */
    private final Object lock = new Object();

    private boolean debugOn = false;  //is debug enable
    private static final String SYMBOL_UNDERSCORE = "_";

    public AccessRateController() {
        debugOn = log.isDebugEnabled();
    }

    /**
     * To check whether caller can access not not base on the controlling  policy
     *
     * @param throttleContext - current states of throttle - RunTime Data
     * @param callerID        - Identifier for remote caller - ex: ip or domainname
     * @param callerType      - the type of the caller
     * @return access information
     * @throws ThrottleException
     */
    public AccessInformation canAccess(ThrottleContext throttleContext, String callerID,
                                       int callerType) throws ThrottleException {

        String type = ThrottleConstants.IP_BASE == callerType ? "IP address" : "domain";

        ThrottleConfiguration throttleConfigurationBean =
                throttleContext.getThrottleConfiguration();
        AccessInformation accessInformation = new AccessInformation();

        if (throttleConfigurationBean == null) {
            if (debugOn) {
                log.debug("Throttle Configuration couldn't find - Throttling will not occur");
            }
            accessInformation.setAccessAllowed(true);
            return accessInformation;
        }

        if (callerID == null) {
            String msg = "Caller host or ip  couldn't find !! - Access will be denied ";
            if (debugOn) {
                log.debug(msg);
            }
            accessInformation.setAccessAllowed(false);
            accessInformation.setFaultReason(msg);
            return accessInformation;
        }
        // The configs are added without unique key hence removing it while retrieving the configuration.
        String callerAddress = callerID.substring(callerID.lastIndexOf(SYMBOL_UNDERSCORE) + 1);
        CallerConfiguration configuration = throttleConfigurationBean.getCallerConfiguration(callerAddress);
        if (configuration == null) {
            if (debugOn) {
                log.debug("Caller configuration couldn't find for " + type + " and for caller " +
                        callerID);
            }
            accessInformation.setAccessAllowed(true);
            return accessInformation;
        }
        if (configuration.getAccessState() == ThrottleConstants.ACCESS_DENIED) {
            log.info(ACCESS_DENIED);
            accessInformation.setAccessAllowed(false);
            accessInformation.setFaultReason(ACCESS_DENIED);
            return accessInformation;
        } else if (configuration.getAccessState() == ThrottleConstants.ACCESS_ALLOWED) {
            accessInformation.setAccessAllowed(true);
            return accessInformation;
        } else if (configuration.getAccessState() == ThrottleConstants.ACCESS_CONTROLLED) {
            synchronized (lock) {
                CallerContext caller = throttleContext.getCallerContext(callerID);
                if (caller == null) {
                    //if caller has not already registered ,then create new caller description and
                    //set it in throttle
                    caller = CallerContextFactory.createCaller(callerType, callerID);
                }
                if (caller != null) {
                    long currentTime = System.currentTimeMillis();

                    if (!caller.canAccess(throttleContext, configuration, currentTime)) {
                        log.info(ACCESS_DENIED_TEMPORALLY);
                        accessInformation.setAccessAllowed(false);
                        accessInformation.setFaultReason(ACCESS_DENIED_TEMPORALLY);
                    } else {
                        if (debugOn) {
                            log.debug("Access  from " + type + " " + callerID + " is successful.");
                        }
                        accessInformation.setAccessAllowed(true);
                    }
                } else {
                    if (debugOn) {
                        log.debug("Caller " + type + " not found! " + callerID);
                    }
                    accessInformation.setAccessAllowed(true);
                }
            }
            //if current caller cannot access , then perform cleaning
            if (!accessInformation.isAccessAllowed()) {
                throttleContext.processCleanList(System.currentTimeMillis());
            }
            return accessInformation;
        }
        accessInformation.setAccessAllowed(true);
        return accessInformation;
    }
}