/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      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 com.artfess.activiti.editor.language.json.converter;

import com.artfess.activiti.editor.constants.HtStencilConstants;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.bpmn.model.BaseElement;
import org.activiti.bpmn.model.ExtensionElement;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.UserTask;
import org.activiti.editor.language.json.converter.BaseBpmnJsonConverter;
import org.activiti.editor.language.json.converter.UserTaskJsonConverter;
import org.activiti.editor.language.json.converter.util.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author Tijs Rademakers
 */
public class HtSignTaskJsonConverter extends UserTaskJsonConverter implements HtStencilConstants{


  public static void fillTypes(Map<String, Class<? extends BaseBpmnJsonConverter>> convertersToBpmnMap, Map<Class<? extends BaseElement>, Class<? extends BaseBpmnJsonConverter>> convertersToJsonMap) {

    fillJsonTypes(convertersToBpmnMap);
    fillBpmnTypes(convertersToJsonMap);
  }

  public static void fillJsonTypes(Map<String, Class<? extends BaseBpmnJsonConverter>> convertersToBpmnMap) {
    convertersToBpmnMap.put(STENCIL_TASK_SIGN, HtSignTaskJsonConverter.class);
  }

  public static void fillBpmnTypes(Map<Class<? extends BaseElement>, Class<? extends BaseBpmnJsonConverter>> convertersToJsonMap) {
    convertersToJsonMap.put(UserTask.class, HtSignTaskJsonConverter.class);
  }

  @Override
  protected String getStencilId(BaseElement baseElement) {
    return STENCIL_TASK_SIGN;
  }

