/************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * Use is subject to license terms.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0. You can also
 * obtain a copy of the License at http://odftoolkit.org/docs/license.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ************************************************************************/
package org.odftoolkit.odfdom.pkg;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;

import org.junit.Assert;
import org.junit.Test;
import org.odftoolkit.odfdom.doc.OdfDocument;
import org.odftoolkit.odfdom.doc.OdfSpreadsheetDocument;
import org.odftoolkit.odfdom.doc.OdfTextDocument;
import org.odftoolkit.odfdom.doc.OdfDocument.OdfMediaType;
import org.odftoolkit.odfdom.doc.table.OdfTable;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.attribute.text.TextAnchorTypeAttribute;
import org.odftoolkit.odfdom.dom.element.draw.DrawObjectElement;
import org.odftoolkit.odfdom.dom.element.text.TextHElement;
import org.odftoolkit.odfdom.dom.element.text.TextPElement;
import org.odftoolkit.odfdom.incubator.doc.draw.OdfDrawFrame;
import org.odftoolkit.odfdom.incubator.doc.text.OdfTextSpan;
import org.odftoolkit.odfdom.pkg.manifest.OdfFileEntry;
import org.odftoolkit.odfdom.utils.ResourceUtilities;
import org.w3c.dom.NodeList;

public class EmbeddedDocumentTest {

	private static final Logger LOG = Logger.getLogger(EmbeddedDocumentTest.class.getName());
	private static final String TEST_FILE_FOLDER = ResourceUtilities.getTestOutputFolder();
	private static final String TEST_FILE_EMBEDDED = "performance/Presentation1_INVALID.odp";
	private static final String TEST_FILE_EMBEDDED_SAVE_OUT = "SaveEmbeddedDoc.odt";
	private static final String TEST_FILE_EMBEDDED_SIDEBYSIDE_SAVE_OUT = "SaveEmbeddedDocSideBySide.odt";
	private static final String TEST_FILE_EMBEDDED_INCLUDED_SAVE_OUT = "SaveEmbeddedDocIncluded.odt";
	private static final String TEST_FILE_REMOVE_EMBEDDED_SAVE_OUT = "RemoveEmbeddedDoc.odt";
	private static final String TEST_FILE_MODIFIED_EMBEDDED = "TestModifiedEmbeddedDoc.odt";
	private static final String TEST_FILE_MODIFIED_EMBEDDED_SAVE_STANDALONE = "SaveModifiedEmbeddedDocAlone.odt";
	private static final String TEST_SPAN_TEXT = "Modify Header";
	private static final String TEST_PIC = "testA.jpg";
	private static final String TEST_PIC_ANOTHER = "testB.jpg";
	private static final String SLASH = "/";

	/**
	 * The document A contains the embedded document E1,
	 * this test case is used to embed E1 to another document B
	 */
	@Test
	public void testEmbedEmbeddedDocument() {
		try {
			OdfDocument doc = OdfDocument.loadDocument(ResourceUtilities.getAbsolutePath(TEST_FILE_EMBEDDED));
			OdfDocument saveDoc = OdfTextDocument.newTextDocument();
			Map<String, OdfDocument> subDocs = doc.loadSubDocuments();
			List<String> subDocNames = new ArrayList<String>();
			for (String childDocPath : subDocs.keySet()) {
				OdfDocument childDoc = subDocs.get(childDocPath);
				String embeddedDocPath = childDoc.getDocumentPath();
				saveDoc.insertDocument(childDoc, embeddedDocPath);
				subDocNames.add(embeddedDocPath);
			}
			Set<String> paths = saveDoc.getPackage().getDocumentPaths();
			int docCount = paths.size();
			paths = saveDoc.getPackage().getDocumentPaths("application/vnd.oasis.opendocument.presentation");
			int presentationDocCount = paths.size();
			Assert.assertTrue(docCount > presentationDocCount);
			saveDoc.save(TEST_FILE_FOLDER + TEST_FILE_EMBEDDED_SAVE_OUT);
			saveDoc.close();
			saveDoc = OdfDocument.loadDocument(ResourceUtilities.getAbsolutePath(TEST_FILE_EMBEDDED_SAVE_OUT));
			Map<String, OdfDocument> reloadedSubDocs = saveDoc.loadSubDocuments();
			Assert.assertTrue(subDocs.size() == reloadedSubDocs.size());
			for (String childDocPath : subDocs.keySet()) {
				Assert.assertEquals(subDocs.get(childDocPath).getMediaTypeString(), reloadedSubDocs.get(childDocPath).getMediaTypeString());
			}
		} catch (Exception ex) {
			LOG.log(Level.SEVERE, null, ex);
			Assert.fail("Failed to embed an embedded Document: '" + ex.getMessage() + "'");
		}
	}

