package com.agilex.vamf.missionhealth.datalayer;

import java.util.*;

import com.agilex.vamf.missionhealth.domain.*;
import com.agilex.vamf.missionhealth.domain.comparator.MissionHealthStandingComparator;
import com.agilex.vamf.missionhealth.domain.enumeration.StandingType;
import com.agilex.vamf.missionhealth.service.*;
import com.agilex.vamf.utils.DateHelper;
import com.agilex.vamf.utils.StringUtil;
import com.mongodb.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Repository;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;

@Repository
public class MissionHealthDao extends AbstractDao {
	private static final Log logger = LogFactory.getLog(MissionHealthDao.class);
    
	private static DB db;
    private static DBCollection missionHealthCollection;
    private static DBCollection pointsRuleCollection;
    private static DBCollection missionHealthRankCollection;
    private static DBCollection missionHealthWeeklyStatusCollection;
    private static DBCollection missionHealthWeeklyUserMetricsCollection;
    private static DBCollection missionHealthWeeklyStandingCollection;
    private static DBCollection missionHealthNotificationStatusCollection;

    @Resource
    private MongoClient mongoClient;
	private static String encoding = "UTF-8";

    @PostConstruct
    public void init(){
        try {
            db = mongoClient.getDB("missionhealthdb");
            missionHealthCollection = db.getCollection("missionhealthdetails");
            missionHealthCollection.ensureIndex(new BasicDBObject("patientId",1));
            missionHealthCollection.ensureIndex(new BasicDBObject("points",1));
            pointsRuleCollection = db.getCollection("pointsrule");
            missionHealthRankCollection = db.getCollection("missionhealthrank");
            missionHealthWeeklyStatusCollection = db.getCollection("missionhealthweeklystatus");
            missionHealthWeeklyUserMetricsCollection = db.getCollection("missionhealthweeklyusermetrics");
            missionHealthWeeklyStandingCollection = db.getCollection("missionhealthweeklystanding");
            missionHealthNotificationStatusCollection = db.getCollection("missionhealthnotificationstatus");
        } catch (com.mongodb.MongoException e) {
			logger.error("Unable to connect to Mongo DB", e);
			throw new RuntimeException("Unable to connect to Mongo DB", e);
		}
    }

    @PreDestroy
    public void closeClientConnection(){
        mongoClient.close();
    }

    public MissionHealthDetail saveMissionHealthDetail(MissionHealthDetail missionHealthDetail) {
        if (logger.isDebugEnabled()) logger.debug("Saving mission health detail ");

        MissionHealthTranslator translator = new MissionHealthTranslator();
        DBObject dbObject = translator.translateMissionHealthDetail(missionHealthDetail);
        missionHealthCollection.insert(dbObject);
        MissionHealthDetail mhd = translator.translateToMissionHealthDetail(dbObject);
        mhd.setStanding(queryStanding(mhd.getPoints()));
        mhd.setTotalUsers(getTotalUsers());

        return mhd;
    }
	
	public MissionHealthDetail getMissionHealthDetail(String patientId) {
		if (logger.isDebugEnabled()) logger.debug("Retrieving Mission Health Detail for patient : " + patientId);
		
		MissionHealthTranslator translator = new MissionHealthTranslator();
		MissionHealthDetail missionHealthDetail = null;

		BasicDBObject whereQuery = new BasicDBObject();
		whereQuery.put("patientId", patientId);
		DBCursor cursor = missionHealthCollection.find(whereQuery);
		while(cursor.hasNext()) {
			DBObject dbMissionHealthDetail = cursor.next();
			missionHealthDetail = translator.translateToMissionHealthDetail(dbMissionHealthDetail) ;
            if(null!=missionHealthDetail){
                missionHealthDetail.setStanding(queryStanding(missionHealthDetail.getPoints()));
                missionHealthDetail.setTotalUsers(getTotalUsers());
            }
		}
		return missionHealthDetail;
	}
	
	public MissionHealthDetail updateMissionHealthDetail(MissionHealthDetail mhd) {
        if (logger.isDebugEnabled()) logger.debug("Update Mission Health Details info");

        MissionHealthTranslator translator = new MissionHealthTranslator();
        DBObject dbObject = translator.translateMissionHealthDetail(mhd);
        
        BasicDBObject searchQuery = new BasicDBObject().append("patientId", mhd.getPatientId());
        BasicDBObject updateDocument = new BasicDBObject().append("$set", dbObject);
        //updateDocument.append("$set", updateDocument);
        		
        missionHealthCollection.update(searchQuery, updateDocument);

        MissionHealthDetail missionHealthDetail = translator.translateToMissionHealthDetail(dbObject);
        if(null!=missionHealthDetail){
            missionHealthDetail.setStanding(queryStanding(missionHealthDetail.getPoints()));
            missionHealthDetail.setTotalUsers(getTotalUsers());
        }
        
        return missionHealthDetail;
    }

	public Object removeMissionHealthDetail(MissionHealthDetail mhd) {
        if (logger.isDebugEnabled()) logger.debug("Remove Mission Health Detail");
        BasicDBObject removeCriteria = new BasicDBObject().append("patientId", mhd.getPatientId());
        return missionHealthCollection.remove(removeCriteria);
	}

    public PointsRule getPointsRuleForLevel(Integer level){
        MissionHealthTranslator translator = new MissionHealthTranslator();
        PointsRule pointsRule = null;
        BasicDBObject query = new BasicDBObject("_id", level);
        DBObject result = pointsRuleCollection.findOne(query);
        if(null!=result)
            pointsRule = translator.translateToPointsRule(result);

        return pointsRule;
    }

