/*******************************************************************************
 * This file is part of GECAMed.
 * 
 * GECAMed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (L-GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GECAMed 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 (L-GPL)
 * along with GECAMed.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * GECAMed is Copyrighted by the Centre de Recherche Public Henri Tudor (http://www.tudor.lu)
 * (c) CRP Henri Tudor, Luxembourg, 2008
 *******************************************************************************/
package lu.tudor.santec.gecamed.core.gui.widgets;

import ij.plugin.BrowserLauncher;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.UUID;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToggleButton;

import lu.tudor.santec.gecamed.core.gui.GECAMedColors;
import lu.tudor.santec.gecamed.core.gui.GECAMedModule;
import lu.tudor.santec.gecamed.core.gui.GECAMedOptionPane;
import lu.tudor.santec.gecamed.core.gui.MainFrame;
import lu.tudor.santec.gecamed.esante.ejb.session.beans.CDAManagerBean;
import lu.tudor.santec.gecamed.esante.ejb.session.interfaces.CDAManager;
import lu.tudor.santec.i18n.Translatrix;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * A General Error Dialog to show Errors and Exceptions. The details/exceptions
 * can be shown or hided by clicking the details button. It allows to save the
 * error details to disk, copy them to the clipboard or file an issue on the bug
 * tracker
 * 
 * @author Johannes Hermen johannes.hermen(at)tudor.lu
 * 
 * @Version <br>
 *          $Log: ErrorDialog.java,v $
 *          Revision 1.20  2013-12-10 11:54:54  ferring
 *          error dialog is by default not bigger than its parent
 *
 *          Revision 1.19  2013-10-15 10:27:42  donak
 *          Fixed missing type code filtering at upload metadata dialog start
 *          Fixed missing file content of error file in case of eSanté error
 *          Added repaint for history item to directly display "my DSP" if document was sucessfully uploaded
 *
 *          Revision 1.18  2013-10-09 12:14:06  donak
 *          Integration of the error dialog in the eSant� document upload process
 * Revision 1.17 2013-10-08 16:43:43 donak
 *          Adjusted ErrorDialog to allow user to copy error details to
 *          clipboard or save them to file system Changed order of code display
 *          names in CdaUpload dialog for desc. to asc. <br>
 *          Revision 1.16 2013-07-15 06:18:34 ferring <br>
 *          logging changed <br>
 * <br>
 *          Revision 1.15 2013-07-02 09:22:27 ferring <br>
 *          Browsing URL changed <br>
 * <br>
 *          Revision 1.14 2011-04-28 08:51:47 hermen <br>
 *          enhanced trac ticket generation <br>
 * <br>
 *          Revision 1.13 2010-05-20 08:21:56 hermen <br>
 *          small fixes <br>
 * <br>
 *          Revision 1.12 2010-04-26 12:44:35 hermen <br>
 *          small improvements <br>
 * <br>
 *          Revision 1.11 2008-09-25 09:43:06 heinemann <br>
 *          fixed copyrights <br>
 * <br>
 *          Revision 1.10 2008-01-15 09:29:38 hermen <br>
 *          updated Javadoc and refactured code <br>
 * <br>
 *          Revision 1.9 2007-12-06 14:46:44 hermen <br>
 *          updated Javadoc and refactured code <br>
 */
public class ErrorDialog extends JDialog implements ActionListener {

	/**
	 * static logger for this class
	 */
	private static Logger logger = Logger.getLogger(ErrorDialog.class.getName());

	private static final long serialVersionUID = 1L;

	private static String lastSaveFileLocation = null;

	private JLabel messageLabel;
	private JTextArea detailsTextArea;
	private JScrollPane details;
	private JPanel buttonPanel;
	private JPanel bottomPanel;
	private JButton okButton;
	private JToggleButton detailsButton;
	private JFrame parent;
	private JPanel content;
	private JButton bugButton;
	private JButton copyToClipboard;
	private JButton saveErrorDetails;
	private String message;
	private String detailsText;
	private String module;

