/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */
package org.biojava.nbio.protmod.io;

import org.biojava.nbio.protmod.ProteinModification;
import org.biojava.nbio.protmod.ProteinModificationRegistry;
import org.biojava.nbio.protmod.structure.*;
import org.biojava.nbio.core.util.PrettyXMLWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.*;

public class ModifiedCompoundXMLConverter {

	private static final Logger logger = LoggerFactory.getLogger(ModifiedCompoundXMLConverter.class);

	public static String toXML(ModifiedCompound mc) throws IOException{

		if ( mc == null) {
			logger.warn("ModifiedCompound == null! ");
			return "<modifiedCompound/>";
		}
		StringWriter out = new StringWriter();

		PrettyXMLWriter xml = new PrettyXMLWriter(new PrintWriter(out));

		ProteinModification protMod = mc.getModification();
		String modificationId = protMod==null?null:protMod.getId();

		xml.openTag("modifiedCompound");
		if ( modificationId != null) {
//			ProteinModificationXMLConverter.toXML(modification, xml);
			xml.openTag("proteinModification");
			xml.attribute("id", modificationId);
			xml.closeTag("proteinModification");
		}


		Set<StructureAtomLinkage > linkages = mc.getAtomLinkages();
		if ( linkages.size() > 0 ) {
			int pos = -1;
			for ( StructureAtomLinkage link: linkages){
				pos ++;
				xml.openTag("linkage");
				xml.attribute("pos", pos+"");
				xml.attribute("total", linkages.size()+"");
				StructureAtom atom1 = link.getAtom1();
				StructureAtom atom2 = link.getAtom2();
				double distance = link.getDistance();

				xml.attribute("distance", distance+"");
				xml.openTag("atom1");
				StructureAtomXMLConverter.toXML(atom1,xml);
				xml.closeTag("atom1");
				xml.openTag("atom2");
				StructureAtomXMLConverter.toXML(atom2,xml);
				xml.closeTag("atom2");
				xml.closeTag("linkage");
			}
		} else {
			// no linkages, need to serialize the residues...
			xml.openTag("linkage");
			xml.closeTag("linkage");
			Set<StructureGroup> groups = mc.getGroups();
			for (StructureGroup group : groups) {
				StructureGroupXMLConverter.toXML(group, xml);
			}
		}




		xml.closeTag("modifiedCompound");
		return out.toString();
	}

	public static ModifiedCompound fromXML(String xml){
		ProteinModification modification = null;
		//Collection<StructureAtomLinkage> linkages = new ArrayList<StructureAtomLinkage>();
		StructureAtomLinkage[] linkages = null;
		List<StructureGroup> structureGroups = new ArrayList<StructureGroup>();
		try
		{
			//Convert string to XML document
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = factory.newDocumentBuilder();
			InputSource inStream = new InputSource();
			inStream.setCharacterStream(new StringReader(xml));
			Document doc = db.parse(inStream);

			// normalize text representation
			doc.getDocumentElement().normalize();

			NodeList listOfmodifications = doc.getElementsByTagName("modifiedCompound");
			//int numArrays = listOfArrays.getLength();
			// go over the blocks
			for(int modPos=0; modPos<listOfmodifications.getLength() ; modPos++)
			{

				Node modificationElement       = listOfmodifications.item(modPos);

				NodeList children = modificationElement.getChildNodes();

				int numChildren  = children.getLength();


				for ( int e =0; e< numChildren ; e++){
					Node  listOfConditions = children.item(e);

					if(!listOfConditions.hasAttributes()) continue;


					if ( listOfConditions.getNodeName().equals("proteinModification")) {
						//modification = ProteinModificationXMLConverter.fromXML(listOfConditions);
						String modId = getAttribute(listOfConditions, "id");
						modification = ProteinModificationRegistry.getById(modId);
						if (modification==null) {
							logger.warn("Error: no modification information.");
						}
					} else if ( listOfConditions.getNodeName().equals("linkage")) {
						double dist = Double.parseDouble(getAttribute(listOfConditions, "distance"));
						int pos = Integer.parseInt(getAttribute(listOfConditions,"pos"));
						int total = Integer.parseInt(getAttribute(listOfConditions,"total"));
						if ( linkages == null)
							linkages = new StructureAtomLinkage[total];

						StructureAtom atom1 = getAtom("atom1", listOfConditions);
						StructureAtom atom2 = getAtom("atom2",listOfConditions);
						StructureAtomLinkage linkage = new StructureAtomLinkage(atom1, atom2, dist);
						//linkages.add(linkage);
						linkages[pos] = linkage;
					} else if (listOfConditions.getNodeName().equals("structureGroup")) {
						StructureGroup group = StructureGroupXMLConverter.fromXML(listOfConditions);
						structureGroups.add(group);
//						logger.info("structureGroups size:" + structureGroups.size());
					}
				}
			}
		} catch (SAXParseException err)	{
			logger.error("** Parsing error, line: {}, uri: {}", err.getLineNumber (), err.getSystemId (), err);
		}
		catch (SAXException e) {
			logger.error("Exception: ", e);
		}
		catch (Throwable t) {
			logger.error("Exception: ", t);
		}


		if ( linkages != null) {
			Collection<StructureAtomLinkage> links = Arrays.asList(linkages);
			return new ModifiedCompoundImpl(modification, links);
		} else if ( structureGroups.size() == 1) {
			return new ModifiedCompoundImpl(modification, structureGroups.get(0));
		}
		return null;

	}



	private static StructureAtom getAtom(String elementName, Node n) {

		NodeList children = n.getChildNodes();

		int numChildren  = children.getLength();

		StructureAtom atom = null;
		for ( int e =0; e< numChildren ; e++){
			Node  atoms = children.item(e);

			if ( atoms.getNodeName().equals(elementName)) {
				NodeList child2 = atoms.getChildNodes();
				int numAtoms = child2.getLength();
				//logger.info("got " + numAtoms + " atoms");
				for ( int a=0;a< numAtoms; a++){
					Node atomNode = child2.item(a);
					if(!atomNode.hasAttributes()) continue;
					atom = StructureAtomXMLConverter.fromXML(atomNode);
					return atom;
				}

			}
		}
		return atom;
	}

	private static String getAttribute(Node node, String attr){
		if( ! node.hasAttributes())
			return null;

		NamedNodeMap atts = node.getAttributes();

		if ( atts == null)
			return null;

		Node att = atts.getNamedItem(attr);
		if ( att == null)
			return null;

		String value = att.getTextContent();

		return value;

	}

}
