package com.google.sps.servlets;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

import error.ErrorHandler;

@WebServlet("/mark-checkbox")
public class MarkCheckboxServlet extends HttpServlet {

  @Override
  public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
    String idString = request.getParameter("id");
    boolean isChecked = Boolean.parseBoolean(request.getParameter("checked"));
    String type = request.getParameter("type");
    long id = this.parseToLong(response, idString);
    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
    String userId = this.getUserId(response);
    Entity optionEntity = this.getEntityFromId(response, id, datastore, type);
    Set<String> votesSet = this.getUpdatedVotes(optionEntity, isChecked, userId);

    // Update datastore
    optionEntity.setProperty("votes", votesSet);
    datastore.put(optionEntity);
  }

  /**
   * Gets the id of the currently logged in user.
   *
   * @param response HttpServletResponse
   * @return String user id
   * @throws IOException exception thrown from send error method.
   */
  private String getUserId(HttpServletResponse response) throws IOException {
    UserService userService = UserServiceFactory.getUserService();
    if (userService.isUserLoggedIn()) {
      return userService.getCurrentUser().getUserId();
    } else {
      ErrorHandler.sendError(response, "User is not logged in.");
    }
    return "";
  }

  /**
   * Parses string to long.
   *
   * @param response HttpServletResponse
   * @param idString id of the current checkbox in the form of a String
   * @return long representing the id of current option
   * @throws IOException
   */
  private long parseToLong(HttpServletResponse response, String idString) throws IOException {
    try {
      return Long.parseLong(idString);
    } catch (NumberFormatException e) {
      ErrorHandler.sendError(response, "Cannot parse to long.");
      return 0;
    }
  }

  /**
   * Retrieves the option entity from the database based on id.
   *
   * @param response HttpServletResponse
   * @param optionId id of current option
   * @param datastore datastore holding all data
   * @return Option entity
   * @throws IOException error thrown from sendError method
   */
  private Entity getEntityFromId(
      HttpServletResponse response, long id, DatastoreService datastore, String type)
      throws IOException {
    try {
      return datastore.get(KeyFactory.createKey(type, id));
    } catch (EntityNotFoundException e) {
      ErrorHandler.sendError(response, "Cannot get entity from datastore");
      return null;
    }
  }

  /**
   * Updates the votes for a particular poll option. Gets an ArrayList from the database
   * representing the list of people who have voted for a particular option, which is passed in as
   * optionEntity. Converts this ArrayList to a Set and then checks whether the checkbox is checked
   * and whether the set already contains the current user id and then updates the set accordingly.
   *
   * @param optionEntity option entity from database
   * @param isOptionChecked boolean whether checkbox is checked for current option
   * @param userId id of user
   * @return set representing users who have voted for current option
   */
  private Set<String> getUpdatedVotes(Entity optionEntity, boolean isChecked, String userId) {
    /*
     * Using ArrayList here because datastore will only return type ArrayList.
     * Casting it to a HashSet will still have O(n) time complexity, so ArrayLists
     * seem to be the best option in this scenario.
     */
    List<String> votes =
        (optionEntity.getProperty("votes") == null)
            ? new ArrayList<>()
            : (ArrayList<String>) optionEntity.getProperty("votes");
    Set<String> votesSet;
    if (votes == null) {
      votesSet = new HashSet<String>();
    } else {
      votesSet = new HashSet<String>(votes);
    }
    /*
     * If checkbox is unchecked and list of votes contains user, remove user id from
     * list of votes for current option
     */
    if (!isChecked && votesSet.contains(userId)) {
      votesSet.remove(userId);
    } else if (isChecked && !votesSet.contains(userId)) {
      /*
       * If checkbox is checked and list of votes does not contain user, add user id
       * to list
       */
      votesSet.add(userId);
    }
    return votesSet;
  }
}

@@ -1091,24 +1091,97 @@
 checked%22));%0A
+    // type representing whether checkbox is for Option or for Challenge%0A
     String t
@@ -1401,31 +1401,25 @@
 %0A    Entity 
-optionE
+e
 ntity = this
@@ -1515,23 +1515,17 @@
 edVotes(
-optionE
+e
 ntity, i
@@ -1572,23 +1572,17 @@
 ore%0A    
-optionE
+e
 ntity.se
@@ -1629,23 +1629,17 @@
 ore.put(
-optionE
+e
 ntity);%0A
@@ -2387,30 +2387,32 @@
  of current 
-option
+checkbox
 %0A   * @throw
@@ -2732,23 +2732,16 @@
 ves the 
-option 
 entity f
@@ -2832,17 +2832,8 @@
 ram 
-optionId 
 id o
@@ -2834,38 +2834,40 @@
 m id of current 
-option
+checkbox
 %0A   * @param dat
@@ -2917,16 +2917,9 @@
 urn 
-Option e
+E
 ntit
@@ -3374,25 +3374,39 @@
 tes the 
-votes for
+people who have checked
  a parti
@@ -3415,19 +3415,16 @@
 lar 
-poll option
+checkbox
 . Ge
@@ -3507,37 +3507,34 @@
 ave 
-voted for a particular option
+checked a certain checkbox
 , wh
@@ -3556,20 +3556,17 @@
 n as
+ an
 %0A   * 
-option
 Enti
@@ -3653,17 +3653,17 @@
 cked
+ and
 %0A   *
- and
  whe
@@ -3771,27 +3771,14 @@
 ram 
-optionEntity option
+entity
  ent
@@ -3809,22 +3809,16 @@
 param is
-Option
 Checked 
@@ -3852,35 +3852,16 @@
  checked
- for current option
 %0A   * @p
@@ -3932,17 +3932,15 @@
 ave 
-voted for
+checked
  cur
@@ -3940,30 +3940,32 @@
 ked current 
-option
+checkbox
 %0A   */%0A  pri
@@ -4004,23 +4004,17 @@
 (Entity 
-optionE
+e
 ntity, b
@@ -4311,23 +4311,17 @@
        (
-optionE
+e
 ntity.ge
@@ -4418,15 +4418,9 @@
 g%3E) 
-optionE
+e
 ntit
@@ -4709,32 +4709,49 @@
  of 
-votes for current option
+people who have checked current checkbox.
 %0A   

fix commenting