package eu.dnetlib.functionality.cql.parse;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.queryparser.classic.QueryParserBase;

public class TermNode extends Node {

	public static final String dnetDefaultField = "__all";

	private String field;
	private Relation rel;
	private String value;
	private Map<String, List<String>> options = Maps.newHashMap();
	private BiMap<String, String> aliases = HashBiMap.create();
	private Map<String, String> weights = Maps.newHashMap();
	private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");

	public TermNode(final String field, final Relation rel, final String value) {
		this.field = field;
		this.rel = rel;
		this.value = value;
		simpleDateFormat.setLenient(false);
	}

	public TermNode(final String field, final Relation rel, final String value, final Map<String, List<String>> options, final BiMap<String, String> aliases,
			final Map<String, String> weights) {
		this(field, rel, value);
		this.options = options;
		this.aliases = aliases;
		this.weights = weights;
	}

	@Override
	public String toString() {
		return TermNode.class.getSimpleName() + "(" + field + " " + rel + " " + value + ")";
	}

	@Override
	public String toLucene() {
		//StringTokenizer termTokenizer = new StringTokenizer(value, " ");
		//StringTokenizer weightTokenizer = new StringTokenizer(value, " ");
		final Iterable<String> termTokenizer = Splitter.on(" ").omitEmptyStrings().split(value);
		final Iterable<String> weightTokenizer = Splitter.on(" ").omitEmptyStrings().split(value);
		switch (rel) {
		case EXACT:
			final String lucene = getFieldName() + ":" + "\"" + value + "\"";
			return StringUtils.isNotBlank(weight()) ? lucene + weight() + " " + expand(value) : lucene;
		case EQUAL:
		case ALL:
			return "(" + handleTokens(termTokenizer, "AND") + " " + expandTokens(weightTokenizer) + ")";
		case ANY:
			return "(" + handleTokens(termTokenizer, "OR") + " " + expandTokens(weightTokenizer) + ")";
		case NOT:
			return "NOT " + field + ":" + "\"" + value + "\"";
		case LT:
			if (isDate(value)) {
				value = checkDate(value);
			}
			return field + ":" + "{* TO " + value + "}" + weight();
		case GT:
			if (isDate(value)) {
				value = checkDate(value);
			}
			return field + ":" + "{" + value + " TO *}" + weight();
		case LTE:
			if (isDate(value)) {
				value = checkDate(value);
			}
			return field + ":" + "[* TO " + value + "]" + weight();
		case GTE:
			if (isDate(value)) {
				value = checkDate(value);
			}
			return field + ":" + "[" + value + " TO *]" + weight();
		case WITHIN:
			String lowerValue = value.split(" ")[0];
			String upperValue = value.split(" ")[1];
			if (isDate(lowerValue)) {
				lowerValue = checkDate(lowerValue);
			}
			if (isDate(upperValue)) {
				upperValue = checkDate(upperValue);
			}
			return field + ":[" + lowerValue + " TO " + upperValue + "]" + weight();
		default:
			throw new RuntimeException("unable to serialize: " + toString());
		}
	}

	private String getFieldName() {
		return aliases.get(field) != null ? aliases.get(field) : field;
	}

	private String weight() {
		return (weights != null) && (weights.get(field) != null) ? "^" + weights.get(field) : "";
	}

	private String expandTokens(final Iterable<String> tokens) {
		return Joiner.on("").skipNulls().join(Iterables.transform(tokens, new Function<String, String>() {
			@Override
			public String apply(final String s) {
				if (field.equals(dnetDefaultField.toLowerCase()) || field.equals(dnetDefaultField.toLowerCase())) {
					return expand(s);
				}
				return null;
			}
		})).trim();
	}

	private String expand(final String token) {
		String ret = "";
		if (!weights.keySet().contains(field)) {
			for (Entry<String, String> e : weights.entrySet()) {
				ret += e.getKey() + ":\"" + checkEscaping(token) + "\"^" + e.getValue() + " ";
			}
		}
		return ret.trim();
	}

	private String handleTokens(final Iterable<String> tokens, final String op) {
		final String separator = " " + op + " ";
		return Joiner.on(separator).join(Iterables.transform(tokens, new Function<String, String>() {
			@Override
			public String apply(final String s) {
				return field + ":" + checkEscaping(s) + weight();
			}
		})).trim();
	}

	private String checkEscaping(String token) {
		boolean isWildcard = token.contains("*") || token.contains("?");
		boolean isWildcardEnabled = ((options.get("wildcard") != null) && options.get("wildcard").contains("true")) || token.equals("*");

		if (!(isWildcard & isWildcardEnabled)) {
			token = QueryParserBase.escape(token);
		}
		return token;
	}

	private boolean isDate(final String aPossibleDate) {
		try {
			simpleDateFormat.parse(aPossibleDate);
		} catch (ParseException pe) {
			return false;
		}
		return true;
	}

	private String checkDate(final String date) {
		if (!date.endsWith("Z")) return date + "T00:00:00Z";
		return date;
	}

	public String getField() {
		return field;
	}

	public Relation getRel() {
		return rel;
	}

	public String getValue() {
		return value;
	}

}