    public Map<Integer, PointsRule> getPointsRuleMapping() {
        MissionHealthTranslator translator = new MissionHealthTranslator();
        Map<Integer, PointsRule> pointsRuleMap = new HashMap<Integer, PointsRule>();
        DBCursor cursor = pointsRuleCollection.find();
        while(cursor.hasNext()) {
            DBObject dbPointsRule = cursor.next();
            PointsRule pointsRule = translator.translateToPointsRule(dbPointsRule);
            pointsRuleMap.put(Integer.valueOf(pointsRule.getId()), pointsRule);
        }
        return pointsRuleMap;
    }

    public MissionHealthRank getMissionHealthRank(String service, Integer level){
        MissionHealthTranslator translator = new MissionHealthTranslator();
        MissionHealthRank missionHealthRank = new MissionHealthRank(level);
        BasicDBObject query = new BasicDBObject("service",service).append("level",level);
        DBCursor cursor = missionHealthRankCollection.find(query);
        while(cursor.hasNext()) {
            DBObject dbMissionHealthRank = cursor.next();
            missionHealthRank = translator.translateToMissionHealthRank(dbMissionHealthRank) ;
        }
        return missionHealthRank;
    }

    public Map<Integer, Map<String, MissionHealthRank>> getMissionHealthRankMapping() {
        MissionHealthTranslator translator = new MissionHealthTranslator();
        Map<Integer, Map<String, MissionHealthRank>> rankMapping = new HashMap<Integer, Map<String, MissionHealthRank>>();
        DBCursor cursor = missionHealthRankCollection.find();
        while(cursor.hasNext()) {
            DBObject dbMissionHealthRank = cursor.next();
            MissionHealthRank missionHealthRank = translator.translateToMissionHealthRank(dbMissionHealthRank);
            if(!rankMapping.containsKey(missionHealthRank.getLevel()))
                rankMapping.put(missionHealthRank.getLevel(), new HashMap<String, MissionHealthRank>());
            rankMapping.get(missionHealthRank.getLevel()).put(missionHealthRank.getService(), missionHealthRank);
        }
        return rankMapping;
    }

    public MissionHealthDetails getAllMissionHealthDetails() {
        MissionHealthTranslator translator = new MissionHealthTranslator();
        MissionHealthDetails missionHealthDetails = new MissionHealthDetails();
        DBCursor cursor = missionHealthCollection.find();
        while(cursor.hasNext()) {
            DBObject dbMissionHealthDetail= cursor.next();
            MissionHealthDetail mhd = translator.translateToMissionHealthDetail(dbMissionHealthDetail);
            mhd.setStanding(queryStanding(mhd.getPoints()));
            mhd.setTotalUsers(getTotalUsers());
            missionHealthDetails.add(mhd);
        }
        return missionHealthDetails;
    }

    public DBCursor getWeeklyJobCursor(){
        return missionHealthCollection.find();
    }

    public MissionHealthWeeklyStatus fetchMostRecentMissionHealthWeeklyStatus(){
        MissionHealthWeeklyStatusTranslator translator = new MissionHealthWeeklyStatusTranslator();
        MissionHealthWeeklyStatus missionHealthWeeklyStatus = null;
        DBObject sort = new BasicDBObject("startDatetime", 1);
        DBCursor cursor = missionHealthWeeklyStatusCollection.find().sort(sort);
        while(cursor.hasNext()){
            DBObject dbMissionHealthWeeklyStatus = cursor.next();
            missionHealthWeeklyStatus = translator.translateToMissionHealthWeeklyStatus(dbMissionHealthWeeklyStatus);
        }
        return missionHealthWeeklyStatus;
    }

    public void saveMissionHealthWeeklyStatus(MissionHealthWeeklyStatus missionHealthWeeklyStatus){
        MissionHealthWeeklyStatusTranslator translator = new MissionHealthWeeklyStatusTranslator();
        DBObject dbMissionHealthWeeklyStatus = translator.translateMissionHealthWeeklyStatus(missionHealthWeeklyStatus);
        if(null== missionHealthWeeklyStatus.getId()){
            missionHealthWeeklyStatusCollection.insert(dbMissionHealthWeeklyStatus);
        }else{
            missionHealthWeeklyStatusCollection.save(dbMissionHealthWeeklyStatus);
        }
    }

    public Integer getTotalMissionsAcrossAllUsers() {

        DBObject group = new BasicDBObject("$group", new BasicDBObject( "_id", null).append("totalMissions", new BasicDBObject("$sum","$totalMissions")));
        AggregationOutput output = missionHealthCollection.aggregate(group);

        Iterable<DBObject> results = output.results();
        Iterator<DBObject> iterator = results.iterator();
        while(iterator.hasNext()){
            DBObject sumResult = iterator.next();
            return Integer.valueOf(StringUtil.objectToString(sumResult.get("totalMissions")));
        }
        return new Integer(0);
    };

