package client.gui;

import java.io.File;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;

import client.gui.element.ActButton;
import client.gui.element.ButtonCallback;
import client.gui.element.GuiList;
import client.gui.element.ListEntry;
import client.gui.element.NavButton;
import client.gui.element.PressType;
import client.renderer.Drawing;
import client.util.FileUtils;
import common.log.Log;
import common.network.IPlayer;
import common.util.EncryptUtil;
import common.util.Pair;
import common.util.Color;
import common.util.Util;

public class GuiConnect extends GuiList<GuiConnect.ServerInfo> implements ButtonCallback {
	public class ServerInfo implements Comparable<ServerInfo>, ListEntry {
		private final boolean direct;
		
		private String name;
		private String address;
		private int port;
		private String user;
		private String password;
		private String access;
		private KeyPair keypair;
		private String keyDigest;
		private PublicKey serverKey;
		private String serverDigest;
		private boolean enforceEncryption;
		private long lastConnected;

		public ServerInfo(String address, int port, String user, String password, String access) {
			this.direct = true;
			this.name = DIRECT_NAME;
			this.address = address;
			this.port = port;
			this.user = user;
			this.password = password;
			this.access = access;
			this.lastConnected = -1L;
		}

		public ServerInfo(String name, String address, int port, String user, String password, String access, KeyPair keypair, long lastConnected, PublicKey serverKey, boolean enforceEnc) {
			this.direct = false;
			this.name = name;
			this.address = address;
			this.port = port;
			this.user = user;
			this.password = password;
			this.access = access;
			this.keypair = keypair;
			this.lastConnected = lastConnected;
			this.serverKey = serverKey;
			this.enforceEncryption = enforceEnc;
			if(this.keypair != null)
				this.keyDigest = EncryptUtil.getXorSha512Hash(this.keypair.getPublic().getEncoded());
			if(this.serverKey != null)
				this.serverDigest = EncryptUtil.getXorSha512Hash(this.serverKey.getEncoded());
		}
		
		public boolean isDirect() {
			return this.direct;
		}

		public String getName() {
			return this.name;
		}

		public String getAddress() {
			return this.address;
		}

		public int getPort() {
			return this.port;
		}

		public String getUser() {
			return this.user;
		}

		public String getPassword() {
			return this.password;
		}

		public String getAccess() {
			return this.access;
		}

		public KeyPair getKeypair() {
			return this.keypair;
		}

		public PublicKey getServerKey() {
			return this.serverKey;
		}

		public boolean requiresEncryption() {
			return this.enforceEncryption;
		}

		public long getLastConnected() {
			return this.lastConnected;
		}

		public void setData(String name, String address, int port, String user, String password, String access, KeyPair keypair, PublicKey serverKey, boolean encryptReq) {
			this.name = name;
			this.address = address;
			this.port = port;
			this.user = user;
			this.password = password;
			this.access = access;
			this.keypair = keypair;
			this.serverKey = serverKey;
			this.enforceEncryption = encryptReq;
			this.keyDigest = this.keypair != null ? EncryptUtil.getXorSha512Hash(this.keypair.getPublic().getEncoded()) : null;
			this.serverDigest = this.serverKey != null ? EncryptUtil.getXorSha512Hash(this.serverKey.getEncoded()) : null;
		}

		public void setLastConnected() {
			this.lastConnected = System.currentTimeMillis();
		}

		public void setServerKey(PublicKey key) {
			this.serverKey = key;
			this.serverDigest = this.serverKey != null ? EncryptUtil.getXorSha512Hash(this.serverKey.getEncoded()) : null;
		}

		public int compareTo(ServerInfo comp) {
			return this.lastConnected < comp.lastConnected ? 1 : (this.lastConnected > comp.lastConnected ? -1 : this.name.compareTo(comp.name));
		}

		public void select(boolean isDoubleClick, int mouseX, int mouseY) {
			GuiConnect.this.selectButton.enabled = true;
			GuiConnect.this.deleteButton.enabled = true;
			GuiConnect.this.editButton.enabled = true;
			GuiConnect.this.copyButton.enabled = true;

			if(isDoubleClick) {
				GuiConnect.this.use(GuiConnect.this.selectButton, PressType.PRIMARY);
			}
		}

