package org.openscience.cdk.io;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Vector;

import nu.xom.Attribute;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.ProcessingInstruction;
import nu.xom.Text;

import org.openscience.cdk.CDKConstants;
import org.openscience.cdk.annotations.TestClass;
import org.openscience.cdk.annotations.TestMethod;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IAtomContainerSet;
import org.openscience.cdk.interfaces.IBond;
import org.openscience.cdk.interfaces.IChemFile;
import org.openscience.cdk.interfaces.IChemModel;
import org.openscience.cdk.interfaces.IChemObject;
import org.openscience.cdk.interfaces.IChemSequence;
import org.openscience.cdk.interfaces.ICrystal;
import org.openscience.cdk.interfaces.IMolecule;
import org.openscience.cdk.interfaces.IMoleculeSet;
import org.openscience.cdk.interfaces.IReaction;
import org.openscience.cdk.interfaces.IReactionSet;
import org.openscience.cdk.io.formats.CMLRSSFormat;
import org.openscience.cdk.io.formats.IResourceFormat;
import org.openscience.cdk.libio.cml.Convertor;

/**
 * Generates an RSS feed. It the object is a {@link IMoleculeSet}, the molecules
 * are put in separately. All other objects are made CML and put in.
 *
 * @cdk.module       libiocml
 * @cdk.githash
 * @cdk.builddepends xom-1.0.jar
 *
 * @author Stefan Kuhn
 *
 * @cdk.keyword RSS
 */
@TestClass("org.openscience.cdk.io.RssWriterTest")
public class RssWriter extends DefaultChemObjectWriter {
	
	private final static String NS_RSS10      = "http://purl.org/rss/1.0/";
	private final static String NS_RDF        = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
	private final static String NS_DCELEMENTS = "http://purl.org/dc/elements/1.1/";
	
    private BufferedWriter writer;
    private Map linkmap=new HashMap();
    private Map datemap=new HashMap();
    private Map titlemap=new HashMap();
    private Map creatormap=new HashMap();
    private Map inchimap=new HashMap();
    private String creator="";
    private String title="";
    private String link="";
    private String description="";
    private String publisher="";
    private String imagelink="";
    private String about="";
    private String timezone="+01:00";
    private Map multiMap=new HashMap();

    /**
     * Flushes the output and closes this object.
     */
    @TestMethod("testClose")
    public void close() throws IOException {
        writer.close();
    }
    
    @TestMethod("testGetFormat")
    public IResourceFormat getFormat() {
        return CMLRSSFormat.getInstance();
    }
    
    
    public void setWriter(Writer out) throws CDKException {
    	if (out instanceof BufferedWriter) {
            writer = (BufferedWriter)out;
        } else {
            writer = new BufferedWriter(out);
        }
    }

    public void setWriter(OutputStream output) throws CDKException {
    	setWriter(new OutputStreamWriter(output));
    }
    
	@TestMethod("testAccepts")
    public boolean accepts(Class classObject) {
		return true;
	}

