package jrummikub.view.impl;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.border.CompoundBorder;
import javax.swing.border.MatteBorder;
import javax.swing.filechooser.FileNameExtensionFilter;

import jrummikub.model.Position;
import jrummikub.model.Stone;
import jrummikub.model.StoneColor;
import jrummikub.model.StoneSet;
import jrummikub.util.Event;
import jrummikub.util.Event1;
import jrummikub.util.IEvent;
import jrummikub.util.IEvent1;
import jrummikub.util.IListener;
import jrummikub.util.Pair;
import jrummikub.view.IConnectPanel;
import jrummikub.view.IGameListPanel;
import jrummikub.view.IHandPanel;
import jrummikub.view.ILoginPanel;
import jrummikub.view.IPlayerPanel;
import jrummikub.view.IQuitWarningPanel;
import jrummikub.view.IScorePanel;
import jrummikub.view.ISettingsPanel;
import jrummikub.view.ISidePanel;
import jrummikub.view.ITablePanel;
import jrummikub.view.IView;

/**
 * Implementation of the top-level view interface
 */
@SuppressWarnings("serial")
public class View extends JFrame implements IView {
	private final static float PLAYER_PANEL_RATIO = 0.14f;
	private final static int PLAYER_PANEL_MAX_HEIGHT = 180;
	private final static int TABLE_BORDER_WIDTH = 1;

	private JLayeredPane layeredPane;
	private JPanel mainLayer;
	private JMenuBar menuBar;
	private JMenuItem saveItem;

	private TablePanel table;
	private PlayerPanel playerPanel;
	private StartTurnPanel startTurnPanel;
	private PausePanel pausePanel;
	private RoundEndPanel roundEndPanel;
	private SettingsPanel settingsPanel;
	private LoginPanel loginPanel;
	private ScorePanel scorePanel;
	private GameListPanel gameListPanel;
	private SidePanel sidePanel;
	private QuitWarningPanel quitWarningPanel;
	private ConnectPanel connectPanel;
	private BottomPanelType bottomPanelType;

	private JFileChooser chooser;

	private Event menuNewGameEvent = new Event();

	private Event networkGameEvent = new Event();
	private Event loadEvent = new Event();
	private Event1<File> loadFileEvent = new Event1<File>();
	private Event1<File> saveEvent = new Event1<File>();
	private Event quitEvent = new Event();
	private JFrame quitWarningFrame;

	private static int even(double d) {
		return 2 * (int) (d / 2);
	}

	@Override
	public ISettingsPanel getSettingsPanel() {
		return settingsPanel;
	}

	@Override
	public ILoginPanel getLoginPanel() {
		return loginPanel;
	}

	@Override
	public IScorePanel getScorePanel() {
		return scorePanel;
	}

	@Override
	public ITablePanel getTablePanel() {
		return table;
	}

	@Override
	public ISidePanel getSidePanel() {
		return sidePanel;
	}

	@Override
	public IHandPanel getHandPanel() {
		return playerPanel.getHandPanel();
	}

	@Override
	public IPlayerPanel getPlayerPanel() {
		return playerPanel;
	}

	@Override
	public IQuitWarningPanel getQuitWarningPanel() {
		return quitWarningPanel;
	}

	@Override
	public IConnectPanel getConnectPanel() {
		return connectPanel;
	}

	@Override
	public IGameListPanel getGameListPanel() {
		return gameListPanel;
	}

	@Override
	public IEvent getMenuNewGameEvent() {
		return menuNewGameEvent;
	}

	@Override
	public IEvent1<File> getLoadFileEvent() {
		return loadFileEvent;
	}

	@Override
	public IEvent getLoadEvent() {
		return loadEvent;
	}

	@Override
	public IEvent1<File> getSaveEvent() {
		return saveEvent;
	}

	@Override
	public IEvent getPauseEvent() {
		return playerPanel.getPauseEvent();
	}

	@Override
	public IEvent getEndPauseEvent() {
		return pausePanel.getEndPauseEvent();
	}

	@Override
	public IEvent getNetworkGameEvent() {
		return networkGameEvent;
	}

	@Override
	public IEvent getQuitEvent() {
		return quitEvent;
	}