		public void draw(int x, int y, int width, int height, int mouseXIn, int mouseYIn, boolean hover) {
			Drawing.drawText(this.name + Color.GRAY + " - " + Color.RESET + this.user, x + 2, y, 0xffffffff);
			if(this.keypair != null || !this.password.isEmpty())
				Drawing.drawTextRight(
					(this.keypair != null ? "Pubkey " + this.keyDigest : "") + (this.keypair != null && !this.password.isEmpty() ? " + " : "") + (!this.password.isEmpty() ? "Passwort" : ""), 
					x + width - 2, y, 0xffffffff);
			Drawing.drawText(this.address + Color.GRAY + " Port " + Color.RESET + this.port + (this.enforceEncryption ? Color.GRAY + ", nur verschlüsselt" : ""), 
				x + 2, y + height - Font.HEIGHT * 2, 0xffb0b0b0);
			if(!this.access.isEmpty())
				Drawing.drawTextRight((this.keypair != null || !this.password.isEmpty() ? "+ " : "") + "Server-Passwort", x + width - 2, y + height - Font.HEIGHT * 2, 0xffb0b0b0);
			Drawing.drawText("Zuletzt verbunden: " + (this.lastConnected == -1L ? "nie" : DATE_FORMAT.format(new Date(this.lastConnected))), x + 2, y + height - Font.HEIGHT, 0xffb0b0b0);
			if(this.serverDigest != null)
				Drawing.drawTextRight("Server-ID: " + this.serverDigest, x + width - 2, y + height - Font.HEIGHT, 0xffb0b0b0);
		}
	}

	public static final GuiConnect INSTANCE = new GuiConnect();
	private static final String DIRECT_NAME = "<Direktverbindung>";
	private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
	private static final File SERVERS_FILE = new File("servers.cfg");

	private ActButton deleteButton;
	private ActButton selectButton;
	private ActButton copyButton;
	private ActButton editButton;
	private ActButton createButton;

	private GuiConnect() {
	}

	public void init(int width, int height) {
		super.init(width, height);
		this.setDimensions(width, height, 32, height - 32);
		this.elements.clear();
		if(SERVERS_FILE.exists()) {
			try {
				String[] lines = FileUtils.read(SERVERS_FILE).split("\n");
				String name = "";
				String address = "";
				int port = -1;
				String user = "";
				String password = "";
				String access = "";
				byte[] key = null;
				byte[] pubkey = null;
				byte[] serverKey = null;
				boolean enforceEnc = false;
				long time = -1L;
				for(int z = 0; z <= lines.length; z++) {
					String line = z == lines.length ? null : lines[z];
					if(line == null || (line.startsWith("[") && line.endsWith("]"))) {
						if(!name.isEmpty() && !address.isEmpty() && !user.isEmpty() && user.length() < IPlayer.MAX_USER_LENGTH && IPlayer.isValidUser(user) && 
								password.length() < IPlayer.MAX_PASS_LENGTH && access.length() < IPlayer.MAX_PASS_LENGTH && address.length() < 128 && name.length() < 128 && 
								port >= 1024 && port <= 32767 && (password.length() >= 8 || password.isEmpty()) && (access.length() >= 8 || access.isEmpty()) && !this.isNameTaken(name)) {
							PrivateKey priv = key == null ? null : EncryptUtil.decodePrivateKey(key);
							PublicKey pub = pubkey == null ? null : EncryptUtil.decodePublicKey(pubkey);
							PublicKey serv = serverKey == null ? null : EncryptUtil.decodePublicKey(serverKey);
							this.elements.add(new ServerInfo(name, address, port, user, password, access, priv == null || pub == null ? null : new KeyPair(pub, priv), time, serv, enforceEnc));
						}
						if(line != null) {
							address = "";
							port = -1;
							user = "";
							password = "";
							access = "";
							key = null;
							pubkey = null;
							serverKey = null;
							enforceEnc = false;
							time = -1L;
							name = line.substring(1, line.length() - 1);
						}
					}
					else {
						Pair<String, String> value = Util.getKeyValue(line);
						if(value.first().equals("address"))
							address = value.second();
						else if(value.first().equals("port"))
							try {
								port = Integer.parseInt(value.second());
							}
							catch(NumberFormatException e) {
							}
						else if(value.first().equals("user"))
							user = value.second();
						else if(value.first().equals("password"))
							password = value.second();
						else if(value.first().equals("access"))
							access = value.second();
						else if(value.first().equals("connected"))
							try {
								time = Long.parseLong(value.second());
							}
							catch(NumberFormatException e) {
							}
						else if(value.first().equals("encryption_enforced"))
							enforceEnc = true;
						else if(value.first().equals("serverkey"))
							serverKey = Util.fromHexString(value.second());
						else if(value.first().equals("key"))
							key = Util.fromHexString(value.second());
						else if(value.first().equals("pubkey"))
							pubkey = Util.fromHexString(value.second());
					}
				}
				Collections.sort(this.elements);
			}
			catch(Exception e) {
				Log.IO.error("Konnte Serverliste nicht laden", e);
				this.elements.clear();
			}
		}

		this.add(this.selectButton = new ActButton(width / 2 - 379, height - 30, 150, 0, this, "Verbinden"));
		this.add(this.createButton = new ActButton(width - 202, 12, 200, 0, this, "Server hinzufügen ..."));
		this.add(this.deleteButton = new ActButton(width / 2 - 75, height - 30, 150, 0, this, "Löschen"));
        this.add(this.editButton = new ActButton(width / 2 + 77, height - 30, 150, 0, this, "Bearbeiten"));
		this.add(this.copyButton = new ActButton(width / 2 - 227, height - 30, 150, 0, this, "Kopieren"));
		this.add(new NavButton(width / 2 + 229, height - 30, 150, 0, GuiMenu.INSTANCE, "Abbrechen"));

		this.selectButton.enabled = false;
		this.deleteButton.enabled = false;
		this.editButton.enabled = false;
		this.copyButton.enabled = false;
	}
	
