/*
 * JBoss, Home of Professional Open Source
 * Copyright 2014, Red Hat, Inc. and individual contributors
 * 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.richfaces.application;

import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.faces.context.FacesContext;

/**
 * In RichFaces 4.5.0 a new EPVC was intorduced that exposed a bug in Mojarra (MyFaces is Ok).
 *
 * This Mojarra issue was resolved in the following versions:
 * * JAVASERVERFACES-3157 fixed in 2.1.28 and 2.1.27.redhat-9
 * * JAVASERVERFACES-3151 fixed in 2.2.6 and 2.2.5-jbossorg-3
 *
 * The public verifyJsfImplVersion method of this class can be used to inspect the runtime and ensure an
 * appropriately patched JSF implementation is present to work with the new EPVC
 *
 * @author <a href="http://community.jboss.org/people/bleathem">Brian Leathem</a>
 */
public class JsfVersionInspector {

    String versionString;

    class Version {
        final int major;
        final int minor;
        final int micro;
        final String qualifier;

        private Version(int major, int minor, int micro, String qualifier) {
            this.major = major;
            this.minor = minor;
            this.micro = micro;
            this.qualifier = qualifier;
        }
    }

    public JsfVersionInspector() {
        versionString = getPackageImplementationVersion();
        if (versionString == null) {
            // JBoss re-packaging of mojarra strips the implementation version
            // see: https://issues.jboss.org/browse/WFLY-3488
            // We'll fall back to parsing the LogStrings.properties file for the impl version
            versionString = getLogStringsImplementationVersion();
        }
    }

    /**
     * A constructor that skips the version detection for the purpose of writing unit tests
     */
    JsfVersionInspector(String versionString) {
        this.versionString = versionString;
    }

    /**
     * This method can be used to inspect the runtime and ensure an
     * appropriately patched JSF implementation is present to work with the new EPVC
     */
    public boolean verifyJsfImplVersion() {
        if (isMojarra()) {
            Version version = parseVersion(versionString);
            return testVersion(version);
        }

        return true;
    }

    public String getVersionString() {
        return versionString;
    }

    boolean testVersion(Version version) {
        if (version.major < 2) {
            return false;
        } else if (version.major > 2) {
            return true;  // Future proof!
        } else { // version.major == 2
            if (version.minor < 1) {
                return false;
            } else if (version.minor == 1) {
                // https://java.net/jira/browse/JAVASERVERFACES-3157 fixed in 2.1.28 and 2.1.27.redhat-9
                if (version.micro < 27) {
                    return false;
                } else if (version.micro == 27) {
                    return version.qualifier.equals("redhat-9");
                } else { // version.micro > 27
                    return true;
                }
            } else if (version.minor == 2) {
                // https://java.net/jira/browse/JAVASERVERFACES-3151 fixed in 2.2.6 and 2.2.5-jbossorg-3
                if (version.micro < 5) {
                    return false;
                } else if (version.micro == 5) {
                    return version.qualifier.equals("jbossorg-3");
                } else { // version.micro > 5
                    return true;
                }
            } else { // version.major > 3
                return true; // Future proof!
            }
        }
    }

    String getPackageImplementationVersion() {
        Package facesPackage = FacesContext.getCurrentInstance().getClass().getPackage();
//        if (facesPackage.getImplementationVersion() == null) {
//            facesPackage = Package.getPackage("javax.faces");
//        }
        return facesPackage.getImplementationVersion();
    }

    String getLogStringsImplementationVersion() {
        Properties prop = new Properties();
        InputStream in = getClass().getResourceAsStream("/com/sun/faces/LogStrings.properties");
        try {
            prop.load(in);
            in.close();
        } catch (IOException e) {
            throw new RuntimeException("Unable to load LogStrings.properties file to determine the mojarra version", e);
        }
        String jbossImplString = prop.getProperty("jsf.config.listener.version");

        return extractVersion(jbossImplString);
    }

    String extractVersion(String string) {
        Pattern pattern = Pattern.compile("([0-9][0-9\\.\\-a-z]*)");
        Matcher matcher = pattern.matcher(string);
        if (matcher.find()) {
            return matcher.group();
        } else {
            return "no match";
        }
    }

    Version parseVersion(String versionString) {
        String qualifier;
        String[] numbers;
        if (versionString.matches("[0-9][0-9\\.]*\\-[a-zA-Z0-9\\-]*")) { // eg. 2.1.4-jbossorg-1 or 2.1.6-SNAPSHOT
            // modifier is present
            String[] parts = versionString.split("-", 2);
            numbers = parts[0].split("\\.", 4);
            qualifier = parts[1];
        } else { // eg. 2.1.27.redhat-9
            numbers = versionString.split("\\.", 5);
            qualifier = numbers.length > 3 ? numbers[3] : "";
        }

        Version version;

        try {
            version = new Version(Integer.parseInt(numbers[0]), Integer.parseInt(numbers[1]), Integer.parseInt(numbers[2]), qualifier);
        } catch (NumberFormatException e) {
            throw new RuntimeException(MessageFormat.format("Error parsing detected JSF version string: {0} ", versionString), e);
        }

        return version;
    }

    boolean isMojarra() {
        String contextClassName = FacesContext.getCurrentInstance().getClass().getName();
        return ("com.sun.faces.context.FacesContextImpl".equals(contextClassName) ||
                "com.sun.faces.config.InitFacesContext".equals(contextClassName));
    }
}
