/******************************************************************************
 * Copyright (C) 2008 Low Heng Sin  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.                     *
 * For the text or an alternative of this public license, you may reach us    *
 * Posterita Ltd., 3, Draper Avenue, Quatre Bornes, Mauritius                 *
 * or via info@posterita.org or http://www.posterita.org/                     *
 *****************************************************************************/

package org.adempiere.webui.panel;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;

import javax.activation.FileDataSource;

import org.adempiere.exceptions.AdempiereException;
import org.adempiere.util.Callback;
import org.adempiere.webui.AdempiereWebUI;
import org.adempiere.webui.ClientInfo;
import org.adempiere.webui.Extensions;
import org.adempiere.webui.apps.AEnv;
import org.adempiere.webui.component.Button;
import org.adempiere.webui.component.ConfirmPanel;
import org.adempiere.webui.component.Label;
import org.adempiere.webui.component.ListItem;
import org.adempiere.webui.component.Listbox;
import org.adempiere.webui.component.Panel;
import org.adempiere.webui.component.Textbox;
import org.adempiere.webui.component.ToolBar;
import org.adempiere.webui.component.Window;
import org.adempiere.webui.event.DialogEvents;
import org.adempiere.webui.factory.ButtonFactory;
import org.adempiere.webui.session.SessionManager;
import org.adempiere.webui.theme.ThemeManager;
import org.adempiere.webui.util.ZKUpdateUtil;
import org.adempiere.webui.window.Dialog;
import org.adempiere.webui.window.WEMailDialog;
import org.adempiere.webui.window.WTextEditorDialog;
import org.compiere.model.MAttachment;
import org.compiere.model.MAttachmentEntry;
import org.compiere.model.MSysConfig;
import org.compiere.model.MTable;
import org.compiere.model.MUser;
import org.compiere.util.CLogger;
import org.compiere.util.Env;
import org.compiere.util.Msg;
import org.compiere.util.Util;
import org.idempiere.ui.zk.media.IMediaView;
import org.idempiere.ui.zk.media.Medias;
import org.zkoss.util.media.AMedia;
import org.zkoss.util.media.Media;
import org.zkoss.zk.au.out.AuEcho;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.UploadEvent;
import org.zkoss.zk.ui.ext.render.DynamicMedia;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.Borderlayout;
import org.zkoss.zul.Center;
import org.zkoss.zul.Filedownload;
import org.zkoss.zul.Hbox;
import org.zkoss.zul.Hlayout;
import org.zkoss.zul.Iframe;
import org.zkoss.zul.North;
import org.zkoss.zul.South;
import org.zkoss.zul.Vlayout;
import org.zkoss.zul.impl.Utils;
import org.zkoss.zul.impl.XulElement;

/**
 * Attachment window
 * @author Low Heng Sin
 *
 */
public class WAttachment extends Window implements EventListener<Event>
{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1041937899860394478L;

	private static final CLogger log = CLogger.getCLogger(WAttachment.class);

	/**	Window No				*/
	private int	m_WindowNo;

	/** Attachment				*/
	private MAttachment m_attachment = null;

	/** Change					*/
	private boolean m_change = false;

	private Iframe preview = new Iframe();

	protected AMedia media;
	private int mediaVersion = 0;

	private Textbox text = new Textbox();

	private Label sizeLabel = new Label();

	private Listbox cbContent = new Listbox();

	private Button bDelete = ButtonFactory.createNamedButton(ConfirmPanel.A_DELETE, false, true);
	private Button bSave = new Button();
	private Button bSaveAllAsZip = new Button();
	private Button bDeleteAll = new Button();
	private Button bLoad = new Button();
	private Button bCancel = ButtonFactory.createNamedButton(ConfirmPanel.A_CANCEL, false, true);
	private boolean bCancelClicked = false;
	private Button bOk = ButtonFactory.createNamedButton(ConfirmPanel.A_OK, false, true);
	private Button bPreview = new Button();
	private Button bEmail = new Button();

