package org.iri_research.renkan.coweb.event;

import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.coweb.CowebException;
import org.iri_research.renkan.RenkanException;
import org.iri_research.renkan.models.IRenkanModel;
import org.iri_research.renkan.models.Project;
import org.iri_research.renkan.models.ProjectSync;
import org.iri_research.renkan.repositories.IRenkanRepository;
import org.iri_research.renkan.repositories.ProjectSyncsRepository;
import org.iri_research.renkan.repositories.ProjectsRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.WriteResult;


public abstract class AbstractSyncEventManager<T extends IRenkanModel<ID>, ID extends Serializable> implements IPersistedSyncEventManager<T,ID> {

	private final Logger logger = LoggerFactory.getLogger(AbstractSyncEventManager.class);
	
	@Inject
	private ProjectsRepository projectsRepository;
	
	@Inject
	private ProjectSyncsRepository projectSyncsRepository;
	
	@Override
	public ProjectsRepository getProjectsRepository() {
		return this.projectsRepository;
	}
	
	@Override
	public abstract IRenkanRepository<T, ID> getObjectRepository();
		
	@Override
	public void dispatchEvent(String clientId, Map<String, Object> data) {
		
		this.saveSyncEvent(data);

		String eventType = (String) data.get("type");
		
		if("null".equalsIgnoreCase(eventType)) {
			this.nullOperation(null, data);
		}
		else if ("update".equalsIgnoreCase(eventType)) {
			this.update(clientId, data);
		}
		else if ("insert".equalsIgnoreCase(eventType)) {
			this.insert(clientId, data);
		}
		else if("delete".equalsIgnoreCase(eventType)) {
			this.delete(clientId, data);
		}
		else {		
			logger.warn(String.format("dispatchEvent : eventType unknown %s", eventType));
		}
	}

	private void saveSyncEvent(Map<String, Object> data) {
		
		String project_id = null;
		String user_id = null;
	
		@SuppressWarnings("unchecked")
		Map<String, Object> values = (Map<String, Object>) data.get("value");
		
		if(values != null) {
			project_id = (String) values.get("_project_id");
		}
		
		if (project_id == null || "".equals(project_id)) {
			logger.warn("saveSyncEvent : project id is null. Can not save sync event");
			return;
		}
		
		Project p = this.projectsRepository.findOne(project_id);
		
		if (p == null) {
			logger.warn("saveSyncEvent : project not found. Can not save sync event");
			return;
		}
		
		p.setUpdated(new Date());
		this.projectsRepository.save(p);
		
		user_id = (String) values.get("_user_id");
		
		if (user_id == null) {
			logger.warn("saveSyncEvent : No user id");
		}

		try {
			ProjectSync ps = this.projectSyncsRepository.getProjectSync(data.toString(), p, user_id);
			this.projectSyncsRepository.save(ps);
		} catch (RenkanException e) {
			logger.warn("saveSyncEvent : Error when getting Projectr syn object",e);
		}
		
	}

	protected abstract List<T> getObjectList(Project project);

	@Override
	public void update(String clientId, Map<String, Object> data) {
		
		this.logger.debug("AbstractSyncEventManager: update " + this.getClass().getName());
		
		@SuppressWarnings("unchecked")
		Map<String, Object> values = (Map<String, Object>) data.get("value");
		String obj_id = (String) values.get("id");
		
		this.logger.debug(String.format("update %s %s", this.getClass().getName(), obj_id));
		
		DBCollection objCollection = this.getObjectRepository().getCollection();
		DBObject obj = objCollection.findOne(obj_id);
		
		if (null == obj) {
			throw new CowebException("Object update: object not found", String.format("Object %s not found in %s", obj_id, objCollection.getName()));
		}
		
		boolean obj_changed = false;
		// update object
		for (String fieldname : values.keySet()) {
			if(!"id".equalsIgnoreCase(fieldname) && !fieldname.startsWith("_"))
			{
				Object new_value = values.get(fieldname);
				Object old_value = obj.get(fieldname);
				if((new_value == null && old_value != null) || (new_value != null && !new_value.equals(old_value))) {
					obj.put(fieldname, new_value);
					obj_changed = true;
				}
			}
		}
		
		if(obj_changed) {
			obj.put("_id", obj_id);
			WriteResult res = this.getObjectRepository().getCollection().update(new BasicDBObject("_id", obj_id), obj, true, false);
			
			if(!res.getLastError().ok()) {
				throw new CowebException(String.format("Error when writing object %s in %s", obj_id, objCollection.getName()), res.getLastError().getErrorMessage()); 
			}
		}
	}

	@Override
	public abstract void insert(String clientId, Map<String, Object> data);

	@Override
	public void delete(String clientId, Map<String, Object> data) {
		
		@SuppressWarnings("unchecked")
		Map<String, Object> values = (Map<String, Object>) data.get("value");
		String project_id = (String) values.get("_project_id");		
		Project project = this.getProjectsRepository().findOne(project_id); 
		
		if (null == project) {
			throw new CowebException(this.getClass().getName() + " delete: project not found", String.format("Project %s not found", project_id));
		}

		Integer position = (Integer)data.get("position");
		
		if(position == null || position < 0) {
			throw new CowebException("object delete: bad delete position", String.format("Bad position %s not found", position==null?"null":position.toString()));
		}
		int index = position.intValue();
		
		@SuppressWarnings("unchecked")
		ID object_id = (ID) values.get("id");
		
		this.logger.debug(String.format("delete object %s in pos %d for project %s", object_id, index, project_id));
		
		IRenkanModel<ID> currentObject = null;
		
		List<T> objList = this.getObjectList(project);
		
		if(index < objList.size()) {
			currentObject = objList.get(index);
		}
			
		if(currentObject == null || !object_id.equals(currentObject.getId())) {
			index = -1;
			this.logger.warn(String.format("delete object %s in pos %d for project %s not current object", object_id, index, project_id));
			for(int i=0;i<project.getNodes().size();i++) {
				if(object_id.equals(objList.get(i).getId())) {
					index = i;
					break;
				}
			}
		}
		
		if(index<0) {
			this.logger.warn(String.format("delete object %s in pos %d for project %s not found", object_id, index, project_id));
		}
		else {
			objList.remove(index);
			this.getObjectRepository().delete(object_id);
			this.getProjectsRepository().save(project);
		}

	}

	@Override
	public abstract void nullOperation(String clientId, Map<String, Object> data);
	
	
}
