package com.framsticks.net.client3D;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;

import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;

import foxtrot.Task;
import foxtrot.Worker;

/**
 * Glowna klasa aplikacji
 * 
 * Poniewaz poczaktowo glownym interfejsem byla linia komend / konsola (w GUI) 
 * dlatego wszystkie akcje wykonywane sa za pomoca polecen tekstowych.  
 * Opcje w menu wywoluja po prostu parseCommand z odpowiednim poleceniem.
 * 
 * @author vorg
 */
//TODO: console scrollLock
public class App extends JFrame {
	static final long serialVersionUID = 1;
	
	private Client client;
	private Viewer viewer;
	private StyledDocument styledDocument;
	private ArrayList<String> commandHistory;
	private int commandId = 0;
	private JTextPane textPane;
	private JTextField inputLine;
	private boolean lockScroll = false;
	private JMenu viewMenu;
	private JMenuBar menuBar;
	private JMenuItem connectItem;
	private JMenuItem disconnectItem;
	private JCheckBoxMenuItem loggingItem;
	private JCheckBoxMenuItem autorefreshItem;
	private JMenu styleMenu;
	
	private final String DEFAULT_HOST = "127.0.0.1"; //"192.168.10.3"; //192.168.1.102
	private final String DEFAULT_PORT = "9009"; 
	
	private class SwitchCreatureAction implements ActionListener {
		private App console;
		private int group;
		private int index;
		public SwitchCreatureAction(App console, int group, int index) {
			this.console = console;
			this.group = group;
			this.index = index;
		}

		public void actionPerformed(ActionEvent e) {
			console.parseCommand("viewcreature " + group + " " + index);
		}
	}
	
	public App() {
		super("Framsticks - Client3D");
		init();
	}
	
	private void init() {
		JFrame.setDefaultLookAndFeelDecorated(true);
		commandHistory = new ArrayList<String>();
		createChildren();
		createMenuBar();
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setVisible(true);
		
		client = new Client();
		viewer = new Viewer(this);
		viewer.showInFrame();
	}
	
	private void createChildren() {
		
		JFrame.setDefaultLookAndFeelDecorated(true);
		setSize(500, 500);
		setLocationRelativeTo(null);
		setLocation(this.getX()+255, this.getY());
		setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);

		getContentPane().setLayout(new BorderLayout());
		createTextArea();
		//poniewaz wszystkie opcje dostepne sa teraz z menu wylaczylem mozliwosc wpisywania polecen tekstowo
		//createInputLine();
		
