/**
 * Copyright (C) 2001-2017 by RapidMiner and the contributors
 * 
 * Complete list of developers available at our web site:
 * 
 * http://rapidminer.com
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Affero General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * 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
 * Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License along with this program.
 * If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.studio.io.data.internal.file;

import com.lowagie.text.Font;
import com.rapidminer.core.io.data.source.DataSourceFactory;
import com.rapidminer.core.io.data.source.DataSourceFactoryRegistry;
import com.rapidminer.core.io.data.source.FileDataSource;
import com.rapidminer.core.io.data.source.FileDataSourceFactory;
import com.rapidminer.gui.tools.ExtendedJFileChooser;
import com.rapidminer.gui.tools.ResourceAction;
import com.rapidminer.gui.tools.ResourceLabel;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.gui.tools.components.LinkLocalButton;
import com.rapidminer.studio.io.gui.internal.DataImportWizardUtils;
import com.rapidminer.studio.io.gui.internal.DataWizardEventType;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileSystemView;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.List;


/**
 * A view that shows a {@link JFileChooser} to allow file selection for a {@link FileDataSource}s.
 *
 * @author Nils Woehler
 * @since 7.0.0
 */
final class LocalFileLocationChooserView extends JPanel {

	private static final long serialVersionUID = 1L;

	private final ExtendedJFileChooser fileChooser;
	private final JPanel selectedTypePanel;
	private final JComboBox<FileDataSourceFactory<?>> factoryDropDownComboBox;
	private final List<ChangeListener> changeListeners = new LinkedList<>();

	private FileDataSourceFactory<?> fileDataSourceFactory;
	private boolean showFileDataSourceComboBox = false;

	private final Action changeTypeAction = new ResourceAction(false, "io.dataimport.step.file_selection.change_type") {

		private static final long serialVersionUID = 1L;

		@Override
		public void actionPerformed(ActionEvent e) {
			showFileDataSourceComboBox();
		}

	};

	private final Action selectTypeAction = new ResourceAction(false, "io.dataimport.step.file_selection.select_type") {

		private static final long serialVersionUID = 1L;

		@Override
		public void actionPerformed(ActionEvent e) {
			chooseDataSourceFileTypeFromComboBox();
		}

	};

	private final ChangeListener fileChangeListener = new ChangeListener() {

		@Override
		public void stateChanged(ChangeEvent e) {

			// Update detected file type each time the users changes the selected file
			final Path selectedLocation = getSelectedLocation();
			if (selectedLocation != null) {
				fileDataSourceFactory = LocalFileDataSourceFactory.lookupFactory(selectedLocation);
			}
			updateFileTypePanel();
		}

	};

    /**
     * The view constructor.
     *
     * @param fileFilters the file filters for this file chooser
     */
    LocalFileLocationChooserView(List<FileFilter> fileFilters) {
		this.fileChooser = new ExtendedJFileChooser("", FileSystemView.getFileSystemView().getDefaultDirectory());
		this.fileChooser.setControlButtonsAreShown(false);
		this.fileChooser.setAcceptAllFileFilterUsed(true);

		if (fileFilters != null) {
			for (FileFilter filter : fileFilters) {
				this.fileChooser.addChoosableFileFilter(filter);
			}
		}

		// create combo box for all available factories
		this.factoryDropDownComboBox = new JComboBox<>();
		FileDataSourceFactory<?>[] comboBoxItems = DataSourceFactoryRegistry.INSTANCE.getFileFactories()
				.toArray(new FileDataSourceFactory[0]);
		ComboBoxModel<FileDataSourceFactory<?>> comboBoxModel = new DefaultComboBoxModel<>(comboBoxItems);
		factoryDropDownComboBox.setModel(comboBoxModel);
		factoryDropDownComboBox.setRenderer(new DefaultListCellRenderer() {

			private static final long serialVersionUID = 1L;

			@Override
			public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected,
					boolean cellHasFocus) {
				Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
				setText(DataImportWizardUtils.getFactoryLabel((DataSourceFactory<?>) value));
				return comp;
			}

		});

		setLayout(new BorderLayout());

		// add file chooser to panel
		add(fileChooser, BorderLayout.CENTER);

		this.selectedTypePanel = new JPanel(new GridBagLayout());
		add(selectedTypePanel, BorderLayout.SOUTH);