    public MissionHealthWeeklyStatus getMetricsForAllUsers(MissionHealthWeeklyStatus missionHealthWeeklyStatus){
        DBObject match = new BasicDBObject("$match", new BasicDBObject("totalLoginsForCycle",new BasicDBObject("$gt",0)));
        DBObject group =
                new BasicDBObject("$group", new BasicDBObject("_id",0).append("usersWithLogin", new BasicDBObject("$sum", 1))
                                                                      .append("totalLogins", new BasicDBObject("$sum", "$totalLoginsForCycle"))
                                                                      .append("totalMissionsCompleted", new BasicDBObject("$sum", "$totalCompletedMissions")));

        AggregationOutput output = missionHealthCollection.aggregate(match, group);
        Iterable<DBObject> results = output.results();
        Iterator<DBObject> iterator = results.iterator();
        while(iterator.hasNext()){
            DBObject result = iterator.next();
            missionHealthWeeklyStatus.setUsersWithLogin(Integer.valueOf(StringUtil.objectToString(result.get("usersWithLogin"))));
            missionHealthWeeklyStatus.setTotalLogins(Integer.valueOf(StringUtil.objectToString(result.get("totalLogins"))));
            missionHealthWeeklyStatus.setTotalMissionsCompleted(Integer.valueOf(StringUtil.objectToString(result.get("totalMissionsCompleted"))));
        }
        return missionHealthWeeklyStatus;
    }

    public List<WeeklyServiceMetrics> getMetricsByService(){
        Map<String, WeeklyServiceMetrics> metricsHashMap = new HashMap<String, WeeklyServiceMetrics>();
        DBObject match = new BasicDBObject("$match", new BasicDBObject("missionHealthProfile",new BasicDBObject("$ne",null)));
        DBObject group =
                new BasicDBObject("$group", new BasicDBObject("_id","$missionHealthProfile.service").append("totalUsers", new BasicDBObject("$sum", 1))
                                                                                                    .append("totalMissionsCompleted", new BasicDBObject("$sum", "$totalCompletedMissions"))
                                                                                                    .append("totalPoints", new BasicDBObject("$sum", "$points"))
                                                                                                    .append("totalTroops", new BasicDBObject("$sum", "$troops")));
        DBObject project = new BasicDBObject("$project", new BasicDBObject("_id", 0).append("service","$_id").append("totalUsers",1).append("totalMissionsCompleted", 1).append("totalPoints", 1).append("totalTroops", 1));

        AggregationOutput output = missionHealthCollection.aggregate(match, group, project);
        Iterable<DBObject> results = output.results();
        Iterator<DBObject> iterator = results.iterator();
        while(iterator.hasNext()){
            DBObject result = iterator.next();
            WeeklyServiceMetrics weeklyServiceMetrics = new WeeklyServiceMetrics();
            weeklyServiceMetrics.setService(StringUtil.objectToString(result.get("service")));
            weeklyServiceMetrics.setTotalUsers(Integer.valueOf(StringUtil.objectToString(result.get("totalUsers"))));
            weeklyServiceMetrics.setTotalMissionsCompleted(Integer.valueOf(StringUtil.objectToString(result.get("totalMissionsCompleted"))));
            weeklyServiceMetrics.setTotalPoints(Integer.valueOf(StringUtil.objectToString(result.get("totalPoints"))));
            weeklyServiceMetrics.setTotalTroops(Integer.valueOf(StringUtil.objectToString(result.get("totalTroops"))));
            metricsHashMap.put(weeklyServiceMetrics.getService(), weeklyServiceMetrics);
        }

        match = new BasicDBObject("$match", new BasicDBObject("missionHealthProfile",new BasicDBObject("$ne",null)).append("loginDate",new BasicDBObject("$gt", DateHelper.formatDate(DateHelper.getOneYearAgo()))));
        group = new BasicDBObject("$group", new BasicDBObject("_id","$missionHealthProfile.service").append("activeUsers", new BasicDBObject("$sum", 1)));
        project = new BasicDBObject("$project", new BasicDBObject("_id", 0).append("service","$_id").append("activeUsers",1));

        output = missionHealthCollection.aggregate(match, group, project);
        results = output.results();
        iterator = results.iterator();
        while(iterator.hasNext()){
            DBObject result = iterator.next();
            String service = StringUtil.objectToString(result.get("service"));
            WeeklyServiceMetrics weeklyServiceMetrics = metricsHashMap.containsKey(service) ? metricsHashMap.get(service) : new WeeklyServiceMetrics(service);
            weeklyServiceMetrics.setActiveUsers(Integer.valueOf(StringUtil.objectToString(result.get("activeUsers"))));
            metricsHashMap.put(weeklyServiceMetrics.getService(), weeklyServiceMetrics);
        }
        
        match = new BasicDBObject("$match", new BasicDBObject("missionHealthProfile",new BasicDBObject("$ne",null)));
        BasicDBObject unwind=new BasicDBObject("$unwind","$missions");
        group = new BasicDBObject("$group", new BasicDBObject("_id","$missions.competingService").append("total", new BasicDBObject("$sum", "$missions.pointsForfeited")));
        output = missionHealthCollection.aggregate(match, unwind,group);
        results = output.results();
        iterator = results.iterator();
        while(iterator.hasNext()){
            DBObject result = iterator.next();
            String service = StringUtil.objectToString(result.get("_id"));
            WeeklyServiceMetrics weeklyServiceMetrics = metricsHashMap.containsKey(service) ? metricsHashMap.get(service) : new WeeklyServiceMetrics(service);
            weeklyServiceMetrics.setTotalPoints(weeklyServiceMetrics.getTotalPoints()+Integer.valueOf(StringUtil.objectToString(result.get("total"))));
            metricsHashMap.put(weeklyServiceMetrics.getService(), weeklyServiceMetrics);
        }
        
        return new ArrayList<WeeklyServiceMetrics>(metricsHashMap.values());
    }

