/*
 * Copyright (C) 2005 - 2011 Jaspersoft Corporation. All rights reserved.
 * http://www.jaspersoft.com.
 *
 * Unless you have purchased  a commercial license agreement from Jaspersoft,
 * the following license terms  apply:
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License  as
 * published by the Free Software Foundation, either version 3 of  the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero  General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public  License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package com.jaspersoft.jasperserver.api.engine.common.service.impl;

import com.jaspersoft.jasperserver.api.engine.common.service.ActionModelSupport;
import com.jaspersoft.jasperserver.api.metadata.user.domain.Role;
import com.jaspersoft.jasperserver.api.metadata.user.domain.User;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdom.Document;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextHolder;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Set;

/**
 * Created by IntelliJ IDEA.
 * User: Papanii
 * Date: Feb 10, 2010
 * Time: 5:12:54 PM
 */

/**
 * Singleton class for the primary navigation.
 */
public class NavigationActionModelSupport implements ActionModelSupport, InitializingBean {
    private static String action_model_context;
    private MessageSource messages;
    private static NavigationActionModelSupport _single_ton;
    private static final String ARGUMENT_DELIMITER = "@@";
    private static final String NO_TENANT_ID = "null";
    private static final String LICENSE_MANAGER = "com.jaspersoft.ji.license.LicenseManager";
    private static boolean isProVersion = false;
    private static Class clazz;
    private static Log log = LogFactory.getLog(NavigationActionModelSupport.class);

    private NavigationActionModelSupport(){
        log.info("Creating NavigationActionModelSupport singleton object.");
    }

    /**
     * Singleton getter method
     * @return singleton object
     */
    public static synchronized NavigationActionModelSupport getInstance(){
        return NavigationActionModelSupport._single_ton;
    }

    /**
     * External facing method called by all menu generating pages
     * @param context name of context listed in application context;
     * @return JSON string of menu created.
     */
    public String getClientActionModelDocument(String context){
        String json = "";
        if(context != null && context.length() > 0){
            action_model_context = context;
            json = getClientActionModelDocument();
        }else{
            log.error("Unable to create "+ context +" menu. Most likely programmer error");
            throw new RuntimeException("Unable to create menu.");
        }
        return json;
    }

    /**
     * @see com.jaspersoft.jasperserver.api.engine.common.service.ActionModelSupport#getClientActionModelDocument()
     */
    public String getClientActionModelDocument(){
        Document document =  ActionModelServiceImpl.getInstance().getActionModelMenu(action_model_context);
        String data;
        try {
            data = ActionModel.getInstance().generateClientActionModel(this, document);
        } catch (Exception e) {
            log.error("Unable to create navigation menu");
            e.printStackTrace();
            data = "";
        }
        return data;
    }


    /**
     * Helper method to determine if we are running the Pro or Enterprise edition
     * @return boolean indicating success..
     */
    public boolean isProVersion(){
        return isProVersion;
    }


    /**
     * Helper method to determine if we are running the community edition
     * @return boolean indicating success..
     */
    public boolean isCEVersion(){
        return !isProVersion();
    }

    public Class getLicenseManager() {
        if (clazz == null) {
            try {
                clazz = Class.forName(LICENSE_MANAGER);
            } catch (Exception e) {
                if(log.isDebugEnabled()){
                    log.info("This is not a pro version. Access is denied");
                }
            }
        }

        return clazz;
    }

    /**
     * Determines if feature is available in for user based on version
     * @param id feature id we are checking for
     * @return boolean indicating success
     */
    public boolean isAvailableProFeature(String id){
        //since pro only
        boolean isAvailable = false;
        if(isProVersion()){
            try {
                Class clazz = getLicenseManager();

                Field field = clazz.getField("ANALYSIS_FEATURES");
                String fid = (String)field.get(clazz);
                if(id != null){
                    if(id.equals(fid)){
                        Method method = clazz.getDeclaredMethod("isAnalysisFeatureSupported");
                        isAvailable  = ((Boolean) method.invoke(method.getDeclaringClass())).booleanValue();
                    }
                }
                
                field = clazz.getField("MULTITENANCY_FEATURES");
                fid = (String)field.get(clazz);

                if (id != null){
                    if(id.equals(fid)) {
                        Method method = clazz.getDeclaredMethod("isMultitenancyFeatureSupported");
                        isAvailable  = ((Boolean) method.invoke(method.getDeclaringClass())).booleanValue();
                    }
                }

                field = clazz.getField("DASHBOARD_FEATURES");
                fid = (String)field.get(clazz);

                if (id != null){
                    if(id.equals(fid)) {
                        Method method = clazz.getDeclaredMethod("isDashboardFeatureSupported");
                        isAvailable  = ((Boolean) method.invoke(method.getDeclaringClass())).booleanValue();
                    }
                }

                field = clazz.getField("ADHOC_FEATURES");
                fid = (String)field.get(clazz);

                if (id != null){
                    if(id.equals(fid)) {
                        Method method = clazz.getDeclaredMethod("isAdHocFeatureSupported");
                        isAvailable  = ((Boolean) method.invoke(method.getDeclaringClass())).booleanValue();
                    }
                }
            } catch (Exception e) {
                if(log.isDebugEnabled()){
                    log.info("This is not a pro version. Access is denied");
                }
            }
        }
        return isAvailable;
    }

