/******************************************************************************
 * Product: Adempiere ERP & CRM Smart Business Solution                       *
 * Copyright (C) 1999-2006 Adempiere, Inc. All Rights Reserved.               *
 * This program is free software; you can redistribute it and/or modify it    *
 * under the terms version 2 of the GNU General Public License as published   *
 * by the Free Software Foundation. This program 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 General Public License for more details.                       *
 * You should have received a copy of the GNU General Public License along    *
 * with this program; if not, write to the Free Software Foundation, Inc.,    *
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.                     *
 *                                                                            *
 * Copyright (C)                                                              *
 * 2004 Robert KLEIN. robeklein@hotmail.com                                   *
 * Contributor(s): Low Heng Sin hengsin@avantz.com                            *
 *                 Teo Sarca teo.sarca@arhipac.ro, SC ARHIPAC SERVICE SRL     *
 *****************************************************************************/
package org.adempiere.pipo2;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import org.compiere.model.MClient;
import org.compiere.model.MSysConfig;
import org.compiere.model.MTable;
import org.compiere.tools.FileUtil;
import org.compiere.util.CLogger;
import org.compiere.util.Trx;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.adempiere.exceptions.AdempiereException;
/**
 *	Convert AD to XML
 *
 *  @author Robert Klein
 *  @version $Id: PackOut.java,v 1.0
 *
 * Contributor: William G. Heath - Export of workflows and dynamic validations
 *
 * @author Teo Sarca, SC ARHIPAC SERVICE SRL
 * 			
BF [ 1819315 ] PackOut: fix xml indentation not working
 * 			BF [ 1819319 ] PackOut: use just active AD_Package_Exp_Detail lines
 */