  @Override
  protected void convertElementToJson(ObjectNode propertiesNode, BaseElement baseElement) {
    UserTask userTask = (UserTask) baseElement;
    String assignee = userTask.getAssignee();

    if (StringUtils.isNotEmpty(assignee) || CollectionUtils.isNotEmpty(userTask.getCandidateUsers()) || CollectionUtils.isNotEmpty(userTask.getCandidateGroups())) {

      ObjectNode assignmentNode = objectMapper.createObjectNode();
      ObjectNode assignmentValuesNode = objectMapper.createObjectNode();

      List<ExtensionElement> idmAssigneeList = userTask.getExtensionElements().get("activiti-idm-assignee");
      List<ExtensionElement> idmAssigneeFieldList = userTask.getExtensionElements().get("activiti-idm-assignee-field");
      if (CollectionUtils.isNotEmpty(idmAssigneeList) || CollectionUtils.isNotEmpty(idmAssigneeFieldList)
          || CollectionUtils.isNotEmpty(userTask.getExtensionElements().get("activiti-idm-candidate-user"))
          || CollectionUtils.isNotEmpty(userTask.getExtensionElements().get("activiti-idm-candidate-group"))) {

        assignmentValuesNode.put("type", "idm");
        ObjectNode idmNode = objectMapper.createObjectNode();
        assignmentValuesNode.set("idm", idmNode);

        List<ExtensionElement> canCompleteList = userTask.getExtensionElements().get("initiator-can-complete");
        if (CollectionUtils.isNotEmpty(canCompleteList)) {
          assignmentValuesNode.put("initiatorCanCompleteTask", Boolean.valueOf(canCompleteList.get(0).getElementText()));
        }

        if (StringUtils.isNotEmpty(userTask.getAssignee())) {
          ObjectNode assigneeNode = objectMapper.createObjectNode();
          if (userTask.getAssignee().contains("${taskAssignmentBean.assignTaskToAssignee(")) {
            idmNode.set("assigneeField", assigneeNode);
            idmNode.put("type", "user");

            fillProperty("id", "activiti-idm-assignee-field", assigneeNode, userTask);
            fillProperty("name", "assignee-field-info-name", assigneeNode, userTask);

          } else {
            assigneeNode.put("id", userTask.getAssignee());
            idmNode.set("assignee", assigneeNode);
            idmNode.put("type", "user");

            fillProperty("externalId", "assignee-info-externalid", assigneeNode, userTask);
            fillProperty("email", "assignee-info-email", assigneeNode, userTask);
            fillProperty("firstName", "assignee-info-firstname", assigneeNode, userTask);
            fillProperty("lastName", "assignee-info-lastname", assigneeNode, userTask);
          }
        }

        List<ExtensionElement> idmCandidateUserList = userTask.getExtensionElements().get("activiti-idm-candidate-user");
        if (CollectionUtils.isNotEmpty(userTask.getCandidateUsers()) && CollectionUtils.isNotEmpty(idmCandidateUserList)) {

          List<String> candidateUserIds = new ArrayList<String>();

          if (userTask.getCandidateUsers().size() == 1 && userTask.getCandidateUsers().get(0).contains("${taskAssignmentBean.assignTaskToCandidateUsers(")) {
            idmNode.put("type", "users");

            String candidateUsersString = userTask.getCandidateUsers().get(0);
            candidateUsersString = candidateUsersString.replace("${taskAssignmentBean.assignTaskToCandidateUsers('", "");
            candidateUsersString = candidateUsersString.replace("', execution)}", "");

            List<String> candidateFieldIds = new ArrayList<String>();

            String[] candidateUserArray = candidateUsersString.split(",");
            for (String candidate : candidateUserArray) {
              if (candidate.contains("field(")) {
                candidateFieldIds.add(candidate.trim().substring(6, candidate.length() - 1));
              } else {
                candidateUserIds.add(candidate.trim());
              }
            }

            if (candidateFieldIds.size() > 0) {
              ArrayNode candidateUserFieldsNode = objectMapper.createArrayNode();
              idmNode.set("candidateUserFields", candidateUserFieldsNode);
              for (String fieldId : candidateFieldIds) {
                ObjectNode fieldNode = objectMapper.createObjectNode();
                fieldNode.put("id", fieldId);
                candidateUserFieldsNode.add(fieldNode);

                fillProperty("name", "user-field-info-name-" + fieldId, fieldNode, userTask);
              }
            }

          } else {
            for (String candidateUser : userTask.getCandidateUsers()) {
              candidateUserIds.add(candidateUser);
            }
          }

          if (candidateUserIds.size() > 0) {
            ArrayNode candidateUsersNode = objectMapper.createArrayNode();
            idmNode.set("candidateUsers", candidateUsersNode);
            idmNode.put("type", "users");
            for (String candidateUser : candidateUserIds) {
              ObjectNode candidateUserNode = objectMapper.createObjectNode();
              candidateUserNode.put("id", candidateUser);
              candidateUsersNode.add(candidateUserNode);

              fillProperty("externalId", "user-info-externalid-" + candidateUser, candidateUserNode, userTask);
              fillProperty("email", "user-info-email-" + candidateUser, candidateUserNode, userTask);
              fillProperty("firstName", "user-info-firstname-" + candidateUser, candidateUserNode, userTask);
              fillProperty("lastName", "user-info-lastname-" + candidateUser, candidateUserNode, userTask);
            }
          }
        }

        List<ExtensionElement> idmCandidateGroupList = userTask.getExtensionElements().get("activiti-idm-candidate-group");
        if (CollectionUtils.isNotEmpty(userTask.getCandidateGroups()) && CollectionUtils.isNotEmpty(idmCandidateGroupList)) {

          List<String> candidateGroupIds = new ArrayList<String>();

          if (userTask.getCandidateGroups().size() == 1 && userTask.getCandidateGroups().get(0).contains("${taskAssignmentBean.assignTaskToCandidateGroups(")) {
            idmNode.put("type", "groups");

            String candidateGroupsString = userTask.getCandidateGroups().get(0);
            candidateGroupsString = candidateGroupsString.replace("${taskAssignmentBean.assignTaskToCandidateGroups('", "");
            candidateGroupsString = candidateGroupsString.replace("', execution)}", "");

            List<String> candidateFieldIds = new ArrayList<String>();

            String[] candidateGroupArray = candidateGroupsString.split(",");
            for (String candidate : candidateGroupArray) {
              if (candidate.contains("field(")) {
                candidateFieldIds.add(candidate.trim().substring(6, candidate.length() - 1));
              } else {
                candidateGroupIds.add(candidate.trim());
              }
            }

            if (candidateFieldIds.size() > 0) {
              ArrayNode candidateGroupFieldsNode = objectMapper.createArrayNode();
              idmNode.set("candidateGroupFields", candidateGroupFieldsNode);
              for (String fieldId : candidateFieldIds) {
                ObjectNode fieldNode = objectMapper.createObjectNode();
                fieldNode.put("id", fieldId);
                candidateGroupFieldsNode.add(fieldNode);

                fillProperty("name", "group-field-info-name-" + fieldId, fieldNode, userTask);
              }
            }

          } else {
            for (String candidateGroup : userTask.getCandidateGroups()) {
              candidateGroupIds.add(candidateGroup);
            }
          }

          if (candidateGroupIds.size() > 0) {
            ArrayNode candidateGroupsNode = objectMapper.createArrayNode();
            idmNode.set("candidateGroups", candidateGroupsNode);
            idmNode.put("type", "groups");
            for (String candidateGroup : candidateGroupIds) {
              ObjectNode candidateGroupNode = objectMapper.createObjectNode();
              candidateGroupNode.put("id", candidateGroup);
              candidateGroupsNode.add(candidateGroupNode);

              fillProperty("externalId", "group-info-externalid-" + candidateGroup, candidateGroupNode, userTask);
              fillProperty("name", "group-info-name-" + candidateGroup, candidateGroupNode, userTask);
            }
          }
        }

      } else {
        assignmentValuesNode.put("type", "static");

        if (StringUtils.isNotEmpty(assignee)) {
          assignmentValuesNode.put(PROPERTY_USERTASK_ASSIGNEE, assignee);
        }

        if (CollectionUtils.isNotEmpty(userTask.getCandidateUsers())) {
          ArrayNode candidateArrayNode = objectMapper.createArrayNode();
          for (String candidateUser : userTask.getCandidateUsers()) {
            ObjectNode candidateNode = objectMapper.createObjectNode();
            candidateNode.put("value", candidateUser);
            candidateArrayNode.add(candidateNode);
          }
          assignmentValuesNode.set(PROPERTY_USERTASK_CANDIDATE_USERS, candidateArrayNode);
        }

        if (CollectionUtils.isNotEmpty(userTask.getCandidateGroups())) {
          ArrayNode candidateArrayNode = objectMapper.createArrayNode();
          for (String candidateGroup : userTask.getCandidateGroups()) {
            ObjectNode candidateNode = objectMapper.createObjectNode();
            candidateNode.put("value", candidateGroup);
            candidateArrayNode.add(candidateNode);
          }
          assignmentValuesNode.set(PROPERTY_USERTASK_CANDIDATE_GROUPS, candidateArrayNode);
        }
      }

      assignmentNode.set("assignment", assignmentValuesNode);
      propertiesNode.set(PROPERTY_USERTASK_ASSIGNMENT, assignmentNode);
    }

    if (userTask.getPriority() != null) {
      setPropertyValue(PROPERTY_USERTASK_PRIORITY, userTask.getPriority().toString(), propertiesNode);
    }

    if (userTask.getPriority() != null) {
        setPropertyValue(PROPERTY_USERTASK_PRIORITY, userTask.getPriority().toString(), propertiesNode);
    }

    setPropertyValue(PROPERTY_USERTASK_DUEDATE, userTask.getDueDate(), propertiesNode);
    setPropertyValue(PROPERTY_USERTASK_CATEGORY, userTask.getCategory(), propertiesNode);

    addFormProperties(userTask.getFormProperties(), propertiesNode);
  }