    /**
     * Writes a IChemObject to the MDL molfile formated output. 
     *
     * @param object Best choice is a set of molecules
     */
	/* (non-Javadoc)
	 * @see org.openscience.cdk.io.IChemObjectWriter#write(org.openscience.cdk.interfaces.IChemObject)
	 */
	public void write(IChemObject object) throws CDKException {
		try{
		    ProcessingInstruction processingInstruction =new ProcessingInstruction("xml-stylesheet", "href=\"http://www.w3.org/2000/08/w3c-synd/style.css\" type=\"text/css\"");
		    Element rdfElement = new Element("rdf:RDF",NS_RDF);
		    rdfElement.addNamespaceDeclaration("",NS_RSS10);
		    rdfElement.addNamespaceDeclaration("mn","http://usefulinc.com/rss/manifest/");
		    rdfElement.addNamespaceDeclaration("dc",NS_DCELEMENTS);
		    rdfElement.addNamespaceDeclaration("cml",Convertor.NS_CML);
		    Document doc = new Document(rdfElement);
		    doc.insertChild(processingInstruction,0);
		    Element channelElement = new Element("channel",NS_RSS10);
		    Element titleElement = new Element("title",NS_RSS10);
		    titleElement.appendChild(new Text(title));
		    channelElement.appendChild(titleElement);
		    Element linkElement = new Element("link",NS_RSS10);
		    linkElement.appendChild(new Text(link));
		    channelElement.appendChild(linkElement);
		    Element descriptionElement = new Element("description",NS_RSS10);
		    descriptionElement.appendChild(new Text(description));
		    channelElement.appendChild(descriptionElement);
		    Element publisherElement = new Element("dc:publisher",NS_DCELEMENTS);
		    publisherElement.appendChild(new Text(publisher));
		    channelElement.appendChild(publisherElement);
		    Element creatorElement = new Element("dc:creator",NS_DCELEMENTS);
		    creatorElement.appendChild(new Text(creator));
		    channelElement.appendChild(creatorElement);
		    Element imageElement = new Element("image",NS_RSS10);
		    imageElement.addAttribute(new Attribute("rdf:resource",NS_RDF,imagelink));
		    channelElement.appendChild(imageElement);
		    Element itemsElement = new Element("items",NS_RSS10);
		    Element seqElement = new Element("rdf:Seq",NS_RDF);
		    itemsElement.appendChild(seqElement);
		    channelElement.appendChild(itemsElement);
		    channelElement.addAttribute(new Attribute("rdf:about",NS_RDF,about));
		    rdfElement.appendChild(channelElement);
		    List list =new Vector();
		    if(object instanceof IAtomContainerSet){
		    	for(int i=0;i<((IAtomContainerSet)object).getAtomContainerCount();i++){
		    		list.add(((IAtomContainerSet)object).getAtomContainer(i));
		    	}
		    }else{
		    	list.add(object);
		    }        	
		    for(int i=0;i<list.size();i++){
		      IChemObject chemObject =(IChemObject)list.get(i);
		      Element itemElement = new Element("item",NS_RSS10);
		      String easylink=(String)linkmap.get(chemObject);
		      if(easylink!=null)
		    	  itemElement.addAttribute(new Attribute("rdf:about",NS_RDF,easylink));
		      Element link2Element = new Element("link",NS_RSS10);
		      link2Element.appendChild(new Text(easylink));
		      itemElement.appendChild(link2Element);          
		      String title=(String)chemObject.getProperties().get(CDKConstants.TITLE);
		      if(titlemap.get(chemObject)!=null){
			      Element title2Element = new Element("title",NS_RSS10);
			      title2Element.appendChild(new Text((String)titlemap.get(chemObject)));
			      itemElement.appendChild(title2Element);
		      }
		      if(title!=null){
		    	  Element description2Element = new Element("description",NS_RSS10);
		    	  description2Element.appendChild(new Text(title));
		    	  itemElement.appendChild(description2Element);
		          Element subjectElement = new Element("dc:subject",NS_DCELEMENTS);
		          subjectElement.appendChild(new Text(title));
		          itemElement.appendChild(subjectElement);
		      }
		      if(datemap.get(chemObject)!=null){
			      Element dateElement = new Element("dc:date",NS_DCELEMENTS);
			      SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
			      dateElement.appendChild(new Text(formatter.format((Date)datemap.get(chemObject))+timezone));
			      itemElement.appendChild(dateElement);
		      }
		      Element creator2Element =new Element("dc:creator",NS_DCELEMENTS);
		      creator2Element.appendChild(new Text((String)creatormap.get(chemObject)));
		      itemElement.appendChild(creator2Element);
		      // add the InChI to the CMLRSS feed
		      if (inchimap.get(chemObject)!=null) {
		    	  Element inchiElement = new Element("cml:identifier","http://www.xml-cml.org/schema/cml2/core/");
		    	  inchiElement.appendChild(new Text((String)inchimap.get(chemObject)));
		    	  itemElement.appendChild(inchiElement);
		      }
		      Element root=null;
		      Convertor convertor=new Convertor(true,null);
		      object=(IChemObject)list.get(i);
		      if (object instanceof IMolecule) {
		      	root = convertor.cdkMoleculeToCMLMolecule((IMolecule)object);
		      }else if (object instanceof IAtomContainer) {
			     root = convertor.cdkAtomContainerToCMLMolecule((IAtomContainer)object);
			  } else if (object instanceof ICrystal) {
		      	root = convertor.cdkCrystalToCMLMolecule((ICrystal)object);
		      } else if (object instanceof IAtom) {
		      	root = convertor.cdkAtomToCMLAtom(null, (IAtom)object);
		      } else if (object instanceof IBond) {
		      	root = convertor.cdkBondToCMLBond((IBond)object);
		      } else if (object instanceof IReaction) {
		      	root = convertor.cdkReactionToCMLReaction((IReaction)object);
		      } else if (object instanceof IReactionSet) {
		      	root = convertor.cdkReactionSetToCMLReactionList((IReactionSet)object);
		      } else if (object instanceof IMoleculeSet) {
		      	root = convertor.cdkMoleculeSetToCMLList((IMoleculeSet)object);
		      } else if (object instanceof IChemSequence) {
		      	root = convertor.cdkChemSequenceToCMLList((IChemSequence)object);
		      } else if (object instanceof IChemModel) {
		      	root = convertor.cdkChemModelToCMLList((IChemModel)object);
		      } else if (object instanceof IChemFile) {
		      	root = convertor.cdkChemFileToCMLList((IChemFile)object);
		      } else {
		    	  throw new CDKException("Unsupported chemObject: " + object.getClass().getName());
		      }
		      itemElement.appendChild(root);
		      if(multiMap.get(chemObject)!=null){
		    	  Collection coll=(Collection)multiMap.get(chemObject);
		    	  Iterator iterator =coll.iterator();
		    	  while(iterator.hasNext()){
		    		 itemElement.appendChild((Element)iterator.next());
		    	  }
		      }
		      rdfElement.appendChild(itemElement);
		      Element imageElement2 = new Element("rdf:li",NS_RDF);
		      imageElement2.addAttribute(new Attribute("rdf:resource",NS_RDF,(String)linkmap.get(chemObject)));
		      seqElement.appendChild(imageElement2);
		    }
	      writer.write(doc.toXML());
	      writer.flush();
		}catch(IOException ex){
			throw new CDKException(ex.getMessage(), ex);
		}

	}

