/*
 * Copyright 1999-2010 Luca Garulli (l.garulli--at--orientechnologies.com)
 *
 * 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.orientechnologies.orient.core.fetch;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.exception.ORecordNotFoundException;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper;

/**
 * Helper class for fetching.
 * 
 * @author Luca Garulli
 * 
 */
public class OFetchHelper {
	private static final String	ANY_FIELD	= "*";

	public static Map<String, Integer> buildFetchPlan(final String iFetchPlan) {
		final Map<String, Integer> fetchPlan;

		if (iFetchPlan != null) {
			// CHECK IF THERE IS SOME FETCH-DEPTH
			final List<String> planParts = OStringSerializerHelper.split(iFetchPlan, ' ');
			if (planParts.size() > 1) {
				fetchPlan = new HashMap<String, Integer>();

				List<String> parts;
				for (String planPart : planParts) {
					parts = OStringSerializerHelper.split(planPart, ':');
					fetchPlan.put(parts.get(0), Integer.parseInt(parts.get(1)));
				}
			} else {
				fetchPlan = null;
			}
		} else
			fetchPlan = null;

		return fetchPlan;
	}

	@SuppressWarnings("unchecked")
	public static void fetch(final ODocument iRootRecord, final Object iUserObject, final Map<String, Integer> iFetchPlan,
			final String iCurrentField, final int iCurrentLevel, final int iMaxFetch, final OFetchListener iListener) {

		if (iMaxFetch > -1 && iListener.size() >= iMaxFetch)
			// MAX FETCH SIZE REACHED: STOP TO FETCH AT ALL
			return;

		Object fieldValue;
		Integer depthLevel;
		final Integer anyFieldDepthLevel = iFetchPlan != null ? iFetchPlan.get(ANY_FIELD) : -1;
		try {
			// BROWSE ALL THE DOCUMENT'S FIELDS
			for (String fieldName : iRootRecord.fieldNames()) {
				fieldValue = iRootRecord.field(fieldName);

				if (fieldValue == null
						|| !(fieldValue instanceof ODocument)
						&& (!(fieldValue instanceof Collection<?>) || (((Collection<?>) fieldValue).size() > 0 && !(((Collection<?>) fieldValue)
								.iterator().next() instanceof ODocument)))
						&& (!(fieldValue instanceof Map<?, ?>) || (((Map<?, ?>) fieldValue).size() > 0 && !(((Map<?, ?>) fieldValue).values()
								.iterator().next() instanceof ODocument))))
					// NULL NEITHER LINK, NOR COLLECTION OF LINKS OR MAP OF LINKS
					continue;

				depthLevel = getDepthLevel(iRootRecord, iFetchPlan, fieldName);

				if (depthLevel == null)
					// NO SPECIFIED: ASSIGN DEFAULT LEVEL TAKEN FROM * WILDCARD IF ANY
					depthLevel = anyFieldDepthLevel;

				if (depthLevel == 0)
					// NO FETCH THIS FIELD PLEASE
					continue;

				if (depthLevel > -1 && iCurrentLevel >= depthLevel)
					// MAX DEPTH REACHED: STOP TO FETCH THIS FIELD
					continue;

				Object userObject;
				if (fieldValue instanceof ODocument) {
					final ODocument linked = (ODocument) fieldValue;
					try {
						userObject = iListener.fetchLinked(iRootRecord, iUserObject, fieldName, linked);
						if (userObject != null)
							// GO RECURSIVELY
							fetch(linked, userObject, iFetchPlan, fieldName, iCurrentLevel + 1, iMaxFetch, iListener);
					} catch (ORecordNotFoundException e) {
						OLogManager.instance().error(null, "Linked record %s was not found", linked);
					}

				} else if (fieldValue instanceof Collection<?>) {
					final Collection<ODocument> linked = (Collection<ODocument>) fieldValue;
					userObject = iListener.fetchLinked(iRootRecord, iUserObject, fieldName, linked);
					if (userObject != null)
						for (ODocument d : (Collection<ODocument>) userObject) {
							// GO RECURSIVELY
							if (d != null)
								fetch(d, d, iFetchPlan, fieldName, iCurrentLevel + 1, iMaxFetch, iListener);
						}
				} else if (fieldValue instanceof Map<?, ?>) {
					final Map<String, ODocument> linked = (Map<String, ODocument>) fieldValue;
					userObject = iListener.fetchLinked(iRootRecord, iUserObject, fieldName, linked);
					if (userObject != null)
						for (ODocument d : ((Map<String, ODocument>) userObject).values()) {
							// GO RECURSIVELY
							if (d != null)
								fetch(d, d, iFetchPlan, fieldName, iCurrentLevel + 1, iMaxFetch, iListener);
						}
				}

				if (iMaxFetch > -1 && iListener.size() >= iMaxFetch)
					// MAX FETCH SIZE REACHED: STOP TO FETCH AT ALL
					return;
			}
		} catch (Exception e) {
			e.printStackTrace();
			OLogManager.instance().error(null, "Fetching error on record %s", e, iRootRecord.getIdentity());
		}
	}

	public static boolean isFetchPlanValid(final String iFetchPlan) {

		if (iFetchPlan != null && iFetchPlan.length() > 0) {
			// CHECK IF THERE IS SOME FETCH-DEPTH
			final List<String> planParts = OStringSerializerHelper.split(iFetchPlan, ' ');
			if (planParts.size() > 0) {
				for (String planPart : planParts) {
					final List<String> parts = OStringSerializerHelper.split(planPart, ':');
					if (parts.size() != 2) {
						return false;
					}
				}
			} else {
				return false;
			}
		}

		return true;

	}

	private static Integer getDepthLevel(final ODocument doc, final Map<String, Integer> iFetchPlan, final String iFieldName) {
		Integer depthLevel;

		if (iFetchPlan != null) {
			// GET THE FETCH PLAN FOR THE GENERIC FIELD IF SPECIFIED
			depthLevel = iFetchPlan.get(iFieldName);

			if (depthLevel == null) {
				OClass cls = doc.getSchemaClass();
				while (cls != null && depthLevel == null) {
					depthLevel = iFetchPlan.get(cls.getName() + "." + iFieldName);

					if (depthLevel == null)
						cls = cls.getSuperClass();
				}
			}
		} else
			// INFINITE
			depthLevel = -1;

		return depthLevel;
	}
}

@@ -1619,17 +1619,17 @@
 ize() %3E 
-1
+0
 ) %7B%0D%0A%09%09%09

Patch by Luca Molino to fix a bug on fetch plan building