  @SuppressWarnings("deprecation")
protected int getExtensionElementValueAsInt(String name, UserTask userTask) {
    int intValue = 0;
    String value = getExtensionElementValue(name, userTask);
    if (value != null && NumberUtils.isNumber(value)) {
      intValue = Integer.valueOf(value);
    }
    return intValue;
  }

  protected String getExtensionElementValue(String name, UserTask userTask) {
    String value = "";
    if (CollectionUtils.isNotEmpty(userTask.getExtensionElements().get(name))) {
      ExtensionElement extensionElement = userTask.getExtensionElements().get(name).get(0);
      value = extensionElement.getElementText();
    }
    return value;
  }

  @Override
  protected FlowElement convertJsonToElement(JsonNode elementNode, JsonNode modelNode, Map<String, JsonNode> shapeMap) {
	  UserTask task = new UserTask();
      //task.setPriority(getPropertyValueAsString(PROPERTY_USERTASK_PRIORITY, elementNode));
      //String formKey = getPropertyValueAsString(PROPERTY_FORMKEY, elementNode);
      /*if (StringUtils.isNotEmpty(formKey)) {
          task.setFormKey(formKey);
      }*/
      task.setDueDate(getPropertyValueAsString(PROPERTY_USERTASK_DUEDATE, elementNode));
      task.setCategory(getPropertyValueAsString(PROPERTY_USERTASK_CATEGORY, elementNode));
      JsonNode assignmentNode = getProperty(PROPERTY_USERTASK_ASSIGNMENT, elementNode);
      if (assignmentNode != null) {
          JsonNode assignmentDefNode = assignmentNode.get("assignment");
          if (assignmentDefNode != null) {

              JsonNode assigneeNode = assignmentDefNode.get(PROPERTY_USERTASK_ASSIGNEE);
              if (assigneeNode != null && assigneeNode.isNull() == false) {
                  task.setAssignee(assigneeNode.asText());
              }

              JsonNode ownerNode = assignmentDefNode.get(PROPERTY_USERTASK_OWNER);
              if (ownerNode != null && ownerNode.isNull() == false) {
                  task.setOwner(ownerNode.asText());
              }

              task.setCandidateUsers(getValueAsList(PROPERTY_USERTASK_CANDIDATE_USERS, assignmentDefNode));
              task.setCandidateGroups(getValueAsList(PROPERTY_USERTASK_CANDIDATE_GROUPS, assignmentDefNode));
          }
      }
      convertJsonToFormProperties(elementNode, task);
      return task;
  }

