package eu.dnetlib.data.graph.model;

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

import com.google.protobuf.*;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;

import com.google.protobuf.GeneratedMessage.GeneratedExtension;
import eu.dnetlib.data.graph.utils.RelDescriptor;
import eu.dnetlib.data.proto.DNGFProtos.DNGF;
import eu.dnetlib.data.proto.DNGFProtos.DNGFEntity;
import eu.dnetlib.data.proto.DNGFProtos.DNGFRel;
import eu.dnetlib.data.proto.FieldTypeProtos.Qualifier;
import eu.dnetlib.data.proto.KindProtos.Kind;

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

	/**
	 * DNGF object
	 */
	private DNGF dngf;

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

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

	protected DNGFDecoder(final byte[] value) {
		this(value, null);
	}

	protected DNGFDecoder(final byte[] value, final GeneratedExtension... ge) {
		try {
			final ExtensionRegistry registry = ExtensionRegistry.newInstance();
			if (ge != null) {
				for(GeneratedExtension e : ge) {
					registry.add(e);
				}
			}
			this.dngf = DNGF.parseFrom(value, registry);

		} catch (InvalidProtocolBufferException e) {
			throw new RuntimeException("unable to deserialize proto: " + new String(value));
		}
	}

	private DNGFDecoder(final DNGF dngf) {
		this.dngf = dngf;
	}

    public static DNGFDecoder decode(final DNGF dngf) {
        return new DNGFDecoder(dngf);
    }

    public static DNGFDecoder decode(final byte[] b) {
        return new DNGFDecoder(b);
    }

    public static DNGFDecoder decode(final byte[] b, final GeneratedExtension... ge) {
        return new DNGFDecoder(b, ge);
    }

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

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

    // Entity

	public DNGF getDNGF() {
		return dngf;
	}

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

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

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

	public DNGFEntity getEntity() {
		return dngf.getEntity();
	}


    // Rel

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

	public DNGFRel getDNGFRel() {
		return dngf.getRel();
	}

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

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

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

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

	private DNGFRelDecoder decodeRel() {
		if (relDecoder == null) {
			relDecoder = DNGFRelDecoder.decode(getDNGFRel());
		}
		return relDecoder;
	}

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

	public String getColumnFamily() {
		switch (getKind()) {
		case entity:
			return "metadata";
		case relation:
			return "rels";
		default:
			throw new IllegalArgumentException("invalid kind");
		}
	}

	public String getRowkey() {
		switch (getKind()) {
		case entity:
			return getEntityId();
		case relation:
			return getDNGFRel().getSource();
		default:
			throw new IllegalArgumentException("invalid kind");
		}
	}

	public String getQualifier() {
		switch (getKind()) {
		case entity:
			return getEntity().getType().toString();
		case relation:
			return getRelDescriptor().qualifier();
		default:
			throw new IllegalArgumentException("invalid kind");
		}
	}

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

		for (Entry<FieldDescriptor, Object> e : dngf.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() + ">");
		}
	}

    public RelDescriptor getRelDescriptor() {
        return decodeRel().getRelDescriptor();
    }
}
