/*
 * Copyright (c) 2017, Adam <Adam@sigterm.info>
 * Copyright (c) 2018, Psikoi <https://github.com/psikoi>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package net.runelite.client.plugins.itemspawns;

import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.ItemComposition;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.PluginPanel;
import net.runelite.client.ui.components.ComboBoxListRenderer;
import net.runelite.client.ui.components.IconTextField;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.NumberFormat;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Slf4j
@Singleton
public class ItemSpawnsPanel extends PluginPanel
{
	private final ConfigManager configManager;

	private final ScheduledExecutorService executor;

	private final Client client;

	private final ItemSpawnsPlugin plugin;

	private final IconTextField searchBar;

	private final JList<SpawnValue> searchResults;

	private final JList<SpawnValue> favourites;


	private final JFormattedTextField spawnIdTextField;

	private final JSpinner itemAmount, respawnTime;

	/* Used to prevent users from switching endpoint tabs while the results are loading */
	private boolean loading = false;

	private static final TypeToken<List<SpawnValue>> SPAWN_TYPE = new TypeToken<List<SpawnValue>>(){};

	private static final Gson GSON = new Gson();

	private List<ItemComposition> itemDefs;

	@Inject
	public ItemSpawnsPanel(ItemSpawnsPlugin plugin, Client client, ScheduledExecutorService executor, ConfigManager configManager)
	{
		super();
		this.client = client;
		this.executor = executor;
		this.plugin = plugin;
		this.configManager = configManager;
		log.info("Executor is null? {}", (executor == null));
		setBorder(new EmptyBorder(7, 10, 0, 10));
		setBackground(ColorScheme.DARK_GRAY_COLOR);
		setLayout(new GridBagLayout());

		// Expand sub items to fit width of panel, align to top of panel
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.BOTH;
		c.anchor = GridBagConstraints.PAGE_END;
		c.gridx = 0;
		c.gridy = 1;
		c.weightx = 1;
		c.weighty = 0;
		c.insets = new Insets(0, 0, 10, 0);

		JPanel container = new JPanel();
		container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
		JPanel totalPanel = new JPanel();

		totalPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
		totalPanel.setLayout(new GridLayout(3, 2));
		totalPanel.setBorder(new EmptyBorder(5, 5, 5, 5));

		spawnIdTextField = new JFormattedTextField(NumberFormat.getInstance());
		spawnIdTextField.setText("0");
		itemAmount = new JSpinner(new SpinnerNumberModel());
		itemAmount.setValue(1);
		respawnTime = new JSpinner(new SpinnerNumberModel());
		respawnTime.setValue(60);
		final ComboBoxListRenderer renderer = new ComboBoxListRenderer();
		totalPanel.add(new JLabel("ID:"));
		totalPanel.add(spawnIdTextField);
		totalPanel.add(new JLabel("Amount:"));
		totalPanel.add(itemAmount);
		totalPanel.add(new JLabel("Respawn seconds:"));
		totalPanel.add(respawnTime);

		JPanel searchPanel = new JPanel();
		searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.Y_AXIS));


		searchBar = new IconTextField();
		searchBar.setIcon(IconTextField.Icon.SEARCH);
		searchBar.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH - 20, 30));
		searchBar.setMaximumSize(new Dimension(1000, 30));
		searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR);
		searchBar.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR);
		searchBar.setMinimumSize(new Dimension(0, 30));
		searchBar.addActionListener(e -> executor.execute(this::lookup));


		// Panel that holds skill icons
		searchResults = new JList(new DefaultListModel());

		//searchBar.setOnClear(() -> ((DefaultListModel) searchResults.getModel()).clear());

		searchPanel.add(searchBar);
		JScrollPane scrollableList = new JScrollPane(searchResults);
		scrollableList.setBackground(ColorScheme.DARKER_GRAY_COLOR);
		scrollableList.setBorder(new EmptyBorder(5, 5, 5, 5));
		scrollableList.setPreferredSize(new Dimension(0, 230));
		scrollableList.setAlignmentY(BOTTOM_ALIGNMENT);

		searchPanel.add(scrollableList);

		favourites = new JList(new DefaultListModel());
		scrollableList = new JScrollPane(favourites);
		scrollableList.setBackground(ColorScheme.DARKER_GRAY_COLOR);
		scrollableList.setBorder(new EmptyBorder(5, 5, 5, 5));
		scrollableList.setPreferredSize(new Dimension(0, 110));
		scrollableList.setAlignmentY(BOTTOM_ALIGNMENT);


		searchPanel.add(scrollableList);

		JPopupMenu popupMenu = new JPopupMenu();
		JMenuItem favItems = new JMenuItem("Add to favourites");
		favItems.addActionListener(evt -> addFavourite(searchResults.getSelectedValue()));
		popupMenu.add(favItems);
		searchResults.setComponentPopupMenu(popupMenu);

		popupMenu = new JPopupMenu();
		JMenuItem unfavItems = new JMenuItem("Remove from favourites");
		unfavItems.addActionListener(evt -> removeFavourite(favourites.getSelectedValue()));
		popupMenu.add(unfavItems);
		favourites.setComponentPopupMenu(popupMenu);

		MouseAdapter adapter = new MouseAdapter(){

			@Override
			public void mousePressed(MouseEvent e){
				if ( SwingUtilities.isRightMouseButton(e) )
				{
					JList list = (JList)e.getSource();
					int row = list.locationToIndex(e.getPoint());
					list.setSelectedIndex(row);
				}
			}


		};
		favourites.addMouseListener(adapter);
		searchResults.addMouseListener(adapter);


		searchResults.addListSelectionListener(listener -> {
			try {
				spawnIdTextField.setText(Integer.toString(((SpawnValue) searchResults.getSelectedValue()).getId()));
			} catch(Exception ex){

			}
		});
		favourites.addListSelectionListener(listener -> {
			try {
				spawnIdTextField.setText(Integer.toString(((SpawnValue) favourites.getSelectedValue()).getId()));
			} catch(Exception ex){

			}
		});


		searchPanel.add(Box.createRigidArea(new Dimension(0, 5)));
		searchPanel.setAlignmentY(BOTTOM_ALIGNMENT);

		container.add(Box.createGlue());
		container.add(totalPanel);
		container.add(Box.createRigidArea(new Dimension(0, 10)));
		container.add(searchPanel);
		container.add(Box.createGlue());

		add(container, c);
		populateFavourites();

	}


	public void populateFavourites(){
		try {
			String savedJson = configManager.getConfiguration("itemSpawns", "favourites");
			if(savedJson != null && !savedJson.isEmpty()) {
				List<SpawnValue> spawnList = GSON.fromJson(savedJson, SPAWN_TYPE.getType());
				DefaultListModel listModel = (DefaultListModel) favourites.getModel();
				spawnList.forEach(listModel::addElement);
			}
		} catch (JsonSyntaxException e) {
			e.printStackTrace();
		}
	}
	public void addFavourite(SpawnValue value){
		try {
			DefaultListModel listModel = (DefaultListModel) favourites.getModel();
			listModel.addElement(value);
			save();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void removeFavourite(SpawnValue value){
		try {
			DefaultListModel listModel = (DefaultListModel) favourites.getModel();
			listModel.removeElement(value);
			save();
		} catch(Exception ex){
			ex.printStackTrace();
		}
	}

	public void save(){
		DefaultListModel listModel = (DefaultListModel) favourites.getModel();
		List<SpawnValue> values = Collections.list(listModel.elements());
		String json = GSON.toJson(values);
		configManager.setConfiguration("itemSpawns", "favourites", json);
	}

	@Override
	public void onActivate()
	{
		super.onActivate();
		searchBar.requestFocusInWindow();
	}

	private void lookup()
	{

		try {
			if(itemDefs == null)
				itemDefs = IntStream.range(0, 27000).mapToObj(client::getItemDefinition).filter(Objects::nonNull).collect(Collectors.toList());

			String lookup = searchBar.getText();

			if (Strings.isNullOrEmpty(lookup))
			{
				return;
			}

			searchBar.setEditable(false);
			searchBar.setIcon(IconTextField.Icon.LOADING_DARKER);
			loading = true;


			DefaultListModel listModel = (DefaultListModel) searchResults.getModel();


			try {
				int searchId = Integer.parseInt(searchBar.getText());
				ItemComposition def = client.getItemDefinition(searchId);

				searchBar.setIcon(IconTextField.Icon.SEARCH);
				searchBar.setEditable(true);


				loading = false;

				listModel.clear();
				listModel.addElement(new SpawnValue(def.getId(), def.getName()));
				return;
			} catch (Exception ex){

			}

			Predicate<ItemComposition> matchesInput = def -> (def.getName().toLowerCase().contains(lookup.toLowerCase()));
			List<SpawnValue> filteredItems = itemDefs.stream().filter(matchesInput).map(def -> new SpawnValue(def.getId(), def.getName())).collect(Collectors.toList());
			if (filteredItems.size() == 0)
			{
				searchBar.setIcon(IconTextField.Icon.ERROR);
				searchBar.setEditable(true);
				loading = false;
				listModel.clear();
				return;
			}

			//successful player search
			searchBar.setIcon(IconTextField.Icon.SEARCH);
			searchBar.setEditable(true);


			loading = false;

			listModel.clear();
			filteredItems.stream().forEach(listModel::addElement);
		} catch (Exception e) {
			searchBar.setIcon(IconTextField.Icon.SEARCH);
			searchBar.setEditable(true);


			loading = false;
			e.printStackTrace();
		}

	}

	@Data
	private static class SpawnValue {
		final int id;
		final String name;

		@Override
		public String toString(){
			return id + ": " + name;
		}
	}

	public int getItemId(){
		return Integer.parseInt(spawnIdTextField.getText().replace(",", ""));
	}

	public int getItemAmount(){
		return (int) itemAmount.getValue();
	}

	public int getRespawnTime(){ return (int) respawnTime.getValue(); }

	void addInputKeyListener(KeyListener l)
	{
		this.searchBar.addKeyListener(l);
	}

	void removeInputKeyListener(KeyListener l)
	{
		this.searchBar.removeKeyListener(l);
	}
}