	/**
	 * The document B is embedded to document A
	 * and the directory path of A and B are absolute from the package
	 * DOCA/ and DOCB/
	 */
	@Test
	public void testembeddedDocumentsLocatedSideBySide() {
		try {
			OdfTextDocument odtRootDoc = OdfTextDocument.newTextDocument();
			odtRootDoc.insertDocument(OdfTextDocument.newTextDocument(), "DOCA/");
			OdfDocument docA = odtRootDoc.loadSubDocument("DOCA");
			docA.newImage(ResourceUtilities.getURI(TEST_PIC));
			docA.insertDocument(OdfSpreadsheetDocument.newSpreadsheetDocument(), "../DOCB/");
			OdfFileDom contentA = docA.getContentDom();
			XPath xpath = contentA.getXPath();
			TextPElement lastPara = (TextPElement) xpath.evaluate("//text:p[last()]", contentA, XPathConstants.NODE);
			addFrameForEmbeddedDoc(contentA, lastPara, "DOCB");
			OdfDocument docB = odtRootDoc.loadSubDocument("DOCB/");
			Assert.assertNotNull(docB);
			Assert.assertNull(odtRootDoc.loadSubDocument("DOCA/DOCB/"));
			docB.newImage(ResourceUtilities.getURI(TEST_PIC_ANOTHER));
			OdfTable table1 = docB.getTableList().get(0);
			table1.setTableName("NewTable");
			updateFrameForEmbeddedDoc(contentA, "./DOCB", "DOCA/DOCB");
			//if user want to save the docA with the side by side embedded document
			//he has to insert it to the sub document of docA and update the xlink:href link
			docA.insertDocument(docB, "DOCB/");
			//save
			docA.save(TEST_FILE_FOLDER + TEST_FILE_EMBEDDED_SIDEBYSIDE_SAVE_OUT);
			OdfDocument testLoad = OdfDocument.loadDocument(TEST_FILE_FOLDER + TEST_FILE_EMBEDDED_SIDEBYSIDE_SAVE_OUT);
			OdfFileEntry imageEntry = testLoad.getPackage().getFileEntry(OdfPackage.OdfFile.IMAGE_DIRECTORY.getPath() + SLASH + TEST_PIC);
			Assert.assertNotNull(imageEntry);
			Map<String, OdfDocument> embDocs = testLoad.loadSubDocuments(OdfDocument.OdfMediaType.SPREADSHEET);
			for(String embedDocPath : embDocs.keySet()){
				OdfDocument doc1 = embDocs.get(embedDocPath);
				imageEntry = doc1.getPackage().getFileEntry(doc1.getDocumentPath() + OdfPackage.OdfFile.IMAGE_DIRECTORY.getPath() + SLASH + TEST_PIC_ANOTHER);
				Assert.assertNotNull(doc1.getTableByName("NewTable"));
			}
		} catch (Exception ex) {
			LOG.log(Level.SEVERE, null, ex);
			Assert.fail("Failed with " + ex.getClass().getName() + ": '" + ex.getMessage() + "'");
		}
	}