    private Integer queryStanding(Integer points){
        Integer standing = 1;
        DBObject project = new BasicDBObject("$project", new BasicDBObject("points", true));
        DBObject match = new BasicDBObject("$match", new BasicDBObject("points", new BasicDBObject("$gt",points)));
        DBObject group = new BasicDBObject("$group", new BasicDBObject("_id", 0).append("standing", new BasicDBObject("$sum", 1)));
        // db.missionhealthdetails.aggregate({$match:{points:{$gt:1295}}},{$group:{_id:"$points"}},{$group:{_id:0,"standing":{$sum:1}}})
        AggregationOutput aggregationOutput = missionHealthCollection.aggregate(project, match, group);
        Iterable<DBObject> results = aggregationOutput.results();
        Iterator<DBObject> iterator = results.iterator();
        while(iterator.hasNext()){
            DBObject result = iterator.next();
            standing = Integer.valueOf(StringUtil.objectToString(result.get("standing"))) + 1;
        }
        return standing;
    }

    public synchronized void updateWeeklyStanding(MissionHealthDetail missionHealthDetail){
        if (logger.isDebugEnabled()) logger.debug("Update Mission Health Weekly Standing");

        DBObject query = new BasicDBObject("_id", missionHealthDetail.getPatientId());
        DBObject dbStanding = missionHealthWeeklyStandingCollection.findOne(query);

        /* points have not changed, do not update standing */
        if(null!=dbStanding && null!=dbStanding.get("points")){
            Integer oldPoints = Integer.valueOf(StringUtil.objectToString(dbStanding.get("points")));
            /* points have not changed, do not update standing */
            if(oldPoints.equals(missionHealthDetail.getPoints())){
                return;
            }else{
                removeStanding(missionHealthDetail.getPatientId(), missionHealthDetail.getPoints(), "vsAll", null, null);
                removeStanding(missionHealthDetail.getPatientId(), missionHealthDetail.getPoints(), "vsService", "service", missionHealthDetail.getMissionHealthProfile().getService());
                removeStanding(missionHealthDetail.getPatientId(), missionHealthDetail.getPoints(), "vsLevel", "level", missionHealthDetail.getRank().getLevel());
                missionHealthWeeklyStandingCollection.remove(query);
            }
        }
        if(null == dbStanding)
            dbStanding = new BasicDBObject("_id", missionHealthDetail.getPatientId());

        String name = null==missionHealthDetail.getMissionHealthProfile().getName() ? "" : missionHealthDetail.getMissionHealthProfile().getName();
        dbStanding.put("name", name);
        dbStanding.put("points", missionHealthDetail.getPoints());
        dbStanding.put("troops", missionHealthDetail.getTroops());
        dbStanding.put("service", missionHealthDetail.getMissionHealthProfile().getService());
        dbStanding.put("level", missionHealthDetail.getRank().getLevel());
        dbStanding.put("vsAll", calculateStanding(missionHealthDetail.getPatientId(), missionHealthDetail.getPoints(), "vsAll", null, null));
        dbStanding.put("vsService", calculateStanding(missionHealthDetail.getPatientId(), missionHealthDetail.getPoints(), "vsService", "service", missionHealthDetail.getMissionHealthProfile().getService()));
        dbStanding.put("vsLevel", calculateStanding(missionHealthDetail.getPatientId(), missionHealthDetail.getPoints(), "vsLevel", "level", missionHealthDetail.getRank().getLevel()));
        missionHealthWeeklyStandingCollection.update(query, dbStanding, true, false);
    }

    private DBObject calculateStanding(String patientId, Integer points, String versus, String filter, Object value){
        DBObject standing = new BasicDBObject("standing", 1).append("count", 1);
        DBObject query = new BasicDBObject("_id", new BasicDBObject("$ne", patientId)).append("points", points);
        DBObject queryLessThan = new BasicDBObject("_id", new BasicDBObject("$ne", patientId)).append("points", new BasicDBObject("$lt", points));
        DBObject queryGtrThan = new BasicDBObject("_id", new BasicDBObject("$ne", patientId)).append("points", new BasicDBObject("$gt", points));
        if(null!= filter){
            query.put(filter, value);
            queryLessThan.put(filter, value);
            queryGtrThan.put(filter, value);
        }

        DBObject dbObject = missionHealthWeeklyStandingCollection.findOne(query);
        if(null!=dbObject){
            standing = (DBObject)dbObject.get(versus);
            standing.put("count", Integer.valueOf(StringUtil.objectToString(standing.get("count"))) + 1);
            DBObject update = new BasicDBObject("$inc", new BasicDBObject(versus+".count", 1));
            missionHealthWeeklyStandingCollection.update(query, update, false, true);
        } else {
            DBObject orderBy = new BasicDBObject("points",-1);
            List<DBObject> dbObjectList = missionHealthWeeklyStandingCollection.find(queryLessThan).sort(orderBy).limit(1).toArray();
            if(null!=dbObjectList && dbObjectList.size()>0 && null!=dbObjectList.get(0) && null !=dbObjectList.get(0).get(versus)){
                standing = (DBObject)dbObjectList.get(0).get(versus);
                standing.put("count",1);
            } else {
                orderBy.put("points", 1);
                dbObjectList = missionHealthWeeklyStandingCollection.find(queryGtrThan).sort(orderBy).limit(1).toArray();
                if(null!=dbObjectList && dbObjectList.size()>0 && null!=dbObjectList.get(0) && null !=dbObjectList.get(0).get(versus)){
                    standing = (DBObject)dbObjectList.get(0).get(versus);
                    standing.put("standing", Integer.valueOf(StringUtil.objectToString(standing.get("standing"))) + Integer.valueOf(StringUtil.objectToString(standing.get("count"))));
                    standing.put("count",1);
                }
            }
        }

        DBObject update = new BasicDBObject("$inc", new BasicDBObject(versus+".standing", 1));
        missionHealthWeeklyStandingCollection.update(queryLessThan, update, false, true);

        return standing;
    }

