/******************************************************************************
 * JBoss, a division of Red Hat                                               *
 * Copyright 2011, Red Hat Middleware, LLC, and individual                    *
 * contributors as indicated by the @authors tag. See the                     *
 * copyright.txt in the distribution for a full listing of                    *
 * individual contributors.                                                   *
 *                                                                            *
 * This is free software; you can redistribute it and/or modify it            *
 * under the terms of the GNU Lesser General Public License as                *
 * published by the Free Software Foundation; either version 2.1 of           *
 * the License, or (at your option) any later version.                        *
 *                                                                            *
 * This software is distributed in the hope that it will be useful,           *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU           *
 * Lesser General Public License for more details.                            *
 *                                                                            *
 * You should have received a copy of the GNU Lesser General Public           *
 * License along with this software; if not, write to the Free                *
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA         *
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.                   *
 ******************************************************************************/
package org.gatein.web.redirect;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.exoplatform.portal.config.model.DevicePropertyCondition;
import org.exoplatform.portal.config.model.PortalRedirect;
import org.exoplatform.portal.config.model.RedirectCondition;
import org.exoplatform.portal.config.model.UserAgentConditions;
import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;
import org.gatein.web.redirect.api.RedirectKey;
import org.gatein.web.redirect.api.RedirectType;

/**
 * Determines what site, if any, should be used for a redirect. Bases this decisions on the portal's redirect conditions and the
 * device properties available.
 *
 * TODO: create an interface for this and configure the service using the kernel TODO: update the tests for this to use
 * portal.xml files instead of creating the configurations manually in code
 *
 * @author <a href="mailto:mwringe@redhat.com">Matt Wringe</a>
 * @version $Revision$
 */
public class Redirector {
    protected static Logger log = LoggerFactory.getLogger(Redirector.class);

    public RedirectKey getRedirectSite(ArrayList<PortalRedirect> portalRedirects, String userAgentString,
            Map<String, String> deviceProperties) {
        if (userAgentString == null) {
            // if the uas is null, then just transform it into 'null'. This allows the admin to configure how to handle the
            // 'null' the null case easily.
            // since the uas is user modifiable, setting it to whatever we want here will not cause any problems.
            userAgentString = "null";
        }

        boolean needsBrowserDetection = false;

        if (portalRedirects != null && !portalRedirects.isEmpty()) {
            for (PortalRedirect redirect : portalRedirects) {
                if (redirect.isEnabled()) {
                    RedirectType redirectCondition = checkConditions(userAgentString, deviceProperties,
                            redirect.getConditions());
                    if (redirectCondition == RedirectType.REDIRECT) {
                        log.debug("Found a match with UAS " + userAgentString + " and DeviceProperties " + deviceProperties
                                + ". Setting redirect to : " + redirect.getRedirectSite());

                        return RedirectKey.redirect(redirect.getRedirectSite());
                    } else if (redirectCondition == RedirectType.NEEDDEVICEINFO) {
                        needsBrowserDetection = true;
                    }
                }
            }

            if (needsBrowserDetection == false) {
                log.debug("Could not find a match with the specified uas and device properties. Returning NO_REDIRECT_DETECTED");
                return RedirectKey.noRedirect();
            } else {
                log.debug("Found a match with the specified uas but it requires device properties. Returning NO_REDIRECT_DETECTED");
                return RedirectKey.needDeviceInfo();
            }
        } else {
            log.debug("No UserAgentString specified and no device properties. Returning NO_REDIRECT_DETECTED");
            return RedirectKey.noRedirect();
        }
    }

