package com.indeed.proctor.service;

import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.indeed.proctor.service.var.ContextVariable;
import com.indeed.proctor.service.var.Identifier;
import com.indeed.proctor.service.var.PrefixVariable;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;

/**
 * Extracts variables from the HTTP Request according to the service configuration.
 */
public class Extractor {
    private final List<ContextVariable> contextList;
    private final List<Identifier> identifierList;

    public Extractor(final List<ContextVariable> contextList, final List<Identifier> identifierList) {
        this.contextList = contextList;
        this.identifierList = identifierList;
    }

    public RawParameters extract(final HttpServletRequest request) {
        return new RawParameters(
            extractAllVars(request, contextList, true),
            extractAllVars(request, identifierList, false),
            extractTest(request)
        );
    }

    /**
     * Parse the comma-separated tests into a List.
     *
     * If the test parameter appears multiple times, then only the first occurrence is used.
     */
    public List<String> extractTest(final HttpServletRequest request) {
        // TODO: might this come from post data?
        final String testParam = request.getParameter("test");
        if (testParam == null) {
            return null;
        } else {
            return Lists.newArrayList(Splitter.on(',').trimResults().omitEmptyStrings().split(testParam));
        }
    }

    /**
     * Extracts vars from either the context vars or the identifiers according to a configuration.
     *
     * @param request The HTTP request.
     * @param varList The list of variables to process.
     * @param isMissingError Whether or not a missing var constitutes an error. Identifiers are optional, so it is not
     *                       an error to omit them in the request.
     * @return A mapping of var name to string var value.
     */
    private Map<String, String> extractAllVars(
            final HttpServletRequest request, final List<? extends PrefixVariable> varList, boolean isMissingError) {

        final Map<String, String> ret = Maps.newHashMap();

        for (PrefixVariable var : varList) {
            final String varName = var.getVarName();
            final String value = var.getExtractor().extract(request);

            if (isMissingError && value == null) {
                // This is not allowed for this type of variable.
                throw new BadRequestException(String.format(
                        "Required variable '%s' not found where expected. See the service configuration.", varName));
            } else {
                ret.put(varName, value);
            }
        }

        return ret;
    }
}

@@ -60,24 +60,106 @@
 on.base.
-Splitter
+Joiner;%0Aimport com.google.common.base.Splitter;%0Aimport com.google.common.collect.Iterators
 ;%0Aimport
@@ -433,24 +433,137 @@
 letRequest;%0A
+import java.util.Collections;%0Aimport java.util.Enumeration;%0Aimport java.util.HashSet;%0Aimport java.util.Iterator;%0A
 import java.
@@ -594,16 +594,38 @@
 til.Map;
+%0Aimport java.util.Set;
 %0A%0A/**%0A *
@@ -735,16 +735,68 @@
 actor %7B%0A
+    private final String TEST_LIST_PARAM = %22test%22;%0A%0A
     priv
@@ -1149,32 +1149,102 @@
 uest request) %7B%0A
+        checkForUnrecognizedParameters(request.getParameterNames());%0A%0A
         return n
@@ -1420,32 +1420,1391 @@
       );%0A    %7D%0A%0A
+    /**%0A     * Checks that all the parameters in our request are valid. If the user passed in something we don't recognize,%0A     * we throw because they made an error in their request.%0A     */%0A    private void checkForUnrecognizedParameters(Enumeration%3CString%3E paramNames) %7B%0A        Set%3CString%3E paramSet = new HashSet%3CString%3E(Collections.list(paramNames));%0A%0A        // Iterate through all possible parameters and remove them from the set.%0A        // It doesn't matter if we remove optional or non-existent parameters. remove() returns false in that case.%0A%0A        paramSet.remove(TEST_LIST_PARAM);%0A%0A        Iterator%3CPrefixVariable%3E iter = Iterators.concat(contextList.iterator(), identifierList.iterator());%0A        while (iter.hasNext()) %7B%0A            PrefixVariable var = iter.next();%0A            if (var.getSource() == Source.QUERY) %7B%0A                paramSet.remove(var.getPrefix() + %22.%22 + var.getSourceKey());%0A                // If the parameter doesn't exist, then an error will be thrown during extraction later on.%0A            %7D%0A        %7D%0A%0A        // All that remains in paramSet are query parameters that our config had no knowledge of.%0A        if (!paramSet.isEmpty()) %7B%0A            throw new BadRequestException(String.format(%0A                    %22Unrecognized query parameters: %25s%22, Joiner.on(%22, %22).join(paramSet))%0A            );%0A        %7D%0A    %7D%0A%0A
     /**%0A     * P
@@ -3130,22 +3130,31 @@
 rameter(
-%22test%22
+TEST_LIST_PARAM
 );%0A     

Throw BadRequestException if some query params unrecognized.