/*
 * Copyright (C) 2007-2020 Crafter Software Corporation. All Rights Reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as published by
 * the Free Software Foundation.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.craftercms.engine.util;

import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.configuration2.Configuration;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.craftercms.commons.http.HttpUtils;
import org.craftercms.engine.model.SiteItem;
import org.craftercms.engine.scripting.impl.GroovyScript;
import org.craftercms.engine.service.context.SiteContext;
import org.craftercms.engine.util.spring.ApplicationContextAccessor;
import org.craftercms.engine.util.spring.security.profile.ProfileUser;
import org.kohsuke.groovy.sandbox.SandboxTransformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

/**
 * Utility methods for Groovy scripts.
 *
 * @author Alfonso Vásquez
 */
public class GroovyScriptUtils {

    public static final Logger GROOVY_SCRIPT_LOGGER = LoggerFactory.getLogger(GroovyScript.class);

    public static final String VARIABLE_APPLICATION = "application";
    public static final String VARIABLE_REQUEST = "request";
    public static final String VARIABLE_RESPONSE = "response";
    public static final String VARIABLE_PARAMS = "params";
    public static final String VARIABLE_PATH_VARS = "pathVars";
    public static final String VARIABLE_HEADERS = "headers";
    public static final String VARIABLE_COOKIES = "cookies";
    public static final String VARIABLE_SESSION = "session";
    public static final String VARIABLE_LOGGER = "logger";
    public static final String VARIABLE_LOCALE = "locale";
    @Deprecated
    public static final String VARIABLE_MODEL = "model";
    public static final String VARIABLE_TEMPLATE_MODEL = "templateModel";
    @Deprecated
    public static final String VARIABLE_CRAFTER_MODEL = "crafterModel";
    public static final String VARIABLE_CONTENT_MODEL = "contentModel";
    public static final String VARIABLE_AUTH = "authentication";
    public static final String VARIABLE_PROFILE = "profile";
    public static final String VARIABLE_AUTH_TOKEN = "authToken";
    public static final String VARIABLE_SITE_CONTEXT = "siteContext";
    public static final String VARIABLE_SITE_CONFIG = "siteConfig";
    public static final String VARIABLE_FILTER_CHAIN = "filterChain";
    public static final String VARIABLE_APPLICATION_CONTEXT = "applicationContext";

    private GroovyScriptUtils() {
    }

    public static void addRestScriptVariables(Map<String, Object> variables, HttpServletRequest request,
                                              HttpServletResponse response, ServletContext servletContext) {
        addCommonVariables(variables, request, response, servletContext);
        addSecurityVariables(variables);
    }

    public static void addSiteItemScriptVariables(Map<String, Object> variables, HttpServletRequest request,
                                                  HttpServletResponse response, ServletContext servletContext,
                                                  SiteItem item, Object templateModel) {
        addCommonVariables(variables, request, response, servletContext);
        addSecurityVariables(variables);
        addContentModelVariable(variables, item);
        addTemplateModelVariable(variables, templateModel);
    }


    public static void addControllerScriptVariables(Map<String, Object> variables, HttpServletRequest request,
                                                    HttpServletResponse response, ServletContext servletContext,
                                                    Object templateModel) {
        addCommonVariables(variables, request, response, servletContext);
        addSecurityVariables(variables);
        addTemplateModelVariable(variables, templateModel);
    }

    public static void addFilterScriptVariables(Map<String, Object> variables, HttpServletRequest request,
                                                HttpServletResponse response, ServletContext servletContext,
                                                FilterChain filterChain) {
        addCommonVariables(variables, request, response, servletContext);
        addSecurityVariables(variables);
        addFilterChainVariable(variables, filterChain);
    }

    public static void addJobScriptVariables(Map<String, Object> variables, ServletContext servletContext) {
        SiteContext siteContext = SiteContext.getCurrent();

        if (siteContext != null && siteContext.getApplicationContext() != null) {
            ApplicationContextAccessor appContext = new ApplicationContextAccessor();
            appContext.setApplicationContext(siteContext.getApplicationContext());

            variables.put(VARIABLE_APPLICATION_CONTEXT, appContext);
        }

        variables.put(VARIABLE_APPLICATION, servletContext);
        variables.put(VARIABLE_LOGGER, GROOVY_SCRIPT_LOGGER);

        addSiteContextVariable(variables);
        addSiteConfigVariable(variables);
    }

    public static CompilerConfiguration getCompilerConfiguration(boolean enableScriptSandbox) {
        CompilerConfiguration config = new CompilerConfiguration();
        if (enableScriptSandbox) {
            config.addCompilationCustomizers(new SandboxTransformer());
        }
        return config;
    }

    private static void addCommonVariables(Map<String, Object> variables, HttpServletRequest request,
                                           HttpServletResponse response, ServletContext servletContext) {
        variables.put(VARIABLE_APPLICATION, servletContext);
        variables.put(VARIABLE_REQUEST, request);
        variables.put(VARIABLE_RESPONSE, response);

        if (request != null) {
            variables.put(VARIABLE_PARAMS, HttpUtils.createRequestParamsMap(request));
            variables.put(VARIABLE_HEADERS, HttpUtils.createHeadersMap(request));
            variables.put(VARIABLE_COOKIES, HttpUtils.createCookiesMap(request));
            variables.put(VARIABLE_SESSION, request.getSession(false));
        } else {
            variables.put(VARIABLE_PARAMS, null);
            variables.put(VARIABLE_HEADERS, null);
            variables.put(VARIABLE_COOKIES, null);
            variables.put(VARIABLE_SESSION, null);
        }

        variables.put(VARIABLE_LOGGER, GROOVY_SCRIPT_LOGGER);
        variables.put(VARIABLE_LOCALE, LocaleContextHolder.getLocale());

        addSiteContextVariable(variables);
        addSiteConfigVariable(variables);
    }

    private static void addTemplateModelVariable(Map<String, Object> variables, Object model) {
        variables.put(VARIABLE_MODEL, model);
        variables.put(VARIABLE_TEMPLATE_MODEL, model);
    }

    private static void addContentModelVariable(Map<String, Object> variables, SiteItem siteItem) {
        variables.put(VARIABLE_CRAFTER_MODEL, siteItem);
        variables.put(VARIABLE_CONTENT_MODEL, siteItem);
    }

    private static void addSecurityVariables(Map<String, Object> variables) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        variables.put(VARIABLE_AUTH_TOKEN, auth);

        // for backwards compatibility with Profile ...

        variables.put(VARIABLE_AUTH, null);
        variables.put(VARIABLE_PROFILE, null);

        if (auth != null && auth.getPrincipal() instanceof ProfileUser) {
            ProfileUser details = (ProfileUser) auth.getPrincipal();
            variables.put(VARIABLE_AUTH, details.getAuthentication());
            variables.put(VARIABLE_PROFILE, details.getProfile());
        }
    }

    private static void addSiteContextVariable(Map<String, Object> variables) {
        variables.put(VARIABLE_SITE_CONTEXT, SiteContext.getCurrent());
    }

    private static void addSiteConfigVariable(Map<String, Object> variables) {
        SiteContext siteContext = SiteContext.getCurrent();
        Configuration config = null;

        if (siteContext != null) {
            config = siteContext.getConfig();
        }

        variables.put(VARIABLE_SITE_CONFIG, config);
    }

    private static void addFilterChainVariable(Map<String, Object> variables, FilterChain filterChain) {
        variables.put(VARIABLE_FILTER_CHAIN, filterChain);
    }

}