    protected RedirectType checkConditions(String userAgentString, Map<String, String> deviceProperties,
            List<RedirectCondition> conditions) {
        log.debug("Checking conditions for redirect with " + userAgentString + " and device properties " + deviceProperties);
        if (conditions != null) {
            for (RedirectCondition condition : conditions) {
                boolean userAgentStringMatch = checkUserAgentStrings(userAgentString, condition.getUserAgentConditions());
                log.debug("UserAgentStringMatch : " + userAgentStringMatch);
                if (userAgentStringMatch == true) {
                    if (condition.getDeviceProperties() == null || condition.getDeviceProperties().isEmpty()) {
                        log.debug("UserAgentStringMatch and no device detection has been specified. Using Redirect");
                        return RedirectType.REDIRECT;
                    } else {
                        if (deviceProperties == null) {
                            log.debug("Conditional device properties exists, but no deviceProperties available. Using Browser Detection");
                            return RedirectType.NEEDDEVICEINFO;
                        } else {
                            boolean devicePropertiesMatch = checkDeviceProperties(deviceProperties,
                                    condition.getDeviceProperties());
                            if (devicePropertiesMatch == true) {
                                log.debug("UserAgentStringMatch and device properties match. Using Redirect");
                                return RedirectType.REDIRECT;
                            } else {
                                return RedirectType.NOREDIRECT;
                            }
                        }
                    }
                }
            }
        }
        return RedirectType.NOREDIRECT;
    }

    protected boolean checkDeviceProperties(Map<String, String> deviceProperties, List<DevicePropertyCondition> conditions) {
        if (conditions != null && !conditions.isEmpty()) {
            if (deviceProperties == null) {
                return false;
            }

            for (DevicePropertyCondition deviceProperty : conditions) {
                if (deviceProperties.containsKey(deviceProperty.getPropertyName())) {
                    if (!checkProperty(deviceProperties.get(deviceProperty.getPropertyName()), deviceProperty)) {
                        return false;
                    }
                } else {
                    return false;
                }
            }
        }
        return true; // if the deviceProperties are null or empty, then the condition is matched
    }

    protected boolean checkProperty(String propertyValue, DevicePropertyCondition deviceProperty) {
        if (deviceProperty.getGreaterThan() != null) {
            try {
                Float propertyValueFloat = Float.parseFloat(propertyValue);
                if (propertyValueFloat <= deviceProperty.getGreaterThan()) {
                    return false;
                }
            } catch (NumberFormatException nfe) {
                log.debug("Encountered a NumberFormatException trying to parse a property value (" + propertyValue
                        + ") which should be a float.");
                return false;
            }
        }

        if (deviceProperty.getLessThan() != null) {
            try {
                Float propertyValueFloat = Float.parseFloat(propertyValue);
                if (propertyValueFloat >= deviceProperty.getLessThan()) {
                    return false;
                }
            } catch (NumberFormatException nfe) {
                log.debug("Encountered a NumberFormatException trying to parse a property value (" + propertyValue
                        + ") which should be a float.");
                return false;
            }
        }

        if (deviceProperty.getEquals() != null) {
            if (!propertyValue.equals(deviceProperty.getEquals())) {
                return false;
            }
        }

        if (deviceProperty.getMatches() != null) {
            try {
                Pattern pattern = Pattern.compile(deviceProperty.getMatches());
                if (propertyValue == null) {
                    propertyValue = "";
                }
                Matcher matcher = pattern.matcher(propertyValue);
                if (!matcher.find()) {
                    return false;
                }

            } catch (PatternSyntaxException pse) {
                log.debug("Encountered a PatternSyntaxException trying to compile a device property pattern ("
                        + deviceProperty.getMatches() + ") which should be a valid string for a pattern.");
                return false;
            }
        }

        return true;
    }

    /**
     * Determines if the current user agent string passes the redirect conditions.
     *
     * @param userAgentString The user agent string
     * @param condition The conditions to check against
     * @return True if the user agent string passes the conditions
     */
    protected boolean checkUserAgentStrings(String userAgentString, UserAgentConditions condition) {
        // Check the black list first for user agent string matches, fail immediately if any match
        if (condition.getDoesNotContain() != null && !condition.getDoesNotContain().isEmpty()) {
            if (userAgentContains(userAgentString, condition.getDoesNotContain())) {
                return false;
            }
        }

        if (condition.getContains() != null && !condition.getContains().isEmpty()) {
            return userAgentContains(userAgentString, condition.getContains());
        } else {
            // if we have no contains or contains is empty, we can never match to this
            return false;
        }
    }

    protected boolean userAgentContains(String userAgentString, List<String> contains) {
        if (userAgentString != null) {
            for (String contain : contains) {
                Pattern pattern = Pattern.compile(contain);
                Matcher matcher = pattern.matcher(userAgentString);
                if (matcher.find()) {
                    return true;
                }
            }
        }
        return false;
    }
}
