package com.google.sps.servlets;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceConfig;
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.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.CompositeFilter;
import com.google.appengine.api.datastore.Query.CompositeFilterOperator;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.FilterPredicate;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.datastore.TransactionOptions;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.FirebaseToken;
import com.google.sps.firebase.FirebaseAppManager;
import java.io.IOException;
import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.List;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/enterqueue")
public final class EnterQueue extends HttpServlet {
  private FirebaseAuth authInstance;
  private DatastoreService datastore;
  private Clock clock;

  @Override
  public void init(ServletConfig config) throws ServletException {
    try {
      authInstance = FirebaseAuth.getInstance(FirebaseAppManager.getApp());
      clock = Clock.systemUTC();
    } catch (IOException e) {
      throw new ServletException(e);
    }
  }

  @Override
  public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
    // Get the input from the form.

    datastore = DatastoreServiceFactory.getDatastoreService();
    System.setProperty(
        DatastoreServiceConfig.DATASTORE_EMPTY_LIST_SUPPORT, Boolean.TRUE.toString());

    try {
      String classCode = request.getParameter("classCode").trim();

      String idToken = request.getParameter("idToken");
      FirebaseToken decodedToken = authInstance.verifyIdToken(idToken);
      String userID = decodedToken.getUid();

      if (request.getParameter("enterTA") == null) {
        int retries = 10;
        while (true) {
          TransactionOptions options = TransactionOptions.Builder.withXG(true);
          Transaction txn = datastore.beginTransaction(options);
          try {
            Key classKey = KeyFactory.stringToKey(classCode);
            Entity classEntity = datastore.get(txn, classKey);

            // Get date
            LocalDate localDate = LocalDate.now(clock);
            ZoneId defaultZoneId = ZoneId.systemDefault();
            Date currDate = Date.from(localDate.atStartOfDay(defaultZoneId).toInstant());

            // Query visit entity for particular day
            Filter classVisitFilter =
                new FilterPredicate("classKey", FilterOperator.EQUAL, classKey);
            Filter dateVisitFilter = new FilterPredicate("date", FilterOperator.EQUAL, currDate);
            CompositeFilter visitFilter =
                CompositeFilterOperator.and(dateVisitFilter, classVisitFilter);
            Query query = new Query("Visit").setFilter(visitFilter);

            // Get visit entity for particular day
            Entity visitEntity;
            if (datastore.prepare(query).countEntities() == 0) {
              visitEntity = new Entity("Visit");
              visitEntity.setProperty("classKey", classKey);
              visitEntity.setProperty("date", currDate);
              visitEntity.setProperty("numVisits", (long) 0);
            } else {
              visitEntity = datastore.prepare(query).asSingleEntity();
            }

            long numVisits = (long) visitEntity.getProperty("numVisits");
            ArrayList<String> updatedQueue = (ArrayList) classEntity.getProperty("studentQueue");

            //Update studentQueue and numVisit properties
            if (!updatedQueue.contains(userID)) {
              updatedQueue.add(userID);
              numVisits++;
            }

            visitEntity.setProperty("numVisits", numVisits);
            datastore.put(txn, visitEntity);

            classEntity.setProperty("studentQueue", updatedQueue);
            datastore.put(txn, classEntity);

            txn.commit();
            break;
          } catch (ConcurrentModificationException e) {
            if (retries == 0) {
              throw e;
            }
            // Allow retry to occur
            --retries;
          } finally {
            if (txn.isActive()) {
              txn.rollback();
            }
          }
        }
        response.sendRedirect("/queue/student.html?classCode=" + classCode);
      } else {
        Key classKey = KeyFactory.stringToKey(classCode);
        Entity classEntity = datastore.get(classKey);

        List<String> taList = (List<String>) classEntity.getProperty("taList");

        if (taList.contains(userID)) {
          response.sendRedirect("/queue/ta.html?classCode=" + classCode);
        } else {
          response.sendError(HttpServletResponse.SC_FORBIDDEN);
        }
      }

    } catch (EntityNotFoundException e) {
      response.sendError(HttpServletResponse.SC_NOT_FOUND);
    } catch (IllegalArgumentException e) {
      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
    } catch (FirebaseAuthException e) {
      response.sendError(HttpServletResponse.SC_FORBIDDEN);
    }
  }
}

@@ -430,32 +430,89 @@
 ore.KeyFactory;%0A
+import com.google.appengine.api.datastore.PreparedQuery;%0A
 import com.googl
@@ -3717,16 +3717,24 @@
         
+Prepared
 Query qu
@@ -3735,24 +3735,42 @@
 ery query = 
+datastore.prepare(
 new Query(%22V
@@ -3798,16 +3798,17 @@
 tFilter)
+)
 ;%0A%0A     
@@ -3904,33 +3904,14 @@
  if 
-(datastore.prepare
 (query
-)
 .cou
@@ -4213,32 +4213,13 @@
 y = 
-datastore.prepare(
 query
-)
 .asS
@@ -4417,24 +4417,24 @@
 ntQueue%22);%0A%0A
-
             
@@ -4435,16 +4435,17 @@
       //
+ 
 Update s

Modify to prepare queue only once