	private static String BUG_URL;
	private static String DEFAULT_TITLE = "GECAMed Error";
	private static Color BACKGROUND = GECAMedColors.c_GECAMedBackground;
	private static ImageIcon ICON = GECAMedModule.getIcon(GECAMedModule.WARNING);
	private static String VERSION;

	// needed for logging on the server (sent or erroneous soap messages)
	private final static CDAManager manager = CDAManagerBean.getInstance();

	/**
	 * Creates a new error dialog
	 * 
	 * @param parent
	 *            The frame upon which the dialog will be displayed
	 * @param title
	 *            The title that is displayed in the error dialog frame
	 */
	public ErrorDialog(JFrame parent, String title) {
		super(parent, title, true);

		this.getContentPane().setBackground(BACKGROUND);

		content = new JPanel(new BorderLayout());
		content.setBackground(BACKGROUND);
		content.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));

		this.messageLabel = new JLabel(ICON);
		this.messageLabel.setHorizontalAlignment(JLabel.LEFT);
		this.messageLabel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
		this.messageLabel.setBackground(BACKGROUND);
		this.messageLabel.setMinimumSize(new Dimension(300, 60));
		this.getContentPane().add(messageLabel, BorderLayout.NORTH);

		this.detailsTextArea = new JTextArea(60, 10);
		this.detailsTextArea.setEditable(false);
		this.detailsTextArea.setLineWrap(true);
		this.details = new JScrollPane(detailsTextArea);
		this.details.setPreferredSize(new Dimension(600, 200));

		this.bottomPanel = new JPanel(new BorderLayout());
		this.bottomPanel.setBackground(BACKGROUND);
		this.buttonPanel = new JPanel(new GridLayout(1, 0));
		this.buttonPanel.setBackground(BACKGROUND);

		this.saveErrorDetails = new JButton("Save Details");
		this.saveErrorDetails.addActionListener(this);
		this.buttonPanel.add(this.saveErrorDetails);

		if (BUG_URL != null) {
			this.bugButton = new JButton("send Bugreport");
			this.bugButton.addActionListener(this);
			this.buttonPanel.add(bugButton);
		}
		this.copyToClipboard = new JButton("copy to clipboard");
		this.copyToClipboard.addActionListener(this);
		this.detailsButton = new JToggleButton("Details");
		this.detailsButton.addActionListener(this);
		this.detailsButton.setSelected(true);
		this.buttonPanel.add(detailsButton);
		this.okButton = new JButton("OK");
		this.okButton.addActionListener(this);
		this.buttonPanel.add(okButton);
		bottomPanel.add(buttonPanel, BorderLayout.EAST);
		content.add(bottomPanel, BorderLayout.SOUTH);
		content.add(details, BorderLayout.CENTER);

		this.getContentPane().add(content);
	}

	/**
	 * Shows the error dialog
	 * 
	 * @param parent
	 *            The frame upon which the dialog will be displayed
	 * @param details
	 *            Exception that has been caused by the occurred error. It will
	 *            be displayed when the details button is pressed
	 */
	public static void showErrorDialog(JFrame parent, Throwable details) {
		showErrorDialog(parent, details, null);
	}

	/**
	 * Shows the error dialog
	 * 
	 * @param parent
	 *            The frame upon which the dialog will be displayed
	 * @param details
	 *            Exception that has been caused by the occurred error. It will
	 *            be displayed when the details button is pressed
	 * @param module
	 *            The name of the module where the error occurred. It is used to
	 *            create a bug report if the corresponding button was pressed
	 */
	public static void showErrorDialog(JFrame parent, Throwable details, String module) {
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		PrintWriter pw = new PrintWriter(bout);
		details.printStackTrace(pw);
		pw.flush();
		showErrorDialog(parent, DEFAULT_TITLE, details.getLocalizedMessage(), bout.toString(), module);
	}

	/**
	 * Shows the error dialog
	 * 
	 * @param parent
	 *            The frame upon which the dialog will be displayed
	 * @param title
	 *            The title of the dialog
	 * @param message
	 *            The error short message that will be displayed when the dialog
	 *            is shown
	 * @param details
	 *            Exception that has been caused by the occurred error. It will
	 *            be displayed when the details button is pressed
	 */
	public static void showErrorDialog(JFrame parent, String title, String message, Throwable details) {
		showErrorDialog(parent, title, message, details, null);
	}

	/**
	 * Shows the error dialog
	 * 
	 * @param parent
	 *            The frame upon which the dialog will be displayed
	 * @param title
	 *            The title of the dialog
	 * @param message
	 *            The error short message that will be displayed when the dialog
	 *            is shown
	 * @param details
	 *            Exception that has been caused by the occurred error. It will
	 *            be displayed when the details button is pressed
	 * @param module
	 *            The name of the module where the error occurred. It is used to
	 *            create a bug report if the corresponding button was pressed
	 */
	public static void showErrorDialog(JFrame parent, String title, String message, Throwable details, String module) {
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		PrintWriter pw = new PrintWriter(bout);
		details.printStackTrace(pw);
		pw.flush();
		showErrorDialog(parent, title, message, bout.toString(), module);
	}

	/**
	 * Shows the error dialog
	 * 
	 * @param parent
	 *            The frame upon which the dialog will be displayed
	 * @param title
	 *            The title of the dialog
	 * @param message
	 *            The error short message that will be displayed when the dialog
	 *            is shown
	 * @param details
	 *            The detailed error message that will be displayed when the
	 *            details button is pressed
	 */
	public static void showErrorDialog(JFrame parent, String title, String message, String details) {
		showErrorDialog(parent, title, message, details, null);
	}

	/**
	 * Shows the error dialog
	 * 
	 * @param parent
	 *            The frame upon which the dialog will be displayed
	 * @param title
	 *            The title of the dialog
	 * @param message
	 *            The error short message that will be displayed when the dialog
	 *            is shown
	 * @param details
	 *            The detailed error message that will be displayed when the
	 *            details button is pressed
	 * @param module
	 *            The name of the module where the error occurred. It is used to
	 *            create a bug report if the corresponding button was pressed
	 */
	public static void showErrorDialog(JFrame parent, String title, String message, String details, String module) {
		ErrorDialog jd = new ErrorDialog(parent, title);
		try {
			message = message.replaceAll("\n", "<br>");
			message = message.replaceAll("\r", "<br>");
			message = message.replaceAll("\t", "&nbsp;&nbsp;&nbsp;");
		} catch (Exception e) {
		}

		jd.module = module;
		jd.parent = parent;
		jd.message = message;
		jd.setDetailsText(details);
		jd.messageLabel.setText("<html><h3>" + message);
		jd.validate();
		center(parent, jd);
		jd.setVisible(true);
	}

	public void actionPerformed(ActionEvent event) {
		if (event.getSource().equals(this.detailsButton)) {
			// show details field
			if (this.detailsButton.isSelected()) {
				content.add(details, BorderLayout.CENTER);
				// only show the copy to clipboard button when error details are
				// visible
				this.buttonPanel.add(copyToClipboard, 0);
			} else {
				content.remove(details);
				// and hide the button otherwise
				this.buttonPanel.remove(this.copyToClipboard);
			}
			this.content.updateUI();
			this.content.validate();
			this.validate();
			center(parent, this);
			this.validate();
			this.setVisible(true);
		} else if (event.getSource().equals(this.okButton)) {
			// hide panel
			this.setVisible(false);
		} else if (event.getSource().equals(this.bugButton)) {
			// send Bugreport
			try {
				StringBuffer sb = new StringBuffer("?reporter=" + getBugReporter());
				sb.append("&summary=" + this.message);
				sb.append("&description=");

				// sb.append("{{{");

				try {
					sb.append("REPORTER: " + getBugReporter() + "\n");
					sb.append("OFFICE: " + MainFrame.getCurrentOffice().getName() + "\n");
					sb.append("SITE: " + MainFrame.getCurrentSite().getName() + "\n");
				} catch (Exception e) {
				}

				if (detailsText.length() > 800)
					sb.append(this.detailsText.toLowerCase().substring(0, 800));
				else
					sb.append(this.detailsText.toLowerCase());

				// sb.append("}}}");

				if (module != null)
					sb.append("&component=" + this.module);
				if (VERSION != null)
					sb.append("&version=" + VERSION);

				String urlParameters = sb.toString();
				urlParameters = urlParameters.replaceAll("\\[", "![");
				urlParameters = urlParameters.replaceAll("\\]", "!]");

				urlParameters = urlParameters.replaceAll("<", "+");
				urlParameters = urlParameters.replaceAll(">", "+");
				urlParameters = urlParameters.replaceAll(" ", "+");
				urlParameters = urlParameters.replaceAll(":", "+");
				urlParameters = urlParameters.replaceAll(";", "+");
				urlParameters = urlParameters.replaceAll("\n", "[[BR]]");
				urlParameters = urlParameters.replaceAll("\t", "++++++");
				urlParameters = urlParameters.replaceAll("\t", "++++++");

				BrowserLauncher.openURL(BUG_URL + urlParameters);

			} catch (Exception e) {
				logger.log(Level.WARN, "launching browser< failed", e);
			}
		} else if (event.getSource().equals(this.copyToClipboard)) {
			// create a new selection
			StringSelection errorDetails = new StringSelection(this.detailsText);
			// get a handle to the clipboard
			Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
			// and copy the selection to the clipboard
			clipboard.setContents(errorDetails, errorDetails);
			// inform the user that the error details are now available in
			// clipboard (localization should also be used for button tags etc.)
			GECAMedOptionPane.showMessageDialog(MainFrame.getInstance(), Translatrix.getTranslationString("core.copyToClipboard"));
		} else if (event.getSource().equals(this.saveErrorDetails)) {
			// save the error details to disk
			final JFileChooser fileChooser = new JFileChooser(ErrorDialog.lastSaveFileLocation);
			fileChooser.setSelectedFile(new File("GECAMED_ErrorDetails_" + System.currentTimeMillis() + ".txt"));
			// Detect which button has been clicked
			int chosenOption = fileChooser.showSaveDialog(this);
			// and also remember the last folder the user has chosen
			ErrorDialog.lastSaveFileLocation = fileChooser.getCurrentDirectory().getAbsolutePath();
			// if the user confirmed action w/ ok button
			if (chosenOption == JFileChooser.APPROVE_OPTION) {
				// obtain selected save path
				File saveFile = fileChooser.getSelectedFile();
				try {
					// create the file (hail to Apache ! ;-) )
					FileUtils.writeStringToFile(saveFile, this.detailsText);
					// and log the action (also if it went wrong)
					logger.info("Saved error details to file \"" + saveFile.getAbsolutePath() + "\"");
				} catch (IOException e) {
					logger.error("Unable to save error details to \"" + saveFile.getAbsolutePath() + "\":\n", e);
				}
			}
		}

	}

	/**
	 * Shows the error dialog for an eSanté DSP related error. In contrary to the normal error dialog, it automatically stores the error details in a file on
	 * the GECAMed server
	 * 
	 * @param parent
	 *            The frame upon which the dialog will be displayed
	 * @param errorMessage
	 *            The error short message that will be displayed when the dialog is shown
	 * @param errorDetails
	 *            Every detail about the error that helps to identify the reason of the failure (exception stack trace, the (partial) cda message, important
	 *            parameters, etc.)
	 * @param patientId
	 *            The id of the patient in the GECAMed system. This will be used as part of the error file name for a more comfortable identification of the
	 *            appropriate error file.
	 * @param errorFileName
	 *            The name of the file containing the error details that will be stored on the eSanté error folder on the GECAMed server. The system takes care
	 *            of generating a unique filename and replaces any provided filename extension by ".txt"
	 */
	public static void showESanteErrorDialog(JFrame parent, String errorMessage, String errorDetails, int patientId, String errorFileName) {
		if(errorFileName==null){
			errorFileName = "ERROR_"+UUID.randomUUID().toString();
		}
		logger.error(errorMessage + "\n(The error details have been stored under \""
				+ manager.logErroneousMessage(errorDetails, patientId+"_"+FilenameUtils.removeExtension(errorFileName)) + "\")");
		showErrorDialog(parent, "eSanté Error", errorMessage, errorDetails);
	}

	/**
	 * center the dialog on the underlying frame
	 * 
	 * @param parent
	 *            The frame upon which the dialog should be centered
	 * @param dialog
	 *            The dialog that should be centered
	 */
	private static void center(JFrame parent, JDialog dialog) {
		dialog.validate();
		dialog.pack();
		int width = dialog.getWidth();
		int height= dialog.getHeight();
		if (width > parent.getWidth())
			width = parent.getWidth();
		if (height > parent.getHeight())
			height = parent.getHeight();
		dialog.setSize(width, height);
		Dimension f = parent.getSize();
		int posX = parent.getX();
		int posY = parent.getY();
		dialog.setLocation(posX + (f.width / 2) - dialog.getSize().width / 2, 
				posY + (f.height / 2) - dialog.getSize().height / 2);
	}

	/**
	 * Sets the version of the GECAMed module where the error has been caused.
	 * This will be used when creating an issue for the bug tracker (by pressing
	 * "report bug" button)
	 * 
	 * @param version
	 *            The GECAMed module
	 */
	public static void setVersion(String version) {
		VERSION = version;
	}

	/**
	 * Sets the background color of the error dialog
	 * 
	 * @param c
	 *            The background color (default: the standard GECAMed background
	 *            color)
	 */
	public static void setBackgroundColor(Color c) {
		BACKGROUND = c;
	}

	/**
	 * Sets the icon object that will be displayed on the error dialog (default:
	 * warning triangle)
	 * 
	 * @param i
	 *            The icon object
	 */
	public static void setIcon(ImageIcon i) {
		ICON = i;
	}

	/**
	 * Sets the title of the error dialog
	 * 
	 * @param title
	 *            The title for the error dialog
	 */
	public static void setDefaultTitle(String title) {
		DEFAULT_TITLE = title;
	}

	/**
	 * Sets the bug tracker url that is used to issue a new incident
	 * 
	 * @param url
	 *            The url of the bug tracker
	 */
	public static void setBugURL(String url) {
		BUG_URL = url;
	}

	/**
	 * Just for Testing
	 * 
	 * @param args
	 *            not used
	 */
	public static void main(String[] args) {
		JFrame p = new JFrame("Parent");
		p.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		p.setSize(800, 600);
		p.setVisible(true);

		ErrorDialog.setBugURL("http://gecamed.lu/trac/gecamed/newticket");
		ErrorDialog.setVersion("1.0");

		try {
			new FileInputStream("bla");
		} catch (Exception e) {
			ErrorDialog
					.showErrorDialog(
							p,
							"error",
							"an error occured",
							"This is a long\n test\n dfsdafqsdfsdfsdfsdfsdfasf asdfsadfasdfasdf asdf sdafasdfa  sdfassfdfasf asdfsdfsdf sdaf aasdfasdfasd sdfasdfasdfasdfsadfopkbndkldfjklsdklöfjasdfklösdjfaslödkgfhjasdöfagjklöa");
		}
	}

	/**
	 * Provides name or email address (if available) of the person that reports
	 * a bug
	 * 
	 * @return The name or email address of the bug reporting person
	 */
	public static String getBugReporter() {
		String reporter = "";
		try {
			if (MainFrame.getCurrentOffice() != null)
				reporter = MainFrame.getCurrentOffice().getEmail();
			if (reporter == null || reporter.length() == 0) {
				if (MainFrame.getCurrentPhysician() != null)
					reporter = MainFrame.getCurrentPhysician().getEmail();
			}
			if (reporter == null || reporter.length() == 0) {
				reporter = MainFrame.getCurrentOffice().getName();
			}
		} catch (Exception ee) {
		}
		reporter += "  " + MainFrame.getCurrentUser().toString();
		return reporter;
	}
	
	private void setDetailsText(String text) {
		try {
			this.detailsText = System.getProperty("java.runtime.version") + " " + VERSION + " " + getBugReporter() + "\n";
		} catch (Exception e) {
			this.detailsText = "";
		}
		this.detailsText += text;
		this.detailsTextArea.setText(this.detailsText);	
	}

}
