/*-
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.sling.query.resource.jcr.query;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.sling.query.resource.jcr.JcrOperator;
import org.apache.sling.query.resource.jcr.JcrTypeResolver;
import org.apache.sling.query.resource.jcr.query.Formula.Operator;
import org.apache.sling.query.selector.parser.Attribute;
import org.apache.sling.query.selector.parser.SelectorSegment;

public class JcrQueryBuilder {

	private final JcrTypeResolver typeResolver;

	public JcrQueryBuilder(JcrTypeResolver typeResolver) {
		this.typeResolver = typeResolver;
	}

	public String buildQuery(List<SelectorSegment> segments, String rootPath) {
		StringBuilder query = new StringBuilder();
		query.append("SELECT * FROM [");
		query.append(findPrimaryType(segments));
		query.append("]");
		query.append(" AS s");

		String conditionString = getConditionString(segments, rootPath);
		if (StringUtils.isNotBlank(conditionString)) {
			query.append(" WHERE ").append(conditionString);
		}
		return query.toString();
	}

	private String getConditionString(List<SelectorSegment> segments, String rootPath) {
		Formula formula = prepareAlternativeConditions(segments);
		if (StringUtils.isNotBlank(rootPath) && !"/".equals(rootPath)) {
			List<Term> conditions = new ArrayList<Term>();
			conditions.add(new Atomic(String.format("ISDESCENDANTNODE('%s')", rootPath)));
			if (formula != null) {
				conditions.add(formula);
			}
			formula = new Formula(Operator.AND, conditions);
		}
		if (formula == null) {
			return null;
		} else {
			return formula.buildString();
		}
	}

	private String findPrimaryType(List<SelectorSegment> segments) {
		String result = null;
		for (SelectorSegment s : segments) {
			String type = s.getType();
			if (!typeResolver.isJcrType(type)) {
				continue;
			}
			if (result == null) {
				result = type;
			} else if (typeResolver.isSubtype(type, result)) {
				result = type;
			} else if (!typeResolver.isSubtype(result, type)) {
				result = "nt:base";
			}
		}
		if (result == null) {
			result = "nt:base";
		}
		return result;
	}

	private static Formula prepareAlternativeConditions(List<SelectorSegment> segments) {
		List<Term> list = new ArrayList<>();
		for (SelectorSegment segment : segments) {
			Formula conditions = prepareSegmentConditions(segment.getType(), segment.getName(),
					segment.getAttributes());
			if (conditions != null) {
				list.add(conditions);
			}
		}
		if (list.isEmpty()) {
			return null;
		} else {
			return new Formula(Operator.OR, list);
		}
	}

	private static Formula prepareSegmentConditions(String resourceType, String resourceName,
			List<Attribute> attributes) {
		List<Term> conditions = new ArrayList<>();
		if (StringUtils.isNotBlank(resourceType) && !StringUtils.contains(resourceType, ':')) {
			conditions.add(new Atomic(String.format("s.[sling:resourceType] = '%s'", resourceType)));
		}
		if (StringUtils.isNotBlank(resourceName)) {
			conditions.add(new Atomic(String.format("NAME(s) = '%s'", resourceName)));
		}
		if (attributes != null) {
			for (Attribute a : attributes) {
				String attributeCondition = getAttributeCondition(a);
				if (StringUtils.isNotBlank(attributeCondition)) {
					conditions.add(new Atomic(attributeCondition));
				}
			}
		}
		if (conditions.isEmpty()) {
			return null;
		} else {
			return new Formula(Operator.AND, conditions);
		}
	}

	private static String getAttributeCondition(Attribute attribute) {
		if (attribute.getKey().contains("/")) {
			return null;
		}

		JcrOperator operator = JcrOperator.getSelectorOperator(attribute.getOperator());
		String value = StringUtils.replace(attribute.getValue(), "'", "''");
		return operator.getJcrQueryFragment(attribute.getKey(), value);
	}
}
