package org.iri_research.renkan.coweb;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Named;

import org.coweb.DefaultSessionModerator;
import org.iri_research.renkan.coweb.event.ISyncEventManager;
import org.iri_research.renkan.models.Project;
import org.iri_research.renkan.models.RenkanSessionModeratorState;
import org.iri_research.renkan.models.RosterUser;
import org.iri_research.renkan.repositories.ProjectsRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

@Named
public class RenkanSessionModerator extends DefaultSessionModerator {
	
	private final Logger logger = LoggerFactory.getLogger(RenkanSessionModerator.class);
	
	@Inject
	private ProjectsRepository projectsRepository;
	
	
	public ProjectsRepository getProjectsRepository() {
		if(this.projectsRepository == null) {
			ApplicationContext context = SpringConfigurer.getInstance().getApplicationContext();
			this.projectsRepository = (ProjectsRepository)context.getBean("projectsRepository");
		}
		return projectsRepository;
	}

	@Override
	public synchronized boolean canClientJoinSession(String clientId, Map<String,Object> userDefined) {
		
		this.logger.debug("canClientJoinSession: " + clientId  + " , " + (userDefined==null?"null":userDefined.toString()));
		
		if(RenkanSessionModeratorState.INSTANCE.getUsersActivationMap().containsKey(clientId)) {
			this.logger.warn("Client id already declared in user activation map: " + clientId);
		}
		
		String projectId = (String)userDefined.get("project_id");
		
		if(projectId == null) {
			return false;
		}
		
		RenkanSessionModeratorState.INSTANCE.getUsersActivationMap().put(clientId, projectId);
		return true;
	}
	
	private synchronized void activateProject(String clientId) {
		
		if(!RenkanSessionModeratorState.INSTANCE.getUsersActivationMap().containsKey(clientId)) {
			this.logger.warn("Client id not declared in user activation map: " + clientId);
			return;
		}
		String project_id = RenkanSessionModeratorState.INSTANCE.getUsersActivationMap().get(clientId);
		if(project_id == null) {
			this.logger.warn("Null project id for client id in user activation map: " + clientId);
			return;
		}
		
		List<String> user_list = RenkanSessionModeratorState.INSTANCE.getProjectsActivationMap().get(project_id);
		if(user_list == null) {
			user_list = new ArrayList<String>();
			RenkanSessionModeratorState.INSTANCE.getProjectsActivationMap().put(project_id, user_list);
		}

		if(!user_list.contains(clientId)) {
			user_list.add(clientId);
		}

	}
 	
	@Override
	public synchronized void onClientJoinSession(String clientId) {
		
		this.logger.debug("onClientJoinSession: " + clientId);
		
		this.activateProject(clientId);
		
	}
	
	@Override
	public synchronized void onClientLeaveSession(String clientId) {
		
		this.logger.debug("onClientLeaveSession: " + clientId);
		
		String project_id = RenkanSessionModeratorState.INSTANCE.getUsersActivationMap().get(clientId);
		
		RenkanSessionModeratorState.INSTANCE.getUsersActivationMap().remove(clientId);
		if(project_id == null) {
			this.logger.warn("Leaving client have no associated project: " + clientId);
			return;
		}
		
		List<String> user_list = RenkanSessionModeratorState.INSTANCE.getProjectsActivationMap().get(project_id);
		if(user_list == null) {
			this.logger.warn("Leaving client have associated project but no project list : " + clientId + ", " + project_id);
			return;
		}
		
		if(!user_list.remove(clientId)) {
			this.logger.warn("Leaving client have associated project but not in project list : " + clientId + ", " + project_id);
			return;
		}
		
		if(user_list.isEmpty()) {
			this.logger.debug("Leaving client, project list empty, removing from active projects: " + clientId + ", " + project_id);
			RenkanSessionModeratorState.INSTANCE.getProjectsActivationMap().remove(project_id);
		}
		
		List<RosterUser> r_user_list = RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().get(project_id);
		if(r_user_list == null) {
			this.logger.warn("Leaving client have associated project but no user list : " + clientId + ", " + project_id);
			return;
		}
		for (RosterUser rosterUser : r_user_list) {
			if( rosterUser.getClient_id().equals(clientId)) {
				r_user_list.remove(rosterUser);
			}
		}
		if(r_user_list.isEmpty()) {
			this.logger.debug("Leaving client, user list empty, removing from active projects: " + clientId + ", " + project_id);
			RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().remove(project_id);
		}		
		
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public synchronized void onSync(String clientId, Map<String, Object> data) {
				
		this.logger.debug("Debugging onSync client id: " + clientId);
		this.logger.debug("Debugging onSync: " + data.toString());
		this.logger.debug("Debugging onSync channel: " + data.get("channel"));
		this.logger.debug("Debugging onSync type: " + data.get("type"));
		this.logger.debug("Debugging onSync site: " + data.get("site"));
		this.logger.debug("Debugging onSync value: " + data.get("value"));
		this.logger.debug("Debugging onSync position: " + data.get("position"));
				
		Map<String, Object> values = null;
		if(data.containsKey("value") && data.get("value") != null) {
			values = ((Map<String, Object>) data.get("value"));
		}
		
		if(values == null) {
			this.logger.warn("onSync : no values in message.");
			return;		
		}
		
		String sync_type = (String) values.get("_type");
		if(sync_type == null || sync_type.length() == 0) {
			this.logger.warn("onSync : no type in value of message.");
			return;
		}
		
		if(sync_type.startsWith("_")) {
			this.logger.debug("onSync : type sync begin with _, ignore. " + sync_type);
			return;
		}
		
		ApplicationContext context = SpringConfigurer.getInstance().getApplicationContext();
				
		String beanName = String.format("%sSyncEventManager", sync_type.toLowerCase());
		
		try {
			ISyncEventManager<?, ?> eventManager = (ISyncEventManager<?, ?>)context.getBean(beanName);
			logger.debug("Debugging on Sync : dispatch to " + beanName);
			eventManager.dispatchEvent(clientId, data);
		}
		catch(Throwable e) {
			this.logger.error(String.format("onSync EventManagerClass %s not found : error %s : %s", sync_type, e.toString(), e.getMessage()));
		}
		
	}

	@Override
	public Map<String, Object> getLateJoinState() {
		this.logger.debug("getLateJoinState");
		
		Map<String, Object> res = super.getLateJoinState();
		ObjectMapper mapper = new ObjectMapper();
		
		for (String clientId : RenkanSessionModeratorState.INSTANCE.getUsersActivationMap().keySet()) {
			this.activateProject(clientId);
		}
		
		this.logger.debug("getLateJoinState : Project activated");
		
		for (String project_id : RenkanSessionModeratorState.INSTANCE.getProjectsActivationMap().keySet()) {

			Project p = this.getProjectsRepository().findOne(project_id);
			if (p != null) {
				try {
					res.put("renkan_"+project_id, mapper.writeValueAsString(p));
				} catch (JsonProcessingException e) {
					this.logger.error("Error when deserializing project " + project_id, e);
				}
			}
			
			String user_res = "[]";
			if(RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().containsKey(project_id)) {
				try {
					user_res = mapper.writeValueAsString(RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().get(project_id));
				} catch (JsonProcessingException e) {
					this.logger.error("Error when deserializing user list " + project_id, e);
				}
			}
			res.put("users_"+project_id, user_res);
		}
		
		
		this.logger.debug("getLateJoinState res : " + res.toString());
		return res;
	}

}