	/**
	 * 1) A new sub document text document DOCA/ is inserted into a new text document
	 * 2) A picture is being added to the subdocument (ie. /DOCA/Pictures/testA.jpg)
	 * 3) A new sub document spreadsheet document is inserted into the first (ie. /DOCA/DOCB)
	 * 4) In the last paragraph of /DOCA a frame with a reference to the subdocument DOCB is added
	 * 5) A picture is being added to the second subdocument (ie. /DOCA/DOCB/Pictures/testB.jpg)
	 * 6) The spreadsheetname of DOCB is set to "NewTable"
	 * 7) DOCA/ is saved in a document for its own
	 */
	@Test
	public void testembeddedDocumentWithSubPath() {
		try {
			OdfTextDocument odtDoc1 = OdfTextDocument.newTextDocument();
			odtDoc1.insertDocument(OdfTextDocument.newTextDocument(), "DOCA/");
			OdfDocument docA = odtDoc1.loadSubDocument("DOCA");
			docA.newImage(ResourceUtilities.getURI(TEST_PIC));
			docA.insertDocument(OdfSpreadsheetDocument.newSpreadsheetDocument(), "DOCB/");
			OdfFileDom contentA = docA.getContentDom();
			XPath xpath = contentA.getXPath();
			TextPElement lastPara = (TextPElement) xpath.evaluate("//text:p[last()]", contentA, XPathConstants.NODE);
			addFrameForEmbeddedDoc(contentA, lastPara, "./DOCB");
			OdfDocument docB = odtDoc1.loadSubDocument("DOCA/DOCB/");
			docB.newImage(ResourceUtilities.getURI(TEST_PIC_ANOTHER));
			OdfTable table1 = docB.getTableList().get(0);
			table1.setTableName("NewTable");
			Assert.assertNotNull(docB);
			Assert.assertNull(odtDoc1.loadSubDocument("DOCB/"));
			docA.save(TEST_FILE_FOLDER + TEST_FILE_EMBEDDED_INCLUDED_SAVE_OUT);
			OdfDocument testLoad = OdfDocument.loadDocument(TEST_FILE_FOLDER + TEST_FILE_EMBEDDED_INCLUDED_SAVE_OUT);
			OdfFileEntry imageEntry = testLoad.getPackage().getFileEntry(OdfPackage.OdfFile.IMAGE_DIRECTORY.getPath() + SLASH + TEST_PIC);
			Assert.assertNotNull(imageEntry);
			Map<String, OdfDocument> embDocs = testLoad.loadSubDocuments(OdfDocument.OdfMediaType.SPREADSHEET);
			for (String childDocPath : embDocs.keySet()) {
				OdfDocument doc1 = embDocs.get(childDocPath);
				imageEntry = doc1.getPackage().getFileEntry(doc1.getDocumentPath() + OdfPackage.OdfFile.IMAGE_DIRECTORY.getPath() + SLASH + TEST_PIC_ANOTHER);
				Assert.assertNotNull(doc1.getTableByName("NewTable"));
			}
		} catch (Exception ex) {
			LOG.log(Level.SEVERE, null, ex);
			Assert.fail("Failed with " + ex.getClass().getName() + ": '" + ex.getMessage() + "'");
		}
	}

	/**
	 * The document A contains the embedded document E1,
	 * This test case is used to show how to save the E1 to a stand alone document.
	 */
	@Test
	public void testSaveEmbeddedDocument() {
		try {
			OdfDocument doc = OdfDocument.loadDocument(ResourceUtilities.getAbsolutePath(TEST_FILE_EMBEDDED));
			Map<String, OdfDocument> embeddedDocs = doc.loadSubDocuments();
			for (String childDocPath : embeddedDocs.keySet()) {
				OdfDocument childDoc = embeddedDocs.get(childDocPath);
				String embedFileName = childDoc.getDocumentPath();
				OdfMediaType embedMediaType = OdfMediaType.getOdfMediaType(childDoc.getMediaTypeString());
				//use '_' replace '/', because '/' is not the valid char in file path
				embedFileName = embedFileName.replaceAll("/", "_") + "." + embedMediaType.getSuffix();
				childDoc.save(TEST_FILE_FOLDER + embedFileName);
				LOG.log(Level.INFO, "Save file : {0}", TEST_FILE_FOLDER + embedFileName);
				OdfDocument embeddedDoc = OdfDocument.loadDocument(ResourceUtilities.getAbsolutePath(embedFileName));
				Assert.assertEquals(embeddedDoc.getMediaTypeString(), embedMediaType.getMediaTypeString());
			}
		} catch (Exception ex) {
			LOG.log(Level.SEVERE, null, ex);
			Assert.fail("Failed with " + ex.getClass().getName() + ": '" + ex.getMessage() + "'");
		}

	}

