/************************************************************************
 *
 *  OPFWriter.java
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *  Copyright: 2001-2010 by Henrik Just
 *
 *  All Rights Reserved.
 * 
 *  version 1.2 (2010-12-16)
 *
 */

package writer2latex.epub;

import java.util.Iterator;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;

import writer2latex.api.ContentEntry;
import writer2latex.api.ConverterResult;
import writer2latex.api.OutputFile;
import writer2latex.util.Misc;
import writer2latex.xmerge.NewDOMDocument;

/** This class writes an OPF-file for an EPUB document (see http://www.idpf.org/2007/opf/OPF_2.0_final_spec.html).
 */
public class OPFWriter extends NewDOMDocument {

	public OPFWriter(ConverterResult cr, String sUUID, boolean bUseDublinCore) {
		super("book", "opf");
		
        // create DOM
        Document contentDOM = null;
        try {
            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = builderFactory.newDocumentBuilder();
            DOMImplementation domImpl = builder.getDOMImplementation();
            DocumentType doctype = domImpl.createDocumentType("package","",""); 
            contentDOM = domImpl.createDocument("http://www.idpf.org/2007/opf","package",doctype);
        }
        catch (ParserConfigurationException t) { // this should never happen
            throw new RuntimeException(t);
        }
        
        // Populate the DOM tree
        Element pack = contentDOM.getDocumentElement();
        pack.setAttribute("version", "2.0");
        pack.setAttribute("xmlns","http://www.idpf.org/2007/opf");
        pack.setAttribute("unique-identifier", "BookId");
        
        // Meta data, at least dc:title, dc:language and dc:identifier are required by the specification
        Element metadata = contentDOM.createElement("metadata");
        metadata.setAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
        metadata.setAttribute("xmlns:opf", "http://www.idpf.org/2007/opf");
        pack.appendChild(metadata);
        
        Element title = contentDOM.createElement("dc:title");
        metadata.appendChild(title);
        title.appendChild(contentDOM.createTextNode(cr.getMetaData().getTitle()));
        
        Element language = contentDOM.createElement("dc:language");
        metadata.appendChild(language);
        language.appendChild(contentDOM.createTextNode(cr.getMetaData().getLanguage()));
        
        // Additional meta data
        if (bUseDublinCore) {
        	// Subject and keywords in ODF both map to Dublin core subjects
        	if (cr.getMetaData().getSubject().length()>0) {
        		Element subject = contentDOM.createElement("dc:subject");
        		metadata.appendChild(subject);
        		subject.appendChild(contentDOM.createTextNode(cr.getMetaData().getSubject()));
        	}
        	if (cr.getMetaData().getKeywords().length()>0) {
        		String[] sKeywords = cr.getMetaData().getKeywords().split(",");
        		for (String sKeyword : sKeywords) {
        			Element subject = contentDOM.createElement("dc:subject");
        			metadata.appendChild(subject);
        			subject.appendChild(contentDOM.createTextNode(sKeyword.trim()));
        		}
        	}
        	if (cr.getMetaData().getDescription().length()>0) {
        		Element description = contentDOM.createElement("dc:description");
        		metadata.appendChild(description);
        		description.appendChild(contentDOM.createTextNode(cr.getMetaData().getDescription()));
        	}
        }
        
        // User defined meta data
        // The identifier, creator, contributor and date has an optional attribute and there may be multiple instances of
        // the first three. The key can be in any of the forms name, name.attribute, name.attribute.id, name..id
        // where the id is some unique id amongst the instances with the same name
        // Thus you can have e.g. creator.aut.1="John Doe" and creator.aut.2="Jane Doe"
        boolean bHasIdentifier = false;
        boolean bHasCreator = false;
        boolean bHasDate = false;
        Map<String,String> userDefined = cr.getMetaData().getUserDefinedMetaData();
        for (String sKey : userDefined.keySet()) {
        	if (sKey.length()>0) {
        		String[] sKeyElements = sKey.toLowerCase().split("\\.");
        		String sValue = userDefined.get(sKey);
        		if ("identifier".equals(sKeyElements[0])) {
        			Element identifier = contentDOM.createElement("dc:identifier");
            		identifier.setAttribute("id", "BookId");
        			if (sKeyElements.length>1 && sKeyElements[1].length()>0) {
        				identifier.setAttribute("opf:scheme", sKeyElements[1]);
        			}
        			metadata.appendChild(identifier);
        			identifier.appendChild(contentDOM.createTextNode(sValue));
        			bHasIdentifier = true;
        		}
        		else if ("creator".equals(sKeyElements[0])) {
        			Element creator = contentDOM.createElement("dc:creator");
        			if (sKeyElements.length>1 && sKeyElements[1].length()>0) {
        				creator.setAttribute("opf:role", sKeyElements[1]);
        			}
        			metadata.appendChild(creator);
        			creator.appendChild(contentDOM.createTextNode(sValue));
        			bHasCreator = true;
        		}
        		else if ("contributor".equals(sKeyElements[0])) {
        			Element contributor = contentDOM.createElement("dc:contributor");
        			if (sKeyElements.length>1 && sKeyElements[1].length()>0) {
        				contributor.setAttribute("opf:role", sKeyElements[1]);
        			}
        			metadata.appendChild(contributor);
        			contributor.appendChild(contentDOM.createTextNode(sValue));
        		}
        		else if ("date".equals(sKeyElements[0])) {
        			Element date = contentDOM.createElement("dc:date");
        			if (sKeyElements.length>1 && sKeyElements[1].length()>0) {
        				date.setAttribute("opf:event", sKeyElements[1]);
        			}
        			metadata.appendChild(date);
        			date.appendChild(contentDOM.createTextNode(sValue));
        			bHasDate = true;
        		}
        		else if (sKeyElements.length==1) {
        			// Remaining meta data elements must be unique
        			if ("publisher".equals(sKeyElements[0])) {
        				Element publisher = contentDOM.createElement("dc:publisher");
        				metadata.appendChild(publisher);
        				publisher.appendChild(contentDOM.createTextNode(sValue));
        			}
        			else if ("type".equals(sKeyElements[0])) {
        				Element type = contentDOM.createElement("dc:type");
        				metadata.appendChild(type);
        				type.appendChild(contentDOM.createTextNode(sValue));
        			}
        			else if ("format".equals(sKeyElements[0])) {
        				Element format = contentDOM.createElement("dc:format");
        				metadata.appendChild(format);
        				format.appendChild(contentDOM.createTextNode(sValue));        			
        			}
        			else if ("source".equals(sKeyElements[0])) {
        				Element source = contentDOM.createElement("dc:source");
        				metadata.appendChild(source);
        				source.appendChild(contentDOM.createTextNode(sValue));        			        			
        			}
        			else if ("relation".equals(sKeyElements[0])) {
        				Element relation = contentDOM.createElement("dc:relation");
        				metadata.appendChild(relation);
        				relation.appendChild(contentDOM.createTextNode(sValue));        			        			
        			}
        			else if ("coverage".equals(sKeyElements[0])) {
        				Element coverage = contentDOM.createElement("dc:coverage");
        				metadata.appendChild(coverage);
        				coverage.appendChild(contentDOM.createTextNode(sValue));        				
        			}
        			else if ("rights".equals(sKeyElements[0])) {
        				Element rights = contentDOM.createElement("dc:rights");
        				metadata.appendChild(rights);
        				rights.appendChild(contentDOM.createTextNode(sValue));        					
        			}
        		}
        	}
        }
        
        // Fall back values for creator and date
        if (bUseDublinCore) {
        	if (!bHasIdentifier) {
        		Element identifier = contentDOM.createElement("dc:identifier");
        		identifier.setAttribute("id", "BookId");
        		identifier.setAttribute("opf:scheme", "UUID");
        		metadata.appendChild(identifier);
        		identifier.appendChild(contentDOM.createTextNode(sUUID));
        	}
        	if (!bHasCreator && cr.getMetaData().getCreator().length()>0) {
        		Element creator = contentDOM.createElement("dc:creator");
        		metadata.appendChild(creator);
        		creator.appendChild(contentDOM.createTextNode(cr.getMetaData().getCreator()));
        	}
        	if (!bHasDate && cr.getMetaData().getDate().length()>0) {
        		Element date = contentDOM.createElement("dc:date");
        		metadata.appendChild(date);
        		date.appendChild(contentDOM.createTextNode(cr.getMetaData().getDate()));
        	}
        }
        
        // Manifest must contain references to all the files in the XHTML converter result
        // Spine should contain references to all the master documents within the converter result
        Element manifest = contentDOM.createElement("manifest");
        pack.appendChild(manifest);
        
        Element spine = contentDOM.createElement("spine");
        spine.setAttribute("toc", "ncx");
        pack.appendChild(spine);
        
        int nMasterCount = 0;
        int nResourceCount = 0;
        Iterator<OutputFile> iterator = cr.iterator();
        while (iterator.hasNext()) {
        	OutputFile file = iterator.next();
        	Element item = contentDOM.createElement("item");
        	manifest.appendChild(item);
        	item.setAttribute("href",Misc.makeHref(file.getFileName()));
        	item.setAttribute("media-type", file.getMIMEType());
        	if (file.isMasterDocument()) {
        		String sId = "text"+(++nMasterCount);
        		item.setAttribute("id", sId);
        		
        		Element itemref = contentDOM.createElement("itemref");
        		itemref.setAttribute("idref", sId);
        		spine.appendChild(itemref);
        	}
        	else {
        		item.setAttribute("id", "resource"+(++nResourceCount));
        	}
        }
        
        Element item = contentDOM.createElement("item");
        item.setAttribute("href", "book.ncx");
        item.setAttribute("media-type", "application/x-dtbncx+xml");
        item.setAttribute("id", "ncx");
        manifest.appendChild(item);
        
        // The guide may contain references to some fundamental structural components
        Element guide = contentDOM.createElement("guide");
        pack.appendChild(guide);        
       	addGuideReference(contentDOM,guide,"title-page",cr.getTitlePageFile());
       	addGuideReference(contentDOM,guide,"text",cr.getTextFile());
       	addGuideReference(contentDOM,guide,"toc",cr.getTocFile());
       	addGuideReference(contentDOM,guide,"index",cr.getIndexFile());
       	addGuideReference(contentDOM,guide,"loi",cr.getLofFile());
       	addGuideReference(contentDOM,guide,"lot",cr.getLotFile());
       	addGuideReference(contentDOM,guide,"bibliography",cr.getBibliographyFile());
        
        setContentDOM(contentDOM);
	}
	
	private void addGuideReference(Document contentDOM, Element guide, String sType, ContentEntry entry) {
		if (entry!=null) {
			Element reference = contentDOM.createElement("reference");
			reference.setAttribute("type", sType);
			reference.setAttribute("title", entry.getTitle());
			String sHref = Misc.makeHref(entry.getFile().getFileName());
			if (entry.getTarget()!=null) { sHref+="#"+entry.getTarget(); }
			reference.setAttribute("href", sHref);
			guide.appendChild(reference);
		}
	}

}