package br.mp.mprj.portal.portlet.gestaomensagens.workflow;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import javax.portlet.PortletPreferences;
import javax.portlet.PortletRequest;

import br.mp.mprj.portal.portlet.gestaomensagens.service.model.Mensagem;

import com.liferay.portal.kernel.dao.orm.QueryUtil;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.util.WebKeys;
import com.liferay.portal.kernel.workflow.WorkflowException;
import com.liferay.portal.kernel.workflow.WorkflowHandlerRegistryUtil;
import com.liferay.portal.kernel.workflow.WorkflowInstance;
import com.liferay.portal.kernel.workflow.WorkflowInstanceManagerUtil;
import com.liferay.portal.kernel.workflow.WorkflowLog;
import com.liferay.portal.kernel.workflow.WorkflowLogManagerUtil;
import com.liferay.portal.kernel.workflow.WorkflowTask;
import com.liferay.portal.kernel.workflow.WorkflowTaskManagerUtil;
import com.liferay.portal.kernel.workflow.comparator.WorkflowComparatorFactoryUtil;
import com.liferay.portal.model.Role;
import com.liferay.portal.model.User;
import com.liferay.portal.model.WorkflowDefinitionLink;
import com.liferay.portal.model.WorkflowInstanceLink;
import com.liferay.portal.service.RoleLocalServiceUtil;
import com.liferay.portal.service.ServiceContext;
import com.liferay.portal.service.UserLocalServiceUtil;
import com.liferay.portal.service.WorkflowDefinitionLinkLocalServiceUtil;
import com.liferay.portal.service.WorkflowInstanceLinkLocalServiceUtil;
import com.liferay.portal.theme.ThemeDisplay;
import com.liferay.util.portlet.PortletProps;

public class WorkflowUtil {
	private static final Log _log = LogFactoryUtil.getLog(WorkflowUtil.class);

	/**
	 * Retorna o roleId referente ao papel que ser&aacute; utilizado para atribuir as atividades de workflow.
	 * @param req - refer&ecirc;ncia do {@link PortletRequest} para obter informa&ccedil;&otilde;es do contexto da requisi&ccedil;&atilde;o.
	 * @return long
	 * @throws PortalException
	 * @throws SystemException
	 */
	public static long getWorkflowRoleId(PortletRequest req) throws PortalException, SystemException {
		ThemeDisplay themeDisplay = (ThemeDisplay) req.getAttribute(WebKeys.THEME_DISPLAY);
		PortletPreferences prefs = req.getPreferences();
		String roleName = prefs.getValue("workflowRoleName", PortletProps.get("configuracao.valor-padrao.workflowRoleName"));
		return getRole(themeDisplay.getCompanyId(), roleName).getRoleId();
	}
	
	public static boolean isAprovador(PortletRequest renderRequest) throws PortalException, SystemException{
		
		ThemeDisplay themeDisplay = (ThemeDisplay)  renderRequest.getAttribute(WebKeys.THEME_DISPLAY);
		boolean retorno = false;
		
		long roleId = WorkflowUtil.getWorkflowRoleId(themeDisplay.getCompanyId());
		List<User> aprovadores = UserLocalServiceUtil.getRoleUsers(roleId);
		
		for (User aprovador : aprovadores){
			if (aprovador.getEmailAddress().equals(themeDisplay.getUser().getEmailAddress())){
				retorno = true;
				break;
				
			}
		}
		
		return retorno;
	}

	/**
	 * Retorna o roleId referente ao papel que ser&aacute; utilizado para atribuir as atividades de workflow. <br>
	 * O nome da role &eacute; definida no arquivo no portlet.properties. <br>
	 * Utilizado em m&eacute;todos onde n&atilde;o &eacute; poss&iacute;vel obter o portlet preferences atrav&eacute;s do {@link PortletRequest}.
	 */
	public static long getWorkflowRoleId(long companyId) throws PortalException, SystemException {
		return getRole(companyId, PortletProps.get("configuracao.valor-padrao.workflowRoleName")).getRoleId();
	}
	
