/*******************************************************************************
 * Copyright (c) 2016 - 2017
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *******************************************************************************/
package jsettlers.main.swing.menu.openpanel;

import java.awt.BorderLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListModel;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.ListCellRenderer;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import jsettlers.common.utils.collections.ChangingList;
import jsettlers.graphics.localization.Labels;
import jsettlers.logic.map.loading.MapLoader;
import jsettlers.main.swing.lookandfeel.ELFStyle;

/**
 * Panel to open an existing map
 *
 * @author Andreas Butti
 *
 */
public class OpenPanel extends JPanel {
	private static final long serialVersionUID = 1L;

	/**
	 * List with the Maps to select
	 */
	private final JList<MapLoader> mapList;

	/**
	 * Filtered list model
	 */
	private final DefaultListModel<MapLoader> listModelFiltered = new DefaultListModel<>();

	/**
	 * Filter buttons
	 */
	private final JPanel filterPanel = new JPanel();

	/**
	 * Search Textfield
	 */
	private final SearchTextField searchTextField;

	/**
	 * Unfiltered map list
	 */
	private MapLoader[] availableMaps;

	/**
	 * Currently active filter
	 */
	private EMapFilter currentFilter = EMapFilter.ALL;

	/**
	 * Constructor
	 *
	 * @param doubleclickListener
	 *            Gets called when an entry is double clicked, can be <code>null</code>
	 */
	public OpenPanel(final ChangingList<? extends MapLoader> maps, IMapSelectedListener mapSelectedListener) {
		this(maps.getItems(), mapSelectedListener);
		maps.setListener(changedLister -> {
			setMapLoaders(changedLister.getItems());
		});
	}

	/**
	 * Constructor
	 *
	 * @param maps
	 *            Maps to display
	 * @param doubleclickListener
	 *            Gets called when an entry is double clicked, can be <code>null</code>
	 */
	public OpenPanel(final List<? extends MapLoader> maps, IMapSelectedListener mapSelectedListener) {
		this(maps, mapSelectedListener, new MapListCellRenderer());
	}

	/**
	 * Constructor
	 *
	 * @param maps
	 *            Maps to display
	 * @param doubleclickListener
	 *            Gets called when an entry is double clicked, can be <code>null</code>
	 * @param cellRenderer
	 *            Cell renderer to use
	 */
	public OpenPanel(final List<? extends MapLoader> maps, final IMapSelectedListener mapSelectedListener,
			final ListCellRenderer<MapLoader> cellRenderer) {
		setMapLoadersWithoutSearchChanged(maps);
		setLayout(new BorderLayout());

		initFilter();

		searchTextField = new SearchTextField();
		searchTextField.putClientProperty(ELFStyle.KEY, ELFStyle.TEXT_DEFAULT);

		Box box = Box.createVerticalBox();
		box.add(filterPanel);
		box.add(searchTextField);

		add(box, BorderLayout.NORTH);

		searchTextField.getDocument().addDocumentListener(new DocumentListener() {
			@Override
			public void removeUpdate(DocumentEvent e) {
				searchChanged();
			}

			@Override
			public void insertUpdate(DocumentEvent e) {
				searchChanged();
			}

			@Override
			public void changedUpdate(DocumentEvent e) {
				searchChanged();
			}
		});

		mapList = new JList<>(listModelFiltered);
		mapList.setCellRenderer(cellRenderer);
		mapList.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (e.getClickCount() == 2) {
					if (mapSelectedListener != null) {
						mapSelectedListener.mapSelected(getSelectedMap());
					}
				}
			}
		});
		mapList.setOpaque(false);
		add(new JScrollPane(mapList), BorderLayout.CENTER);

		searchChanged();

		if (maps.size() > 0) {
			mapList.setSelectedIndex(0);
		}
	}

	public void setMapLoaders(final List<? extends MapLoader> maps) {
		setMapLoadersWithoutSearchChanged(maps);
		searchChanged();
	}

	private void setMapLoadersWithoutSearchChanged(final List<? extends MapLoader> maps) {
		availableMaps = maps.toArray(new MapLoader[maps.size()]);
		Arrays.sort(availableMaps);
	}

	/**
	 * Initialize the filter buttons
	 */
	private void initFilter() {
		JLabel filterLabel = new JLabel(Labels.getString("mapfilter.title"));
		filterLabel.putClientProperty(ELFStyle.KEY, ELFStyle.LABEL_SHORT);

		filterPanel.add(filterLabel);

		boolean first = true;
		ButtonGroup group = new ButtonGroup();
		for (final EMapFilter filter : EMapFilter.values()) {
			JToggleButton bt = new JToggleButton(filter.getName());
			bt.putClientProperty(ELFStyle.KEY, ELFStyle.TOGGLE_BUTTON_STONE);
			bt.addActionListener(e -> {
				currentFilter = filter;
				searchChanged();
			});

			if (first) {
				first = false;
				bt.setSelected(true);
			}

			group.add(bt);
			filterPanel.add(bt);
		}
	}

	/**
	 * Search has changed, update the list
	 */
	protected void searchChanged() {
		String search = searchTextField.getText().toLowerCase(Locale.ENGLISH);

		listModelFiltered.clear();

		Arrays.stream(availableMaps)
				.filter(currentFilter::filter)
				.filter(mapLoader -> matchesSearch(mapLoader, search))
				.forEach(listModelFiltered::addElement);
	}

	/**
	 * Checks if a map matches the search criteria
	 *
	 * @param m
	 *            Map
	 * @param search
	 *            Criteria
	 * @return true if yes, false if no
	 */
	private boolean matchesSearch(MapLoader m, String search) {
		if (search.isEmpty()) {
			return true;
		}

		if (m.getMapName().toLowerCase(Locale.ENGLISH).contains(search)) {
			return true;
		}
		if (m.getDescription().toLowerCase(Locale.ENGLISH).contains(search)) {
			return true;
		}
		return m.getMapId().toLowerCase(Locale.ENGLISH).contains(search);

	}

	/**
	 * @return The selected map
	 */
	public MapLoader getSelectedMap() {
		return mapList.getSelectedValue();
	}

	/**
	 * @return true if there are no maps in the list
	 */
	public boolean isEmpty() {
		return availableMaps.length == 0;
	}
}
