/*
 * 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.war.cascade.token;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import com.jaspersoft.jasperserver.api.engine.common.service.BuiltInParameterProvider;
import com.jaspersoft.jasperserver.api.metadata.jasperreports.domain.ReportDataSource;

/**
 * FilterCore
 * @author jwhang
 * @version 1.2
 */

public class FilterCore implements FilterResolver {
    public final static String STANDARD_PARAMETER_START = "$P{";
    public final static String INCLUDE_PARAMETER_START = "$P!{";
    public final static String DYNAMIC_QUERY_PARAMETER_START = "$X{";
    public final static String PARAMETER_END = "}";

    private static final byte NOTHING = 0;
    private static final byte STANDARD = 1;
    private static final byte INCLUDE = 2;
    private static final byte DYNAMIC = 3;
    private static Logger log = Logger.getLogger(FilterCore.class);

    List builtInParameterProviders;

    public List getBuiltInParameterProviders() {
        return builtInParameterProviders;
    }

    public void setBuiltInParameterProviders(List builtInParameterProviders) {
        this.builtInParameterProviders = builtInParameterProviders;
    }

    /* (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.war.cascade.token.FilterResolver#hasParameters(java.lang.String)
	 */
    public boolean hasParameters(String queryString, Map parameters) {
        return hasParameters(queryString, 0);
    }

    public boolean hasParameters(String queryString, int start) {
        Parameter p = nextParameter(queryString, start);
        return p.type != NOTHING;
    }

    private Parameter nextParameter(String queryString, int start) {

        Parameter p = new Parameter();

        if (start < 0 || start > queryString.length() - 1) {
            return p;
        }

        int nextPosition = queryString.indexOf(STANDARD_PARAMETER_START, start);
        if (nextPosition > -1) {
            p.type = STANDARD;
            p.position = nextPosition;
            p.endPosition = queryString.indexOf(PARAMETER_END, p.position);

            if (p.endPosition > -1) {
                p.name = queryString.substring(nextPosition + STANDARD_PARAMETER_START.length(), p.endPosition).trim();
            }
        }

        int nextTest = queryString.indexOf(INCLUDE_PARAMETER_START, start);
        if (nextTest > -1 && ((nextPosition > -1 && nextTest < nextPosition) || nextPosition == -1)) {
            nextPosition = nextTest;
            p.type = INCLUDE;
            p.position = nextTest;
            p.endPosition = queryString.indexOf(PARAMETER_END, p.position);

            if (p.endPosition > -1) {
                p.name = queryString.substring(nextPosition + INCLUDE_PARAMETER_START.length(), p.endPosition).trim();
            }
        }

        nextTest = queryString.indexOf(DYNAMIC_QUERY_PARAMETER_START, start);
        if (nextTest > -1 && ((nextPosition > -1 && nextTest < nextPosition) || nextPosition == -1)) {
            nextPosition = nextTest;
            p.type = DYNAMIC;
            p.position = nextTest;
            p.endPosition = queryString.indexOf(PARAMETER_END, p.position);

            // TODO deal with pluggable functions, not just IN and NOTIN
            if (p.endPosition > -1) {
                String parameterContents = queryString.substring(nextPosition + DYNAMIC_QUERY_PARAMETER_START.length(), p.endPosition).trim();

                String[] parts = parameterContents.split(",");

                // need to get the 3rd parameter from $X{IN, ShipCountry, CountryList}
                if (parts.length == 3 && (
                        parts[0].trim().equalsIgnoreCase("IN") ||
                        parts[0].trim().equalsIgnoreCase("NOTIN") ||
                        parts[0].trim().equalsIgnoreCase("EQUAL") ||
                        parts[0].trim().equalsIgnoreCase("NOTEQUAL"))) {
                    p.name = parts[2].trim();
                } else {
                    throw new RuntimeException("Unknown $X parameter function in query: " + queryString);
                }
            }
        }

        return p;
    }