    private void removeStanding(String patientId, Integer points, String versus, String filter, Object value){
        DBObject query = new BasicDBObject("_id", new BasicDBObject("$ne", patientId)).append("points", points);
        if(null!= filter)
            query.put(filter, value);
        DBObject dbObject = missionHealthWeeklyStandingCollection.findOne(query);
        if(null!=dbObject){ // there are standings at the same level, decrement count
            DBObject update = new BasicDBObject("$inc", new BasicDBObject(versus+".count", -1));
            missionHealthWeeklyStandingCollection.update(query, update, false, true);
        }
        query.put("points", new BasicDBObject("$lt", points));
        List<DBObject> list = missionHealthWeeklyStandingCollection.find(query).toArray();
        DBObject update = new BasicDBObject("$inc", new BasicDBObject(versus+".standing", -1));
        missionHealthWeeklyStandingCollection.update(query, update, false, true);
    }

    public Integer getTotalUsers(){
        return Integer.valueOf(missionHealthCollection.find().count());
    }

    public MissionHealthStandings getMissionHealthStandings(MissionHealthDetail missionHealthDetail){
        MissionHealthStandings missionHealthStandings = new MissionHealthStandings();
        if(null!=missionHealthDetail){
            missionHealthStandings.addAll(getMissionHealthStandings(missionHealthDetail, StandingType.ALL, null, null));
            String service = null != missionHealthDetail.getMissionHealthProfile().getServiceLastRankedAs() ? missionHealthDetail.getMissionHealthProfile().getServiceLastRankedAs() : missionHealthDetail.getMissionHealthProfile().getService() ;
            missionHealthStandings.addAll(getMissionHealthStandings(missionHealthDetail, StandingType.SERVICE, "service" , service));
            missionHealthStandings.addAll(getMissionHealthStandings(missionHealthDetail, StandingType.LEVEL, "level" , missionHealthDetail.getRank().getLevel()));
        }
        return missionHealthStandings;
    }

    public MissionHealthStandings getMissionHealthStandings(MissionHealthDetail missionHealthDetail, StandingType standingType){
        MissionHealthStandings missionHealthStandings = new MissionHealthStandings();
        if(null!=missionHealthDetail){
            if(standingType.equals(StandingType.SERVICE)){
                missionHealthStandings.addAll(getMissionHealthStandings(missionHealthDetail, StandingType.SERVICE, "service" , missionHealthDetail.getMissionHealthProfile().getServiceLastRankedAs()));
            }else if (standingType.equals(StandingType.LEVEL)){
                missionHealthStandings.addAll(getMissionHealthStandings(missionHealthDetail, StandingType.LEVEL, "level" , missionHealthDetail.getRank().getLevel()));
            }else{
                missionHealthStandings.addAll(getMissionHealthStandings(missionHealthDetail, StandingType.ALL, null, null));
            }
        }
        return missionHealthStandings;
    }

    // TODO : setup '25' above/below as a property
    private MissionHealthStandings getMissionHealthStandings(MissionHealthDetail missionHealthDetail, StandingType standingType, String filter, Object value){
        if (logger.isDebugEnabled()) logger.debug("Get Mission Health Standings patientId:"+missionHealthDetail.getPatientId()+" type:("+standingType+")");

        MissionHealthTranslator translator = new MissionHealthTranslator();

        /*  find this user's standing */
        DBObject where = new BasicDBObject("_id", missionHealthDetail.getPatientId());
        DBObject dbObject = missionHealthWeeklyStandingCollection.findOne(where);
        MissionHealthStanding standing = null;
        Integer points = null;
        if(null!=dbObject){
            standing = translator.translateToMissionHealthStanding(dbObject, standingType);
            points = standing.getPoints();
        }else{
            points = missionHealthDetail.getPoints();
        }

        MissionHealthStandings mhs = new MissionHealthStandings();
        List<MissionHealthStanding> tempList = new LinkedList<MissionHealthStanding>();
        /*  find higher ranked */
        where = new BasicDBObject(new BasicDBObject("points", new BasicDBObject("$gt", points)));
        if(null!=filter)
            where.put(filter, value);
        DBObject orderBy = new BasicDBObject("points",1);
        DBCursor cursor = missionHealthWeeklyStandingCollection.find(where).sort(orderBy).limit(25);
        Integer lastStanding = 0;
        while(cursor.hasNext()){
            MissionHealthStanding missionHealthStanding = translator.translateToMissionHealthStanding(cursor.next(), standingType);
            if(missionHealthStanding.getStanding()>lastStanding)
                lastStanding = missionHealthStanding.getStanding();
            mhs.add(missionHealthStanding);
        }
        //mhs.addAll(tempList);

        if(null!=standing){
            mhs.add(standing);
        }else{
            /* returns possible standing value as negative, so client knows if this user is in list and where */
            mhs.add(new MissionHealthStanding(missionHealthDetail.getPatientId(), missionHealthDetail.getMissionHealthProfile().getName(),
                    missionHealthDetail.getMissionHealthProfile().getServiceLastRankedAs(), missionHealthDetail.getRank().getLevel(),
                    points, missionHealthDetail.getTroops(), standingType, (-1*lastStanding)-1));
        }

        /*  find lower ranked */
        tempList = new LinkedList<MissionHealthStanding>();
        where = new BasicDBObject(new BasicDBObject("points", new BasicDBObject("$lte", points)).append("_id", new BasicDBObject("$ne", missionHealthDetail.getPatientId())));
        if(null!=filter)
            where.put(filter, value);
        orderBy = new BasicDBObject("points",-1);
        cursor = missionHealthWeeklyStandingCollection.find(where).sort(orderBy).limit(25);
        while(cursor.hasNext()){
            mhs.add(translator.translateToMissionHealthStanding(cursor.next(), standingType));
        }
        //mhs.addAll(tempList);
        Collections.sort(mhs, new MissionHealthStandingComparator());
        return mhs;
    }