  protected void fillAssigneeInfo(JsonNode idmDefNode, JsonNode canCompleteTaskNode, UserTask task) {
    JsonNode assigneeNode = idmDefNode.get("assignee");
    JsonNode assigneeFieldNode = idmDefNode.get("assigneeField");

    if (assigneeNode != null && assigneeNode.isNull() == false) {
      JsonNode idNode = assigneeNode.get("id");
      JsonNode emailNode = assigneeNode.get("email");
      if (idNode != null && idNode.isNull() == false && StringUtils.isNotEmpty(idNode.asText())) {
        task.setAssignee(idNode.asText());
        addExtensionElement("activiti-idm-assignee", String.valueOf(true), task);
        addExtensionElement("assignee-info-email", emailNode, task);
        addExtensionElement("assignee-info-firstname", assigneeNode.get("firstName"), task);
        addExtensionElement("assignee-info-lastname", assigneeNode.get("lastName"), task);
        addExtensionElement("assignee-info-externalid", assigneeNode.get("externalId"), task);

      } else if (emailNode != null && emailNode.isNull() == false && StringUtils.isNotEmpty(emailNode.asText())) {
        task.setAssignee(emailNode.asText());

        // The email is added as extension element. Later (eg on deploy) the assignee
        // is replaced by a real user id, but the email information kept in this extension element
        addExtensionElement("activiti-assignee-email", task.getAssignee(), task);
        addExtensionElement("activiti-idm-assignee", String.valueOf(true), task);
      }

    } else if (assigneeFieldNode != null && assigneeFieldNode.isNull() == false) {
      JsonNode idNode = assigneeFieldNode.get("id");
      if (idNode != null && idNode.isNull() == false && StringUtils.isNotEmpty(idNode.asText())) {
        task.setAssignee("${taskAssignmentBean.assignTaskToAssignee('" + idNode.asText() + "', execution)}");
        addExtensionElement("activiti-idm-assignee-field", idNode.asText(), task);
        addExtensionElement("assignee-field-info-name", assigneeFieldNode.get("name"), task);
      }
    }

    if (canCompleteTaskNode != null && canCompleteTaskNode.isNull() == false) {
      addInitiatorCanCompleteExtensionElement(Boolean.valueOf(canCompleteTaskNode.asText()), task);
    } else {
      addInitiatorCanCompleteExtensionElement(false, task);
    }
  }

