package eu.dnetlib.pace.model;

import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.GeneratedMessage;

import eu.dnetlib.data.proto.FieldTypeProtos.StructuredProperty;

public class ProtoDocumentBuilder extends DocumentBuilder {

	public static MapDocument newInstance(final String id, final GeneratedMessage proto, final List<FieldDef> fields) {
		final Map<String, FieldListImpl> fieldMap = new ProtoDocumentBuilder().generateFieldMap(proto, fields);
		return newInstance(id, fieldMap);
	}

	private Map<String, FieldListImpl> generateFieldMap(final GeneratedMessage proto, final List<FieldDef> fields) {
		Map<String, FieldListImpl> fieldMap = Maps.newHashMap();

		for (FieldDef fd : fields) {
			fieldMap.put(fd.getName(), processPath(fd.getName(), proto, fd.getPathList()));
		}

		return fieldMap;
	}

	public FieldListImpl processPath(final String name, final GeneratedMessage proto, final List<String> list) {

		final FieldListImpl response = new FieldListImpl(name);

		if (list.isEmpty()) { throw new RuntimeException("ProtoBuf navigation path is empty"); }

		FieldDescriptor desc = proto.getDescriptorForType().findFieldByName(list.get(0));
		if (desc != null) {
			if (desc.isRepeated()) {
				int count = proto.getRepeatedFieldCount(desc);
				for (int i = 0; i < count; i++) {
					Object field = proto.getRepeatedField(desc, i);
					if (!mustSkip(desc, field)) {
						response.addAll(generateFields(name, field, list));
					}
				}
			} else {
				Object field = proto.getField(desc);
				if (!mustSkip(desc, field)) {
					response.addAll(generateFields(name, field, list));
				}
			}
		} else {
			throw new RuntimeException("Invalid protobuf path (field not found): " + StringUtils.join(list, ">"));
		}

		return response;
	}

	private boolean mustSkip(final FieldDescriptor desc, final Object field) {
		if (desc.getName().equals("title") && !((StructuredProperty) field).getQualifier().getClassid().equals("main title")) { return true; }

		if (desc.getName().equals("pid") && !((StructuredProperty) field).getQualifier().getClassid().equals("doi")) { return true; }

		return false;
	}

	private List<Field> generateFields(final String name, final Object field, final List<String> list) {
		if (field instanceof GeneratedMessage) {
			if (list.size() > 1) {
				return processPath(name, (GeneratedMessage) field, list.subList(1, list.size()));
			} else {
				throw new RuntimeException("No primitive type found");
			}
		} else {
			if (list.size() == 1) {
				eu.dnetlib.pace.config.Type type = field instanceof Integer ? eu.dnetlib.pace.config.Type.Int : eu.dnetlib.pace.config.Type.String;

				return Lists.newArrayList(new FieldValueImpl(type, name, field));
			} else {
				throw new RuntimeException("Found a primitive type before the path end");
			}
		}
	}

}
