/*
 * Copyright 2008-2019 by Emeric Vernat
 *
 *     This file is part of Java Melody.
 *
 * 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
 *
 * 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 net.bull.javamelody;

import java.awt.BorderLayout;
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import net.bull.javamelody.internal.common.I18N;
import net.bull.javamelody.internal.model.Action;
import net.bull.javamelody.internal.model.Counter;
import net.bull.javamelody.internal.model.CounterRequest;
import net.bull.javamelody.internal.model.CounterRequestAggregation;
import net.bull.javamelody.internal.model.Period;
import net.bull.javamelody.internal.model.Range;
import net.bull.javamelody.internal.model.RemoteCollector;
import net.bull.javamelody.internal.web.pdf.PdfOtherReport;
import net.bull.javamelody.swing.MButton;
import net.bull.javamelody.swing.Utilities;
import net.bull.javamelody.swing.table.MMultiLineTableCellRenderer;
import net.bull.javamelody.swing.table.MTable;
import net.bull.javamelody.swing.util.MWaitCursor;

/**
 * Panel des statistiques.
 * @author Emeric Vernat
 */
class StatisticsPanel extends MelodyPanel {
	static final ImageIcon PLUS_ICON = ImageIconCache.getImageIcon("bullets/plus.png");
	static final ImageIcon MINUS_ICON = ImageIconCache.getImageIcon("bullets/minus.png");

	private static final long serialVersionUID = 1L;

	@SuppressWarnings("all")
	private final CounterRequestAggregation counterRequestAggregation;
	private final Counter counter;
	private final Range range;
	private final StatisticsTablePanel tablePanel;
	private final JPanel mainPanel;
	private StatisticsPanel detailsPanel;
	private CounterErrorPanel lastErrorsPanel;

	StatisticsPanel(RemoteCollector remoteCollector, Counter counter, Range range,
			boolean includeGraph) {
		this(remoteCollector, counter, range, null, includeGraph);
	}

	private StatisticsPanel(RemoteCollector remoteCollector, Counter counter, Range range,
			CounterRequestAggregation counterRequestAggregation, boolean includeGraph) {
		super(remoteCollector);

		assert counter != null;
		assert range != null;
		this.counter = counter;
		this.range = range;

		if (counter.getRequestsCount() == 0) {
			this.tablePanel = null;
			this.counterRequestAggregation = null;
		} else {
			if (counterRequestAggregation == null) {
				this.counterRequestAggregation = new CounterRequestAggregation(counter);
			} else {
				this.counterRequestAggregation = counterRequestAggregation;
			}
			this.tablePanel = new StatisticsTablePanel(getRemoteCollector(), counter,
					this.counterRequestAggregation, includeGraph);
		}
		this.mainPanel = new JPanel(new BorderLayout());
		this.mainPanel.setOpaque(false);
		add(this.mainPanel, BorderLayout.NORTH);
	}

	public void showGlobalRequests() {
		if (counter.getRequestsCount() == 0) {
			final JLabel noRequestsLabel = createNoRequestsLabel();
			mainPanel.add(noRequestsLabel, BorderLayout.CENTER);
		} else {
			List<CounterRequest> requests = counterRequestAggregation.getRequests();
			final CounterRequest globalRequest = counterRequestAggregation.getGlobalRequest();
			if (isErrorAndNotJobCounter()) {
				// il y a au moins une "request" d'erreur puisque la liste n'est pas vide
				assert !requests.isEmpty();
				requests = Collections.singletonList(requests.get(0));
				final MTable<CounterRequest> myTable = tablePanel.getTable();
				addMouseClickedListener(myTable);
			} else {
				requests = Arrays.asList(globalRequest,
						counterRequestAggregation.getWarningRequest(),
						counterRequestAggregation.getSevereRequest());
			}

			showRequests(requests);

			final JPanel requestsSizeAndButtonsPanel = createRequestsSizeAndButtonsPanel();
			mainPanel.add(requestsSizeAndButtonsPanel, BorderLayout.EAST);
		}
	}