	private Panel previewPanel = new Panel();

	private Borderlayout mainPanel = new Borderlayout();

	private ToolBar toolBar = new ToolBar();

	private Hlayout confirmPanel = new Hlayout();

	private int displayIndex;

	private String orientation;

	private int maxPreviewSize;

	private Component customPreviewComponent;

	private static List<String> autoPreviewList;
	
	/* SysConfig USE_ESC_FOR_TAB_CLOSING */
	private boolean isUseEscForTabClosing = MSysConfig.getBooleanValue(MSysConfig.USE_ESC_FOR_TAB_CLOSING, false, Env.getAD_Client_ID(Env.getCtx()));

	static {
		autoPreviewList = new ArrayList<String>();
		autoPreviewList.add("image/jpeg");
		autoPreviewList.add("image/png");
		autoPreviewList.add("image/gif");
		autoPreviewList.add("text/plain");
		autoPreviewList.add("application/pdf");
		autoPreviewList.add("text/xml");
		autoPreviewList.add("application/json");
		// autoPreviewList.add("text/html"); IDEMPIERE-3980
	}

	/**
	 *	Constructor.
	 *	loads Attachment, if ID &lt;&gt; 0
	 *  @param WindowNo window no
	 *  @param AD_Attachment_ID attachment
	 *  @param AD_Table_ID table
	 *  @param Record_ID record key
	 *  @param trxName transaction
	 */
	@Deprecated
	public WAttachment(	int WindowNo, int AD_Attachment_ID,
						int AD_Table_ID, int Record_ID, String trxName)
	{
		this(WindowNo, AD_Attachment_ID, AD_Table_ID, Record_ID, null, trxName, (EventListener<Event>)null);
	}

	/**
	 *	Constructor.
	 *	loads Attachment, if ID &lt;&gt; 0
	 *  @param WindowNo window no
	 *  @param AD_Attachment_ID attachment
	 *  @param AD_Table_ID table
	 *  @param Record_ID record key
	 *  @param trxName transaction
	 *  @param eventListener
	 */
	@Deprecated
	public WAttachment(	int WindowNo, int AD_Attachment_ID,
			int AD_Table_ID, int Record_ID, String trxName, EventListener<Event> eventListener)
	{
		this(WindowNo, AD_Attachment_ID, AD_Table_ID, Record_ID, null, trxName, eventListener);
	}

	/**
	 *	Constructor.
	 *	loads Attachment, if ID &lt;&gt; 0
	 *  @param WindowNo window no
	 *  @param AD_Attachment_ID attachment
	 *  @param AD_Table_ID table
	 *  @param Record_ID record key
	 *  @param Record_UU record UUID
	 *  @param trxName transaction
	 *  @param eventListener listener for ON_WINDOW_CLOSE event
	 */
	public WAttachment(	int WindowNo, int AD_Attachment_ID,
						int AD_Table_ID, int Record_ID, String Record_UU, String trxName, EventListener<Event> eventListener)
	{
		super();
		maxPreviewSize = MSysConfig.getIntValue(MSysConfig.ZK_MAX_ATTACHMENT_PREVIEW_SIZE, 1048576, Env.getAD_Client_ID(Env.getCtx()));

		if (log.isLoggable(Level.CONFIG)) log.config("ID=" + AD_Attachment_ID + ", Table=" + AD_Table_ID + ", Record=" + Record_ID + ", RecordUU=" + Record_UU);

		m_WindowNo = WindowNo;
		this.addEventListener(DialogEvents.ON_WINDOW_CLOSE, this);
		if (eventListener != null) 
		{
			this.addEventListener(DialogEvents.ON_WINDOW_CLOSE, eventListener);
		}
		
		try
		{
			staticInit();
		}
		catch (Exception ex)
		{
			log.log(Level.SEVERE, "", ex);
		}

		//	Create Model

		if (AD_Attachment_ID > 0)
			m_attachment = new MAttachment (Env.getCtx(), AD_Attachment_ID, trxName);
		else
			m_attachment = new MAttachment (Env.getCtx(), AD_Table_ID, Record_ID, Record_UU, trxName);

		loadAttachments();

		try
		{
			setAttribute(Window.MODE_KEY, Window.MODE_HIGHLIGHTED);
			AEnv.showWindow(this);
			preview.setVisible(false);
			autoPreview(0, true);
		}
		catch (Exception e)
		{
		}

		if (m_attachment.isReadOnly(false)) {
			toolBar.removeChild(bLoad);
			toolBar.removeChild(bDelete);
			confirmPanel.removeChild(bDeleteAll);
			text.setReadonly(true);
		}else {
			// If getUuid is called before the component is attached to page, it's considered a temporary value
			// when component attach to page uuid is re-generate and use as id of DOM element on client
			this.setWidgetOverride("_id_uploadButtonId", "'" + bLoad.getUuid() + "'");
			// set to whole attachment dialog become drop area
			this.setWidgetOverride("_id_isFileDragDropArea", "true");
		}
	} // WAttachment

