package amidst.gui.profileselect;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import javax.swing.JPanel;

import net.miginfocom.swing.MigLayout;

import amidst.documentation.AmidstThread;
import amidst.documentation.CalledOnlyBy;
import amidst.documentation.NotThreadSafe;
import amidst.settings.Setting;

@NotThreadSafe
public class ProfileSelectPanel {
	@NotThreadSafe
	@SuppressWarnings("serial")
	private class Component extends JPanel {
		private static final int INVALID_EMPTY_MESSAGE_WIDTH = -1;

		private int emptyMessageWidth = INVALID_EMPTY_MESSAGE_WIDTH;
		private String oldEmptyMessage;

		@CalledOnlyBy(AmidstThread.EDT)
		public Component() {
			this.oldEmptyMessage = emptyMessage;
		}

		@CalledOnlyBy(AmidstThread.EDT)
		@Override
		public void paintChildren(Graphics g) {
			super.paintChildren(g);
			Graphics2D g2d = (Graphics2D) g;
			drawSeparatorLines(g2d);
		}

		@CalledOnlyBy(AmidstThread.EDT)
		private void drawSeparatorLines(Graphics2D g2d) {
			g2d.setColor(Color.gray);
			for (int i = 1; i <= profileComponents.size(); i++) {
				g2d.drawLine(0, i * 40, getWidth(), i * 40);
			}
		}

		@CalledOnlyBy(AmidstThread.EDT)
		@Override
		public void paintComponent(Graphics g) {
			super.paintComponent(g);
			Graphics2D g2d = (Graphics2D) g;
			drawBackground(g2d);
			if (profileComponents.isEmpty()) {
				drawEmptyMessage(g2d);
			}
		}

		@CalledOnlyBy(AmidstThread.EDT)
		private void drawBackground(Graphics2D g2d) {
			g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
			g2d.setColor(Color.white);
			g2d.fillRect(0, 0, getWidth(), getHeight());
		}

		@CalledOnlyBy(AmidstThread.EDT)
		private void drawEmptyMessage(Graphics2D g2d) {
			g2d.setColor(Color.gray);
			g2d.setFont(EMPTY_MESSAGE_FONT);
			updateEmptyMessageWidth(g2d);
			int x = (getWidth() >> 1) - (emptyMessageWidth >> 1);
			g2d.drawString(emptyMessage, x, 30);
		}

		@CalledOnlyBy(AmidstThread.EDT)
		private void updateEmptyMessageWidth(Graphics2D g2d) {
			if (!oldEmptyMessage.equals(emptyMessage) || emptyMessageWidth == INVALID_EMPTY_MESSAGE_WIDTH) {
				emptyMessageWidth = g2d.getFontMetrics().stringWidth(emptyMessage);
			}
		}
	}

	private static final Font EMPTY_MESSAGE_FONT = new Font("arial", Font.BOLD, 30);
	private static final int INVALID_INDEX = -1;

	private final Setting<String> lastProfileSetting;
	private final Component component;
	private final List<ProfileComponent> profileComponents = new ArrayList<>();

	private ProfileComponent selected = null;
	private String emptyMessage;

	@CalledOnlyBy(AmidstThread.EDT)
	public ProfileSelectPanel(Setting<String> lastProfileSetting, String emptyMessage) {
		this.lastProfileSetting = lastProfileSetting;
		this.emptyMessage = emptyMessage;
		this.component = createComponent();
	}

	@CalledOnlyBy(AmidstThread.EDT)
	private Component createComponent() {
		Component component = new Component();
		component.setLayout(new MigLayout("ins 0", "", "[]0[]"));
		component.addMouseListener(createMouseListener());
		return component;
	}

	@CalledOnlyBy(AmidstThread.EDT)
	private MouseListener createMouseListener() {
		return new MouseAdapter() {
			@CalledOnlyBy(AmidstThread.EDT)
			@Override
			public void mousePressed(MouseEvent e) {
				if (!isLoading()) {
					doMousePressed(e.getPoint());
				}
			}
		};
	}