	@Override
	public void clearView() {
		showScorePanel(false);
		showSettingsPanel(false);
		showLoginPanel(false);
		showGameListPanel(false);
		getHandPanel().setStones(Collections.<Pair<Stone, Position>> emptyList());
		getTablePanel().setStoneSets(
				Collections.<Pair<StoneSet, Position>> emptyList());
		setSelectedStones(Collections.<Stone> emptyList());
	}

	@Override
	public void showLoadingError() {
		JOptionPane.showMessageDialog(this, "Kein g\u00fcltiger Spielstand",
				"Fehler", JOptionPane.ERROR_MESSAGE);
	}

	@Override
	public void showServerStartupError(boolean alreadyRunning) {
		JOptionPane
				.showMessageDialog(
						this,
						alreadyRunning ? "Ein XMPP-Server l\u00e4ft bereits auf diesem Computer"
								: "Der Server konnte nicht gestartet werden", "Fehler",
						JOptionPane.ERROR_MESSAGE);

	}

	private void createFileChooser() {
		chooser = new JFileChooser();
		FileNameExtensionFilter filter = new FileNameExtensionFilter(
				"JRummikub-Spiele", "rum");
		chooser.setFileFilter(filter);

		String defaultFile = "spiel.rum";
		chooser.setSelectedFile(new File(defaultFile));
	}