    /**
     * Determines if feature is available in for user based on version
     * @return boolean indicating success
     */
    public boolean isMainFeaturesDisabled() {
        //since pro only
        boolean isDisabled = false;
        if(isProVersion()){
            try {
                Class clazz = getLicenseManager();

                Method method = clazz.getDeclaredMethod("isDashboardFeatureSupported");
                boolean isDashboardSupported  = ((Boolean) method.invoke(method.getDeclaringClass())).booleanValue();

                method = clazz.getDeclaredMethod("isAdHocFeatureSupported");
                boolean isAdHocSupported = ((Boolean) method.invoke(method.getDeclaringClass())).booleanValue();

                if (!isDashboardSupported && !isAdHocSupported) {
                    isDisabled = true;
                }
            } catch (Exception e) {
                if(log.isDebugEnabled()){
                    log.info("This is not a pro version. Access is denied");
                }
            }
        }
        return isDisabled;
    }

    /**
     * Determines if users count exceeded
     * @return boolean exceeded success
     */
    public boolean isUsersExceeded() {
        //since pro only
        boolean isExceeded = false;
        if(isProVersion()){
            try {
                Class clazz = getLicenseManager();
                Method method = clazz.getDeclaredMethod("checkUsers");
                isExceeded  = ((Boolean) method.invoke(method.getDeclaringClass())).booleanValue();
            } catch (Exception e) {
                if(log.isDebugEnabled()){
                    log.info("This is not a pro version. Access is denied");
                }
            }
        }
        return isExceeded;
    }

    /**
     * Determines if grace period has passed
     * @return boolean passed success
     */
    public boolean banUserRole() {
        //since pro only
        boolean banUser = false;
        if(isProVersion()){
            try {
                Class clazz = getLicenseManager();
                Method method = clazz.getDeclaredMethod("banUserRole");
                banUser  = ((Boolean) method.invoke(method.getDeclaringClass())).booleanValue();
            } catch (Exception e) {
                if(log.isDebugEnabled()){
                    log.info("This is not a pro version. Access is denied");
                }
            }
        }
        return banUser;
    }

    /**
     * Helper method used to retrieve the logged in users roles
     * @return set of roles
     */
    private Set getAuthenticationRoles(){
        SecurityContext secContext = SecurityContextHolder.getContext();
        Authentication authentication = secContext.getAuthentication();
        User principal = (User)authentication.getPrincipal();

        if(principal == null){
            log.error("Authentication is null.");
            throw new RuntimeException("[Error]: Authentication is null.");
        }
        return principal.getRoles();
    }


    /**
     * Used to check if user has an allowable role
     * @param allowedRoles roles that are allowed
     * @return boolean indicating a match was found
     */
    public boolean checkAuthenticationRoles(String allowedRoles){
        String roles[] = allowedRoles.split(ARGUMENT_DELIMITER);
        for(String role : roles){
            if(checkRole(role)){
                return true;
            }
        }
        return false;
    }


    /**
     * Checking a specific role
     * @param role role we are checking
     * @return boolean indicating success
     */
    private boolean checkRole(String role){
        String tenantIdArray[] = role.split("\\|");
        String expectedTenantId = (tenantIdArray.length > 1) ? tenantIdArray[1] : NO_TENANT_ID;
        String expectedRole = tenantIdArray[0];
        Set userRoles = getAuthenticationRoles();

        for(Object obj : userRoles){
            String roleName = ((Role)obj).getRoleName();
            String roleTenantId = ((Role)obj).getTenantId();
            if(roleTenantId == null){
                roleTenantId = NO_TENANT_ID;  
            }

            if(roleTenantId.equals(expectedTenantId)){
                if(roleName.equals(expectedRole)){
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * Used to get function id. //todo: talk Andrew about it.
     * @return
     */
    public int getFunctionId(){
        return 3;
    }


    /**
     * @see com.jaspersoft.jasperserver.api.engine.common.service.ActionModelSupport#getMessage(String)
     * @param label i18n code
     */
    public String getMessage(String label){
        return messages.getMessage(label, null,LocaleContextHolder.getLocale());
    }


    /*
     * Spring setters and getters for bean
     */
    public void setDefaultMessageSource(MessageSource messages) {
        this.messages = messages;
    }

    public MessageSource getDefaultMessageSource() {
        return messages;
    }

    public void afterPropertiesSet() throws Exception {
        _single_ton = this;
    }

    public static void setProVersion(boolean proVersion) {
        isProVersion = proVersion;
    }
}