	/**
	 *	layout window
	 *  <pre>
	 *  - northPanel
	 *      - toolBar
	 *      - title
	 *  - centerPane [split]
	 * 		- previewPanel (left)
	 *  	- text (right)
	 *  - confirmPanel
	 *  </pre>
	 *  @throws Exception
	 */
	protected void staticInit() throws Exception
	{
		this.setAttribute(AdempiereWebUI.WIDGET_INSTANCE_NAME, "attachment");
		this.setMaximizable(true);
		if (!ThemeManager.isUseCSSForWindowSize())
		{
			ZKUpdateUtil.setWindowWidthX(this, 700);
			ZKUpdateUtil.setHeight(this, "85%");
		}
		else
		{
			addCallback(AFTER_PAGE_ATTACHED, t -> {
				ZKUpdateUtil.setCSSHeight(this);
				ZKUpdateUtil.setCSSWidth(this);
			});
		}
		this.setTitle(Msg.getMsg(Env.getCtx(), "Attachment"));
		this.setClosable(true);
		this.setSizable(true);
		this.setBorder("normal");
		this.setSclass("popup-dialog attachment-dialog");
		this.setShadow(true);
		this.appendChild(mainPanel);
		ZKUpdateUtil.setHeight(mainPanel, "100%");
		ZKUpdateUtil.setWidth(mainPanel, "100%");

		North northPanel = new North();
		northPanel.setCollapsible(false);
		northPanel.setSplittable(false);

		cbContent.setMold("select");
		cbContent.setRows(0);
		cbContent.addEventListener(Events.ON_SELECT, this);

		toolBar.setAlign("center");
		toolBar.setOverflowPopup(true);
		toolBar.appendChild(bLoad);
		toolBar.appendChild(bDelete);
		toolBar.appendChild(bSave);
		toolBar.appendChild(bSaveAllAsZip);
		toolBar.appendChild(bEmail);
		toolBar.appendChild(cbContent);
		toolBar.appendChild(sizeLabel);

		mainPanel.appendChild(northPanel);
		
		Vlayout div = new Vlayout();
		div.appendChild(toolBar);
		text.setRows(3);
		ZKUpdateUtil.setHflex(text, "1");
		
		div.appendChild(text);
		northPanel.appendChild(div);

		bSave.setEnabled(false);
		bSave.setSclass("img-btn");
		if (ThemeManager.isUseFontIconForImage())
			bSave.setIconSclass("z-icon-Export");
		else
			bSave.setImage(ThemeManager.getThemeResource("images/Export24.png"));
		bSave.setTooltiptext(Msg.getMsg(Env.getCtx(), "AttachmentSave"));
		bSave.addEventListener(Events.ON_CLICK, this);

		bSaveAllAsZip.setEnabled(false);
		bSaveAllAsZip.setSclass("img-btn");
		if (ThemeManager.isUseFontIconForImage())
			bSaveAllAsZip.setIconSclass("z-icon-file-zip-o");
		else
			bSaveAllAsZip.setImage(ThemeManager.getThemeResource("images/SaveAsZip24.png"));
		bSaveAllAsZip.setTooltiptext(Msg.getMsg(Env.getCtx(), "ExportZIP"));
		bSaveAllAsZip.addEventListener(Events.ON_CLICK, this);

		if (ThemeManager.isUseFontIconForImage())
			bLoad.setIconSclass("z-icon-Import");
		else
			bLoad.setImage(ThemeManager.getThemeResource("images/Import24.png"));
		bLoad.setSclass("img-btn");
		bLoad.setId("bLoad");
		bLoad.setTooltiptext(Msg.getMsg(Env.getCtx(), "Load"));
		bLoad.setUpload("multiple=true," + AdempiereWebUI.getUploadSetting());
		bLoad.addEventListener(Events.ON_UPLOAD, this);

		bDelete.addEventListener(Events.ON_CLICK, this);

		bEmail.setEnabled(false);
		if (ThemeManager.isUseFontIconForImage())
			bEmail.setIconSclass("z-icon-SendMail");
		else
			bEmail.setImage(ThemeManager.getThemeResource("images/SendMail24.png"));
		bLoad.setSclass("img-btn");
		bEmail.setTooltiptext(Msg.getMsg(Env.getCtx(), "EMail"));
		bEmail.addEventListener(Events.ON_CLICK, this);

		previewPanel.appendChild(preview);
		ZKUpdateUtil.setHeight(preview, "99%");
		ZKUpdateUtil.setWidth(preview, "99%");
		
		Center centerPane = new Center();
		centerPane.setSclass("dialog-content");
		mainPanel.appendChild(centerPane);
		centerPane.appendChild(previewPanel);
		ZKUpdateUtil.setVflex(previewPanel, "1");
		ZKUpdateUtil.setHflex(previewPanel, "1");

		South southPane = new South();
		southPane.setSclass("dialog-footer");
		mainPanel.appendChild(southPane);
		southPane.appendChild(confirmPanel);
		ZKUpdateUtil.setVflex(southPane, "min");

		bCancel.addEventListener(Events.ON_CLICK, this);
		bOk.addEventListener(Events.ON_CLICK, this);

		if (ThemeManager.isUseFontIconForImage())
			bDeleteAll.setIconSclass("z-icon-Delete");
		else
			bDeleteAll.setImage(ThemeManager.getThemeResource("images/Delete24.png"));
		bDeleteAll.setSclass("img-btn");
		bDeleteAll.addEventListener(Events.ON_CLICK, this);
		bDeleteAll.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "DeleteAll")));

		if (ThemeManager.isUseFontIconForImage())
			bPreview.setIconSclass("z-icon-Find");
		else
			bPreview.setImage(ThemeManager.getThemeResource("images/Find24.png"));
		bPreview.setSclass("img-btn");
		bPreview.addEventListener(Events.ON_CLICK, this);
		bPreview.setTooltiptext(Msg.getMsg(Env.getCtx(), "Preview"));

		confirmPanel.appendChild(bDeleteAll);
		confirmPanel.appendChild(bPreview);
		ZKUpdateUtil.setHflex(confirmPanel, "1");
		Hbox hbox = new Hbox();
		hbox.setPack("end");
		ZKUpdateUtil.setHflex(hbox, "1");
		confirmPanel.appendChild(hbox);
		hbox.appendChild(bOk);
		hbox.appendChild(bCancel);
		

		text.setTooltiptext(Msg.getElement(Env.getCtx(), "TextMsg"));
		
		if (ClientInfo.isMobile())
		{
			orientation = ClientInfo.get().orientation;
			ClientInfo.onClientInfo(this, this::onClientInfo);
		}
		addEventListener(Events.ON_CANCEL, e -> onCancel());
	}
	
	/**
	 * Handle onClientInfo event
	 */
	protected void onClientInfo()
	{		
		if (getPage() != null)
		{
			String newOrienation = ClientInfo.get().orientation;
			if (!newOrienation.equals(orientation))
			{
				orientation = newOrienation;
				ZKUpdateUtil.setCSSHeight(this);
				ZKUpdateUtil.setCSSWidth(this);
				invalidate();
			}
		}
	}

	/**
	 * Dispose
	 */
	public void dispose ()
	{
		preview = null;
		this.detach();
	} // dispose

	/**
	 * Load Attachment items
	 */
	private void loadAttachments()
	{
		if (log.isLoggable(Level.CONFIG))
			log.config("");

		//	Set Text/Description

		String sText = m_attachment.getTextMsg();

		if (sText == null)
			text .setText("");
		else
			text.setText(sText);

		//	Set Combo

		int size = m_attachment.getEntryCount();

		for (int i = 0; i < size; i++)
			cbContent.appendItem(m_attachment.getEntryName(i), m_attachment.getEntryName(i));

		if (size > 0)
		{
			cbContent.setSelectedIndex(0);
		}

	} // loadAttachment

	/**
	 * auto preview attachment item
	 * @param index
	 * @param immediate
	 * @return true if preview is available for attachment item
	 */
	private boolean autoPreview(int index, boolean immediate)
	{
		MAttachmentEntry entry = m_attachment.getEntry(index);
		if (entry != null)
		{
			String mimeType = entry.getContentType();
			byte[] data = entry.getData();
			String unit = " KB";
			BigDecimal size = new BigDecimal(data != null ? data.length : 0);
			size = size.divide(new BigDecimal("1024"));
			if (size.compareTo(new BigDecimal("1024")) >= 0)
			{
				size = size.divide(new BigDecimal("1024"));
				unit = " MB";
			}
			size = size.setScale(2, RoundingMode.HALF_EVEN);
			sizeLabel.setText(size.toPlainString() + unit);

			bSave.setEnabled(true);
			bSaveAllAsZip.setEnabled(true);
			bDelete.setEnabled(true);
			bEmail.setEnabled(true);

			if (autoPreviewList.contains(mimeType))
			{
				if (data.length <= maxPreviewSize) {
					displayData(index, immediate);
				} else {
					clearPreview();
					String msg = WTextEditorDialog.sanitize(Msg.getMsg(Env.getCtx(), "FileTooBigForPreview"));
					Media media = new AMedia(null, null, "text/html", msg.getBytes());
					preview.setContent(media);
					preview.setVisible(true);
					bPreview.setEnabled(true);
					return false;
				}
				return true;
			}
			else
			{
				clearPreview();
				IMediaView view = Extensions.getMediaView(mimeType, getExtension(entry.getName()), ClientInfo.isMobile());
				if (view != null) 
				{
					if (data.length <= maxPreviewSize) {
						media = new AMedia(entry.getName(), null, mimeType, entry.getData());
						try {
							customPreviewComponent = view.renderMediaView(previewPanel, media, true);
						} catch (Exception e) {
							log.warning("Error previewing file in attachment entry " + entry.getName() + " -> " + e.getLocalizedMessage());
							e.printStackTrace();
							clearPreview();
							String msg = WTextEditorDialog.sanitize(Msg.getMsg(Env.getCtx(), "ErrorPreviewingFile"));
							Media mediaErr = new AMedia(null, null, "text/html", msg.getBytes());
							preview.setContent(mediaErr);
							preview.setVisible(true);
							bPreview.setEnabled(true);
							return false;
						}
						return true;
					} else {
						return false;
					}
				}
				
				return false;
			}
		}
		else
		{
			bSave.setEnabled(false);
			bSaveAllAsZip.setEnabled(false);
			bDelete.setEnabled(false);
			sizeLabel.setText("");
			bEmail.setEnabled(false);
			return false;
		}
	}

	/**
	 * Get file extension
	 * @param name
	 * @return file extension or empty string
	 */
	private String getExtension(String name) {
		int index = name.lastIndexOf(".");
		if (index > 0) {
			return name.substring(index+1);
		}
		return "";
	}

	/**
	 *  Display attachment item
	 * 	@param index index
	 */

	private void displayData (int index, boolean immediate)
	{
		//	Reset UI
		preview.setSrc(null);

		displayIndex = index;

		if (immediate)
			displaySelected();
		else
			Clients.response(new AuEcho(this, "displaySelected", null));
		bPreview.setEnabled(false);
	}   //  displayData

	/**
	 * Clear preview content ({@link #preview} and {@link #customPreviewComponent})
	 */
	private void clearPreview()
	{
		preview.setSrc(null);
		preview.setVisible(false);
		if (customPreviewComponent != null)
		{
			customPreviewComponent.detach();
			customPreviewComponent = null;
		}
	}

	/**
	 * Use to refresh preview frame, don't call directly.
	 */
	public void displaySelected() {
		MAttachmentEntry entry = m_attachment.getEntry(displayIndex);
		if (log.isLoggable(Level.CONFIG)) log.config("Index=" + displayIndex + " - " + entry);
		if (entry != null && entry.getData() != null && autoPreviewList.contains(entry.getContentType()))
		{
			if (log.isLoggable(Level.CONFIG)) log.config(entry.toStringX());

			try
			{
				String contentType = entry.getContentType();
				media = new AMedia(entry.getName(), null, contentType, entry.getData());
				if (   MSysConfig.getBooleanValue(MSysConfig.ZK_USE_PDF_JS_VIEWER, false, Env.getAD_Client_ID(Env.getCtx())) 
					&& Medias.PDF_MIME_TYPE.equals(contentType)) {
					mediaVersion++;
					String url = Utils.getDynamicMediaURI(this, mediaVersion, media.getName(), media.getFormat());	
					String pdfJsUrl = AEnv.toPdfJsUrl(url);
					preview.setSrc(pdfJsUrl);
				} else {
					preview.setContent(media);
				}
				preview.setVisible(true);
				preview.invalidate();
			}
			catch (Exception e)
			{
				log.log(Level.SEVERE, "attachment", e);
			}
		}
	}

	/**
	 * 	Get file Name by index
	 *	@param index index
	 *	@return file name or null
	 */
	private String getFileName (int index)
	{
		String fileName = null;

		if (index>=0 && cbContent.getItemCount() > index)
		{
			ListItem listitem = cbContent.getItemAtIndex(index);
			fileName = (String)listitem.getValue();
		}

		return fileName;
	}	//	getFileName

	/**
	 *	handle event
	 *  @param e event
	 */
	@Override
	public void onEvent(Event e)
	{
		//	Save and Close
		if (e instanceof UploadEvent) {
			preview.setVisible(false);
			UploadEvent ue = (UploadEvent) e;
			for (Media media : ue.getMedias()) {
				processUploadMedia(media);
			}
			clearPreview();
			autoPreview (cbContent.getSelectedIndex(), false);
		} else if (e.getTarget() == bOk || (!bCancelClicked && DialogEvents.ON_WINDOW_CLOSE.equals(e.getName()))) {
			if (m_attachment != null) {
				String newText = text.getText();
				if (newText == null)
					newText = "";
				String oldText = m_attachment.getTextMsg();
				if (oldText == null)
					oldText = "";

				if (!m_change)
					m_change = !newText.equals(oldText);

				if (newText.length() > 0 || m_attachment.getEntryCount() > 0) {
					if (m_change) {
						saveAttachment();
					}
				} else {
					m_attachment.delete(true);
					m_attachment = null;
				}

				dispose();
			}
		} else if (e.getTarget() == bCancel) {
			bCancelClicked = true;
			onCancel();
		} else if (e.getTarget() == bDeleteAll) {
			//	Delete Attachment
			deleteAttachment();
		} else if (e.getTarget() == bDelete) {
			//	Delete individual entry and Return
			deleteAttachmentEntry();
		} else if (e.getTarget() == cbContent) {
			//	Show Data
			clearPreview();
			autoPreview (cbContent.getSelectedIndex(), false);
		} else if (e.getTarget() == bSave) {
			//	Open Attachment
			exportAttachmentToFile();
		} else if (e.getTarget() == bPreview) {
			displayData(cbContent.getSelectedIndex(), true);
		} else if (e.getTarget() == bSaveAllAsZip) {
			exportAllAsZip();
		} else if(e.getTarget()==bEmail){
			sendMail();
		}

	}	//	onEvent

	/**
	 * Save the attachment to database
	 */
	private void saveAttachment() {
		m_attachment.setBinaryData(new byte[0]); // ATTENTION! HEAVY HACK HERE... Else it will not save :(
		m_attachment.setTextMsg(text.getText());
		m_attachment.saveEx();
		m_change = false;
	}

	/**
	 * Handle onCancel event
	 */
	private void onCancel() {		
		// do not allow to close tab for Events.ON_CTRL_KEY event
		if(isUseEscForTabClosing)
			SessionManager.getAppDesktop().setCloseTabWithShortcut(false);
		//	Cancel
		dispose();
	}

	/**
	 * Process uploaded media
	 * @param media
	 */
	private void processUploadMedia(Media media) {
		if (media != null && ((media.isBinary() && media.getByteData().length>0) || (!media.isBinary() && media.getStringData().length() > 0)))
		{
			;
		}
		else
		{
			preview.setVisible(true);
			preview.invalidate();
			return;
		}

		String fileName = media.getName();
		log.config(fileName);
		int cnt = m_attachment.getEntryCount();

		//update
		for (int i = 0; i < cnt; i++)
		{
			if (m_attachment.getEntryName(i).equals(fileName))
			{
				m_attachment.updateEntry(i, getMediaData(media));
				cbContent.setSelectedIndex(i);
				m_change = true;
				return;
			}
		}

		//new
		if (m_attachment.addEntry(fileName, getMediaData(media)))
		{
			cbContent.appendItem(media.getName(), media.getName());
			cbContent.setSelectedIndex(cbContent.getItemCount()-1);
			m_change = true;
		}
	}

	/**
	 * Get byte[] data from media
	 * @param media
	 * @return byte[] data
	 */
	private byte[] getMediaData(Media media)  {
		byte[] bytes = null;
		
		try {

			if (media.inMemory())
				bytes = media.isBinary() ? media.getByteData() : media.getStringData().getBytes(getCharset(media.getContentType()));
			else {
				InputStream is = null;
				try {
					is = media.getStreamData();
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					byte[] buf = new byte[1000];
					int byteread = 0;

					while ((byteread = is.read(buf)) != -1)
						baos.write(buf, 0, byteread);
					bytes = baos.toByteArray();
				} finally {
					if (is != null)
						is.close();
				}
			}
		} catch (IOException e) {
			log.log(Level.SEVERE, e.getLocalizedMessage(), e);
			throw new IllegalStateException(e.getLocalizedMessage());
		}

		return bytes;
	}

	/**
	 * Delete entire Attachment
	 */
	private void deleteAttachment()
	{
		if (log.isLoggable(Level.INFO))
			log.info("");

		Dialog.ask(m_WindowNo, "AttachmentDelete?", new Callback<Boolean>() {
			
			@Override
			public void onCallback(Boolean result) 
			{
				if (result)
				{
					if (m_attachment != null) {
						m_attachment.delete(true);
						m_attachment = null;
					}
					dispose();
				}					
			}
		});			
	}	//	deleteAttachment

	/**
	 * Delete current Attachment Entry
	 */
	private void deleteAttachmentEntry()
	{
		if (log.isLoggable(Level.INFO))
			log.info("");

		final int index = cbContent.getSelectedIndex();
		String fileName = getFileName(index);

		if (fileName == null)
			return;

		Dialog.ask(m_WindowNo, "AttachmentDeleteEntry?", new Callback<Boolean>() {

			@Override
			public void onCallback(Boolean result) 
			{
				if (result)
				{
					if (m_attachment.deleteEntry(index)) {
						// must save the attachment immediately, on external storage providers the file doesn't exist at this point
						saveAttachment();
						cbContent.removeItemAt(index);
						clearPreview();
						autoPreview (cbContent.getSelectedIndex(), true);
					}

				}				
			}
		});		
	}	//	deleteAttachment

	/**
	 * Export current Attachment entry to File
	 */
	private void exportAttachmentToFile()
	{
		int index = cbContent.getSelectedIndex();
		if (log.isLoggable(Level.INFO))
			log.info("index=" + index);

		if (m_attachment.getEntryCount() < index)
			return;

		MAttachmentEntry entry = m_attachment.getEntry(index);
		if (entry != null && entry.getData() != null)
		{
			try
			{
				media = new AMedia(entry.getName(), null, entry.getContentType(), entry.getData());
				Filedownload.save(media);
			}
			catch (Exception e)
			{
				log.log(Level.SEVERE, "attachment", e);
			}
		}
	}	//	saveAttachmentToFile
	
	/**
	 * Get charset from content type header. Fallback to UTF-8
	 * @param contentType
	 * @return charset
	 */
	static private String getCharset(String contentType) {
		if (contentType != null) {
			int j = contentType.indexOf("charset=");
			if (j >= 0) {
				String cs = contentType.substring(j + 8).trim();
				if (cs.length() > 0) return cs;
			}
		}
		return "UTF-8";
	}	

	/**
	 * Export all attachment items as zip file
	 */
	private void exportAllAsZip() {
		File zipFile = m_attachment.saveAsZip();
		
		if (zipFile != null) {
			String name = MTable.get(Env.getCtx(), m_attachment.getAD_Table_ID()).getTableName() + "_" + m_attachment.getRecord_ID();
			media = null;
			try {
				media = new AMedia(name, null, "application/zip", zipFile, true);
			} catch (Exception e) {
				throw new AdempiereException("Error when converting zip file to media : " + e);
			}			
			Filedownload.save(media);
		}
	}
	
	/**
	 * Email current attachment entry
	 */
	private void sendMail()
	{
		int index = cbContent.getSelectedIndex();

		MUser from = MUser.get(Env.getCtx(), Env.getAD_User_ID(Env.getCtx()));
		String fileName = System.getProperty("java.io.tmpdir") +
		System.getProperty("file.separator") + m_attachment.getEntryName(index);
		File attachment = new File(fileName);
		m_attachment.getEntryFile(index, attachment);

		WEMailDialog dialog = new WEMailDialog (Msg.getMsg(Env.getCtx(), "SendMail"),
			from, "", "", "", new FileDataSource(attachment),
			m_WindowNo, m_attachment.getAD_Table_ID(), m_attachment.getRecord_ID(), m_attachment.getRecord_UU(), null);

		AEnv.showWindow(dialog);
	}

	//-- ComponentCtrl --//
	public Object getExtraCtrl() {
		return new ExtraCtrl();
	}
	/** A utility class to implement {@link #getExtraCtrl}.
	 * It is used only by component developers.
	 */
	protected class ExtraCtrl extends XulElement.ExtraCtrl
	implements DynamicMedia {
		//-- DynamicMedia --//
		public Media getMedia(String pathInfo) {
			return media;
		}
	}

}
