package org.iri_research.renkan.repositories;

import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.iri_research.renkan.models.Project;
import org.iri_research.renkan.models.ProjectRevision;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapreduce.GroupBy;
import org.springframework.data.mongodb.core.mapreduce.GroupByResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;

import com.mongodb.AggregationOutput;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;

@Component
public class ProjectsRepositoryImpl implements ProjectsRepositoryCustom {

    @SuppressWarnings("unused")
    private final Logger logger = LoggerFactory
            .getLogger(ProjectsRepositoryImpl.class);

    @Autowired
    private ProjectsRepository projectsRepository;

    @Autowired
    private NodesRepository nodesRepository;

    @Autowired
    private EdgesRepository edgesRepository;

    @Autowired
    private ProjectRevisionsRepository projectRevisionsRepository;

    private class GroupSpaceResult {
        public String space_id;
        public int count;
    }

    @Autowired
    private MongoTemplate mongoTemplate;

    @Override
    public int getRevCounter(String projectId) {
        Project p = this.mongoTemplate.findAndModify(
                query(where("id").is(projectId)),
                new Update().inc("rev_counter", 1), Project.class);

        if (p == null) {
            return -1;
        }
        return p.getRevCounter();
    }

    @Override
    public Map<String, Integer> getCountBySpace(Collection<String> spaceIds) {

        Criteria filter = null;

        if (spaceIds != null) {
            filter = Criteria.where("space_id").in(spaceIds);
        }

        GroupByResults<GroupSpaceResult> groupResult = this.mongoTemplate
                .group(filter,
                        this.mongoTemplate.getCollectionName(Project.class),
                        GroupBy.key("space_id")
                                .initialDocument("{ count: 0 }")
                                .reduceFunction(
                                        "function(doc, prev) { prev.count += 1; }"),
                        GroupSpaceResult.class);

        HashMap<String, Integer> res = new HashMap<>();
        for (GroupSpaceResult gr : groupResult) {
            res.put(gr.space_id, new Integer(gr.count));
        }

        return res;

    }

    @Override
    public Map<String, Integer> getCountBySpace() {
        return this.getCountBySpace(null);
    }

    @Override
    public Project copy(Project p, String newTitle) {

        Project res = new Project(p);
        res.setTitle(newTitle);
        this.nodesRepository.save(res.getNodes());
        this.edgesRepository.save(res.getEdges());

        return this.projectsRepository.save(res);
    }

    @Override
    public void deleteRecursive(String projectId) {
        this.deleteRecursive(this.projectsRepository.findOne(projectId));
    }

    @Override
    public void deleteRecursive(Project project) {
        this.deleteRecursive(Arrays.asList(new Project[] { project }));
    }

    @Override
    public void deleteRecursive(Iterable<? extends Project> projects) {

        for (Project p : projects) {
            if (p == null) {
                continue;
            }

            ProjectRevision pr = this.projectRevisionsRepository
                    .getProjectRevision(p,
                            this.projectsRepository.getRevCounter(p.getId()));
            this.projectRevisionsRepository.save(pr);

            // delete edges
            this.edgesRepository.delete(p.getEdges());
            // delete nodes
            this.nodesRepository.delete(p.getNodes());
            // delete project
            this.projectsRepository.delete(p);
        }
    }

    @Override
    public Map<String, Integer> getCountByUser() {
        return getCountByUser(null);
    }

    @Override
    public Map<String, Integer> getCountByUser(Collection<String> userIds) {

        Criteria filter = null;

        if (userIds != null) {
            filter = Criteria.where("users.user_id").in(userIds);
        }

        DBObject projectOp = new BasicDBObject("$project", new BasicDBObject(
                "users", 1));
        DBObject unwindOp = new BasicDBObject("$unwind", "$users");
        DBObject groupOpFields = new BasicDBObject("_id", "$users.user_id");
        groupOpFields.put("count", new BasicDBObject("$sum", 1));
        DBObject groupOp = new BasicDBObject("$group", groupOpFields);
        DBObject matchOp = null;
        if (filter != null) {
            matchOp = new BasicDBObject("$match", filter.getCriteriaObject());
        }

        AggregationOutput output = null;
        if (filter != null) {
            output = this.projectsRepository.getCollection().aggregate(matchOp,
                    projectOp, unwindOp, matchOp, groupOp);
        } else {
            output = this.projectsRepository.getCollection().aggregate(
                    projectOp, unwindOp, groupOp);
        }

        HashMap<String, Integer> res = new HashMap<>();

        for (DBObject groupRes : output.results()) {
            res.put((String) groupRes.get("_id"),
                    (Integer) groupRes.get("count"));
        }

        return res;
    }

    @Override
    public Map<String, Integer> getCountByUsername() {
        return this.getCountByUsername(null);
    }

    @Override
    public Map<String, Integer> getCountByUsername(Collection<String> usernames) {

        Criteria filter = null;

        if (usernames != null) {
            filter = Criteria.where("users.username").in(usernames);
        }

        DBObject projectOp = new BasicDBObject("$project", new BasicDBObject(
                "users", 1));
        DBObject unwindOp = new BasicDBObject("$unwind", "$users");
        DBObject groupOpFields = new BasicDBObject("_id", "$users.username");
        groupOpFields.put("count", new BasicDBObject("$sum", 1));
        DBObject groupOp = new BasicDBObject("$group", groupOpFields);
        DBObject matchOp = null;
        if (filter != null) {
            matchOp = new BasicDBObject("$match", filter.getCriteriaObject());
        }

        AggregationOutput output = null;
        if (filter != null) {
            output = this.projectsRepository.getCollection().aggregate(matchOp,
                    projectOp, unwindOp, matchOp, groupOp);
        } else {
            output = this.projectsRepository.getCollection().aggregate(
                    projectOp, unwindOp, groupOp);
        }

        HashMap<String, Integer> res = new HashMap<>();

        for (DBObject groupRes : output.results()) {
            res.put((String) groupRes.get("_id"),
                    (Integer) groupRes.get("count"));
        }

        return res;

    }
}