    /* (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.war.cascade.token.FilterResolver#getParameters(java.lang.String)
	 */
    public List<Parameter> getParameters(String queryString) {
        List<Parameter> parameters = new ArrayList<Parameter>();

        int index = 0;
        while (index < queryString.length() && hasParameters(queryString, index)) {
            Parameter p = nextParameter(queryString, index);

            if (p.type == NOTHING) {
                break;
            }

            index = p.endPosition;

            // skip over escaped parameters
            if (index > 0 && queryString.charAt(index - 1) == '$') {
                index++;
                continue;
            }

            // we have a parameter!
            parameters.add(p);
            index++;
        }

        return parameters;
    }

    /* (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.war.cascade.token.FilterResolver#resolveParameters(java.lang.String, java.util.Map)
	 */
    public Object getCacheKey(String queryString, Map providedParameters) {
    	Map resolvedParameters = resolveParameters(queryString, providedParameters);
    	if (resolvedParameters != null) {
            return queryString + resolvedParameters;
    	}
    	return null;
    }
    
    /**
     * for queryString and params, try to resolve params
     * @return map of actual params resolved
     */
    public Map resolveParameters(String queryString, Map providedParameters) {
        // get the set of parameter names from the query
        List<Parameter> queryParameters = getParameters(queryString);

        Set<String> missingQueryParameterNames = new HashSet<String>(queryParameters.size());
        Map resolvedParams = new HashMap();
        for (Parameter p : queryParameters) {
        	Object value = providedParameters.get(p.name);
            // see whether parameter is present in map, even if it's null #18608
            if (!providedParameters.containsKey(p.name)) {
                // record the parameters we are missing
                missingQueryParameterNames.add(p.name);
            } else {
            	resolvedParams.put(p.name, value);
            }
        }
        if (log.isDebugEnabled()) {
        	log.debug("provided params: " + providedParameters);
        	log.debug("resolved params: " + resolvedParams);
        	log.debug("missing params: " + missingQueryParameterNames);
        }

        // See whether the missing parameters are built in, and fill them in
        if (missingQueryParameterNames.size() > 0) { 
	        Map builtInParams = resolveBuiltInParameters(missingQueryParameterNames);
	        if (log.isDebugEnabled()) {
	        	log.debug("built in params found: " + builtInParams);
	        }
	        if (builtInParams == null) {
	        	resolvedParams = null;
	        } else {
	        	resolvedParams.putAll(builtInParams);
	        }
        }
        
        if (log.isDebugEnabled() && missingQueryParameterNames.size() > 0) {
        	log.debug("final resolved params: " + resolvedParams);
        }
        return resolvedParams;
    }

    /**
     * fill in missing param values using the BuiltInParameterProvider impls
     * @param missingQueryParameterNames list of params with missing values
     * @return map of resolved params if you could resolve all params, or null if you couldn't
     */
    protected Map resolveBuiltInParameters(Set<String> missingQueryParameterNames) {
        Map resolvedParams = new HashMap();
		if (missingQueryParameterNames.size() > 0) {
			// copy name set
			missingQueryParameterNames = new HashSet<String>(missingQueryParameterNames);
            for (Object o : getBuiltInParameterProviders() ) {
                BuiltInParameterProvider pProvider = (BuiltInParameterProvider) o;

                Set<String> currentMissingQueryParameterNames = new HashSet<String>(missingQueryParameterNames);
                for (String name : currentMissingQueryParameterNames) {
                    Object[] aResult = pProvider.getParameter(null, null, null, name);
                    if (aResult != null) {
                    	resolvedParams.put(name, aResult[1]);
                    	missingQueryParameterNames.remove(name);
                    }
                }
            }
        }
    	return missingQueryParameterNames.size() > 0 ? null : resolvedParams;
	}

    class Parameter {

        byte type = NOTHING;
        String name;
        int position;
        int endPosition;
    }

	/* (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.war.cascade.token.FilterResolver#paramTestNeedsDataSourceInit()
	 */
	public boolean paramTestNeedsDataSourceInit(ReportDataSource dataSource) {
		return false;
	}
}