  protected void fillCandidateUsers(JsonNode idmDefNode, JsonNode canCompleteTaskNode, UserTask task) {
    List<String> candidateUsers = new ArrayList<String>();
    JsonNode candidateUsersNode = idmDefNode.get("candidateUsers");
    if (candidateUsersNode != null && candidateUsersNode.isArray()) {
      List<String> emails = new ArrayList<String>();
      for (JsonNode userNode : candidateUsersNode) {
        if (userNode != null && userNode.isNull() == false) {
          JsonNode idNode = userNode.get("id");
          JsonNode emailNode = userNode.get("email");
          if (idNode != null && idNode.isNull() == false && StringUtils.isNotEmpty(idNode.asText())) {
            String id = idNode.asText();
            candidateUsers.add(id);

            addExtensionElement("user-info-email-" + id, emailNode, task);
            addExtensionElement("user-info-firstname-" + id, userNode.get("firstName"), task);
            addExtensionElement("user-info-lastname-" + id, userNode.get("lastName"), task);
            addExtensionElement("user-info-externalid-" + id, userNode.get("externalId"), task);

          } else if (emailNode != null && emailNode.isNull() == false && StringUtils.isNotEmpty(emailNode.asText())) {
            String email = emailNode.asText();
            candidateUsers.add(email);
            emails.add(email);
          }
        }
      }

      if (emails.size() > 0) {
        // Email extension element
        addExtensionElement("activiti-candidate-users-emails", StringUtils.join(emails, ","), task);
      }

      if (candidateUsers.size() > 0) {
        addExtensionElement("activiti-idm-candidate-user", String.valueOf(true), task);
        if (canCompleteTaskNode != null && canCompleteTaskNode.isNull() == false) {
          addInitiatorCanCompleteExtensionElement(Boolean.valueOf(canCompleteTaskNode.asText()), task);
        } else {
          addInitiatorCanCompleteExtensionElement(false, task);
        }
      }
    }

    JsonNode candidateUserFieldsNode = idmDefNode.get("candidateUserFields");
    if (candidateUserFieldsNode != null && candidateUserFieldsNode.isArray()) {
      for (JsonNode fieldNode : candidateUserFieldsNode) {
        JsonNode idNode = fieldNode.get("id");
        if (idNode != null && idNode.isNull() == false && StringUtils.isNotEmpty(idNode.asText())) {
          String id = idNode.asText();
          candidateUsers.add("field(" + id + ")");

          addExtensionElement("user-field-info-name-" + id, fieldNode.get("name"), task);
        }
      }
    }

    if (candidateUsers.size() > 0) {
      if (candidateUserFieldsNode != null && candidateUserFieldsNode.isArray() && candidateUserFieldsNode.size() > 0) {
        String candidateUsersString = StringUtils.join(candidateUsers, ",");
        candidateUsersString = "${taskAssignmentBean.assignTaskToCandidateUsers('" + candidateUsersString + "', execution)}";
        candidateUsers.clear();
        candidateUsers.add(candidateUsersString);
        task.setCandidateUsers(candidateUsers);

      } else {
        task.setCandidateUsers(candidateUsers);
      }
    }
  }