	/**
	 * There are two document, one is Presentation1_INVALID.odp
	 * another is a new text document TestModifiedEmbeddedDoc.odt
	 * Presentation1_INVALID.odp contains an embed document named "Object 1/", add one paragraph to Object 1
	 * then embed "Object 1" to the new text document, and save this text document
	 * reload TestModifiedEmbeddedDoc.odt, then get and modify embed document "DocA" and save it to a standalone document
	 * load the saved standalone document, and check the content of it
	 */
	@Test
	public void testEmbedModifiedEmbeddedDocument() {
		try {
			OdfDocument doc = OdfDocument.loadDocument(ResourceUtilities.getAbsolutePath(TEST_FILE_EMBEDDED));
			OdfDocument saveDoc = OdfTextDocument.newTextDocument();
			OdfDocument embeddedDoc = doc.loadSubDocument("Object 1/");
			//modify content of "Object 1"
			OdfFileDom embedContentDom = embeddedDoc.getContentDom();
			XPath xpath = embedContentDom.getXPath();
			TextHElement header = (TextHElement) xpath.evaluate("//text:h[1]", embedContentDom, XPathConstants.NODE);
			LOG.log(Level.INFO, "First para: {0}", header.getTextContent());
			OdfTextSpan spanElem = new OdfTextSpan(embedContentDom);
			spanElem.setTextContent(TEST_SPAN_TEXT);
			header.appendChild(spanElem);
			//insert image to "Object 1"
			embeddedDoc.newImage(ResourceUtilities.getURI(TEST_PIC));
			//embed "Object 1" to TestModifiedEmbeddedDoc.odt as the path /DocA
			String embedPath = "DocA";
			saveDoc.insertDocument(embeddedDoc, embedPath);
			saveDoc.save(TEST_FILE_FOLDER + TEST_FILE_MODIFIED_EMBEDDED);
			saveDoc.close();
			//reload TestModifiedEmbeddedDoc.odt
			saveDoc = OdfDocument.loadDocument(ResourceUtilities.getAbsolutePath(TEST_FILE_MODIFIED_EMBEDDED));
			embeddedDoc = saveDoc.loadSubDocument(embedPath);
			//check the content of "DocA" and modify it again
			embedContentDom = embeddedDoc.getContentDom();
			header = (TextHElement) xpath.evaluate("//text:h[1]", embedContentDom, XPathConstants.NODE);
			Assert.assertTrue(header.getTextContent().contains(TEST_SPAN_TEXT));
			header.setTextContent("");
			String packagePath = embeddedDoc.getDocumentPath() + SLASH + OdfPackage.OdfFile.IMAGE_DIRECTORY.getPath() + SLASH + TEST_PIC;
			OdfFileEntry imageEntry = embeddedDoc.getPackage().getFileEntry(packagePath);
			Assert.assertNotNull(imageEntry);
			embeddedDoc.newImage(ResourceUtilities.getURI(TEST_PIC_ANOTHER));
			//save the "DocA" as the standalone document
			embeddedDoc.save(TEST_FILE_FOLDER + TEST_FILE_MODIFIED_EMBEDDED_SAVE_STANDALONE);
			//load the standalone document and check the content
			OdfDocument standaloneDoc = OdfDocument.loadDocument(ResourceUtilities.getAbsolutePath(TEST_FILE_MODIFIED_EMBEDDED_SAVE_STANDALONE));
			embedContentDom = standaloneDoc.getContentDom();
			header = (TextHElement) xpath.evaluate("//text:h[1]", embedContentDom, XPathConstants.NODE);
			Assert.assertTrue(header.getTextContent().length() == 0);
			imageEntry = standaloneDoc.getPackage().getFileEntry(OdfPackage.OdfFile.IMAGE_DIRECTORY.getPath() + SLASH + TEST_PIC);
			Assert.assertNotNull(imageEntry);
			OdfFileEntry anotherImageEntry = standaloneDoc.getPackage().getFileEntry(OdfPackage.OdfFile.IMAGE_DIRECTORY.getPath() + SLASH + TEST_PIC_ANOTHER);
			Assert.assertNotNull(anotherImageEntry);
		} catch (Exception ex) {
			LOG.log(Level.SEVERE, null, ex);
			Assert.fail("Failed with " + ex.getClass().getName() + ": '" + ex.getMessage() + "'");
		}
	}