	void showDetailRequests() {
		if (detailsPanel == null) {
			final boolean includeGraph = CounterRequestTable.isRequestGraphDisplayed(counter);
			detailsPanel = new StatisticsPanel(getRemoteCollector(), counter, range,
					counterRequestAggregation, includeGraph);
			detailsPanel.setVisible(false);
			final List<CounterRequest> requests = counterRequestAggregation.getRequests();
			detailsPanel.showRequests(requests);

			final MTable<CounterRequest> myTable = detailsPanel.tablePanel.getTable();
			// ajoute un renderer multi-lignes pour la colonne "name" dans le tableau de "Détails",
			// ce qui est utile pour les requêtes sql ou les erreurs http par exemple
			myTable.setColumnCellRenderer("name", new MMultiLineTableCellRenderer());
			// invokeLater nécessaire pour que les dimensions et l'affichage soient corrects
			// avec le MMultiLineTableCellRenderer (tester par exemple l'erreur de compilation dans la page jsp avec tomcat)
			SwingUtilities.invokeLater(new Runnable() {
				@Override
				public void run() {
					Utilities.adjustTableHeight(myTable);
				}
			});
			addMouseClickedListener(myTable);

			add(detailsPanel, BorderLayout.CENTER);
		}
		detailsPanel.setVisible(!detailsPanel.isVisible());
		detailsPanel.validate();
	}