    public void saveWeeklyUserMetrics(WeeklyUserMetrics weeklyUserMetrics){
        MissionHealthWeeklyStatusTranslator translator = new MissionHealthWeeklyStatusTranslator();
        DBObject dbWeeklyUserMetrics = translator.translateWeeklyUserMetrics(weeklyUserMetrics);

        missionHealthWeeklyUserMetricsCollection.insert(dbWeeklyUserMetrics);
    }

    public List<WeeklyUserMetrics> getWeeklyUserMetricsByDate(Date createDatetime){
        List<WeeklyUserMetrics> weeklyUserMetricsList = new ArrayList<WeeklyUserMetrics>();
        MissionHealthWeeklyStatusTranslator translator = new MissionHealthWeeklyStatusTranslator();

        DBObject query = new BasicDBObject("createDatetime", createDatetime);
        DBCursor cursor = missionHealthWeeklyUserMetricsCollection.find(query);
        while(cursor.hasNext()){
            weeklyUserMetricsList.add(translator.translateToWeeklyUserMetrics(cursor.next()));
        }
        return weeklyUserMetricsList;
    }

    public void clearWeeklyStanding(){
        missionHealthWeeklyStandingCollection.remove(new BasicDBObject());
    }
    
    public List<WeeklyServiceMetrics> getAnnualMetricsByService(String newYearDate){
        Map<String, WeeklyServiceMetrics> metricsHashMap = new HashMap<String, WeeklyServiceMetrics>();
        DBObject match = new BasicDBObject("$match", new BasicDBObject("missionHealthProfile",new BasicDBObject("$ne",null)).append("lastMissionCreateDate", new BasicDBObject("$gt",newYearDate)));
        DBObject group =
                new BasicDBObject("$group", new BasicDBObject("_id","$missionHealthProfile.service").append("annualTotalCompletedMissions", new BasicDBObject("$sum", "$annualTotalCompletedMissions"))
                                                                                                    .append("annualTotalPoints", new BasicDBObject("$sum", "$annualTotalPoints"))
                                                                                                    .append("annualTotalTroops", new BasicDBObject("$sum", "$annualTotalTroops")));
        DBObject project = new BasicDBObject("$project", new BasicDBObject("_id", 0).append("service","$_id").append("annualTotalCompletedMissions", 1).append("annualTotalPoints", 1).append("annualTotalTroops", 1));

        AggregationOutput output = missionHealthCollection.aggregate(match, group, project);
        Iterable<DBObject> results = output.results();
        Iterator<DBObject> iterator = results.iterator();
        while(iterator.hasNext()){
            DBObject result = iterator.next();
            WeeklyServiceMetrics weeklyServiceMetrics = new WeeklyServiceMetrics();
            weeklyServiceMetrics.setService(StringUtil.objectToString(result.get("service")));
            weeklyServiceMetrics.setTotalMissionsCompleted(Integer.valueOf(StringUtil.objectToString(result.get("annualTotalCompletedMissions"))));
            weeklyServiceMetrics.setTotalPoints(Integer.valueOf(StringUtil.objectToString(result.get("annualTotalPoints"))));
            weeklyServiceMetrics.setTotalTroops(Integer.valueOf(StringUtil.objectToString(result.get("annualTotalTroops"))));
            metricsHashMap.put(weeklyServiceMetrics.getService(), weeklyServiceMetrics);
        }
        
        match = new BasicDBObject("$match", new BasicDBObject("missionHealthProfile",new BasicDBObject("$ne",null)).append("missions", new BasicDBObject("$ne",null)).append("missions.startDate", new BasicDBObject("$gt", newYearDate)));
        group = new BasicDBObject("$group", new BasicDBObject("_id","$missionHealthProfile.service").append("activeUsers", new BasicDBObject("$sum", 1)));
        project = new BasicDBObject("$project", new BasicDBObject("_id", 0).append("service","$_id").append("activeUsers",1));

        output = missionHealthCollection.aggregate(match, group, project);
        results = output.results();
        iterator = results.iterator();
        while(iterator.hasNext()){
            DBObject result = iterator.next();
            String service = StringUtil.objectToString(result.get("service"));
            WeeklyServiceMetrics weeklyServiceMetrics = metricsHashMap.containsKey(service) ? metricsHashMap.get(service) : new WeeklyServiceMetrics(service);
            weeklyServiceMetrics.setActiveUsers(weeklyServiceMetrics.getActiveUsers()+Integer.valueOf(StringUtil.objectToString(result.get("activeUsers"))));
            metricsHashMap.put(weeklyServiceMetrics.getService(), weeklyServiceMetrics);
        }
        
       match = new BasicDBObject("$match", new BasicDBObject("missionHealthProfile",new BasicDBObject("$ne",null)).append("missions", new BasicDBObject("$ne",null)).append("missions.startDate", new BasicDBObject("$gt", newYearDate)));
        BasicDBObject unwind=new BasicDBObject("$unwind","$missions");
        group = new BasicDBObject("$group", new BasicDBObject("_id","$missions.competingService").append("total", new BasicDBObject("$sum", "$missions.pointsForfeited")).append("totalTroops", new BasicDBObject("$sum", "$missions.troopsForfeited")));
        output = missionHealthCollection.aggregate(match, unwind,group);
        results = output.results();
        iterator = results.iterator();
        while(iterator.hasNext()){
            DBObject result = iterator.next();
            String service = StringUtil.objectToString(result.get("_id"));
            WeeklyServiceMetrics weeklyServiceMetrics = metricsHashMap.containsKey(service) ? metricsHashMap.get(service) : new WeeklyServiceMetrics(service);
            weeklyServiceMetrics.setTotalPoints(weeklyServiceMetrics.getTotalPoints()+Integer.valueOf(StringUtil.objectToString(result.get("total"))));
            weeklyServiceMetrics.setTotalTroops(weeklyServiceMetrics.getTotalTroops() + Integer.valueOf(StringUtil.objectToString(result.get("totalTroops"))));
            metricsHashMap.put(weeklyServiceMetrics.getService(), weeklyServiceMetrics);
        }
        for (Service service : Service.values()) {
    		if(!(metricsHashMap.containsKey(service.getService()))){
    			metricsHashMap.put(service.getService(), new WeeklyServiceMetrics(service.getService()));
    			}
    	}
    	
        return new ArrayList<WeeklyServiceMetrics>(metricsHashMap.values());
    }
    