  protected void fillCandidateGroups(JsonNode idmDefNode, JsonNode canCompleteTaskNode, UserTask task) {
    List<String> candidateGroups = new ArrayList<String>();
    JsonNode candidateGroupsNode = idmDefNode.get("candidateGroups");
    if (candidateGroupsNode != null && candidateGroupsNode.isArray()) {
      for (JsonNode groupNode : candidateGroupsNode) {
        if (groupNode != null && groupNode.isNull() == false) {
          JsonNode idNode = groupNode.get("id");
          JsonNode nameNode = groupNode.get("name");
          if (idNode != null && idNode.isNull() == false && StringUtils.isNotEmpty(idNode.asText())) {
            String id = idNode.asText();
            candidateGroups.add(id);

            addExtensionElement("group-info-name-" + id, nameNode, task);
            addExtensionElement("group-info-externalid-" + id, groupNode.get("externalId"), task);
          }
        }
      }
    }

    JsonNode candidateGroupFieldsNode = idmDefNode.get("candidateGroupFields");
    if (candidateGroupFieldsNode != null && candidateGroupFieldsNode.isArray()) {
      for (JsonNode fieldNode : candidateGroupFieldsNode) {
        JsonNode idNode = fieldNode.get("id");
        if (idNode != null && idNode.isNull() == false && StringUtils.isNotEmpty(idNode.asText())) {
          String id = idNode.asText();
          candidateGroups.add("field(" + id + ")");

          addExtensionElement("group-field-info-name-" + id, fieldNode.get("name"), task);
        }
      }
    }

    if (candidateGroups.size() > 0) {
      if (candidateGroupFieldsNode != null && candidateGroupFieldsNode.isArray() && candidateGroupFieldsNode.size() > 0) {
        String candidateGroupsString = StringUtils.join(candidateGroups, ",");
        candidateGroupsString = "${taskAssignmentBean.assignTaskToCandidateGroups('" + candidateGroupsString + "', execution)}";
        candidateGroups.clear();
        candidateGroups.add(candidateGroupsString);
        task.setCandidateGroups(candidateGroups);

      } else {
        task.setCandidateGroups(candidateGroups);
      }

      addExtensionElement("activiti-idm-candidate-group", String.valueOf(true), task);
      if (canCompleteTaskNode != null && canCompleteTaskNode.isNull() == false) {
        addInitiatorCanCompleteExtensionElement(Boolean.valueOf(canCompleteTaskNode.asText()), task);
      } else {
        addInitiatorCanCompleteExtensionElement(false, task);
      }
    }
  }

  protected void addInitiatorCanCompleteExtensionElement(boolean canCompleteTask, UserTask task) {
    addExtensionElement("initiator-can-complete", String.valueOf(canCompleteTask), task);
  }

  protected void addExtensionElement(String name, JsonNode elementNode, UserTask task) {
    if (elementNode != null && elementNode.isNull() == false && StringUtils.isNotEmpty(elementNode.asText())) {
      addExtensionElement(name, elementNode.asText(), task);
    }
  }

  protected void addExtensionElement(String name, String elementText, UserTask task) {
    ExtensionElement extensionElement = new ExtensionElement();
    extensionElement.setNamespace(NAMESPACE);
    extensionElement.setNamespacePrefix("modeler");
    extensionElement.setName(name);
    extensionElement.setElementText(elementText);
    task.addExtensionElement(extensionElement);
  }

  protected void fillProperty(String propertyName, String extensionElementName, ObjectNode elementNode, UserTask task) {
    List<ExtensionElement> extensionElementList = task.getExtensionElements().get(extensionElementName);
    if (CollectionUtils.isNotEmpty(extensionElementList)) {
      elementNode.put(propertyName, extensionElementList.get(0).getElementText());
    }
  }
}