		registerChangeListener(fileChangeListener);
		fileChangeListener.stateChanged(null);
	}

    /**
     * Applies the file type selection made by the user in the file type combobox.
     */
    protected void chooseDataSourceFileTypeFromComboBox() {

		// toggle marker variable
		this.showFileDataSourceComboBox = false;

		String oldFileType = fileDataSourceFactory != null ? fileDataSourceFactory.getI18NKey() : "unknown";

		// update selected factory
		this.fileDataSourceFactory = (FileDataSourceFactory<?>) factoryDropDownComboBox.getSelectedItem();

		// log file type change
		String newFileType = fileDataSourceFactory.getI18NKey();
		DataImportWizardUtils.logStats(DataWizardEventType.FILE_TYPE_CHANGED, oldFileType + "->" + newFileType);

		fireChangeEvent();
		updateFileTypePanel();
	}

	private void showFileDataSourceComboBox() {

		// toggle marker variable
		this.showFileDataSourceComboBox = true;
		updateFileTypePanel();
	}

    /**
     * Gets selected location.
     *
     * @return the selected file location. Might be {@code null} in case no file has been selected.
     */
    Path getSelectedLocation() {
		File selectedFile = fileChooser.getSelectedFile();
		return selectedFile != null ? selectedFile.toPath() : null;
	}

    /**
     * Gets file data source factory.
     *
     * @return the data source factory for the selected file. Might be {@code null} in case no file         has been selected.
     */
    FileDataSourceFactory<?> getFileDataSourceFactory() {
		return fileDataSourceFactory;
	}

    /**
     * Registers a new {@link ChangeListener} for the file chooser.
     *
     * @param listener the {@link ChangeListener} to register
     */
    void registerChangeListener(ChangeListener listener) {
		this.fileChooser.addChangeListener(listener);
		this.changeListeners.add(listener);
	}

	/**
	 * Fires a state changed event for registered {@link ChangeListener}. Skips the
	 * {@link #fileChangeListener} to avoid new file type lookup when choosing a different file
	 * import type.
	 */
	private void fireChangeEvent() {
		for (ChangeListener listener : changeListeners) {
			if (listener == fileChangeListener) {
				// skip local file change listener to avoid new file type lookup
				continue;
			}
			try {
				listener.stateChanged(new ChangeEvent(fileChooser));
			} catch (RuntimeException e) {
				// ignore
			}
		}
	}

    /**
     * Sets the selected file and updates the {@link JFileChooser} accordingly.
     *
     * @param selectedFile the selected file. Must not be <code>null</code>.
     */
    void setSelectedFile(Path selectedFile) {
		this.fileChooser.setSelectedFile(selectedFile.toFile());
	}

    /**
     * Sets file data source factory.
     *
     * @param fileDataSourceFactory the file data source factory
     */
    public void setFileDataSourceFactory(FileDataSourceFactory<?> fileDataSourceFactory) {
		this.fileDataSourceFactory = fileDataSourceFactory;
	}

	private void updateFileTypePanel() {
		SwingTools.invokeLater(new Runnable() {

			@Override
			public void run() {
				selectedTypePanel.removeAll();

				GridBagConstraints constraint = new GridBagConstraints();
				constraint.fill = GridBagConstraints.NONE;
				constraint.anchor = GridBagConstraints.WEST;
				constraint.insets = new Insets(10, 3, 10, 0);

				constraint.fill = GridBagConstraints.BOTH;
				constraint.weightx = 1.0;
				JPanel fillerPanel = new JPanel();
				selectedTypePanel.add(fillerPanel, constraint);

				// set constraints for other component
				constraint.fill = GridBagConstraints.NONE;
				constraint.weightx = 0.0;

				if (getSelectedLocation() != null) {

					boolean showComboBox = showFileDataSourceComboBox;

					// add first label
					if (fileDataSourceFactory != null) {
						if (showComboBox) {
							JLabel typeLabel = new ResourceLabel("io.dataimport.step.file_selection.select_type");
							selectedTypePanel.add(typeLabel, constraint);
						} else {
							JLabel typeLabel = new ResourceLabel("io.dataimport.step.file_selection.type_detected");
							selectedTypePanel.add(typeLabel, constraint);

							String selectedTypeName = DataImportWizardUtils.getFactoryLabel(fileDataSourceFactory);
							JLabel selectedTypeLabel = new JLabel(selectedTypeName);
							selectedTypeLabel.setFont(selectedTypeLabel.getFont().deriveFont(Font.BOLD));
							selectedTypePanel.add(selectedTypeLabel, constraint);
						}
					} else {
						JLabel unknownTypeLabel = new ResourceLabel("io.dataimport.step.file_selection.unknown_type");
						selectedTypePanel.add(unknownTypeLabel, constraint);
						showComboBox = true;
					}

					if (showComboBox) {
						selectedTypePanel.add(factoryDropDownComboBox, constraint);

						LinkLocalButton changeTypeButton = new LinkLocalButton(selectTypeAction);
						changeTypeButton.setAlignmentX(SwingConstants.CENTER);
						selectedTypePanel.add(changeTypeButton, constraint);
					} else {
						LinkLocalButton changeTypeButton = new LinkLocalButton(changeTypeAction);
						changeTypeButton.setAlignmentX(SwingConstants.CENTER);
						selectedTypePanel.add(changeTypeButton, constraint);
					}

				} else {
					// add a no file selected label
					selectedTypePanel.add(new ResourceLabel("io.dataimport.step.file_selection.no_file_selected"),
							constraint);
				}

				// ensure same height for every panel content to avoid flickering
				selectedTypePanel.setPreferredSize(new Dimension(700, 41));
				selectedTypePanel.setMinimumSize(new Dimension(700, 41));

				selectedTypePanel.revalidate();
				selectedTypePanel.repaint();
			}
		});
	}

}
