/* 
* Copyright (C) 2003-2015 eXo Platform SAS.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see http://www.gnu.org/licenses/ .
*/
package org.exoplatform.task.injector;

import org.exoplatform.commons.api.persistence.ExoTransactional;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.services.bench.DataInjector;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.task.domain.Comment;
import org.exoplatform.task.domain.Project;
import org.exoplatform.task.domain.Status;
import org.exoplatform.task.domain.Task;
import org.exoplatform.task.exception.EntityNotFoundException;
import org.exoplatform.task.service.ProjectService;
import org.exoplatform.task.service.StatusService;
import org.exoplatform.task.service.TaskService;

import java.util.*;

/**
 * Created by The eXo Platform SAS
 * Author : Thibault Clement
 * tclement@exoplatform.com
 * 5/14/15
 */
public class TaskInjector extends DataInjector {

  private static final Log LOG = ExoLogger.getLogger(TaskInjector.class);

  //Parameters

  private static final String NUMBER_PROJECTS = "nbProject";
  private static final int DEFAULT_NUMBER_PROJECTS = 15;

  private static final String NUMBER_TASKS = "nbTaskPerProject";
  private static final int DEFAULT_NUMBER_TASKS = 42;

  private static final String NUMBER_INCOMING_TASKS = "nbIncomingTask";
  private static final int DEFAULT_NUMBER_INCOMING_TASKS = 10;

  private static final String NUMBER_COMMENTS = "nbComPerTask";
  private static final int DEFAULT_NUMBER_COMMENTS = 2;

  private static final String PERCENTAGE_COMPLETED = "perCompleted";
  private static final int DEFAULT_PERCENTAGE_COMPLETED = 70;

  private static final String TASK_TYPE = "type"; // [user,space]
  private static final String USER_TASK_TYPE = "user";
  private static final String SPACE_TASK_TYPE = "space";
  private static final String DEFAULT_TASK_TYPE = USER_TASK_TYPE;

  private static final String NAME_PREFIX = "prefix";
  private static final String DEFAULT_USER_PREFIX = "bench.user";
  private static final String DEFAULT_SPACE_PREFIX = "bench.space";

  private static final String TASK_PREFIX = "taskPrefix";
  private static final String DEFAULT_TASK_PREFIX = "Task";

  private static final String PROJECT_PREFIX = "projectPrefix";
  private static final String DEFAULT_PROJECT_PREFIX = "Project";

  private static final String NAME_SUFFIX = "suffix";
  private static final int DEFAULT_SUFFIX = 4;

  private static final String USER_FROM = "from";
  private static final int DEFAULT_USER_FROM = 0;

  private static final String USER_TO = "to";
  private static final int DEFAULT_USER_TO = 10;

  //Service

  private final TaskService taskService;
  private final ProjectService projectService;
  private final StatusService statusService;

  //

  private List<String> statusName = Arrays.asList(new String[]{"To Do", "On Going", "Done"});

  private Random random = new Random();

  private int nbProject;

  private int nbTasks;
  private int nbIncomingTasks;

  private int nbComments;
  private int perCompleted;

  private String taskType;

  private String namePrefix;
  private int nameSuffix;
  private String suffixPattern;

  private String taskPrefix;
  private String projectPrefix;

  private int userFrom;
  private int userTo;

  public TaskInjector() {
    PortalContainer container = PortalContainer.getInstance();
    taskService = (TaskService)container.getComponentInstanceOfType(TaskService.class);
    projectService = (ProjectService)container.getComponentInstanceOfType(ProjectService.class);
    statusService = (StatusService)container.getComponentInstanceOfType(StatusService.class);
  }

  private void init() {
    suffixPattern = "%s%0" + nameSuffix + "d";
  }

