package org.iri_research.renkan.coweb;

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

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;
import com.fasterxml.jackson.datatype.joda.JodaModule;

public class RenkanSessionModerator extends DefaultSessionModerator {

    private final Logger logger = LoggerFactory
            .getLogger(RenkanSessionModerator.class);

    private ProjectsRepository projectsRepository;

    public RenkanSessionModerator() {
        super();
    }

    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.getClientId().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()));
            //TODO: better manage errors (message back to the user, close the session ?
        }

    }

    @Override
    public Map<String, Object> getLateJoinState() {
        this.logger.debug("getLateJoinState");

        Map<String, Object> res = super.getLateJoinState();
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JodaModule());

        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;
    }

}