	private static Role getRole(long companyId, String roleName) throws PortalException, SystemException {
		Role role = RoleLocalServiceUtil.getRole(companyId, roleName);
		return role;				
	}
	
	/**
	 * Obtem o {@link WorkflowDefinitionLink} referente ao configura&ccedil;&atilde;o definida para a classe.
	 * Primeiro tenta obter atrav&eacute;s da configura&ccedil;&atilde;o p/ site, caso n&atilde;o encontre, 
	 * tenta obter nas configura&ccedil;&otilde;es globais do portal.
	 * @param mensagem
	 * @param groupId
	 * @param companyId
	 * @param classPK - ID (primary key) referente a classe que est&aacute; sendo verificada.
	 * @return {@link WorkflowDefinitionLink}
	 */
	public static WorkflowDefinitionLink getWorkflowDefinitionLink(Mensagem mensagem)
			throws WorkflowException {
		WorkflowDefinitionLink wdl = null;
		try {
			//tenta carregar as definições do site atual
			wdl = WorkflowDefinitionLinkLocalServiceUtil
							.getWorkflowDefinitionLink(mensagem.getCompanyId(), 
													mensagem.getGroupId(), 
													Mensagem.class.getName(), 
													0, 
													0);
		}
		catch (Exception ex) {
			//tenta carregar as definições globais
			try {
				wdl = WorkflowDefinitionLinkLocalServiceUtil
							.getDefaultWorkflowDefinitionLink(mensagem.getCompanyId(), 
															Mensagem.class.getName(), 
															0, 
															0);
			}
			catch (Exception exGlobal) {
				_log.warn("Configuração de Workflow não encontrado para a classe: " + Mensagem.class.getName(), exGlobal);
				throw new WorkflowException("Workflow não configurado para o portlet", ex);
			}
		}
		return wdl;
	}
	
	public static void inicializarWorkflowMensagem(Mensagem mensagem, ServiceContext serviceContext)
			throws SystemException, PortalException {
		WorkflowDefinitionLink wdl = getWorkflowDefinitionLink(mensagem);
		if (wdl != null) {
			WorkflowHandlerRegistryUtil.startWorkflowInstance(mensagem.getCompanyId(), 
					mensagem.getGroupId(), 
					mensagem.getUserIdCriador(), 
					Mensagem.class.getName(), 
					mensagem.getMensagemId(), 
					mensagem, 
					serviceContext);
		}
	}
	
	/**
	 * Retorna um array de objeto contendo o {@link WorkflowTask} (0) e um Map referente ao workflowContext (1).
	 * @param className
	 * @param companyId
	 * @param groupId
	 * @param classPK
	 * @param userId
	 * @return Object[]
	 * @throws PortalException
	 * @throws SystemException
	 */
	@SuppressWarnings("unchecked")
	private static Object[] getWorkflowTaskContext(String className, long companyId, long groupId, 
												long classPK, long userId)
				throws PortalException, SystemException{
		Object[] logContexto = getWorkflowContextLog(className, companyId, groupId, classPK);
		
		List<WorkflowLog> workflowLogs_assign = (List<WorkflowLog>)logContexto[0];
		
		if (workflowLogs_assign.size() > 0) {
			Map<String, Serializable> workflowContext = (Map<String, Serializable>)logContexto[1];
			
			long taskId = workflowLogs_assign.get(workflowLogs_assign.size()-1).getWorkflowTaskId();
			
			return new Object[]{WorkflowTaskManagerUtil.getWorkflowTask(companyId, taskId), workflowContext} ;
		}
		
		throw new PortalException("WorkflowTask não encontrado.");
	}
	