  private void initParam(HashMap<String, String> params) {
    nbProject = getIntegerParam(params, NUMBER_PROJECTS, DEFAULT_NUMBER_PROJECTS);
    nbTasks = getIntegerParam(params, NUMBER_TASKS, DEFAULT_NUMBER_TASKS);
    nbComments = getIntegerParam(params, NUMBER_COMMENTS, DEFAULT_NUMBER_COMMENTS);
    perCompleted = getIntegerParam(params, PERCENTAGE_COMPLETED, DEFAULT_PERCENTAGE_COMPLETED);
    nbIncomingTasks = getIntegerParam(params, NUMBER_INCOMING_TASKS, DEFAULT_NUMBER_INCOMING_TASKS);
    taskType = getStringParam(params, TASK_TYPE, DEFAULT_TASK_TYPE);
    if (!USER_TASK_TYPE.equals(taskType) && !SPACE_TASK_TYPE.equals(taskType)) {
      LOG.warn(TASK_TYPE+" parameter ["+taskType+"] is invalid, the default one will be used."+TASK_TYPE
          +" parameter must be equal to "+USER_TASK_TYPE+" (default) or "+SPACE_TASK_TYPE);
      taskType = USER_TASK_TYPE;
    }
    namePrefix = (USER_TASK_TYPE.equals(taskType)) ? getStringParam(params, NAME_PREFIX, DEFAULT_USER_PREFIX) :
        getStringParam(params, NAME_PREFIX, DEFAULT_SPACE_PREFIX);
    nameSuffix = getIntegerParam(params, NAME_SUFFIX, DEFAULT_SUFFIX);
    taskPrefix = getStringParam(params, TASK_PREFIX, DEFAULT_TASK_PREFIX);
    projectPrefix = getStringParam(params, PROJECT_PREFIX, DEFAULT_PROJECT_PREFIX);
    userFrom = getIntegerParam(params, USER_FROM, DEFAULT_USER_FROM);
    userTo = getIntegerParam(params, USER_TO, DEFAULT_USER_TO);
  }

  @Override
  public void inject(HashMap<String, String> hashMap) throws Exception {
    LOG.info("Start Inject Data for Task...");
    initParam(hashMap);
    init();
    //Create personal tasks
    if (USER_TASK_TYPE.equals(taskType)) {
      addPersonalTasks();
    }
    //Create space tasks
    else {
      addSpaceTasks();
    }
    LOG.info("... Finish Inject Data for Task");
  }

  @Override
  public void reject(HashMap<String, String> hashMap) throws Exception {

  }

  @Override
  public Log getLog() {
    return LOG;
  }

  @Override
  public Object execute(HashMap<String, String> hashMap) throws Exception {
    return null;
  }

  private void addPersonalTasks() {

    LOG.info("Adding " + nbProject + " personal projects with " + nbTasks + " tasks for "+(userTo-userFrom)
        +" user(s)" );

    for (int i = userFrom; i < userTo; i++) {
      addPersonalTasksByUser(i);
    }

  }

  @ExoTransactional
  private void addPersonalTasksByUser(int userNb) {

    //Get username
    String userName = getUsername(userNb);

    LOG.info("Add personnal tasks to user: "+userName);

    //Create tasks
    //Create tasks associated to personal project of the user
    //Loop on number of projects
    for (int j = 0; j < nbProject; j++) {
      //Create project
      Project project = createProject(userName, j);
      project = projectService.createProject(project);

      //Create status for project
      List<Status> statuses = createStatus(project);

      //Loop on number of tasks per project
      for (int k = 0; k < nbTasks; k++) {
        createTask(statuses.get(random.nextInt(3)), userName, project.getName(), k, true);
      }
    }

    //Create Incoming Task (not attached to project) of the user
    for (int j = 0; j < nbIncomingTasks; j++) {
      createTask(null, userName, "Incoming-"+userName, j, false);
    }
  }

  private void addSpaceTasks() {

    LOG.info("Adding " + nbProject + " space projects with " + nbTasks + " tasks for "+(userTo-userFrom)+" space(s)" );

    for (int i = userFrom; i < userTo; i++) {
      addSpaceTasksByUser(i);
    }

  }

