/*
 * Copyright 2017 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/
package org.jbpm.services.task.assignment.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.jbpm.services.task.assignment.UserTaskLoad;
import org.jbpm.services.task.utils.ClassUtil;
import org.kie.api.task.TaskContext;
import org.kie.api.task.model.User;
import org.kie.internal.task.api.TaskPersistenceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TaskCountLoadCalculator extends AbstractLoadCalculator {
	private static final String IDENTIFIER = "TaskCountLoadCalculator";
	private static final Logger logger = LoggerFactory.getLogger(TaskCountLoadCalculator.class);
    private static final String SINGLE_USER_QUERY = 
            "select new Integer(count(t)) "
            + "from TaskImpl t "
            + "where t.taskData.actualOwner.id = :owner and t.taskData.status in ('Reserved', 'InProgress', 'Suspended') ";
    private static final String MULTI_USER_QUERY = 
            "select new org.jbpm.services.task.assignment.impl.AssignmentImpl(t.taskData.actualOwner.id, count(t)) "
            + "from TaskImpl t "
            + "where t.taskData.actualOwner.id in (:owners) and t.taskData.status in ('Reserved', 'InProgress', 'Suspended') "
            + "group by t.taskData.actualOwner "
            + "order by count(t) asc, t.taskData.actualOwner.id asc";
    
    
    public TaskCountLoadCalculator() {
    	super(IDENTIFIER);
    }

    Function<AssignmentImpl, String> assignKey = (assignment) -> { return assignment.getUser(); };
    
    @Override
	public UserTaskLoad getUserTaskLoad(User user, TaskContext context) {
		UserTaskLoad load = new UserTaskLoad(getIdentifier(), user);
        TaskPersistenceContext persistenceContext = ((org.jbpm.services.task.commands.TaskContext)context).getPersistenceContext();
        Map<String, Object> params = new HashMap<>();
        params.put("owner", user.getId());
        
        logger.debug("DB query to be used for finding assignments :: '{}'", getSingleUserQuery());
        List<Integer> assignmentCounts = persistenceContext.queryStringWithParametersInTransaction(getSingleUserQuery(), params, ClassUtil.<List<Integer>>castClass(List.class));
        if (assignmentCounts != null && !assignmentCounts.isEmpty()) {
        	load.setCalculatedLoad(new Double(assignmentCounts.get(0)));
        } else {
        	load.setCalculatedLoad(new Double(0));
        }
		return load;
	}
    

	@Override
	public Collection<UserTaskLoad> getUserTaskLoads(List<User> users, TaskContext context) {
		Collection<UserTaskLoad> userTaskLoads = new ArrayList<>();
		List<String> userIds = users.stream().map(user -> {return user.getId();}).collect(Collectors.toList());
		
        TaskPersistenceContext persistenceContext = ((org.jbpm.services.task.commands.TaskContext)context).getPersistenceContext();
        Map<String, Object> params = new HashMap<>();
        params.put("owners", userIds);
        
        logger.debug("DB query to be used for finding assignments :: '{}'", getMultiUserQuery());
        List<AssignmentImpl> assignments = persistenceContext.queryStringWithParametersInTransaction(getMultiUserQuery(), params, ClassUtil.<List<AssignmentImpl>>castClass(List.class));
        Map<String,AssignmentImpl> assignmentMap = assignments.stream().collect(Collectors.toMap(assignKey,(assign)->assign));
        if (assignments != null && !assignments.isEmpty()) {
        	users.forEach(usr -> {
        		String uid = usr.getId();
        		if (assignmentMap.containsKey(uid)) {
        			Long loadValue = assignmentMap.get(uid).getCurrentlyAssigned();
        			userTaskLoads.add(new UserTaskLoad(getIdentifier(), usr, new Double(loadValue != null ? loadValue:0)));
        		} else {
        			userTaskLoads.add(new UserTaskLoad(getIdentifier(), usr, new Double(0)));
        		}
        	});
        } else {
        	users.forEach(u -> {
        		userTaskLoads.add(new UserTaskLoad(getIdentifier(),u,new Double(0)));
        	});
        }
		return userTaskLoads;
	}

	private String getSingleUserQuery() {
		return SINGLE_USER_QUERY;
	}
	
	private String getMultiUserQuery() {
		return MULTI_USER_QUERY;
	}

}