		Log.getInstance().addLoggerListener(new ILogListener() {
			public void onMesssage(String category, String text) {
				try {
					styledDocument.insertString(styledDocument.getLength(), text + "\n", styledDocument.getStyle(category));
					if (!lockScroll) textPane.scrollRectToVisible(textPane.getVisibleRect());
				} catch (BadLocationException ble) {
					System.err.println("Couldn't insert initial text into text pane.");
				}
			}
		});
		
	}
	
	public void setInputLineText(String text) {
		inputLine.setText(text);
	}
	
	private void createTextArea() {		
        textPane = new JTextPane();
        textPane.setEditable(false);
        styledDocument = textPane.getStyledDocument();
        addStylesToDocument(styledDocument);
        
        JScrollPane paneScrollPane = new JScrollPane(textPane);
        paneScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

        getContentPane().add(paneScrollPane, BorderLayout.CENTER);        
    }
	
	class StyleListener implements ActionListener
	{
		String styleName;
		StyleListener(String name)
		{
			styleName = name;
		}
		public void actionPerformed(ActionEvent event)
		{
			parseCommand("style "+styleName);
		}
	}
	public void AddStyle(String name){
		
		JMenuItem item = new JMenuItem(name);
		styleMenu.add(item);
		item.addActionListener(new StyleListener(name));
	}
	
	
	private void createMenuBar() {
		menuBar = new JMenuBar();			
		
		JMenu serverItem = new JMenu("Server");
		menuBar.add(serverItem);
		
		JMenuItem testModeItem = new JMenuItem("Test mode");
		serverItem.add(testModeItem);
		
		testModeItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				parseCommand("connect mock");
			}		
		});
		
		connectItem = new JMenuItem("Connect");
		serverItem.add(connectItem);
		
		final App window = this;
		connectItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				String params = (String)JOptionPane.showInputDialog(
					window,
					"Enter server address (ip:port)\n",
					"Connect",
					JOptionPane.PLAIN_MESSAGE,
					null,
					null,
					DEFAULT_HOST + ":" + DEFAULT_PORT
				);
				if (params != null) {
					params = params.replaceAll(":", " ");
					parseCommand("connect " + params);
				}
			}		
		});
		
		disconnectItem = new JMenuItem("Disonnect");
		serverItem.add(disconnectItem);
		disconnectItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				parseCommand("disconnect");
			}		
		});
		
		JMenu simulationItem = new JMenu("Simulation");
		menuBar.add(simulationItem);
		
		JMenuItem initItem = new JMenuItem("Init");
		simulationItem.add(initItem);
		initItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				parseCommand("> call /simulator init");
			}		
		});
		
		JMenuItem startItem = new JMenuItem("Start");
		simulationItem.add(startItem);
		startItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				parseCommand("> set /simulator running 1");
			}		
		});
		
		JMenuItem stopItem = new JMenuItem("Stop");
		simulationItem.add(stopItem);
		stopItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				parseCommand("> set /simulator running 0");
			}		
		});
		
		JMenuItem refreshCreaturesItem = new JMenuItem("Refresh creatures");
		simulationItem.add(refreshCreaturesItem);
		refreshCreaturesItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				parseCommand("refreshcreatures");
			}		
		});
		
		JMenuItem refreshWorldItem = new JMenuItem("Refresh world");
		simulationItem.add(refreshWorldItem);
		refreshWorldItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				parseCommand("refreshworld");
			}		
		});
		
		styleMenu = new JMenu("Style");
		menuBar.add(styleMenu);
				
		viewMenu = new JMenu("View");
		menuBar.add(viewMenu);
		rebuildViewMenu(null);
		
		JMenu optionsMenu = new JMenu("Options");
		menuBar.add(optionsMenu);
		
		autorefreshItem = new JCheckBoxMenuItem("Autorefresh");
		autorefreshItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				switchAutorefresh();
			}
		});
		optionsMenu.add(autorefreshItem);
			
		loggingItem = new JCheckBoxMenuItem("Logging");
		loggingItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				switchLogging();
			}
		});
		optionsMenu.add(loggingItem);
		loggingItem.setSelected(true);
		
		setJMenuBar(menuBar);
	}
	
	public void switchLogging() {
		Log.getInstance().setEnabled(!Log.getInstance().isEnabled());
	}
	
	public void switchAutorefresh() {
		if (autorefreshItem.isSelected()) {
			parseCommand("refreshcreatures");
		}
	}
	
	/**
	 * Przebudowujemy menu z lista stworow
	 * @param creatures
	 */
	public void rebuildViewMenu(Creature[] creatures) {
		viewMenu.removeAll();
		JMenuItem allMenuItem = new JMenuItem("World");
		allMenuItem.addActionListener(new SwitchCreatureAction(this, -1, -1));
		viewMenu.add(allMenuItem);
		if (creatures != null) {
			for(Creature creature : creatures) {
				JMenuItem item = new JMenuItem("Creature " + creature.toString());
				item.addActionListener(new SwitchCreatureAction(this, creature.getGroup(), creature.getIndex()));
				viewMenu.add(item);
			}
		}
	}
	
	protected void addStylesToDocument(StyledDocument doc) {
        //Initialize some styles
		Style def = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);

        Style s = doc.addStyle("dbg", def);
        StyleConstants.setForeground(s, new Color(150, 150, 150));
        
        s = doc.addStyle("wrn", def);
        StyleConstants.setForeground(s, new Color(255, 150, 0));

        s = doc.addStyle("err", def);
        StyleConstants.setForeground(s, new Color(255, 0, 0));
        
        s = doc.addStyle("cmd", def);
        StyleConstants.setForeground(s, new Color(0, 150, 0));
        
        s = doc.addStyle("<<<", def);
        StyleConstants.setForeground(s, new Color(100, 40, 200));
        //StyleConstants.setFontFamily(s, "Courier New");
        
        s = doc.addStyle(">>>", def);
        StyleConstants.setForeground(s, new Color(30, 100, 200));
        //StyleConstants.setFontFamily(s, "Courier New");
    }
	
	private void createInputLine() {
		inputLine = new JTextField();
		getContentPane().add(inputLine, BorderLayout.PAGE_END);
		inputLine.addKeyListener(new KeyListener() {
			public void keyPressed(KeyEvent e) {
				int keyCode = e.getKeyCode();
				if (keyCode == KeyEvent.VK_ENTER) {
					parseCommand(inputLine.getText());
					inputLine.setText("");
				}
				else if (keyCode == KeyEvent.VK_UP) {
					inputLine.setText(getPrevCommand());
				}
				else if (keyCode == KeyEvent.VK_DOWN) {
					inputLine.setText(getNextCommand());
				}
			}
	
			public void keyReleased(KeyEvent e) {}
			public void keyTyped(KeyEvent e) {}
		});
	}
	
	private void parseCommand(String line) {
		commandHistory.add(line);
		commandId = commandHistory.size();
		
		int endIndex;
		if (line.contains(" ")) endIndex = line.indexOf(" ");
		else endIndex = line.length();
		
		String command = line.substring(0, endIndex).trim();
		String params = line.substring(endIndex).trim();
		
		handleCommand(command, params);
	}
	
	private String getPrevCommand() {
		if (commandId > 0) {
			commandId--;
		}
		if (commandId < commandHistory.size()) {
			return commandHistory.get(commandId);
		}
		return "";
	}
	
	private String getNextCommand() {
		if (commandId < commandHistory.size()) {
			commandId++;
		}
		if (commandId < commandHistory.size()) { 
			return commandHistory.get(commandId);
		}
		else return "";
	}
	
	private void handleCommand(String command, String params) {
		Log.getInstance().log("cmd", command + " " + params);
		if (command.equals("connect") || command.equals("c")) {
			if (params.equals("mock")) {
				connectMockAction();
			}
			else {
				String[] paramList = params.split(" ");
				String host = "";
				int port = 9009;
				if (paramList.length>0) host = paramList[0];
				try {
					if (paramList.length>1) port = Integer.parseInt(paramList[1]);
				}
				catch (NumberFormatException e) {
					Log.getInstance().log("err", "Invalid port, setting default value 9009");
				}
				connectHostAction(host, port); 
			}
		}
		else if (command.equals("disconnect")) {
			viewer.setCreatures(null);
			viewer.setWorld(null);
			client.closeConnection();
		}
		else if (command.equals("refreshcreatures") || command.equals("rc")) {
			refreshCreaturesAction();
		}
		else if (command.equals("viewcreature")) {
			viewCreatureAction(params);
		}
		else if (command.equals("refreshworld") || command.equals("rw")) {
			refreshWorldAction();
		}
		else if (command.equals("style") || command.equals("s")) {
			styleAction(params);
		}
		else if (command.equals(">")) {
			serverRequestAction(params);
		}
		else if (command.equals("quit") || command.equals("q")) {
			//TODO: quit command
		}
	}
	
	private void connectMockAction() {
		client.initConnectionMock();
		refreshWorldAction();
		refreshCreaturesAction();
	}
	
	private void connectHostAction(String host, int port) {
		client.initConnection(host, port);
		if (client.isConnected()) {
			refreshWorldAction();
			refreshCreaturesAction();
		}
	}
	
	private void refreshCreaturesAction() {
		Log.getInstance().log("dbg", "refreshCreaturesAction");
		try {
			Creature[] creatures = (Creature[])Worker.post(new Task() {
	           public Object run() throws Exception {
	              return client.readCreatures();
	           }
	        });
			viewer.setCreatures(creatures);
			rebuildViewMenu(creatures);
		}
		catch(Exception e) {
			Log.getInstance().log("err", e.toString());
			e.printStackTrace();
		}
		Log.getInstance().log("autorefresh " + autorefreshItem.isSelected() + " " + client.isConnected());
		if (autorefreshItem.isSelected() && client.isConnected()) {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					refreshCreaturesAction();
				}
			});
		}
	}
	
	private void refreshWorldAction() {
		Log.getInstance().log("dbg", "refreshWorldAction");
		try {
			World world = (World)Worker.post(new Task() {
	           public Object run() throws Exception {
	              return client.readWorld();
	           }
	        });
			viewer.setWorld(world);
		}
		catch(Exception e) {
			Log.getInstance().log("err", e.toString());
			e.printStackTrace();
		}
	}
	
	private void styleAction(String styleName) {
		Log.getInstance().log("dbg", "SetStyle " + styleName);
		viewer.setStyle(styleName);
	}
	
	private void viewCreatureAction(String params) {
		int group = -1;
		int index = -1;
		try {
			String[] arr = params.split(" ");
			if (arr.length > 0) {
				group = Integer.parseInt(arr[0]);
			}
			if (arr.length > 1) {
				index = Integer.parseInt(arr[1]);
			}
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		
		viewer.setView(group, index);
	}
	
	private void serverRequestAction(String request) {
		Log.getInstance().log("dbg", "rerverRequestAction \"" + request + "\"");
		final String finalRequest = request;
		try {
			Worker.post(new Task() {
	           public Object run() throws Exception {
	              client.send(finalRequest);
	              return null;
	           }
	        });
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		Log.getInstance().log("dbg", "rerverRequestAction finished \"" + request + "\"");
	}
	
	public static void main(String[] args) {
		App app = new App();
	}
}