	@CalledOnlyBy(AmidstThread.EDT)
	public KeyListener createKeyListener() {
		return new KeyAdapter() {
			@CalledOnlyBy(AmidstThread.EDT)
			@Override
			public void keyPressed(KeyEvent e) {
				if (!isLoading()) {
					doKeyPressed(e.getKeyCode());
				}
			}
		};
	}

	@CalledOnlyBy(AmidstThread.EDT)
	private void doKeyPressed(int key) {
		if (key == KeyEvent.VK_DOWN) {
			select(profileComponents.indexOf(selected) + 1);
		} else if (key == KeyEvent.VK_UP) {
			select(profileComponents.indexOf(selected) - 1);
		} else if (key == KeyEvent.VK_ENTER) {
			loadSelectedProfile();
		}
	}

	@CalledOnlyBy(AmidstThread.EDT)
	private void doMousePressed(Point mousePosition) {
		select(getSelectedIndexFromYCoordinate(mousePosition));
		if (isLoadButtonClicked(mousePosition)) {
			loadSelectedProfile();
		}
	}

	@CalledOnlyBy(AmidstThread.EDT)
	private int getSelectedIndexFromYCoordinate(Point mousePosition) {
		return mousePosition.y / 40;
	}

	@CalledOnlyBy(AmidstThread.EDT)
	private boolean isLoadButtonClicked(Point mousePosition) {
		return mousePosition.x > component.getWidth() - 40;
	}

	@CalledOnlyBy(AmidstThread.EDT)
	public void selectFirst() {
		select(0);
	}

	@CalledOnlyBy(AmidstThread.EDT)
	public void select(String profileName) {
		select(getIndex(profileName));
	}

	@CalledOnlyBy(AmidstThread.EDT)
	private int getIndex(String profileName) {
		for (int i = 0; i < profileComponents.size(); i++) {
			if (profileComponents.get(i).getProfileName().equals(profileName)) {
				return i;
			}
		}
		return INVALID_INDEX;
	}

	@CalledOnlyBy(AmidstThread.EDT)
	private void select(int index) {
		deselectSelected();
		doSelect(getBoundedIndex(index));
	}

	@CalledOnlyBy(AmidstThread.EDT)
	private void deselectSelected() {
		if (selected != null) {
			selected.setSelected(false);
			selected = null;
		}
	}

	@CalledOnlyBy(AmidstThread.EDT)
	private int getBoundedIndex(int index) {
		if (profileComponents.isEmpty()) {
			return INVALID_INDEX;
		} else if (index < 0) {
			return 0;
		} else if (index >= profileComponents.size()) {
			return profileComponents.size() - 1;
		} else {
			return index;
		}
	}

	@CalledOnlyBy(AmidstThread.EDT)
	private void doSelect(int index) {
		if (index != INVALID_INDEX) {
			selected = profileComponents.get(index);
			selected.setSelected(true);
		}
	}

	@CalledOnlyBy(AmidstThread.EDT)
	private void loadSelectedProfile() {
		if (selected != null && selected.isReadyToLoad()) {
			lastProfileSetting.set(selected.getProfileName());
			selected.load();
		}
	}

	@CalledOnlyBy(AmidstThread.EDT)
	public void addProfile(ProfileComponent profile) {
		profileComponents.add(profile);

		// Sort and re-add all components
		profileComponents.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
		component.removeAll();
		for (ProfileComponent p: profileComponents) {
			component.add(p.getComponent(), "growx, pushx, wrap");
		}
	}

	@CalledOnlyBy(AmidstThread.EDT)
	public void setEmptyMessage(String emptyMessage) {
		this.emptyMessage = emptyMessage;
		component.repaint();
	}

	@CalledOnlyBy(AmidstThread.EDT)
	public JPanel getComponent() {
		return component;
	}

	@CalledOnlyBy(AmidstThread.EDT)
	private boolean isLoading() {
		for (ProfileComponent profileComponent : profileComponents) {
			if (profileComponent.isLoading()) {
				return true;
			}
		}
		return false;
	}

	@CalledOnlyBy(AmidstThread.EDT)
	public void resolveAllLater() {
		if (!isLoading()) {
			profileComponents.forEach(ProfileComponent::resolveLater);
		}
	}
}