/*
 * Created by Andrey Cherkashin (acherkashin)
 * http://acherkashin.me
 * 
 * License
 * Copyright (c) 2015 Andrey Cherkashin
 * The project released under the MIT license: http://opensource.org/licenses/MIT
 */
package ragefist.core.distribution;

import com.juniform.JUniform;
import com.juniform.JUniformMutableObject;
import com.juniform.JUniformObject;
import java.net.ConnectException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import ragefist.core.Processor;
import ragefist.core.ProcessorTask;
import ragefist.core.ProcessorTask.IProcessorTaskDelegate;
import ragefist.core.network.Client;
import ragefist.core.network.ClientProcessor;
import ragefist.core.network.ClientProcessor.ClientProcessorTask;
import ragefist.core.network.Server;

/**
 *
 * @author acherkashin
 */
public class DistributedServerConnector extends Processor implements Runnable, IProcessorTaskDelegate
{
	// ---------------------------------------------------------------------- //
	// TASKS MANAGEMENTS
	// ---------------------------------------------------------------------- //
	
	public static class DistributedServerConnectorTask extends ProcessorTask
	{
		private final String _serverGroupName;
		private final int _serverId;
		private final JUniformMutableObject _packet;
		
		public DistributedServerConnectorTask(String groupName, int serverId, JUniformMutableObject packet) {
			_serverGroupName = groupName;
			_serverId = serverId;
			_packet = packet;
			setAsync(true);
		}

		@Override
		protected boolean _executeImpl(Processor processor) {
			DistributedServerConnector target = (DistributedServerConnector)processor;
			Client client;
			try {
				client = target._getRemoteServerSocket(_serverGroupName, _serverId);
			} catch (IllegalArgumentException | ConnectException ex) {
				Logger.getLogger(DistributedServerConnector.class.getName()).log(Level.SEVERE, "failed to connect to a remote server", ex);
				this.failWithError("Failed to connect to a remote server: "+ex.getMessage());
				return false;
			}
			if (client == null) {
				this.failWithError("Failed to connect to a remote server: unknown reason");
				return false;
			}
			
			ClientProcessorTask sendTask = new ClientProcessorTask(client, _packet);
			sendTask.setDelegate(target);
			sendTask.setAttachment(this);
			target._clientProcessor.addTask(sendTask);
			return true;
		}
	}
	
	@Override
	public void whenTaskIsFinished(ProcessorTask task) {
		DistributedServerConnectorTask parentTask = (DistributedServerConnectorTask) task.getAttachment();
		
		JUniformObject resp = (JUniformObject) task.getData();
		
		Long rs = resp.getProperty("rs").toLong();
		JUniformObject data = null;
		if (resp.getProperty("data").isNotNull()) {
			data = resp.getProperty("data");
		}
		
		String error = null;
		if (resp.getProperty("error").isNotNull()) {
			error = resp.getProperty("error").toString();
		}
		
		if (rs == 1) {
			if (data != null) {
				parentTask.completeWithData(data);
			} else {
				parentTask.complete();
			}
		} else {
			parentTask.failWithError(error);
		}
	}
	
	// ---------------------------------------------------------------------- //
	// PRIVATE
	// ---------------------------------------------------------------------- //
	
	private final Map<String,Map<Integer,Client>> _connectorHash = new HashMap<>();
	private final Map<String,DistributedServerGroup> _serverGroups;
	private final ClientProcessor _clientProcessor;
	
	public DistributedServerConnector(Map<String,DistributedServerGroup> serverGroups) {
		_serverGroups = serverGroups;
		_clientProcessor = new ClientProcessor();
	}
	
	@Override
	public void run() {
		while(true) {
			this.processTasks();
			_clientProcessor.processTasks();
			
			try {
				Thread.sleep(1);
			} catch (InterruptedException ex) {
				Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
			}
		}
	}
	
	private Client _getRemoteServerSocket(String groupName, int serverId) throws IllegalArgumentException, ConnectException {
		if (_connectorHash.containsKey(groupName)) {
			Map<Integer,Client> socketMap = _connectorHash.get(groupName);
			if (socketMap.containsKey(serverId)) {
				return socketMap.get(serverId);
			}
		}
		
		// Getting a server's info
		if (!_serverGroups.containsKey(groupName)) {
			throw new IllegalArgumentException("Failed to find server group with name "+groupName);
		}
		DistributedServerGroup serverGroup = _serverGroups.get(groupName);
		DistributedServer server;
		try {
			server = serverGroup.getServers().get(serverId);
		} catch (IndexOutOfBoundsException ex) {
			throw new IllegalArgumentException("Failed to find server "+serverId+" in group with name "+groupName);
		}
		
		// Creating a socket
		Client socket = _clientProcessor.getClientForHostPort(server.getRPCHost(), server.getRPCPort());
		socket.setPacketPacker(JUniform.getPackerInstance(server.getRPCPacketFormat()));
		
		// Save
		Map<Integer,Client> socketMap;
		if (!_connectorHash.containsKey(groupName)) {
			socketMap = new HashMap<>();
			_connectorHash.put(groupName, socketMap);
		} else {
			socketMap = _connectorHash.get(groupName);
		}
		socketMap.put(serverId, socket);
		return socket;
	}
}