	/**
	 * 
	 * @param className
	 * @param companyId
	 * @param groupId
	 * @param classPK
	 * @param userId
	 * @param roleId
	 * @param comentario
	 * @throws PortalException
	 * @throws SystemException
	 */
	@SuppressWarnings("unchecked")
	public static void atribuirWorkflowRole(String className, long companyId, long groupId, 
											long classPK, long userId, long roleId,
											String comentario)
			throws PortalException, SystemException {
			
		Object[] obj = getWorkflowTaskContext(className, companyId, groupId, classPK, userId);
		
		Map<String, Serializable> workflowContext = (Map<String, Serializable>)obj[1];
			
		WorkflowTask task = (WorkflowTask)obj[0];
		
		WorkflowTaskManagerUtil.assignWorkflowTaskToRole(
										companyId, 
										userId, 
										task.getWorkflowTaskId(), 
										roleId, 
										comentario, task.getDueDate(), 
										workflowContext); 		
	}
	
	/**
	 * Atribui uma tarefa de workflow para um determinado usuario (userIdWorkflow)
	 * @param className
	 * @param companyId
	 * @param groupId
	 * @param classPK
	 * @param userId - ID do usu&aacute;rio que originou a a&ccedil;&atilde;o.
	 * @param userIdWorkflow - ID do usu&aacute;rio que ser&aacute; respons&aacute;vel pela 
	 * aprova&ccedil;&atilde;o/reprova&ccedil;&atilde;o do workflow.
	 * @param comentario - coment&aacute;rio da tarefa de workflow.
	 * @throws PortalException
	 * @throws SystemException
	 */
	@SuppressWarnings("unchecked")
	public static void atribuirWorkflowUser(String className, long companyId,
											long groupId, long classPK, 
											long userId, long userIdWorkflow, String comentario) 
			throws PortalException, SystemException {

		Object[] obj = getWorkflowTaskContext(className, companyId, groupId,
				classPK, userId);

		Map<String, Serializable> workflowContext = (Map<String, Serializable>) obj[1];

		WorkflowTask task = (WorkflowTask) obj[0];

		if (!task.isAssignedToSingleUser()) {
			WorkflowTaskManagerUtil.assignWorkflowTaskToUser(companyId, userId,
					task.getWorkflowTaskId(), userIdWorkflow, comentario,
					task.getDueDate(), workflowContext);
		}
	}
	
	/**
	 * Atualiza o status do workflow, aprovando ou reprovando, 
	 * definindo a a&ccedil;&atilde;o atrav&eacute;s do {@link AcaoWorkflowEnum}.
	 * @param companyId
	 * @param userId
	 * @param task
	 * @param workflowContext
	 * @param acaoWorkflow
	 * @param comentario
	 * @throws WorkflowException
	 */
	public static void atualizarStatusWorkflow(long companyId, long userId,  
										WorkflowTask task, Map<String, Serializable> workflowContext,
										AcaoWorkflowEnum acaoWorkflow, String comentario)
			throws WorkflowException {
		//recupera as proximas transicoes permitidas para a tarefa de workflow atual
		List<String> nextTransitions = WorkflowTaskManagerUtil.getNextTransitionNames(companyId, userId, task.getWorkflowTaskId());
		for (String transition : nextTransitions) {
			if (transition.equals(acaoWorkflow.getNome())) {
				WorkflowTaskManagerUtil.completeWorkflowTask(companyId, userId, 
						task.getWorkflowTaskId(), acaoWorkflow.getNome(), 
						comentario, workflowContext);		
				return;
			}
		}
		//_log.warn("A acao de workflow nao esta disponivel: " + acaoWorkflow.getNome() 
		//			+ ", mensagemId: " + (long)workflowContext.get(WorkflowConstants.CONTEXT_ENTRY_CLASS_PK));
	}
	