  @ExoTransactional
  private void addSpaceTasksByUser(int userNb) {

    //Get space name
    String spaceName = getSpaceName(userNb);
    String manager = "manager:/spaces/" + spaceName;
    String member = "member:/spaces/" + spaceName;

    LOG.info("Add tasks to space: " + spaceName);

    //Create tasks
    //Create tasks associated to personal project of the user
    //Loop on number of projects
    for (int j = 0; j < nbProject; j++) {
      //Create project
      Project project = createProject(manager, j);
      //Add space member, member of the project
      Set<String> members = new HashSet<String>();
      members.add(member);
      project.setParticipator(members);

      project = projectService.createProject(project);


      //Add default status to the project
      List<Status> statuses = createStatus(project);

      //Loop on number of tasks per project
      for (int k = 0; k < nbTasks; k++) {
        createTask(statuses.get(random.nextInt(3)), manager, project.getName(), k, true);
      }
    }

  }

  private Task createTask(Status status, String username, String project, int numtask, boolean isRandomCompleted) {
    Task task = new Task();
    task.setTitle(taskPrefix+"-"+numtask);
    task.setDescription(randomWords(20));
    task.setCreatedBy(username);
    task.setAssignee(username);
    task.setCreatedTime(new Date());

    //Set tasks as completed
    if (isRandomCompleted) {
      if (random.nextInt(100) < perCompleted) {
        task.setCompleted(true);
      }
    }

    //
    if (status != null) {
      task.setStatus(status);
    }

    //
    task = taskService.createTask(task);

    //
    createComments(task, username, nbComments);

    return task;
  }

  private Set<Comment> createComments(Task task, String username, int nbComments) {
    Set<Comment> comments = new HashSet<Comment>();
    for (int i = 0; i < nbComments; i++) {
      try {
        Comment comment = taskService.addComment(task.getId(), username, randomWords(20));
        comments.add(comment);
      } catch (EntityNotFoundException ex) {
        throw new IllegalArgumentException("Task with id: " + task.getId() + " is not found in database");
      }
    }
    return comments;
  }

  private Project createProject(String username, int numProject) {
    Project project = new Project();
    project.setName(projectPrefix+"-"+username+"-"+numProject);
    project.setDescription(randomWords(20));

    //Add current user as manager of the project
    Set<String> managers = new HashSet<String>();
    managers.add(username);
    project.setManager(managers);

    return project;
  }

  private List<Status> createStatus(Project project) {
    List<Status> statusList = new ArrayList<Status>();
    //int rank = 1;

    for (String name : statusName) {
      Status status = statusService.createStatus(project, name);
      /*status.setName(name);
      status.setRank(rank);

      //Attach status to project
      status.setProject(project);
      project.getStatus().add(status);

      statusList.add(status);

      rank++;*/

      statusList.add(status);
    }
    return statusList;
  }

  private String getUsername(int userNumber) {
      return String.format(suffixPattern, namePrefix, userNumber);
  }

  private String getSpaceName(int spaceNumber) {
    return String.format(suffixPattern, namePrefix.replace(".", ""), spaceNumber);
  }

  private int getIntegerParam(HashMap<String, String> params, String name, int defaultNb) {

    if (params == null) {
      return defaultNb;
    }

    if (name == null) {
      return defaultNb;
    }

    try {
      String value = params.get(name);
      if (value != null) {
        return Integer.valueOf(value);
      }
    } catch (NumberFormatException e) {
      LOG.warn("Integer number expected for property " + name +" ! Default value will be use instead");
    }
    return defaultNb;

  }

  protected String getStringParam(HashMap<String, String> params, String name, String defaultParam) {

    if (params == null) {
      return defaultParam;
    }

    if (name == null) {
      return defaultParam;
    }

    String value = params.get(name);
    if (value != null) {
      return value;
    }

    return defaultParam;

  }

}

