/*
 * Copyright (c) 2006-2013 by Public Library of Science http://plos.org http://ambraproject.org
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.ambraproject.wombat.freemarker;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Collections2;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimap;
import freemarker.core.Environment;
import freemarker.template.SimpleHash;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Freemarker custom directive that writes out URL parameters based on the current request parameters.  A single
 * parameter's values can be replaced or added.
 * <p/>
 * There is one required parameter named params.  This should be a map of the request parameters.
 * <p/>
 * Optional parameters are name and value.  If present, the named parameter will be added (or all values
 * will be replaced if it is already present).
 * <p/>
 * Example usage:
 * <p/>
 * <a href="foo?<@replaceParams params=RequestParameters name="bar" value="baz" />">link</a>
 * <p/>
 * This will write out a URL beginning with foo and including all the parameters in the current request, with an
 * additional parameter "bar" added (or replaced) with the value "baz".
 */
public class ReplaceParametersDirective implements TemplateDirectiveModel {

  private static final String PARAMETER_MAP_KEY = "parameterMap";
  private static final String REPLACEMENTS_KEY = "replacements";
  private static final ImmutableSet<String> EXPECTED_KEYS = ImmutableSet.of(PARAMETER_MAP_KEY, REPLACEMENTS_KEY);

  public void execute(Environment environment, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
      throws TemplateException, IOException {
    if (!params.keySet().equals(EXPECTED_KEYS)) {
      throw new TemplateException("ReplaceParametersDirective requires keys: " + EXPECTED_KEYS, environment);
    }

    // I have no idea why freemarker feels the need to invent their own collection classes...
    SimpleHash parameterMap = (SimpleHash) params.get(PARAMETER_MAP_KEY);
    Multimap<String, TemplateModel> replacements = TemplateModelUtil.getAsMultimap((TemplateModel) params.get(REPLACEMENTS_KEY));
    Multimap<String, String> outputParams = replaceParameters(parameterMap, replacements);
    List<NameValuePair> paramList = new ArrayList<>(outputParams.size());
    for (String key : outputParams.keySet()) {
      for (String value : outputParams.get(key)) {
        paramList.add(new BasicNameValuePair(key, value));
      }
    }
    environment.getOut().write(URLEncodedUtils.format(paramList, "UTF-8"));
  }

  @VisibleForTesting
  static Multimap<String, String> replaceParameters(SimpleHash parameterMap,
                                                    Multimap<String, TemplateModel> replacements)
      throws TemplateException {
    Multimap<String, String> result = HashMultimap.create();

    // The map is passed in as a Map<String, String[]>, but Freemarker doesn't support generics
    // (and wraps the map in its own data structure).
    Map map = parameterMap.toMap();
    Iterator iter = map.entrySet().iterator();
    while (iter.hasNext()) {
      Map.Entry entry = (Map.Entry) iter.next();
      String[] values = (String[]) entry.getValue();
      for (String value : values) {
        result.put((String) entry.getKey(), value);
      }
    }

    for (Map.Entry<String, Collection<TemplateModel>> replacementEntry : replacements.asMap().entrySet()) {
      Collection<String> replacementValues = Collections2.transform(replacementEntry.getValue(), Object::toString);
      result.replaceValues(replacementEntry.getKey(), replacementValues);
    }

    return ImmutableSetMultimap.copyOf(result);
  }
}

@@ -1651,30 +1651,17 @@
 rs. 
- A single%0A * p
+P
 arameter
 's v
@@ -1660,17 +1660,18 @@
 eter
-'s
  values
+%0A *
  can
@@ -1702,35 +1702,33 @@
 * %3Cp
-/
 %3E%0A * 
-There is one required
+A required directive
  par
@@ -1744,21 +1744,22 @@
 med 
+%22
 param
-s.  This
+eterMap%22
  sho
@@ -1807,42 +1807,111 @@
 * %3Cp
-/
 %3E%0A * 
-Optional parameters are
+The other directive parameter is %22replacements%22, which is a FreeMarker hash of URL parameter
  name
+s
  and
@@ -1920,42 +1920,58 @@
 alue
-.  If present, the named parameter
+s to%0A * replace. All URL parameters given this way
  wil
@@ -1980,18 +1980,18 @@
 be added
+,
  
-(
 or all v
@@ -1995,19 +1995,16 @@
 l values
-%0A *
  will be
@@ -2041,16 +2041,174 @@
 sent
+.%0A * The value may be a sequence, in which case multiple URL parameters with the same name will be added (replacing any%0A * number of parameters with that name
 ).%0A * %3Cp
 /%3E%0A 
@@ -2199,25 +2199,24 @@
 name).%0A * %3Cp
-/
 %3E%0A * Example
@@ -2224,25 +2224,24 @@
 usage:%0A * %3Cp
-/
 %3E%0A * %3Ca href
@@ -2267,17 +2267,23 @@
 ms param
-s
+eterMap
 =Request
@@ -2297,30 +2297,35 @@
 ers 
-name=%22bar%22 value=
+replacements=%7B%22bar%22: 
 %22baz%22
+%7D
  /%3E%22
@@ -2339,17 +2339,16 @@
 a%3E%0A * %3Cp
-/
 %3E%0A * Thi

Update ReplaceParametersDirective Javadoc