	/**
	 * Obtem a lista de {@link WorkflowLog} e um Map referente ao contexto do Workflow para atribui&ccedil;&atilde;o de atividade.
	 * @param classe
	 * @param companyId
	 * @param groupId
	 * @param classPK
	 * @param userId
	 * @return Object[] : [0] = List<WorkflowLog>, [1] = Map<String, Serializable> (workflow context).
	 * @throws PortalException
	 * @throws SystemException
	 */
	private static Object[] getWorkflowContextLog(String className, 
					long companyId, long groupId, long classPK) 
			throws PortalException, SystemException {
		//atribuir o workflow para o usuário atual
		WorkflowInstanceLink wil = WorkflowInstanceLinkLocalServiceUtil
										.getWorkflowInstanceLink(companyId, 
												groupId, className, 
												classPK);
		
		WorkflowInstance wi = WorkflowInstanceManagerUtil.getWorkflowInstance(companyId, wil.getWorkflowInstanceId());
		
		Map<String, Serializable> workflowContext = wi.getWorkflowContext();
		
		List<Integer> logTypesAssign = new ArrayList<Integer>();
		logTypesAssign.add(WorkflowLog.TASK_ASSIGN);
		
		List<WorkflowLog> workflowLogs_assign = WorkflowLogManagerUtil.getWorkflowLogsByWorkflowInstance(companyId, 
													wil.getWorkflowInstanceId(), 
													logTypesAssign, 
													QueryUtil.ALL_POS, 
													QueryUtil.ALL_POS, 
													WorkflowComparatorFactoryUtil.getLogCreateDateComparator(true));
		
		return new Object[]{workflowLogs_assign, workflowContext};
	}
	
	/**
	 * Atualiza o status de workflow da {@link Mensagem}.
	 * @param mensagem - inst&acirc;ncia da {@link Mensagem} devidamente inicializada e persistida em banco.
	 * @param acaoWorkflow - a&ccedil;&atilde;o ({@link AcaoWorkflowEnum}) a ser executada p/ tarefa.
	 * @param comentario - comentário do workflow.
	 * @param userId - userId que executou a a&ccedil;&atilde;o.
	 * @throws SystemException
	 * @throws PortalException
	 * @throws WorkflowException
	 */
	@SuppressWarnings("unchecked")
	public static void atualizarStatusWorkflow(Mensagem mensagem, AcaoWorkflowEnum acaoWorkflow, 
												String comentario, long userId,
												ServiceContext serviceContext)
			throws SystemException, PortalException, WorkflowException {
		Object[] obj = getWorkflowTaskContext(Mensagem.class.getName(), mensagem.getCompanyId(), 
				mensagem.getGroupId(), mensagem.getMensagemId(), userId);

		Map<String, Serializable> workflowContext = (Map<String, Serializable>) obj[1];

		WorkflowTask task = (WorkflowTask) obj[0];
		
		atribuirWorkflowUser(Mensagem.class.getName(), mensagem.getCompanyId(), mensagem.getGroupId(), 
							 mensagem.getMensagemId(), userId, userId, comentario);

		if (!AcaoWorkflowEnum.CANCELAR.equals(acaoWorkflow)) {
			atualizarStatusWorkflow(mensagem.getCompanyId(), userId, task, workflowContext, acaoWorkflow, comentario);
		}
	}
	
	public static List<StatusWorkflowEnum> listaStatusWorkflow() {
		if (StatusWorkflowEnum.values().length > 0) {
			return Arrays.asList(StatusWorkflowEnum.values());
		}
		return null;
	}
	
	public static List<StatusWorkflowEnum> listaStatusParaFiltroPainel() {
		
		List<StatusWorkflowEnum> lista =  new ArrayList<>();
		
		lista.add(StatusWorkflowEnum.APROVADO);
		lista.add(StatusWorkflowEnum.PENDENTE_APROVACAO);
		lista.add(StatusWorkflowEnum.PENDENTE_REVISAO);
		lista.add(StatusWorkflowEnum.CANCELADO);
		
		return lista;
	}
	
}