public class PackOut
{
	/*** 1.0.0 **/
	public final static String PackOutVersion = "100";
	private final static CLogger log = CLogger.getCLogger(PackOut.class);
	private String packageDirectory;
	private int blobCount = 0;
	private IHandlerRegistry handlerRegistry = new OSGiHandlerRegistry();
	private PackoutItem packoutItem;
	private String trxName;
	private PackoutDocument packoutDocument;
	private int processedCount;
	private String exportFile;
	private String packoutDirectory;
	private PIPOContext pipoContext = new PIPOContext();
	private Timestamp fromDate;
	private boolean isExportDictionaryEntity = false;
	public static final int MAX_OFFICIAL_ID = MTable.MAX_OFFICIAL_ID;
	public static final String PACKOUT_BLOB_FILE_EXTENSION = ".dat";
	public static void addTextElement(TransformerHandler handler, String qName, String text, AttributesImpl atts) throws SAXException {
		handler.startElement("", "", qName, atts);
		append(handler, text);
		handler.endElement("", "", qName);
	}
	private static void append(TransformerHandler handler, String str) throws SAXException
	{
		char[] contents = str != null ? str.toCharArray() : new char[0];
		handler.characters(contents,0,contents.length);
	}
	/**
	 * Start the transformation to XML
	 * @param packoutDirectory
	 * @param destinationPath
	 * @param packoutDocument
	 * @param packoutItems
	 * @param trxName
	 * @throws java.lang.Exception
	 */
	public void export(String packoutDirectory, String destinationPath, PackoutDocument packoutDocument, List packoutItems, String trxName) throws java.lang.Exception
	{
		this.packoutDirectory = packoutDirectory;
		this.packoutDocument = packoutDocument;
		this.trxName = trxName;
		initContext();
		OutputStream  docStream = null;
		OutputStream  packoutStream = null;
		processedCount = 0;
		try {
			packageDirectory = packoutDirectory+ packoutDocument.getPackageName();
			File docDirectoryFile = new File(packageDirectory+File.separator+"doc"+File.separator);
			if (!docDirectoryFile.exists()) {
				boolean success = docDirectoryFile.mkdirs();
				if (!success) {
					throw new AdempiereException("Failed to create directory for pack out. " + packageDirectory+File.separator+"doc"+File.separator);
				}
			}
			String docFileName = packageDirectory+File.separator+"doc"+File.separator+packoutDocument.getPackageName()+"Doc.xml";
			docStream = new FileOutputStream (docFileName, false);
			TransformerHandler docHandler = createDocHandler(docStream);
			String packoutFileName = packageDirectory+File.separator+ "dict"+File.separator+"PackOut.xml";
			packoutStream = new FileOutputStream (packoutFileName, false);
			TransformerHandler packoutHandler = createPackoutHandler(packoutStream);
			for(PackoutItem packoutItem : packoutItems){
				this.packoutItem = packoutItem;
				String type = packoutItem.getType();
				ElementHandler handler = handlerRegistry.getHandler(type);
				if (handler != null)
					handler.packOut(this,packoutHandler,docHandler,packoutItem.getRecordId(),packoutItem.getUUID());
				else
					throw new IllegalArgumentException("Packout handler not found for type " + type);
				processedCount++;
			}
			packoutHandler.endElement("","","idempiere");
			packoutHandler.endDocument();
			docHandler.endElement("","","idempiereDocument");
			docHandler.endDocument();
		}
		catch (Exception e)
		{
			log.log(Level.SEVERE,e.getLocalizedMessage(), e);
			throw e;
		}
		finally
		{
			// Close streams - teo_sarca [ 1704762 ]
			if (docStream != null)
				try {
					docStream.close();
				} catch (Exception e) {}
			if (packoutStream != null)
				try {
					packoutStream.close();
				} catch (Exception e) {}
		}
		//create compressed packages
		//set the files
		File srcFolder = new File(packoutDirectory);
		File destZipFile = null;
		if (destinationPath != null && destinationPath.trim().length() > 0)
		{
			destZipFile = new File(destinationPath);
		} else {
			destZipFile = new File(packageDirectory+".zip");
		}
		//delete the old packages if necessary
		destZipFile.delete();
		//create the compressed packages
		String includesdir = packoutDocument.getPackageName() + File.separator +"**";
		Zipper.zipFolder(srcFolder, destZipFile, includesdir);
		exportFile = destZipFile.getAbsolutePath();
		FileUtil.deleteFolderRecursive(new File(packageDirectory));
	}	//	doIt
	private TransformerHandler createPackoutHandler(
			OutputStream packoutStream) throws UnsupportedEncodingException, TransformerConfigurationException, SAXException {
		StreamResult packoutStreamResult = new StreamResult(new OutputStreamWriter(packoutStream,"UTF-8"));
		SAXTransformerFactory packoutFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
		//indent-number attribute support is not guarantee
		try {
			packoutFactory.setAttribute("indent-number", Integer.valueOf(4));
		} catch (Exception e) {}
		TransformerHandler packoutHandler = packoutFactory.newTransformerHandler();
		Transformer packoutTransformer = packoutHandler.getTransformer();
		packoutTransformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
		packoutTransformer.setOutputProperty(OutputKeys.INDENT,"yes");
		//indent-amount property support is not guarantee
		try {
			packoutTransformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount","4");
		} catch (Exception e) {}
		packoutHandler.setResult(packoutStreamResult);
		packoutHandler.startDocument();
		AttributesImpl atts = new AttributesImpl();
		atts.addAttribute("","","Name","CDATA",packoutDocument.getPackageName());
		atts.addAttribute("","","Version","CDATA",packoutDocument.getPackageVersion());
		atts.addAttribute("","","idempiereVersion","CDATA",emptyIfNull(packoutDocument.getAdempiereVersion()));
		atts.addAttribute("","","DataBaseVersion","CDATA",emptyIfNull(packoutDocument.getDatabaseVersion()));
		atts.addAttribute("","","Description","CDATA",emptyIfNull(packoutDocument.getDescription()));
		atts.addAttribute("","","Author","CDATA",emptyIfNull(packoutDocument.getAuthor()));
		atts.addAttribute("","","AuthorEmail","CDATA",emptyIfNull(packoutDocument.getAuthorEmail()));
		atts.addAttribute("","","CreatedDate","CDATA",packoutDocument.getCreated().toString());
		atts.addAttribute("","","UpdatedDate","CDATA",packoutDocument.getUpdated().toString());
		atts.addAttribute("","","PackOutVersion","CDATA",PackOutVersion);
		atts.addAttribute("","","UpdateDictionary","CDATA", isExportDictionaryEntity ? "true" : "false");
		MClient client = MClient.get(pipoContext.ctx);
		StringBuilder sb = new StringBuilder ()
			.append(client.get_ID())
			.append("-")
			.append(client.getValue())
			.append("-")
			.append(client.getName());
		atts.addAttribute("", "", "Client", "CDATA", sb.toString());
		if (client.getAD_Client_UU() == null)
			throw new IllegalStateException("2Pack requires UUID on AD_Client");
		atts.addAttribute("", "", "AD_Client_UU", "CDATA", client.getAD_Client_UU());
		packoutHandler.startElement("","","idempiere",atts);
		return packoutHandler;
	}
	private String emptyIfNull(String input) {
		return input != null ? input : "";
	}
	private TransformerHandler createDocHandler(OutputStream docStream) throws UnsupportedEncodingException, TransformerConfigurationException, SAXException {
		StreamResult docStreamResult = new StreamResult(new OutputStreamWriter(docStream,"UTF-8"));
		SAXTransformerFactory transformerFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
		//indent-number attribute support is not guarantee
		try {
			transformerFactory.setAttribute("indent-number", Integer.valueOf(4));
		} catch (Exception e) {}
		TransformerHandler docHandler = transformerFactory.newTransformerHandler();
		Transformer transformer = docHandler.getTransformer();
		transformer.setOutputProperty(OutputKeys.ENCODING,"UTF-8");
		transformer.setOutputProperty(OutputKeys.INDENT,"yes");
		//indent-amount property support is not guarantee
		try {
			transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount","4");
		} catch (Exception e) {}
		docHandler.setResult(docStreamResult);
		docHandler.startDocument();
		docHandler.processingInstruction("xml-stylesheet","type=\"text/css\" href=\"idempiereDocument.css\"");
		AttributesImpl atts = new AttributesImpl();
		docHandler.startElement("","","idempiereDocument",atts);
		addTextElement(docHandler, "header", packoutDocument.getPackageName()+" Package Description", atts);
		addTextElement(docHandler, "H1", "Package Name:", atts);
		addTextElement(docHandler, "packagename", packoutDocument.getPackageName(), atts);
		addTextElement(docHandler, "H1", "Author:", atts);
		addTextElement(docHandler, "Name:", packoutDocument.getAuthor(), atts);
		addTextElement(docHandler, "H1", "Email Address:", atts);
		addTextElement(docHandler, "Email", packoutDocument.getAuthorEmail(), atts);
		addTextElement(docHandler, "H1", "Created:", atts);
		addTextElement(docHandler, "Date", packoutDocument.getCreated().toString(), atts);
		addTextElement(docHandler, "H1", "Updated:", atts);
		addTextElement(docHandler, "Date", packoutDocument.getUpdated().toString(), atts);
		addTextElement(docHandler, "H1", "Description:", atts);
		addTextElement(docHandler, "description", packoutDocument.getDescription(), atts);
		addTextElement(docHandler, "H1", "Instructions:", atts);
		addTextElement(docHandler, "instructions", packoutDocument.getInstructions(), atts);
		addTextElement(docHandler, "H1", "Files in Package:", atts);
		addTextElement(docHandler, "file", "File: PackOut.xml", atts);
		addTextElement(docHandler, "filedirectory", "Directory: \\dict\\", atts);
		addTextElement(docHandler, "filenotes", "Notes: Contains all application/object settings for package", atts);
		MClient client = MClient.get(pipoContext.ctx);
		StringBuilder sb = new StringBuilder ()
			.append(client.get_ID())
			.append("-")
			.append(client.getValue())
			.append("-")
			.append(client.getName());
		addTextElement(docHandler, "H1", "Client:", atts);
		addTextElement(docHandler, "Client", sb.toString(), atts);
		File packageDictDirFile = new File(packageDirectory+File.separator+ "dict"+File.separator);
		if (!packageDictDirFile.exists()) {
			boolean success = packageDictDirFile.mkdirs();
			if (!success)
				throw new AdempiereException("Failed to create directory. " + packageDirectory+File.separator+ "dict"+File.separator);
		}
		return docHandler;
	}
	private void initContext() {
		if (trxName != null)
			pipoContext.trx = Trx.get(trxName, true);
		pipoContext.ctx.setProperty("isHandleTranslations", MSysConfig.getValue(MSysConfig.TWOPACK_HANDLE_TRANSLATIONS));
		pipoContext.packOut = this;
	}
	/**
	 * @param sourceName
	 * @param destName
	 */
	public void copyFile (String sourceName, String destName ) {
		InputStream source = null;  // Stream for reading from the source file.
		OutputStream copy= null;   // Stream for writing the copy.
		boolean force;  // This is set to true if the "-f" option
		//    is specified on the command line.
		int byteCount;  // Number of bytes copied from the source file.
		force = true;
		try {
			source = new FileInputStream(sourceName);
		} catch (FileNotFoundException e) {
			System.out.println("Can't find file \"" + sourceName + "\".");
			return;
		}
		try {
			File file = new File(destName);
			if (file.exists() && force == false) {
				System.out.println("Output file exists. Use the -f option to replace it.");
				return;
			}
			try {
				copy = new FileOutputStream(destName, false);
			} catch (IOException e) {
				System.out.println("Can't open output file \""
						+ destName + "\".");
				return;
			}
			byteCount = 0;
			try {
				while (true) {
					int data = source.read();
					if (data < 0)
						break;
					copy.write(data);
					byteCount++;
				}
				source.close();
				copy.close();
				System.out.println("Successfully copied " + byteCount + " bytes.");
			} catch (Exception e) {
				System.out.println("Error occurred while copying.  "+ byteCount + " bytes copied.");
				System.out.println(e.toString());
			}
		} finally {
			if (source != null) {
				try {
					source.close();
				} catch (IOException e) {}
			}
			if (copy != null) {
				try {
					copy.close();
				} catch (IOException e) {}
			}
		}
	}
	public PIPOContext getCtx() {
		return pipoContext;
	}
	/**
	 * @param data
	 * @return
	 * @throws IOException
	 */
	public String writeBlob(byte[] data) throws IOException {
		blobCount++;
		String fileName = blobCount + PACKOUT_BLOB_FILE_EXTENSION;
		File path = new File(packageDirectory+File.separator+"blobs"+File.separator);
		path.mkdirs();
		File file = new File(path, fileName);
		FileOutputStream os = null;
		try {
			os = new FileOutputStream(file);
			os.write(data);
			os.flush();
		} finally {
			if (os != null) {
				try {
					os.close();
				} catch (IOException e) {}
			}
		}
		return fileName;
	}
	/**
	 * @return MPackageExpDetail
	 */
	public PackoutItem getCurrentPackoutItem() {
		return packoutItem;
	}
	/**
	 *
	 * @return PackoutDocument
	 */
	public PackoutDocument getPackoutDocument() {
		return packoutDocument;
	}
	public String getPackoutDirectory() {
		return packoutDirectory;
	}
	/**
	 * @param name
	 * @return ElementHandler
	 */
	public ElementHandler getHandler(String name) {
		return handlerRegistry.getHandler(name);
	}
	/**
	 * @return number of records exported
	 */
	public int getExportCount() {
		return processedCount;
	}
	/**
	 * @return absolute path for export file
	 */
	public String getExportFile() {
		return exportFile;
	}
	/**
	 * @param fromDate
	 */
	public void setFromDate(Timestamp fromDate) {
		this.fromDate = fromDate;
	}
	/**
	 * @return from date
	 */
	public Timestamp getFromDate() {
		return fromDate;
	}
	/**
	 * @param ctx
	 */
	public void setCtx(Properties ctx) {
		pipoContext.ctx = ctx;
	}
	private List processedRecords = new ArrayList();
	public boolean isExported(String key) {
		if (processedRecords.contains(key))
			return true;
		processedRecords.add(key);
		return false;
	}
	public boolean isExportDictionaryEntity() {
		return isExportDictionaryEntity;
	}
	public void setExportDictionaryEntity(boolean isExportDictionaryEntity) {
		this.isExportDictionaryEntity = isExportDictionaryEntity;
	}
}	//	PackOut