	@Test
	public void testRemoveEmbeddedDocument() {
		try {
			OdfDocument doc = OdfDocument.loadDocument(ResourceUtilities.getAbsolutePath(TEST_FILE_EMBEDDED));
			Map<String, OdfDocument> embeddedDocs = doc.loadSubDocuments();
			List<String> subDocNames = new ArrayList<String>();
			for (String childDocPath : embeddedDocs.keySet()) {
				OdfDocument childDoc = embeddedDocs.get(childDocPath);
				Assert.assertNotNull(childDoc);
				String embedFileName = childDoc.getDocumentPath();
				subDocNames.add(embedFileName);
				doc.removeDocument(embedFileName);
			}
			doc.save(TEST_FILE_FOLDER + TEST_FILE_REMOVE_EMBEDDED_SAVE_OUT);
			doc.close();
			//check manifest entry for the embed document
			//the sub entry of the embed document such as the pictures of the document should also be removed
			doc = OdfDocument.loadDocument(ResourceUtilities.getAbsolutePath(TEST_FILE_REMOVE_EMBEDDED_SAVE_OUT));
			Map<String, OdfDocument> reloadedSubDocs = doc.loadSubDocuments();
			Assert.assertTrue(0 == reloadedSubDocs.size());
			Set<String> entries = doc.getPackage().getFilePaths();
			Iterator<String> entryIter = null;
			for (int i = 0; i < subDocNames.size(); i++) {
				entryIter = entries.iterator();
				String embeddedDocPath = subDocNames.get(i);
				while (entryIter.hasNext()) {
					String entry = entryIter.next();
					Assert.assertFalse(entry.startsWith(embeddedDocPath));
				}
			}
			doc.close();
		} catch (Exception ex) {
			LOG.log(Level.SEVERE, null, ex);
			Assert.fail("Failed with " + ex.getClass().getName() + ": '" + ex.getMessage() + "'");
		}
	}

	private void addFrameForEmbeddedDoc(OdfFileDom dom, TextPElement para, String path) throws Exception {
		OdfDrawFrame drawFrame = new OdfDrawFrame(dom);
		drawFrame.setDrawNameAttribute(path);
		drawFrame.setTextAnchorTypeAttribute(TextAnchorTypeAttribute.Value.PARAGRAPH.toString());
		drawFrame.setSvgXAttribute("0.834cm");
		drawFrame.setSvgYAttribute("2.919cm");
		drawFrame.setSvgWidthAttribute("13.257cm");
		drawFrame.setSvgHeightAttribute("11.375cm");
		drawFrame.setDrawZIndexAttribute(0);

		DrawObjectElement object = new DrawObjectElement(dom);

		object.setXlinkHrefAttribute(path);
		object.setXlinkActuateAttribute("onLoad");
		object.setXlinkShowAttribute("embed");
		object.setXlinkTypeAttribute("simple");
		drawFrame.appendChild(object);
		para.appendChild(drawFrame);
	}

	private void updateFrameForEmbeddedDoc(OdfFileDom dom, String originPath, String newPath) throws Exception {
		NodeList objNodes = dom.getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "object");
		for (int i = 0; i < objNodes.getLength(); i++) {
			OdfElement object = (OdfElement) objNodes.item(i);
			String refObjPath = object.getAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href");
			if (refObjPath.equals(originPath)) {
				object.setAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href", newPath);
			}
		}
	}
}