	public void onGuiClosed() {
		this.save();
    }
	
	public void applyServer(ServerInfo server) {
		if(this.selectedElement < 0)
			this.elements.add(server);
		this.save();
		this.gm.show(this);
	}
	
	public boolean isNameTaken(String name) {
		for(int z = 0; z < this.elements.size(); z++) {
			if(this.selectedElement != z && this.elements.get(z).getName().equals(name))
				return true;
		}
		return DIRECT_NAME.equals(name);
	}
	
	public void editServer(ServerInfo server) {
		if(!server.isDirect())
			this.save();
	}
	
	public void connect(String address, int port, String user, String pass, String access) {
		this.gm.connect(new ServerInfo(address, port, user, pass, access));
	}
	
	private void save() {
		try {
			StringBuilder sb = new StringBuilder();
			for(ServerInfo server : this.elements) {
				if(sb.length() > 0)
					sb.append("\n");
				sb.append("[" + server.getName() + "]");
				sb.append("\naddress " + server.getAddress());
				sb.append("\nport " + server.getPort());
				sb.append("\nuser " + server.getUser());
				if(!server.getPassword().isEmpty())
					sb.append("\npassword " + server.getPassword());
				if(!server.getAccess().isEmpty())
					sb.append("\naccess " + server.getAccess());
				if(server.getKeypair() != null) {
					sb.append("\nkey " + Util.getHexString(server.getKeypair().getPrivate().getEncoded()));
					sb.append("\npubkey " + Util.getHexString(server.getKeypair().getPublic().getEncoded()));
				}
				if(server.requiresEncryption())
					sb.append("\nencryption_enforced");
				if(server.getLastConnected() != -1L)
					sb.append("\nconnected " + server.getLastConnected());
				if(server.getServerKey() != null)
					sb.append("\nserverkey " + Util.getHexString(server.getServerKey().getEncoded()));
			}
			FileUtils.write(SERVERS_FILE, sb.toString());
		}
		catch(Exception e) {
			Log.IO.error("Konnte Serverliste nicht speichern", e);
		}
	}

	public String getTitle() {
		return "Server auswählen";
	}

	public int getSlotHeight() {
		return 56;
	}

	public void use(ActButton button, PressType mode) {
		if(button == this.deleteButton) {
			if(this.selectedElement >= 0) {
				this.elements.remove(this.selectedElement);
				this.gm.show(this);
			}
		}
		else if(button == this.selectButton) {
			ServerInfo server = this.getSelected();
			if(server != null) {
				server.setLastConnected();
				this.gm.connect(server);
			}
		}
		else if(button == this.createButton) {
			this.setSelected(-1);
			this.gm.show(new GuiServer(new ServerInfo("", "", -1, "", "", "", null, -1L, null, false)));
		}
		else if(button == this.editButton) {
			ServerInfo server = this.getSelected();
			if(server != null)
				this.gm.show(new GuiServer(server));
		}
		else if(button == this.copyButton) {
			ServerInfo server = this.getSelected();
			if(server != null) {
				this.setSelected(-1);
				this.gm.show(new GuiServer(new ServerInfo(server.name, server.address, server.port, server.user, server.password, server.access, server.keypair, -1L, null, server.enforceEncryption)));
			}
		}
	}
}
