package eu.dnetlib.data.mapreduce.util;

import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.Message.Builder;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.ProtocolMessageEnum;

import eu.dnetlib.data.proto.OafProtos.OafEntity;
import eu.dnetlib.data.proto.OafProtos.OafRel;
import eu.dnetlib.data.proto.RelMetadataProtos.RelMetadata;
import eu.dnetlib.data.proto.RelTypeProtos.RelType;
import eu.dnetlib.data.proto.RelTypeProtos.SubRelType;
import eu.dnetlib.data.proto.TypeProtos.Type;

public class OafRelDecoder {

	private static final String SEPARATOR = "_";

	private final OafRel oafRel;

	public static OafRelDecoder decode(final OafRel oafRel) {
		return new OafRelDecoder(oafRel);
	}

	private OafRelDecoder(final OafRel oafRel) {
		this.oafRel = oafRel;
	}

	public RelType getRelType() {
		return oafRel.getRelType();
	}

	public String relTypeName() {
		return getRelType().toString();
	}

	public SubRelType getSubRelType() {
		return oafRel.getSubRelType();
	}

	public String relSubTypeName() {
		return getSubRelType().toString();
	}

	public String getCF() {
		return OafRelDecoder.getCF(getRelType(), getSubRelType());
	}

	public String getCFQ() {
		return OafRelDecoder.getCFQ(getRelType(), getSubRelType(), getRelClass());
	}

	public static String getCFQ(final RelType relType, final SubRelType subRelType, final ProtocolMessageEnum relClass) {
		return OafRelDecoder.getCFQ(relType, subRelType, relClass.getValueDescriptor().getName());
	}

	public static String getCFQ(final RelType relType, final SubRelType subRelType, final String relClass) {
		return OafRelDecoder.getCF(relType, subRelType) + SEPARATOR + relClass;
	}

	public static String getCF(final RelType relType, final SubRelType subRelType) {
		return relType + SEPARATOR + subRelType;
	}

	public String getRelClass() {
		return oafRel.getRelClass();
	}

	public RelDescriptor getRelDescriptor() {
		return new RelDescriptor(getCFQ());
	}

	public GeneratedMessage getRel() {

		FieldDescriptor fd = oafRel.getDescriptorForType().findFieldByName(relTypeName());
		return (GeneratedMessage) oafRel.getField(fd);
	}

	public GeneratedMessage getSubRel() {
		GeneratedMessage rel = getRel();
		FieldDescriptor fd = rel.getDescriptorForType().findFieldByName(relSubTypeName());
		return (GeneratedMessage) rel.getField(fd);
	}

	public RelMetadata getRelMetadata() {
		GeneratedMessage rel = getSubRel();
		FieldDescriptor fd = rel.getDescriptorForType().findFieldByName("relMetadata");
		return fd != null ? (RelMetadata) rel.getField(fd) : null;
	}

	public OafRel.Builder setClassId(final String classid) {
		RelMetadata.Builder relMetadataBuilder = RelMetadata.newBuilder(getRelMetadata());
		relMetadataBuilder.getSemanticsBuilder().setClassid(classid).setClassname(classid);

		OafRel.Builder builder = OafRel.newBuilder(oafRel);

		FieldDescriptor fdRel = fd(oafRel, relTypeName());
		Builder relBuilder = builder.newBuilderForField(fdRel);

		FieldDescriptor fdSubRel = fd(relBuilder, relSubTypeName());
		Builder subRelBuilder = relBuilder.newBuilderForField(fdSubRel).mergeFrom(getSubRel());

		subRelBuilder.setField(fd(getSubRel(), "relMetadata"), relMetadataBuilder.build());

		relBuilder.setField(fdSubRel, subRelBuilder.build());
		builder.setField(fdRel, relBuilder.build());

		return builder.setRelClass(classid);
	}

	public Type getTargetType(final Type sourceType) {
		switch (getRelType()) {
		case datasourceOrganization:
			return sourceType.equals(Type.datasource) ? Type.organization : Type.datasource;
		case organizationOrganization:
			return Type.organization;
		case personPerson:
			return Type.person;
		case personResult:
			return sourceType.equals(Type.person) ? Type.result : Type.person;
		case projectOrganization:
			return sourceType.equals(Type.project) ? Type.organization : Type.project;
		case projectPerson:
			return sourceType.equals(Type.project) ? Type.person : Type.project;
		case resultOrganization:
			return sourceType.equals(Type.result) ? Type.organization : Type.result;
		case resultProject:
			return sourceType.equals(Type.result) ? Type.project : Type.result;
		case resultResult:
			return Type.result;
		default:
			throw new IllegalArgumentException("Unknown relationship type: " + relTypeName());
		}
	}

	protected FieldDescriptor fd(final MessageOrBuilder mb, final int fieldNumber) {
		return mb.getDescriptorForType().findFieldByNumber(fieldNumber);
	}

	protected FieldDescriptor fd(final MessageOrBuilder mb, final String fieldName) {
		return mb.getDescriptorForType().findFieldByName(fieldName);
	}

	public String getCachedTargedId() {

		if (!oafRel.hasCachedTarget()) return null;

		final OafEntity entity = oafRel.getCachedTarget();
		return OafEntityDecoder.decode(entity).getId();
	}

	public String getRelSourceId() {
		return oafRel.getSource();
	}

	public String getRelTargetId() {
		return oafRel.getTarget();
	}

}
