/*
 * Copyright 2014 Daniel Nilsson
 * 
 * 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.0
 * 
 * Unless 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 com.github.dannil.scbjavaclient.utility;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;

/**
 * <p>Class which is responsible for constructing the query which should be sent to the
 * SCB API.</p>
 * 
 * @author Daniel Nilsson
 */
public final class QueryBuilder {

	/**
	 * <p>Private constructor to prevent instantiation.</p>
	 */
	private QueryBuilder() {

	}

	/**
	 * <p>Filter out the specified value from the input collection.</p>
	 * 
	 * @param collection
	 *            the <code>Collection</code> to filter
	 * @param value
	 *            the value to remove from the <code>Collection</code>
	 * @return a <code>Collection</code> with the specified value removed
	 */
	private static List<?> filterValue(Collection<?> collection, Object value) {
		List<Object> filteredValues = new ArrayList<>();
		for (Object o : collection) {
			if (!Objects.equals(o, value)) {
				filteredValues.add(o);
			}
		}
		return filteredValues;
	}

	/**
	 * <p>Constructs a query which matches the format the SCB API expects. This method
	 * performs two distinct steps:</p>
	 * 
	 * <h1>1. Filter out the null keys</h1>
	 * 
	 * <p>If a key (such as region or year) is defined and all of this key's values are
	 * defined as null it means that all data for this key should be fetched (such as
	 * fetching the data for all available years). By not sending this key at all to the
	 * SCB API it recognizes that it should respond with all data corresponding to this
	 * key.</p>
	 * 
	 * <h1>2. Construct the query</h1>
	 * 
	 * <p>For every key in the input map, it creates an entry for this key. It then writes
	 * all of the values corresponding with this key (defined somewhere by using the
	 * put-method on the input map) into this entry. For example, if the input for the key
	 * Tid (Swedish for year) contains the years (values) 2011 and 2012, the resulting
	 * entry looks like this:</p>
	 * 
	 * <p>{ "code": "Tid", "selection": { "filter": "item", "values": [ "2011", "2012" ] }
	 * }</p>
	 * 
	 * <p>The method works on the principle of pasting all these blocks together in an
	 * orderly fashion to conform to the SCB API specification.</p>
	 * 
	 * @param inputMap
	 *            the input map which contains the keys and the values for every key
	 * @return a string which can be sent as the payload to the SCB API
	 */
	public static String build(Map<String, Collection<?>> inputMap) {
		// 1: Filter out null values
		Map<String, List<?>> filteredMap = new HashMap<>();

		for (Entry<String, Collection<?>> entry : inputMap.entrySet()) {
			if (entry.getValue() != null) {
				filteredMap.put(entry.getKey(), filterValue(entry.getValue(), null));
			}
		}

		// Approximate a good initial capacity for the string builder
		int size = Math.max(44 + (80 * filteredMap.size()), 256);
		StringBuilder builder = new StringBuilder(size);

		// 2: Construct the query
		builder.append("{\"query\": [");
		int i = 0;
		for (Entry<String, List<?>> entry : filteredMap.entrySet()) {
			builder.append("{\"code\": \"");
			builder.append(entry.getKey());
			builder.append("\", \"selection\": {\"filter\": \"item\", \"values\": [");
			List<?> values = entry.getValue();
			for (int j = 0; j < values.size(); j++) {
				builder.append("\"" + values.get(j) + "\"");
				if (j != values.size() - 1) {
					builder.append(',');
				}
			}
			builder.append("]}}");
			if (i != filteredMap.size() - 1) {
				builder.append(',');
			}
			i++;
		}
		builder.append("],\"response\": {\"format\": \"json\"}}");

		return builder.toString();
	}
}

@@ -714,24 +714,51 @@
 il.HashMap;%0A
+import java.util.Iterator;%0A
 import java.
@@ -1450,23 +1450,27 @@
 e static
+ %3CT%3E
  List%3C
-?
+T
 %3E filter
@@ -1486,17 +1486,17 @@
 lection%3C
-?
+T
 %3E collec
@@ -1501,22 +1501,17 @@
 ection, 
-Object
+T
  value) 
@@ -1519,22 +1519,17 @@
 %0A%09%09List%3C
-Object
+T
 %3E filter
@@ -1569,16 +1569,15 @@
 or (
-Object o
+T entry
  : c
@@ -1612,17 +1612,21 @@
 .equals(
-o
+entry
 , value)
@@ -1652,17 +1652,21 @@
 ues.add(
-o
+entry
 );%0A%09%09%09%7D%0A
@@ -3668,26 +3668,22 @@
 ;%0A%09%09
-int i = 0;%0A%09%09for (
+for (Iterator%3C
 Entr
@@ -3700,24 +3700,27 @@
 List%3C?%3E%3E
+%3E
  entr
-y :
+ies =
  filtere
@@ -3730,27 +3730,108 @@
 p.entrySet()
-) %7B
+.iterator(); entries.hasNext();) %7B%0A%09%09%09Entry%3CString, List%3C?%3E%3E entry = entries.next();
 %0A%09%09%09builder.
@@ -4208,35 +4208,25 @@
 if (
-i != filteredMap.size() - 1
+entries.hasNext()
 ) %7B%0A
@@ -4259,16 +4259,8 @@
 %09%09%7D%0A
-%09%09%09i++;%0A
 %09%09%7D%0A
@@ -4353,10 +4353,11 @@
 g();%0A%09%7D%0A
+%0A
 %7D%0A

Change to use iterator when looping