    public AnnualServiceRollUp annualServiceComparisonRollUp(String newYearDate,AnnualServiceRollUp annualServiceRollUp){
    	DBObject match=new BasicDBObject("$match",new BasicDBObject("complete",new BasicDBObject("$ne",false)));
        DBObject sort = new BasicDBObject("$sort", new BasicDBObject("startDate",-1));
    	DBObject limit=new BasicDBObject("$limit",1);
    	DBObject unwind=new BasicDBObject("$unwind","$annualServiceMetrics");
    	DBObject project =new BasicDBObject("$project",new BasicDBObject("annualServiceMetrics",1));
    	DBObject sortForWinner=new BasicDBObject("$sort", new BasicDBObject("annualServiceMetrics.totalPoints",-1).append("annualServiceMetrics.totalMissionsCompleted", -1).append("annualServiceMetrics.totalTroops", -1));
    	
    	AggregationOutput output = missionHealthWeeklyStatusCollection.aggregate(match,sort,limit,unwind,project,sortForWinner);
    	Iterable<DBObject> results = output.results();
        Iterator<DBObject> iterator = results.iterator();
        int i=0;
        while(iterator.hasNext()){
        	DBObject result = iterator.next();
            DBObject serviceMetrics=(BasicDBObject) result.get("annualServiceMetrics");
           WeeklyServiceMetrics rollUp=new WeeklyServiceMetrics();
           rollUp.setService(StringUtil.objectToString(serviceMetrics.get("service")));
           rollUp.setActiveUsers(Integer.valueOf(StringUtil.objectToString(serviceMetrics.get("activeUsers"))));
           rollUp.setTotalMissionsCompleted(Integer.valueOf(StringUtil.objectToString(serviceMetrics.get("totalMissionsCompleted"))));
           rollUp.setTotalPoints(Integer.valueOf(StringUtil.objectToString(serviceMetrics.get("totalPoints"))));
           rollUp.setTotalTroops(Integer.valueOf(StringUtil.objectToString(serviceMetrics.get("totalTroops")==null?0:serviceMetrics.get("totalTroops"))));
           rollUp.setWinner(Integer.valueOf(i));
           annualServiceRollUp.getData().add(rollUp);
           i++;
        }
        match=new BasicDBObject("complete",true).append("startDatetime",new BasicDBObject("$gt",newYearDate)).append("annualServiceMetrics",new BasicDBObject("$ne",null));
        project=new BasicDBObject("annualServiceMetrics",1);
        sort = new BasicDBObject("$sort", new BasicDBObject("_id",1));
        DBCursor cursor=missionHealthWeeklyStatusCollection.find(match,project).sort(sort);
        while(cursor.hasNext()) {
			DBObject db = cursor.next();
			BasicDBList dbServiceMetricsList= (BasicDBList) db.get("annualServiceMetrics");
			
			Iterator<Object> dbServiceMetrics = dbServiceMetricsList.iterator();
			while (dbServiceMetrics.hasNext()) {
			    BasicDBObject dbServiceMetric = (BasicDBObject) dbServiceMetrics.next();
			    WeeklyServiceMetrics rollUp = new WeeklyServiceMetrics(StringUtil.objectToString(dbServiceMetric.get("service")));
			    int rollUpPosition=annualServiceRollUp.getData().indexOf(rollUp);			    
			    if(rollUpPosition>=0){
			    rollUp=annualServiceRollUp.getData().get(rollUpPosition);
			    List<Integer> totalPointsForGraph = null==rollUp.getGraph()?new ArrayList<Integer>():rollUp.getGraph();
			    totalPointsForGraph.add(Integer.valueOf(StringUtil.objectToString(dbServiceMetric.get("totalPoints"))));   
			    rollUp.setGraph(totalPointsForGraph);
			    }
			}
		}
       return annualServiceRollUp;
    }

    public void triggerWeeklyTasks() {
        if (logger.isDebugEnabled()) logger.debug("Trigger weekly tasks");

        MissionHealthWeeklyStatus missionHealthWeeklyStatus = new MissionHealthWeeklyStatus();
        missionHealthWeeklyStatus.setStatus(MissionHealthWeeklyStatus.READY);
        saveMissionHealthWeeklyStatus(missionHealthWeeklyStatus);
    }