	private void createMenuBar() {
		menuBar = new JMenuBar();

		JMenu gameMenu = new JMenu("Spiel");
		menuBar.add(gameMenu);

		gameMenu.add(createMenuItem("Neu", menuNewGameEvent));
		gameMenu.add(createMenuItem("Netzwerkspiel...", networkGameEvent));

		gameMenu.addSeparator();

		JMenuItem loadItem = new JMenuItem("Laden...");
		loadItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				loadEvent.emit();
			}
		});
		gameMenu.add(loadItem);

		saveItem = new JMenuItem("Speichern...");
		saveItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				save();
			}
		});
		gameMenu.add(saveItem);

		gameMenu.addSeparator();

		gameMenu.add(createMenuItem("Beenden", quitEvent));

		setJMenuBar(menuBar);
	}

	private JMenuItem createMenuItem(String text, final Event event) {
		JMenuItem item = new JMenuItem(text);
		item.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				event.emit();
			}
		});
		return item;
	}

	@Override
	public void enableSave(boolean enable) {
		saveItem.setEnabled(enable);
	}

	@Override
	public void load() {
		int returnVal = chooser.showOpenDialog(View.this);
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			loadFileEvent.emit(chooser.getSelectedFile());
		}
	}

	private void save() {
		int returnVal = chooser.showSaveDialog(View.this);
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			saveEvent.emit(chooser.getSelectedFile());
		}
	}

	/**
	 * Create a new instance of the view
	 */
	public View() {
		super("JRummikub");
		setLayout(null);

		setSize(1000, 700);
		setMinimumSize(new Dimension(750, 550));

		setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				if (isEnabled()) {
					quitEvent.emit();
				}
			}
		});

		createFileChooser();
		createMenuBar();

		layeredPane = new JLayeredPane();
		layeredPane.setLayout(null);
		add(layeredPane);

		createMainLayer();

		createSettingsPanel();

		createLayeredPane();

		addComponentListener(new ComponentAdapter() {
			@Override
			public void componentResized(ComponentEvent e) {
				rescale();
			}

			@Override
			public void componentMoved(ComponentEvent e) {
				quitWarningFrame.setLocationRelativeTo(View.this);
			}
		});

		setVisible(true);
	}

	private void createLayeredPane() {
		layeredPane.setLayer(settingsPanel, JLayeredPane.POPUP_LAYER);
		layeredPane.add(settingsPanel);

		loginPanel = new LoginPanel();
		loginPanel.setVisible(false);
		layeredPane.setLayer(loginPanel, JLayeredPane.POPUP_LAYER);
		layeredPane.add(loginPanel);

		gameListPanel = new GameListPanel();
		gameListPanel.setVisible(false);
		layeredPane.setLayer(gameListPanel, JLayeredPane.POPUP_LAYER);
		layeredPane.add(gameListPanel);

		connectPanel = new ConnectPanel();
		connectPanel.setVisible(false);
		layeredPane.setLayer(connectPanel, JLayeredPane.POPUP_LAYER);
		layeredPane.add(connectPanel);

		quitWarningPanel = new QuitWarningPanel();
		quitWarningFrame = new JFrame();
		quitWarningFrame.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
		quitWarningFrame.add(quitWarningPanel);
		quitWarningFrame.setAlwaysOnTop(true);
		quitWarningFrame.setUndecorated(true);

		// layeredPane.setLayer(quitWarningPanel, JLayeredPane.POPUP_LAYER);
		// layeredPane.add(quitWarningPanel);
		// quitWarningPanel.setVisible(true);
		scorePanel = new ScorePanel();
		scorePanel.setVisible(false);
		layeredPane.setLayer(scorePanel, JLayeredPane.POPUP_LAYER);
		layeredPane.add(scorePanel);
	}

	private void createSettingsPanel() {
		settingsPanel = new SettingsPanel();
		settingsPanel.setVisible(false);

		settingsPanel.getNetworkGameEvent().add(new IListener() {
			@Override
			public void handle() {
				networkGameEvent.emit();
			}
		});
		settingsPanel.getLoadGameEvent().add(new IListener() {
			@Override
			public void handle() {
				load();
			}
		});
	}

	private void createMainLayer() {
		mainLayer = new JPanel();
		mainLayer.setLayout(null);
		layeredPane.add(mainLayer);

		table = new TablePanel();
		mainLayer.add(table);
		table.setBorder(new MatteBorder(0, 0, TABLE_BORDER_WIDTH, 0, Color.BLACK));

		playerPanel = new PlayerPanel();
		mainLayer.add(playerPanel);

		startTurnPanel = new StartTurnPanel();
		startTurnPanel.setVisible(false);
		mainLayer.add(startTurnPanel);

		pausePanel = new PausePanel();
		pausePanel.setVisible(false);
		mainLayer.add(pausePanel);

		roundEndPanel = new RoundEndPanel();
		roundEndPanel.setVisible(false);
		mainLayer.add(roundEndPanel);

		sidePanel = new SidePanel();
		sidePanel.setVisible(false);
		mainLayer.add(sidePanel);
		sidePanel
				.setBorder(new CompoundBorder(new MatteBorder(0, 0, 0, 1, Color.BLACK),
						new MatteBorder(0, 0, TABLE_BORDER_WIDTH, 0, Color.GRAY)));
	}

	@Override
	public void setMayPause(boolean mayPause) {
		playerPanel.setMayPause(mayPause);
	}

	@Override
	public void enablePauseMode(boolean enable) {
		table.enablePauseMode(enable);

		doSetBottomPanel(enable ? null : bottomPanelType);
		pausePanel.setVisible(enable);
	}

	private void rescale() {
		int width = getContentPane().getWidth(), height = getContentPane()
				.getHeight();

		layeredPane.setBounds(0, 0, width, height);
		mainLayer.setBounds(0, 0, width, height);

		int playerPanelHeight = even(Math.pow((double) width * width * height,
				1 / 3.0) * PLAYER_PANEL_RATIO);
		if (playerPanelHeight > PLAYER_PANEL_MAX_HEIGHT)
			playerPanelHeight = PLAYER_PANEL_MAX_HEIGHT;

		int tableHeight = height - playerPanelHeight;

		playerPanel.setBounds(0, tableHeight, width, playerPanelHeight);
		playerPanel.rescale();

		int sideWidth = sidePanel.isVisible() ? playerPanel.getLeftPanelWidth() + 1
				: 0;

		table.setBounds(sideWidth, 0, width - sideWidth, tableHeight);
		table.validate();
		if (sidePanel.isVisible()) {
			sidePanel.setBounds(0, 0, sideWidth, tableHeight);
		}
		startTurnPanel.setBounds(0, tableHeight, width, playerPanelHeight);
		pausePanel.setBounds(0, tableHeight, width, playerPanelHeight);
		roundEndPanel.setBounds(0, tableHeight, width, playerPanelHeight);

		rescaleSubpanel(settingsPanel, 1 / 2.0, 1 / 2.0, 475, 300);
		rescaleSubpanel(scorePanel, 3 / 4.0, 1 / 2.0, 450, 300);
		rescaleSubpanel(loginPanel, 1 / 3.0, 1 / 3.0, 350, 200);
		rescaleSubpanel(gameListPanel, 1 / 2.0, 1 / 2.0, 475, 300);
		rescaleSubpanel(quitWarningPanel, 1 / 2.0, 1 / 6.0, 400, 150);
		quitWarningPanel.setLocation(0, 0);
		quitWarningFrame.setSize(quitWarningPanel.getSize());
		quitWarningFrame.setLocationRelativeTo(this);
		rescaleSubpanel(connectPanel, 1 / 2.0, 1 / 6.0, 400, 150);
	}

	private void rescaleSubpanel(JPanel sub, double widthFactor,
			double heightFactor, int minWidth, int minHeight) {
		int width = getContentPane().getWidth(), height = getContentPane()
				.getHeight();

		int panelWidth = Math.max(minWidth, (int) (width * widthFactor));
		int panelHeight = Math.max(minHeight, (int) (height * heightFactor));

		int x = (width - panelWidth) / 2;
		int y = (height - panelHeight) / 2;
		sub.setBounds(x, y, panelWidth, panelHeight);
	}

	@Override
	public void setSelectedStones(Collection<Stone> stones) {
		table.setSelectedStones(stones);
		playerPanel.getHandPanel().setSelectedStones(stones);
	}

	@Override
	public void setInvalidStoneSets(Collection<StoneSet> sets) {
		table.setInvalidStoneSets(sets);
	}

	@Override
	public void showSettingsPanel(boolean show) {
		if (show) {
			settingsPanel.resetTabbedPane();
		}

		settingsPanel.setVisible(show);
	}

	@Override
	public void showLoginPanel(boolean show) {
		loginPanel.setVisible(show);
	}

	@Override
	public void resetLoginPanel() {
		loginPanel.resetLoginPanel();
	}

	@Override
	public void showGameListPanel(boolean show) {
		if (show) {
			gameListPanel.reset();
		}

		gameListPanel.setVisible(show);
	}

	@Override
	public void showScorePanel(boolean show) {
		scorePanel.setVisible(show);
	}

	@Override
	public void showSidePanel(boolean show) {
		sidePanel.setVisible(show);
		rescale();
	}

	@Override
	public void showQuitWarningPanel(boolean show) {
		quitWarningFrame.setLocationRelativeTo(this);
		quitWarningFrame.setVisible(show);

		if (System.getProperty("os.name").startsWith("Windows")) {
			// disabling is broken under windows
			layeredPane.setVisible(!show);
			menuBar.setVisible(!show);
		} else {
			setEnabled(!show);
		}
	}

	@Override
	public void showConnectPanel(boolean show) {
		connectPanel.setVisible(show);
	}

	@Override
	public void setCurrentPlayerName(String playerName) {
		playerPanel.setCurrentPlayerName(playerName);
		startTurnPanel.setCurrentPlayerName(playerName);
		pausePanel.setCurrentPlayerName(playerName);
	}

	@Override
	public void setInitialMeldError(int points) {
		startTurnPanel.setInitialMeldError(points);
	}

	@Override
	public void setInitialMeldFirstError() {
		startTurnPanel.setInitialMeldFirstError();
	}

	@Override
	public void setCurrentPlayerColor(Color color) {
		playerPanel.setCurrentPlayerColor(color);
		startTurnPanel.setCurrentPlayerColor(color);
		pausePanel.setCurrentPlayerColor(color);
	}

	@Override
	public void setRedealedPlayerName(String name) {
		startTurnPanel.setRedealedPlayerName(name);
	}

	@Override
	public void setLastStonePlayerName(String name) {
		startTurnPanel.setLastStonePlayerName(name);
	}

	@Override
	public void setCurrentPlayerHasLaidOut(boolean hasLaidOut) {
		playerPanel.setHasLaidOut(hasLaidOut);
	}

	@Override
	public IEvent getStartTurnEvent() {
		return startTurnPanel.getStartTurnEvent();
	}

	@Override
	public IEvent getAcknowledgeInvalidEvent() {
		return startTurnPanel.getAcknowledgeInvalidEvent();
	}

	@Override
	public IEvent getAcknowledgeConnectionLostEvent() {
		return startTurnPanel.getAcknowledgeConnectionLostEvent();
	}

	@Override
	public IEvent getNewRoundEvent() {
		return roundEndPanel.getNewRoundEvent();
	}

	@Override
	public IEvent getNewGameEvent() {
		return roundEndPanel.getNewGameEvent();
	}

	@Override
	public IEvent getEndProgramEvent() {
		return roundEndPanel.getEndProgramEvent();
	}

	@SuppressWarnings("unchecked")
	private List<Pair<Stone, Position>> createDecorationStones() {
		Pair<Stone, Position> stoneJ = new Pair<Stone, Position>(new Stone(-'J',
				StoneColor.BLACK), new Position(2.5f, 0));
		Pair<Stone, Position> stoneR = new Pair<Stone, Position>(new Stone(-'R',
				StoneColor.ORANGE), new Position(3.5f, 0));
		Pair<Stone, Position> stoneu1 = new Pair<Stone, Position>(new Stone(-'u',
				StoneColor.BLUE), new Position(4.5f, 0));
		Pair<Stone, Position> stonem1 = new Pair<Stone, Position>(new Stone(-'m',
				StoneColor.RED), new Position(5.5f, 0));
		Pair<Stone, Position> stonem2 = new Pair<Stone, Position>(new Stone(-'m',
				StoneColor.GREEN), new Position(6.5f, 0));
		Pair<Stone, Position> stonei = new Pair<Stone, Position>(new Stone(-'i',
				StoneColor.VIOLET), new Position(7.5f, 0));
		Pair<Stone, Position> stonek = new Pair<Stone, Position>(new Stone(-'k',
				StoneColor.AQUA), new Position(8.5f, 0));
		Pair<Stone, Position> stoneu2 = new Pair<Stone, Position>(new Stone(-'u',
				StoneColor.GRAY), new Position(9.5f, 0));
		Pair<Stone, Position> stoneb = new Pair<Stone, Position>(new Stone(-'b',
				StoneColor.BLACK), new Position(10.5f, 0));

		Pair<Stone, Position> stone1 = new Pair<Stone, Position>(new Stone(
				StoneColor.RED), new Position(2, 1));
		Pair<Stone, Position> stone2 = new Pair<Stone, Position>(new Stone(13,
				StoneColor.BLACK), new Position(5, 1));
		Pair<Stone, Position> stone3 = new Pair<Stone, Position>(new Stone(13,
				StoneColor.ORANGE), new Position(6, 1));
		Pair<Stone, Position> stone4 = new Pair<Stone, Position>(new Stone(13,
				StoneColor.BLUE), new Position(7, 1));
		Pair<Stone, Position> stone5 = new Pair<Stone, Position>(new Stone(13,
				StoneColor.RED), new Position(8, 1));
		Pair<Stone, Position> stone6 = new Pair<Stone, Position>(new Stone(
				StoneColor.BLACK), new Position(11, 1));

		return Arrays
				.asList(stoneJ, stoneR, stoneu1, stonem1, stonem2, stonei, stonek,
						stoneu2, stoneb, stone1, stone2, stone3, stone4, stone5, stone6);
	}

	@Override
	public void setBottomPanel(BottomPanelType type) {
		bottomPanelType = type;
		doSetBottomPanel(type);
	}

	private void doSetBottomPanel(BottomPanelType type) {
		boolean showStartTurnPanel = (type == BottomPanelType.START_TURN_PANEL
				|| type == BottomPanelType.START_REDEAL_TURN_PANEL
				|| type == BottomPanelType.START_LAST_TURN_PANEL
				|| type == BottomPanelType.INVALID_TURN_PANEL || type == BottomPanelType.NETWORK_SERVER_CONNECTION_LOST_PANEL);
		startTurnPanel.setType(type);
		startTurnPanel.setVisible(showStartTurnPanel);

		boolean showWinPanel = (type == BottomPanelType.WIN_PANEL
				|| type == BottomPanelType.NETWORK_WIN_PANEL || type == BottomPanelType.NETWORK_CONNECTION_LOST_PANEL);
		roundEndPanel.setType(type);
		roundEndPanel.setVisible(showWinPanel);

		playerPanel.setVisible((!showStartTurnPanel) && (!showWinPanel)
				&& type != null);

		if (type == BottomPanelType.START_GAME_PANEL) {
			table.setStoneSets(Collections.<Pair<StoneSet, Position>> emptyList());
			playerPanel.getHandPanel().setStones(createDecorationStones());
		}

		playerPanel.showButtons(type != BottomPanelType.START_GAME_PANEL);
		playerPanel.enableButtons(type != BottomPanelType.NONHUMAN_HAND_PANEL);
	}

	@Override
	public void setStoneCollectionHidden(boolean enable) {
		table.getStoneCollectionPanel().setHidden(enable);
	}
}