	/**
	 * @return the datemap. If you put a java.util.Date in this map with one of the objects you want to write as key, it will be added as a date to this object (no validity check is done)
	 */
	public Map getDatemap() {
		return datemap;
	}

	/**
	 * @param datemap the datemap. If you put a java.uitl.Date in this map with one of the objects you want to write as key, it will be added as a datek to this object (no validity check is done)
	 */
	public void setDatemap(Map datemap) {
		this.datemap = datemap;
	}

	/**
	 * @return the linkmap. If you put a String in this map with one of the objects you want to write as key, it will be added as a link to this object (no validity check is done)
	 */
	public Map getLinkmap() {
		return linkmap;
	}

	/**
	 * @param linkmap the linkmap. If you put a String in this map with one of the objects you want to write as key, it will be added as a link to this object (no validity check is done)
	 */
	public void setLinkmap(Map linkmap) {
		this.linkmap = linkmap;
	}

	/**
	 * @return the titlemap. If you put a String in this map with one of the objects you want to write as key, it will be added as a title to this object (no validity check is done)
	 */
	public Map getTitlemap() {
		return titlemap;
	}

	/**
	 * @param titlemap the titlemap. If you put a String in this map with one of the objects you want to write as key, it will be added as a titel to this object (no validity check is done)
	 */
	public void setTitlemap(Map titlemap) {
		this.titlemap = titlemap;
	}

	/**
	 * @return the creatoremap. If you put a String in this map with one of the objects you want to write as key, it will be added as a creator to this object (no validity check is done)
	 */
	public Map getCreatormap() {
		return creatormap;
	}

	/**
	 * @param creatormap the creatormap. If you put a String in this map with one of the objects you want to write as key, it will be added as a creator to this object (no validity check is done)
	 */
	public void setCreatormap(Map creatormap) {
		this.creatormap = creatormap;
	}

	/**
	 * @param about This will be the about for the rss feed
	 */
	public void setAbout(String about) {
		this.about = about;
	}

	/**
	 * @param creator This will be the creator for the rss feed
	 */
	public void setCreator(String creator) {
		this.creator = creator;
	}

	/**
	 * @param description This will be the description for the rss feed
	 */
	public void setDescription(String description) {
		this.description = description;
	}

	/**
	 * @param imagelink This will be the imagelink for the rss feed
	 */
	public void setImagelink(String imagelink) {
		this.imagelink = imagelink;
	}

	/**
	 * @param link This will be the link for the rss feed
	 */
	public void setLink(String link) {
		this.link = link;
	}

	/**
	 * @param publisher This will be the publisher for the rss feed
	 */
	public void setPublisher(String publisher) {
		this.publisher = publisher;
	}

	/**
	 * @param title This will be the title for the rss feed
	 */
	public void setTitle(String title) {
		this.title = title;
	}

	/**
	 * @return the multimap. If you put any number of nu.xom.Elements in this map with one of the objects you want to write as key, it will be added as a child to the same node as the cml code of the object
	 */
	public Map getMultiMap() {
		return multiMap;
	}

	/**
	 * @param multiMap If you put any number of nu.xom.Elements in this map with one of the objects you want to write as key, it will be added as a child to the same node as the cml code of the object
	 */
	public void setMultiMap(Map multiMap) {
		this.multiMap = multiMap;
	}

	/**
	 * @param timezone This will be added to the data as timezone. format according to 23c. Examples "+01:00" "-05:00"
	 */
	public void setTimezone(String timezone) {
		this.timezone = timezone;
	}

	/**
	 * @return inchimap If you put any number of Strings in this map with one of the objects you want to write as key, it will be added as a child to the same node as the cml code of the object
	 */
	public Map getInchimap() {
		return inchimap;
	}

	/**
	 * @param inchimap If you put any number of Strings in this map with one of the objects you want to write as key, it will be added as a child to the same node as the cml code of the object
	 */
	public void setInchimap(Map inchimap) {
		this.inchimap = inchimap;
	}

}