    /**
     * 1) Looks for any weekly status records with a status of PROCESSING
     *      If any are processing, no additional tasks will be started
     * @return null
     * 2) If no tasks are processing, update ALL records with a status of READY to PROCESSING
     * 3) If there were multiple records with a status of READY (since updated to PROCESSING)
     *      remove all but one of these records
     * @return the resulting status record
     */
    public MissionHealthWeeklyStatus startWeeklyTasks(){
        if (logger.isDebugEnabled()) logger.debug("Start weekly tasks called");

        MissionHealthWeeklyStatusTranslator translator = new MissionHealthWeeklyStatusTranslator();
        DBObject procQuery = new BasicDBObject("status", MissionHealthWeeklyStatus.PROCESSING);
        int procCnt = missionHealthWeeklyStatusCollection.find(procQuery).count();
        if(procCnt==0) {
            Date start = new Date();
            DBObject readyQuery = new BasicDBObject("status", MissionHealthWeeklyStatus.READY);
            DBObject update = new BasicDBObject("$set", new BasicDBObject("status", MissionHealthWeeklyStatus.PROCESSING));
            WriteResult result = missionHealthWeeklyStatusCollection.update(readyQuery, update, false, true);
            if(result.getN()>0){
                DBCursor cursor = missionHealthWeeklyStatusCollection.find(procQuery).snapshot();
                while(cursor.hasNext()){
                    DBObject dbObject = cursor.next();
                    if(cursor.hasNext()) {
                        missionHealthWeeklyStatusCollection.remove(dbObject);
                    } else {
                        if (logger.isDebugEnabled()) logger.debug("Starting weekly tasks ...");
                        MissionHealthWeeklyStatus status = translator.translateToMissionHealthWeeklyStatus(dbObject);
                        status.setStartDatetime(new Date());
                        saveMissionHealthWeeklyStatus(status);
                        return status;
                    }
                }
            } else {
                if (logger.isDebugEnabled()) logger.debug("Weekly tasks were not triggered and will not be started");
            }
        } else {
            if (logger.isDebugEnabled()) logger.debug("Weekly tasks already in progress and cannot not be re-started");
        }
        return null;
    }

    public void triggerNotificationTask(String notificationType) {
        if (logger.isDebugEnabled()) logger.debug("Start notification task");

        MissionHealthNotificationStatus notificationStatus = new MissionHealthNotificationStatus();
        notificationStatus.setNotificationType(notificationType);
        notificationStatus.setStatus(MissionHealthNotificationStatus.READY);
        saveNotificationStatus(notificationStatus);
    }

    public void saveNotificationStatus(MissionHealthNotificationStatus missionHealthNotificationStatus){
        if (logger.isDebugEnabled()) logger.debug("Saving notification status record");

        MissionHealthWeeklyStatusTranslator translator = new MissionHealthWeeklyStatusTranslator();
        DBObject dbMissionNotificationStatus = translator.translateNotificationStatus(missionHealthNotificationStatus);
        if(null== missionHealthNotificationStatus.getId()){
            missionHealthNotificationStatusCollection.insert(dbMissionNotificationStatus);
        }else{
            missionHealthNotificationStatusCollection.save(dbMissionNotificationStatus);
        }
    }

    /**
     * 1) Looks for any notification status records with the specified notificationType with a status of PROCESSING
     *      If any are processing, no additional tasks will be started
     * @return null
     * 2) If no tasks are processing, update ALL records with a status of READY to PROCESSING
     * 3) If there were multiple records with a status of READY (since updated to PROCESSING)
     *      remove all but one of these records
     * @return the resulting status record
     */
    public MissionHealthNotificationStatus startNotificationTask(String notificationType){
        if (logger.isDebugEnabled()) logger.debug("Start notification task");

        MissionHealthWeeklyStatusTranslator translator = new MissionHealthWeeklyStatusTranslator();
        DBObject procQuery = new BasicDBObject("status", MissionHealthWeeklyStatus.PROCESSING).append("notificationType", notificationType);
        int procCnt = missionHealthNotificationStatusCollection.find(procQuery).count();
        if(procCnt==0) {
            Date start = new Date();
            DBObject readyQuery = new BasicDBObject("status", MissionHealthWeeklyStatus.READY).append("notificationType", notificationType);
            DBObject update = new BasicDBObject("$set", new BasicDBObject("status", MissionHealthWeeklyStatus.PROCESSING));
            WriteResult result = missionHealthNotificationStatusCollection.update(readyQuery, update, false, true);
            if(result.getN()>0){
                DBCursor cursor = missionHealthNotificationStatusCollection.find(procQuery).snapshot();
                while(cursor.hasNext()){
                    DBObject dbObject = cursor.next();
                    if(cursor.hasNext()) {
                        missionHealthNotificationStatusCollection.remove(dbObject);
                    } else {
                        if (logger.isDebugEnabled()) logger.debug("Starting notification task tasks ...");
                        MissionHealthNotificationStatus status = translator.translateToNotificationStatus(dbObject);
                        status.setStartDatetime(new Date());
                        saveNotificationStatus(status);
                        return status;
                    }
                }
            } else {
                if (logger.isDebugEnabled()) logger.debug("Notification task of type :"+notificationType+" was not triggered and will not be started");
            }
        } else {
            if (logger.isDebugEnabled()) logger.debug("Notification task of type :"+notificationType+" is already in progress and cannot not be re-started");
        }
        return null;
    }

}