	void showRequestsAggregatedOrFilteredByClassName(String requestId,
			final MButton detailsButton) {
		final List<CounterRequest> requests = new CounterRequestAggregation(counter)
				.getRequestsAggregatedOrFilteredByClassName(requestId);
		tablePanel.setList(requests);
		add(tablePanel, BorderLayout.CENTER);

		final MTable<CounterRequest> myTable = tablePanel.getTable();
		if (detailsButton != null) {
			// le double-clique sur une ligne de la table des requêtes aggrégrées par classe
			// permet d'afficher la table des requêtes filtrées par la classe sélectionnée
			myTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
				@Override
				public void valueChanged(ListSelectionEvent e) {
					detailsButton.setEnabled(myTable.getSelectedObject() != null);
				}
			});
			detailsButton.setEnabled(myTable.getSelectedObject() != null);
			myTable.addMouseListener(new MouseAdapter() {
				@Override
				public void mouseClicked(MouseEvent e) {
					if (e.getClickCount() == 2) {
						detailsButton.doClick();
					}
				}
			});
			detailsButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					actionCounterSummaryPerClass(true);
				}
			});
		} else {
			// et sinon, le double-clique sur la table des requêtes filtrées pour une classe
			// permet d'afficher le détail d'une requête
			addMouseClickedListener(myTable);
		}
	}

	private void addMouseClickedListener(final MTable<CounterRequest> myTable) {
		myTable.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (e.getClickCount() == 2) {
					final MWaitCursor waitCursor = new MWaitCursor(myTable);
					final CounterRequest request = myTable.getSelectedObject();
					try {
						showRequestDetail(request);
					} catch (final IOException ex) {
						showException(ex);
					} finally {
						waitCursor.restore();
					}
				}
			}
		});
	}

	void showLastErrors() {
		if (lastErrorsPanel == null) {
			lastErrorsPanel = new CounterErrorPanel(getRemoteCollector(), counter);
			lastErrorsPanel.setVisible(false);

			add(lastErrorsPanel, BorderLayout.SOUTH);
		}
		lastErrorsPanel.setVisible(!lastErrorsPanel.isVisible());
		lastErrorsPanel.validate();
	}

	private JLabel createNoRequestsLabel() {
		final String key;
		if (isJobCounter()) {
			key = "Aucun_job";
		} else if (isErrorCounter()) {
			key = "Aucune_erreur";
		} else {
			key = "Aucune_requete";
		}
		return new JLabel(' ' + getString(key));
	}

	private void showRequests(List<CounterRequest> requests) {
		tablePanel.setList(requests);
		Utilities.adjustTableHeight(tablePanel.getTable());
		mainPanel.add(tablePanel, BorderLayout.NORTH);
	}

	private JPanel createRequestsSizeAndButtonsPanel() {
		final CounterRequest globalRequest = this.counterRequestAggregation.getGlobalRequest();
		final long end;
		if (range.getEndDate() != null) {
			// l'utilisateur a choisi une période personnalisée de date à date,
			// donc la fin est peut-être avant la date du jour
			end = Math.min(range.getEndDate().getTime(), System.currentTimeMillis());
		} else {
			end = System.currentTimeMillis();
		}
		// delta ni négatif ni à 0
		final long deltaMillis = Math.max(end - counter.getStartDate().getTime(), 1);
		final long hitsParMinute = 60 * 1000 * globalRequest.getHits() / deltaMillis;
		// Rq : si serveur utilisé de 8h à 20h (soit 12h) on peut multiplier par 2 ces hits par minute indiqués
		// pour avoir une moyenne sur les heures d'activité sans la nuit
		final String nbKey;
		if (isJobCounter()) {
			nbKey = "nb_jobs";
		} else if (isErrorCounter()) {
			nbKey = "nb_erreurs";
		} else {
			nbKey = "nb_requetes";
		}
		final DecimalFormat integerFormat = I18N.createIntegerFormat();
		final String text = getFormattedString(nbKey, integerFormat.format(hitsParMinute),
				integerFormat.format(counterRequestAggregation.getRequests().size()));

		final JPanel panel = Utilities.createButtonsPanel(new JLabel(text));

		if (counter.isBusinessFacadeCounter()) {
			panel.add(createCounterSummaryPerClassButton());
			panel.add(createRuntimeDependenciesButton());
		}

		panel.add(createDetailsButton());

		if (isErrorCounter()) {
			panel.add(createLastErrorsButton());
		}
		if (range.getPeriod() == Period.TOUT) {
			panel.add(createClearCounterButton());
		}

		return panel;
	}

	private MButton createCounterSummaryPerClassButton() {
		final MButton counterSummaryPerClassButton = new MButton(getString("Resume_par_classe"));
		counterSummaryPerClassButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				actionCounterSummaryPerClass(false);
			}
		});
		return counterSummaryPerClassButton;
	}

	private MButton createRuntimeDependenciesButton() {
		final MButton runtimeDependenciesButton = new MButton(getString("Dependencies"),
				ImageIconCache.getImageIcon("pdf.png"));
		runtimeDependenciesButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				try {
					actionRuntimeDependencies();
				} catch (final IOException ex) {
					showException(ex);
				}
			}
		});
		return runtimeDependenciesButton;
	}

	private MButton createDetailsButton() {
		final MButton detailsButton = new MButton(getString("Details"), PLUS_ICON);
		detailsButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				showDetailRequests();
				changePlusMinusIcon(detailsButton);
			}
		});
		return detailsButton;
	}

	private MButton createLastErrorsButton() {
		final MButton lastErrorsButton = new MButton(getString("Dernieres_erreurs"), PLUS_ICON);
		lastErrorsButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				showLastErrors();
				changePlusMinusIcon(lastErrorsButton);
			}
		});
		return lastErrorsButton;
	}

	private MButton createClearCounterButton() {
		final MButton clearCounterButton = new MButton(getString("Reinitialiser"));
		clearCounterButton.setToolTipText(getFormattedString("Vider_stats", counter.getName()));
		final Counter myCounter = counter;
		clearCounterButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (confirm(getFormattedString("confirm_vider_stats", myCounter.getName()))) {
					actionClearCounter(myCounter);
				}
			}
		});
		return clearCounterButton;
	}

	final void actionClearCounter(Counter myCounter) {
		try {
			final String message = getRemoteCollector().executeActionAndCollectData(
					Action.CLEAR_COUNTER, myCounter.getName(), null, null, null, null);
			showMessage(message);
			MainPanel.refreshMainTabFromChild(this);
		} catch (final IOException ex) {
			showException(ex);
		}
	}

	final void actionCounterSummaryPerClass(boolean detailsForAClass) {
		final String requestId;
		if (detailsForAClass) {
			requestId = tablePanel.getTable().getSelectedObject().getId();
		} else {
			requestId = null;
		}
		final CounterSummaryPerClassPanel panel = new CounterSummaryPerClassPanel(
				getRemoteCollector(), counter, range, requestId);
		MainPanel.addOngletFromChild(this, panel);
	}

	final void actionRuntimeDependencies() throws IOException {
		final File tempFile = createTempFileForPdf();
		final PdfOtherReport pdfOtherReport = createPdfOtherReport(tempFile);
		try {
			pdfOtherReport.writeRuntimeDependencies(counter, range);
		} finally {
			pdfOtherReport.close();
		}
		Desktop.getDesktop().open(tempFile);
	}

	final void changePlusMinusIcon(MButton detailsButton) {
		if (detailsButton.getIcon() == PLUS_ICON) {
			detailsButton.setIcon(MINUS_ICON);
		} else {
			detailsButton.setIcon(PLUS_ICON);
		}
	}

	private boolean isErrorCounter() {
		return counter.isErrorCounter();
	}

	private boolean isJobCounter() {
		return counter.isJobCounter();
	}

	private boolean isErrorAndNotJobCounter() {
		return isErrorCounter() && !isJobCounter();
	}

	void showRequestDetail(CounterRequest request) throws IOException {
		final CounterRequestDetailPanel panel = new CounterRequestDetailPanel(getRemoteCollector(),
				request);
		MainPanel.addOngletFromChild(this, panel);
	}
}
