package org.iri_research.renkan.coweb.event;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

import org.coweb.CowebException;
import org.iri_research.renkan.models.Project;
import org.iri_research.renkan.models.View;
import org.iri_research.renkan.repositories.UsersRepository;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.geo.Point;

import com.google.common.base.CaseFormat;

@Named
public class ViewSyncEventManager extends AbstractBaseSyncEventManager<View, String> {

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

    @Inject
    private UsersRepository usersRepository;

    public UsersRepository getUsersRepository() {
        return usersRepository;
    }
    

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

        // get project
        this.logger.debug("ViewSyncEventManager: insert view");

        @SuppressWarnings("unchecked")
        Map<String, Object> values = (Map<String, Object>) data.get("value");
        Project project = getProject(values);

        String view_id = (String) values.get("id");
        //check that view id is unique
        for (View pview : project.getViews()) {
            if(pview.getId() != null && pview.getId().equals(view_id)) {
                throw new CowebException("view insert: view exists",
                        String.format("view %s already exists", view_id));
            }
        }
        View view = new View(view_id, null, null , null, null, null, 1.0, null, null); 

        updateViewInstance(values, view_id, view);

        int index = this.getPosition(data);
        
        List<View> views = project.getViews();
        if (index > views.size()) {
            index = views.size();
        }
        views.add(index, view);

        this.getProjectsRepository().save(project);

    }

    private Project getProject(Map<String, Object> values)
            throws CowebException {
        String project_id = (String) values.get("_project_id");

        Project project = this.getProjectsRepository().findOne(project_id);

        if (null == project) {
            throw new CowebException("View insert: project not found",
                    String.format("Project %s not found", project_id));
        }
        return project;
    }

    @Override
    public void nullOperation(String clientId, Map<String, Object> data) {
        this.logger.debug("nullOperation: NOP");
    }

    @Override
    public void update(String clientId, Map<String, Object> data) {
        this.logger.debug("ViewSyncEventManager: 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));

        Project project = getProject(values);

        int position = getPosition(data);

        if(position<0 || position >= project.getViews().size()) {
            throw new CowebException("View update: bad position",
                    String.format("View %s bad position %d", obj_id, position));
        }
        View targetView = project.getViews().get(position);
        
        boolean obj_changed = updateViewInstance(values, obj_id, targetView);
        
        if(obj_changed) {
            targetView.setUpdated(new DateTime());
            this.getProjectsRepository().save(project);
        }
    }

    private boolean updateViewInstance(Map<String, Object> values,
            String obj_id, View targetView) throws CowebException {
        boolean obj_changed = false;
        // update object
        for (String fieldname : values.keySet()) {
            if (!"id".equalsIgnoreCase(fieldname) && !fieldname.startsWith("_")) {
                String upperCaseFieldname = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, fieldname);
                switch(fieldname) {
                case "offset":
                    @SuppressWarnings("unchecked")
                    HashMap<String, Double> offset = (HashMap<String, Double>) values.get("offset");
                    Point offsetPoint = null;
                    if(offset != null) {
                        offsetPoint = new Point(offset.get("x")!= null?offset.get("x").doubleValue():0, offset.get("y")!=null?offset.get("y").doubleValue():0);
                    }
                    Point oldOffsetPoint = targetView.getOffset();
                    if ((offsetPoint == null && oldOffsetPoint != null)
                            || (offsetPoint != null && !offsetPoint.equals(oldOffsetPoint))) {
                        targetView.setOffset(offsetPoint);
                        obj_changed = true;
                    }
                    break;
                case "zoom_level":
                    Double newZoomLevelDouble = (Double)values.get("zoom_level");
                    double newZoomLevel = newZoomLevelDouble==null?1.0:newZoomLevelDouble.doubleValue();
                    double oldNewZoomLevel = targetView.getZoomLevel();
                    if(newZoomLevel != oldNewZoomLevel) {
                        targetView.setZoomLevel(newZoomLevel);
                        obj_changed = true;
                    }
                    break;
                case "hidden_nodes":
                    Object[] newValues = (Object[])values.get("hidden_nodes");
                    if(newValues == null) {
                        targetView.setHiddenNodes(null);
                    }
                    else {
                        List<String> hiddenNodesNew = Arrays.asList(Arrays.copyOf(newValues, newValues.length, String[].class));
                        List<String> hiddenNodesOld = targetView.getHiddenNodes();
                        if( hiddenNodesOld == null || !hiddenNodesOld.containsAll(hiddenNodesNew)) {
                            targetView.setHiddenNodes(hiddenNodesNew);
                        }
                    }
                    break;
                default:
                    try {
                        Object new_value = values.get(fieldname);
                        logger.debug(String.format("field %s : new value class : %s ", fieldname, new_value == null?"NULL":new_value.getClass().getName()));
                        Object old_value = View.class.getMethod("get"+upperCaseFieldname).invoke(targetView);
                        if ((new_value == null && old_value != null)
                                || (new_value != null && !new_value.equals(old_value))) {
                            View.class.getMethod("set"+upperCaseFieldname, View.class.getMethod("get"+upperCaseFieldname).getReturnType()).invoke(targetView, new_value);
                            obj_changed = true;
                        }
                    }
                    catch (IllegalAccessException | IllegalArgumentException
                            | InvocationTargetException | NoSuchMethodException
                            | SecurityException e) {
                        throw new CowebException("View update: problem on field update",
                                String.format("View %s bad field update %s : %s", obj_id, fieldname, e.toString()));
                    }
                    break;
                }
            }
        }
        return obj_changed;
    }

    private int getPosition(Map<String, Object> data) throws CowebException {
        Integer position = (Integer) data.get("position");

        if (position == null || position < 0) {
            throw new CowebException("get position: bad insert position",
                    String.format("Bad position %s not found",
                            position == null ? "null" : position.toString()));
        }
        
        return position.intValue();
    }

    @Override
    public void delete(String clientId, Map<String, Object> data) {
        
        this.logger.debug("ViewSyncEventManager: delete "
                + this.getClass().getName());
        
        @SuppressWarnings("unchecked")
        Map<String, Object> values = (Map<String, Object>) data.get("value");
        
        Project project = getProject(values);
        int position = getPosition(values);

        this.logger.debug(String.format("delete %s %d", this.getClass()
                .getName(), position));

        if(position<0 || position > project.getViews().size()) {
            throw new CowebException("node delete: bad delete position",
                    String.format("Bad position %d not found", position));
        }

        project.getViews().remove(position);
        
        this.getProjectsRepository().save(project);
    }

}
