package eu.dnetlib.data.mapreduce.util;

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

import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;

import eu.dnetlib.data.proto.KindProtos.Kind;
import eu.dnetlib.data.proto.OafProtos.Oaf;
import eu.dnetlib.data.proto.OafProtos.OafEntity;
import eu.dnetlib.data.proto.OafProtos.OafRel;
import eu.dnetlib.data.proto.RelTypeProtos.RelType;

/**
 * Helper class, to be used as accessor helper over the Oaf structure.
 *
 * @author claudio
 *
 */
public class OafDecoder {

	/**
	 * Oaf object
	 */
	private Oaf oaf;

	/**
	 * Cached sub decoder
	 */
	private OafEntityDecoder entityDecoder = null;

	/**
	 * Cached sub decoder
	 */
	private OafRelDecoder relDecoder = null;

	public static OafDecoder decode(final Oaf oaf) {
		return new OafDecoder(oaf);
	}

	public static OafDecoder decode(final byte[] oaf) {
		return new OafDecoder(oaf);
	}

	protected OafDecoder(final byte[] value) {
		try {
			this.oaf = Oaf.parseFrom(value);
		} catch (InvalidProtocolBufferException e) {
			throw new RuntimeException("unable to deserialize proto: " + new String(value));
		}
	}

	private OafDecoder(final Oaf oaf) {
		this.oaf = oaf;
	}

	public Kind getKind() {
		return oaf.getKind();
	}

	public Oaf getOaf() {
		return oaf;
	}

	// / Entity

	public GeneratedMessage getMetadata() {
		return decodeEntity().getMetadata();
	}

	public GeneratedMessage getOafEntity() {
		return decodeEntity().getEntity();
	}

	public String getEntityId() {
		return decodeEntity().getId();
	}

	public OafEntity getEntity() {
		return oaf.getEntity();
	}

	public OafEntityDecoder decodeEntity() {
		if (entityDecoder == null) {
			entityDecoder = OafEntityDecoder.decode(getEntity());
		}
		return entityDecoder;
	}

	// / Rel

	public OafRel getOafRel() {
		return oaf.getRel();
	}

	public GeneratedMessage getRel() {
		return decodeRel().getRel();
	}

	public RelType relType() {
		return decodeRel().getRelType();
	}

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

	public String relSourceId() {
		return decodeRel().getRelSourceId();
	}

	public String relTargetId() {
		return decodeRel().getRelTargetId();
	}

	public String getCFQ() {
		switch (getKind()) {
		case entity:
			return getEntity().getType().toString();
		case relation:
			return decodeRel().getCFQ();
		default:
			throw new IllegalArgumentException("Invalid Kind: " + getKind());
		}
	}

	public RelDescriptor getRelDescriptor() {
		if (!getKind().equals(Kind.relation)) return null;
		return decodeRel().getRelDescriptor();
	}

	private OafRelDecoder decodeRel() {
		if (relDecoder == null) {
			relDecoder = OafRelDecoder.decode(getOafRel());
		}
		return relDecoder;
	}

	public byte[] toByteArray() {
		return oaf.toByteArray();
	}

	public String asXml() {
		StringBuilder sb = new StringBuilder("<oaf>");

		for (Entry<FieldDescriptor, Object> e : oaf.getAllFields().entrySet()) {
			asXml(sb, e.getKey(), e.getValue());
		}
		sb.append("</oaf>");
		return sb.toString();
	}

	@SuppressWarnings("unchecked")
	private void asXml(final StringBuilder sb, final FieldDescriptor fd, final Object value) {

		if (fd.isRepeated() && (value instanceof List<?>)) {
			for (Object o : ((List<Object>) value)) {
				asXml(sb, fd, o);
			}
		} else if (fd.getType().equals(FieldDescriptor.Type.MESSAGE)) {
			sb.append("<" + fd.getName() + ">");
			for (Entry<FieldDescriptor, Object> e : ((Message) value).getAllFields().entrySet()) {
				asXml(sb, e.getKey(), e.getValue());
			}
			sb.append("</" + fd.getName() + ">");
		} else if (fd.getType().equals(FieldDescriptor.Type.ENUM)) {
			sb.append("<" + fd.getName() + ">");
			sb.append(((EnumValueDescriptor) value).getName());
			sb.append("</" + fd.getName() + ">");
		} else {
			sb.append("<" + fd.getName() + ">");
			sb.append(escapeXml(value.toString()));
			sb.append("</" + fd.getName() + ">");
		}
	}

	private static String escapeXml(final String value) {
		return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
	}

}
