text:
package repositories.application

import com.github.nscala_time.time.OrderingImplicits.DateTimeOrdering
import config.MicroserviceAppConfig
import factories.DateTimeFactory
import model.ApplicationRoute.ApplicationRoute
import model.ApplicationStatus._
import model.Exceptions._
import model.OnlineTestCommands.OnlineTestApplication
import model.ProgressStatuses.{EventProgressStatuses, PREVIEW}
import model.command._
import model.exchange.{CandidateEligibleForEvent, CandidatesEligibleForEventResponse}
import model.persisted._
import model.persisted.eventschedules.EventType
import model.persisted.eventschedules.EventType.EventType
import model.persisted.fsb.ScoresAndFeedback
import model.{ApplicationStatus, _}
import org.joda.time.{DateTime, LocalDate}
import org.mongodb.scala.MongoCollection
import org.mongodb.scala.bson.collection.immutable.Document
import org.mongodb.scala.bson.{BsonArray, BsonDocument, BsonRegularExpression}
import org.mongodb.scala.model.Indexes.ascending
import org.mongodb.scala.model.{IndexModel, IndexOptions, Projections}
import org.mongodb.scala.model.Sorts.{ascending => sortAsc}
import repositories._
import scheduler.fixer.FixBatch
import scheduler.fixer.RequiredFixes.{AddMissingPhase2ResultReceived, PassToPhase1TestPassed, PassToPhase2, ResetPhase1TestInvitedSubmitted}
import uk.gov.hmrc.mongo.MongoComponent
import uk.gov.hmrc.mongo.play.json.{Codecs, CollectionFactory, PlayMongoRepository}

import java.util.UUID
import java.util.regex.Pattern
import javax.inject.{Inject, Singleton}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try

// TODO FAST STREAM
// This is far too large an interface - we should look at splitting up based on
// functional concerns.

// scalastyle:off number.of.methods
trait GeneralApplicationRepository {
  def create(userId: String, frameworkId: String, applicationRoute: ApplicationRoute,
             sdipDiversityOpt: Option[Boolean] = None): Future[ApplicationResponse]
  def find(applicationId: String): Future[Option[Candidate]]
  def findAllFileInfo: Future[Seq[CandidateFileInfo]]
  def find(applicationIds: Seq[String]): Future[List[Candidate]]
  def findProgress(applicationId: String): Future[ProgressResponse]
  def findStatus(applicationId: String): Future[ApplicationStatusDetails]
  def findByUserId(userId: String, frameworkId: String): Future[ApplicationResponse]
  def findCandidateByUserId(userId: String): Future[Option[Candidate]]
  def findByCriteria(firstOrPreferredName: Option[String], lastName: Option[String],
                     dateOfBirth: Option[LocalDate], userIds: List[String] = List.empty): Future[Seq[Candidate]]
  def submit(applicationId: String): Future[Unit]
  def withdraw(applicationId: String, reason: WithdrawApplication): Future[Unit]
  def withdrawScheme(applicationId: String, schemeWithdraw: WithdrawScheme, schemeStatus: Seq[SchemeEvaluationResult]): Future[Unit]
  def preview(applicationId: String): Future[Unit]
  def updateQuestionnaireStatus(applicationId: String, sectionKey: String): Future[Unit]
  def confirmAdjustments(applicationId: String, data: Adjustments): Future[Unit]
  def findAdjustments(applicationId: String): Future[Option[Adjustments]]
  def updateAdjustmentsComment(applicationId: String, adjustmentsComment: AdjustmentsComment): Future[Unit]
  def findAdjustmentsComment(applicationId: String): Future[AdjustmentsComment]
  def removeAdjustmentsComment(applicationId: String): Future[Unit]
  def gisByApplication(applicationId: String): Future[Boolean]
  def updateStatus(applicationId: String, applicationStatus: ApplicationStatus): Future[Unit]
  def updateApplicationStatusOnly(applicationId: String, applicationStatus: ApplicationStatus): Future[Unit]
  def updateSubmissionDeadline(applicationId: String, newDeadline: DateTime): Future[Unit]
  def getOnlineTestApplication(appId: String): Future[Option[OnlineTestApplication]]
  def addProgressStatusAndUpdateAppStatus(applicationId: String, progressStatus: ProgressStatuses.ProgressStatus): Future[Unit]
  def removeProgressStatuses(applicationId: String, progressStatuses: List[ProgressStatuses.ProgressStatus]): Future[Unit]
  def findTestForNotification(notificationType: NotificationTestType): Future[Option[TestResultNotification]]
  def findTestForSdipFsNotification(notificationType: NotificationTestTypeSdipFs): Future[Option[TestResultSdipFsNotification]]
  def getApplicationsToFix(issue: FixBatch): Future[Seq[Candidate]]
  def fix(candidate: Candidate, issue: FixBatch): Future[Option[Candidate]]
  def fixDataByRemovingETray(appId: String): Future[Unit]
  def fixDataByRemovingVideoInterviewFailed(appId: String): Future[Unit]
  def fixDataByRemovingProgressStatus(appId: String, progressStatus: String): Future[Unit]
  def updateApplicationRoute(appId: String, appRoute: ApplicationRoute, newAppRoute: ApplicationRoute): Future[Unit]
  def archive(appId: String, originalUserId: String, userIdToArchiveWith: String,
              frameworkId: String, appRoute: ApplicationRoute): Future[Unit]
  def findCandidatesEligibleForEventAllocation(locations: List[String], eventType: EventType,
                                               schemeId: Option[SchemeId]): Future[CandidatesEligibleForEventResponse]
  def resetApplicationAllocationStatus(applicationId: String, eventType: EventType): Future[Unit]
  def setFailedToAttendAssessmentStatus(applicationId: String, eventType: EventType): Future[Unit]
  def findAllocatedApplications(applicationIds: List[String]): Future[CandidatesEligibleForEventResponse]
  def getCurrentSchemeStatus(applicationId: String): Future[Seq[SchemeEvaluationResult]]
  def findSdipFaststreamInvitedToVideoInterview: Future[Seq[Candidate]]
  def findSdipFaststreamExpiredPhase2InvitedToSift: Future[Seq[Candidate]]
  def findSdipFaststreamExpiredPhase3InvitedToSift: Future[Seq[Candidate]]
  def getApplicationRoute(applicationId: String): Future[ApplicationRoute]
  def getLatestProgressStatuses: Future[Seq[String]]
  def countByStatus(applicationStatus: ApplicationStatus): Future[Long]
  def getProgressStatusTimestamps(applicationId: String): Future[List[(String, DateTime)]]

  // Implemented by Hmrc ReactiveRepository class - don't use until it gets fixed. Use countLong instead
//  @deprecated("At runtime throws a JsResultException: errmsg=readConcern.level must be either 'local', 'majority' or 'linearizable'", "")
//  def count(implicit ec: scala.concurrent.ExecutionContext) : Future[Int] //TODO: fix

  // Implemented in ReactiveRepositoryHelpers
  def countLong(implicit ec: scala.concurrent.ExecutionContext) : Future[Long]
  def updateCurrentSchemeStatus(applicationId: String, results: Seq[SchemeEvaluationResult]): Future[Unit]
  def removeCurrentSchemeStatus(applicationId: String): Future[Unit]
  def removeWithdrawReason(applicationId: String): Future[Unit]
  def findEligibleForJobOfferCandidatesWithFsbStatus: Future[Seq[String]]
  def listCollections: Future[Seq[String]]
  def removeCollection(name: String): Future[Either[Exception, Unit]]
  def removeCandidate(applicationId: String): Future[Unit]
  def getApplicationStatusForCandidates(applicationIds: Seq[String]): Future[Seq[(String, ApplicationStatus)]]
}

// scalastyle:off number.of.methods
// scalastyle:off file.size.limit
@Singleton
class GeneralApplicationMongoRepository @Inject() (val dateTimeFactory: DateTimeFactory,
                                                   appConfig: MicroserviceAppConfig,
                                                   mongo: MongoComponent
                                                  )
  extends PlayMongoRepository[CreateApplicationRequest](
    collectionName = CollectionNames.APPLICATION,
    mongoComponent = mongo,
    domainFormat = CreateApplicationRequest.createApplicationRequestFormat,
    indexes = Seq(
      IndexModel(ascending("applicationId", "userId"), IndexOptions().unique(true)),
      IndexModel(ascending("userId", "frameworkId"), IndexOptions().unique(true)),
      IndexModel(ascending("applicationStatus"), IndexOptions().unique(false)),
      IndexModel(ascending("assistance-details.needsSupportForOnlineAssessment"), IndexOptions().unique(false)),
      IndexModel(ascending("assistance-details.needsSupportAtVenue"), IndexOptions().unique(false)),
      IndexModel(ascending("assistance-details.guaranteedInterview"), IndexOptions().unique(false))
    )
  ) with GeneralApplicationRepository with RandomSelection with CommonBSONDocuments
    with GeneralApplicationRepoBSONReader with ReactiveRepositoryHelpers with CurrentSchemeStatusHelper {

  // Additional collections configured to work with the appropriate domainFormat and automatically register the
  // codec to work with BSON serialization
  val applicationResponseCollection: MongoCollection[ApplicationResponse] = {
    CollectionFactory.collection(
      collectionName = CollectionNames.APPLICATION,
      db = mongo.database,
      domainFormat = ApplicationResponse.mongoFormat
    )
  }

  // Use this collection when using hand written bson documents
  val applicationCollection: MongoCollection[Document] = mongo.database.getCollection(CollectionNames.APPLICATION)

  override def getApplicationStatusForCandidates(applicationIds: Seq[String]): Future[Seq[(String, ApplicationStatus)]] = {
    val query = Document("applicationId" -> Document("$in" -> applicationIds))
    val projection = Projections.include("applicationId", "applicationStatus")

    collection.find[Document](query).projection(projection).toFuture()
      .map { docList =>
        docList.map { doc =>
          val applicationId = doc.get("applicationId").get.asString().getValue
          val applicationStatus = Codecs.fromBson[ApplicationStatus](doc.get("applicationStatus").get)
          applicationId -> applicationStatus
        }
      }
  }

  override def create(userId: String, frameworkId: String, route: ApplicationRoute,
                      sdipDiversityOpt: Option[Boolean] = None): Future[ApplicationResponse] = {
    val applicationId = UUID.randomUUID().toString
    val testAccountId = UUID.randomUUID().toString

    // Only try and store the sdipDiversity value if we have a value otherwise null will be persisted
    val sdipDiversityDoc = sdipDiversityOpt.map { sdipDiversity => Document("sdipDiversity" -> sdipDiversity) }
      .getOrElse(Document.empty)

    val applicationBSON = Document(
      "applicationId" -> applicationId,
      "userId" -> userId,
      "testAccountId" -> testAccountId,
      "frameworkId" -> frameworkId,
      "applicationStatus" -> CREATED.toBson,
      "applicationRoute" -> route.toBson
    ) ++ sdipDiversityDoc

    applicationCollection.insertOne(applicationBSON).toFuture() flatMap { _ =>
      findProgress(applicationId).map { p =>
        ApplicationResponse(
          applicationId, CREATED, route, userId, testAccountId, p, civilServiceExperienceDetails = None, overriddenSubmissionDeadline = None
        )
      }
    }
  }

  def findAllFileInfo: Future[Seq[CandidateFileInfo]] = {
    val query = Document("testGroups.FSAC.tests.analysisExercise" -> Document("$exists" -> true))
    val projection = Projections.include("applicationId", "testGroups.FSAC.tests.analysisExercise")
    collection.find[Document](query).projection(projection).toFuture().map { docs =>
      docs.map { doc =>
        val testGroups = doc.get("testGroups")
        val fsac = testGroups.map(_.asDocument().get("FSAC"))
        val tests = fsac.map(_.asDocument().get("tests"))
        val analysisExercise = tests.map(_.asDocument().get("analysisExercise"))

        CandidateFileInfo(
          doc.get("applicationId").get.asString().getValue,
          analysisExercise.map(_.asDocument().get("fileId").asString().getValue).get
        )
      }
    }
  }

  def find(applicationId: String): Future[Option[Candidate]] = {
    val query = Document("applicationId" -> applicationId)
    applicationCollection.find[BsonDocument](query).headOption().map {
      _.map { doc =>
        Codecs.fromBson[Candidate](doc)
      }
    }
  }

  override def find(applicationIds: Seq[String]): Future[List[Candidate]] = {
    val query = Document("applicationId" -> Document("$in" -> applicationIds))
    collection.find[BsonDocument](query).toFuture().map { _.map { doc =>
      Codecs.fromBson[Candidate](doc)
    }.toList }
  }

  override def findProgress(applicationId: String): Future[ProgressResponse] = {
    val query = Document("applicationId" -> applicationId)
    val projection = Projections.include("progress-status")

    collection.find[Document](query).projection(projection).headOption() map {
      case Some(document) => toProgressResponse(applicationId)(document)
      case None => throw ApplicationNotFound(s"No application found for $applicationId")
    }
  }

  override def getCurrentSchemeStatus(applicationId: String): Future[Seq[SchemeEvaluationResult]] = {
    val projection = Projections.include("currentSchemeStatus")
    collection.find[Document](Document("applicationId" -> applicationId)).projection(projection).headOption().map {
      _.map { doc =>
        doc.get("currentSchemeStatus").map { bsonValue =>
          Codecs.fromBson[List[SchemeEvaluationResult]](bsonValue)
        }.getOrElse(Nil)
      }.getOrElse(Nil)
    }
  }

  def findStatus(applicationId: String): Future[ApplicationStatusDetails] = {
    val query = Document("applicationId" -> applicationId)
    val projection = Projections.include(
      "applicationStatus",
      "progress-status-timestamp",
      "progress-status-dates",
      "applicationRoute",
      "submissionDeadline",
    )

    import uk.gov.hmrc.mongo.play.json.formats.MongoJodaFormats.Implicits._

    import scala.collection.JavaConverters._

    def progressStatusDateFallback(applicationStatus: ApplicationStatus, document: Document) = {
      val docOpt = document.get("progress-status-dates").map( _.asDocument() )
      docOpt.flatMap { doc =>
        Try(Codecs.fromBson[LocalDate](doc.get(applicationStatus.toLowerCase))).toOption.map( _.toDateTimeAtStartOfDay )
      }
    }

    collection.find[Document](query).projection(projection).headOption() map {
      case Some(doc) =>
        val applicationStatus = Codecs.fromBson[ApplicationStatus](doc.get("applicationStatus").get)
        val applicationRoute = Codecs.fromBson[ApplicationRoute](doc.get("applicationRoute").getOrElse(ApplicationRoute.Faststream.toBson))
        val progressStatusTimeStampDoc = doc.get("progress-status-timestamp").map(_.asDocument())

        val latestProgressStatus = progressStatusTimeStampDoc.flatMap { timestamps =>
          val convertedTimestamps = timestamps.entrySet().asScala.toSet
          val relevantProgressStatuses = convertedTimestamps.filter( _.getKey.startsWith(applicationStatus) )
          val latestRelevantProgressStatus = relevantProgressStatuses.maxBy(element => Codecs.fromBson[DateTime](timestamps.get(element.getKey)))
          Try(ProgressStatuses.nameToProgressStatus(latestRelevantProgressStatus.getKey)).toOption
        }

        val progressStatusTimeStamp = progressStatusTimeStampDoc.flatMap { timestamps =>
          val convertedTimestamps = timestamps.entrySet().asScala.toSet
          val relevantProgressStatuses = convertedTimestamps.filter( _.getKey.startsWith(applicationStatus) )
          val latestRelevantProgressStatus = relevantProgressStatuses.maxBy(element => Codecs.fromBson[DateTime](timestamps.get(element.getKey)))
          Try(Codecs.fromBson[DateTime](timestamps.get(latestRelevantProgressStatus.getKey))).toOption
        }
          .orElse(
            progressStatusDateFallback(applicationStatus, doc)
          )
        val submissionDeadline = doc.get("submissionDeadline").map( sd => Codecs.fromBson[DateTime](sd) )
        ApplicationStatusDetails(applicationStatus, applicationRoute, latestProgressStatus, progressStatusTimeStamp, submissionDeadline)

      case None => throw ApplicationNotFound(applicationId)
    }
  }

  def findByUserId(userId: String, frameworkId: String): Future[ApplicationResponse] = {
    val query = Document("userId" -> userId, "frameworkId" -> frameworkId)

    collection.find[Document](query).headOption() flatMap {
      case Some(doc) =>
        val applicationId = doc.get("applicationId").get.asString().getValue
        val testAccountId = doc.get("testAccountId").get.asString().getValue
        val applicationStatus = Codecs.fromBson[ApplicationStatus](doc.get("applicationStatus").get)
        val applicationRoute = Codecs.fromBson[ApplicationRoute](doc.get("applicationRoute").getOrElse(ApplicationRoute.Faststream.toBson))
        val civilServiceExperienceDetails = doc.get("civil-service-experience-details").map(_.asDocument()).map { doc =>
          Codecs.fromBson[CivilServiceExperienceDetails](doc)
        }

        import uk.gov.hmrc.mongo.play.json.formats.MongoJodaFormats.Implicits._
        val submissionDeadline = doc.get("submissionDeadline").map { bsonValue =>
          Codecs.fromBson[DateTime](bsonValue.asDateTime())
        }

        findProgress(applicationId).map { progress =>
          ApplicationResponse(
            applicationId, applicationStatus, applicationRoute, userId, testAccountId,
            progress, civilServiceExperienceDetails, submissionDeadline
          )
        }
      case None => throw ApplicationNotFound(userId)
    }
  }

  def findCandidateByUserId(userId: String): Future[Option[Candidate]] = {
    val query = Document("userId" -> userId)
    collection.find[BsonDocument](query).headOption().map( _.map( bson => Candidate.fromBson(bson) ))
  }

  override def findByCriteria(firstOrPreferredNameOpt: Option[String],
                              lastNameOpt: Option[String],
                              dateOfBirthOpt: Option[LocalDate],
                              filterByUserIds: List[String]
                    ): Future[Seq[Candidate]] = {

    def matchIfSome(value: Option[String]) = value.map(v => BsonRegularExpression("^" + Pattern.quote(v) + "$", "i"))

    // If the search criteria is None then we specifically need to not include it in the filter otherwise the driver processes the None
    // value as null eg. "filter": {"personal-details.lastName": null, "personal-details.dateOfBirth": null... and no data is fetched
    val lastNameBson = matchIfSome(lastNameOpt).map( v => Document("personal-details.lastName" -> v) ).getOrElse(Document.empty)
    val dobBson = dateOfBirthOpt.map( v => Document("personal-details.dateOfBirth" -> v.toString) ).getOrElse(Document.empty)
    val firstNameBson = matchIfSome(firstOrPreferredNameOpt).map( v => Document("personal-details.firstName" -> v) ).getOrElse(Document.empty)
    val preferredNameBson = matchIfSome(firstOrPreferredNameOpt).map( v => Document("personal-details.preferredName" -> v) )
      .getOrElse(Document.empty)

    val innerQuery =
      Document("$or" -> BsonArray(
        firstNameBson,
        preferredNameBson
      )) ++
      Document("$and" -> BsonArray(
        lastNameBson,
        dobBson
      ))

    val query = if (filterByUserIds.isEmpty) {
      innerQuery
    } else {
      Document("userId" -> Document("$in" -> filterByUserIds)) ++ innerQuery
    }

    val projection = Projections.include("userId", "applicationId", "applicationRoute", "applicationStatus", "personal-details")

    applicationCollection.find[BsonDocument](query).projection(projection).toFuture().map { _.map {
      doc => Candidate.fromBson(doc)
    }}
  }

  override def findSdipFaststreamInvitedToVideoInterview: Future[Seq[Candidate]] = {
    val query = Document(
      "applicationRoute" -> ApplicationRoute.SdipFaststream.toBson,
      s"progress-status.${ProgressStatuses.PHASE3_TESTS_INVITED}" -> Document("$exists" -> true)
    )

    val projection = Projections.include("userId", "applicationId", "applicationRoute",
      "applicationStatus", "personal-details")

    applicationCollection.find[BsonDocument](query).projection(projection).toFuture().map { _.map {
      doc => Candidate.fromBson(doc)
    }}
  }

  override def findSdipFaststreamExpiredPhase2InvitedToSift: Future[Seq[Candidate]] = {
    val query = Document("$and" -> BsonArray(
      Document("applicationRoute" -> ApplicationRoute.SdipFaststream.toBson),
      Document("applicationStatus" -> ApplicationStatus.SIFT.toBson),
      Document(s"progress-status.${ProgressStatuses.PHASE2_TESTS_EXPIRED}" -> Document("$exists" -> true)),
      Document(s"testGroups.PHASE1.evaluation.result" -> Document("$elemMatch" ->
        Document("schemeId" -> "Sdip", "result" -> EvaluationResults.Green.toString)
      ))
    ))

    val projection = Projections.include("userId", "applicationId", "applicationRoute",
      "applicationStatus", "personal-details")

    applicationCollection.find[BsonDocument](query).projection(projection).toFuture().map { _.map {
      doc => Candidate.fromBson(doc)
    }}
  }

  override def findSdipFaststreamExpiredPhase3InvitedToSift: Future[Seq[Candidate]] = {
    val query = Document("$and" -> BsonArray(
      Document("applicationRoute" -> ApplicationRoute.SdipFaststream.toBson),
      Document("applicationStatus" -> ApplicationStatus.SIFT.toBson),
      Document(s"progress-status.${ProgressStatuses.PHASE3_TESTS_EXPIRED}" -> Document("$exists" -> true)),
      Document(s"testGroups.PHASE2.evaluation.result" -> Document("$elemMatch" ->
        Document("schemeId" -> "Sdip", "result" -> EvaluationResults.Green.toString)
      ))
    ))

    val projection = Projections.include("userId", "applicationId", "applicationRoute",
      "applicationStatus", "personal-details")

    applicationCollection.find[BsonDocument](query).projection(projection).toFuture().map { _.map {
      doc => Candidate.fromBson(doc)
    }}
  }

  override def submit(applicationId: String): Future[Unit] = {
    val guard = progressStatusGuardBSON(PREVIEW)
    val query = Document("applicationId" -> applicationId) ++ guard

    val updateBSON = Document("$set" -> applicationStatusBSON(SUBMITTED))

    val validator = singleUpdateValidator(applicationId, actionDesc = "submitting",
      new IllegalStateException(s"Already submitted $applicationId"))

    collection.updateOne(query, updateBSON).toFuture() map validator
  }

  override def withdraw(applicationId: String, reason: WithdrawApplication): Future[Unit] = {
    val query = Document("applicationId" -> applicationId)
    val applicationBSON = Document("$set" -> (Document("withdraw" -> reason.toBson) ++ applicationStatusBSON(WITHDRAWN)))

    val validator = singleUpdateValidator(applicationId, actionDesc = "withdrawing application")
    collection.updateOne(query, applicationBSON).toFuture() map validator
  }

  override def removeWithdrawReason(applicationId: String): Future[Unit] = {
    val query = Document("applicationId" -> applicationId)
    val update = Document("$unset" -> Document("withdraw" -> ""))

    val validator = singleUpdateValidator(applicationId, actionDesc = "removing withdrawal reason")

    collection.updateOne(query, update).toFuture() map validator
  }

  override def withdrawScheme(applicationId: String, withdrawScheme: WithdrawScheme, schemeStatus: Seq[SchemeEvaluationResult]): Future[Unit] = {
    // Note that the info about who performed the withdraw operation is not persisted even though it is contained
    // in the WithdrawScheme class
    val update = Document("$set" -> (Document(
      s"withdraw.schemes.${withdrawScheme.schemeId}" -> withdrawScheme.reason
    ) ++ (currentSchemeStatusBSON(schemeStatus))))

    val predicate = Document(
      "applicationId" -> applicationId
    )

    collection.updateOne(predicate, update).toFuture().map(_ => ())
  }

  override def updateQuestionnaireStatus(applicationId: String, sectionKey: String): Future[Unit] = {
    val query = Document("applicationId" -> applicationId)
    val progressStatusBSON = Document("$set" -> Document(
      s"progress-status.questionnaire.$sectionKey" -> true
    ))

    val validator = singleUpdateValidator(applicationId, actionDesc = "update questionnaire status")

    collection.updateOne(query, progressStatusBSON).toFuture() map validator
  }

  override def preview(applicationId: String): Future[Unit] = {
    val query = Document("applicationId" -> applicationId)
    val progressStatusBSON = Document("$set" -> Document(
      "progress-status.preview" -> true
    ))

    val validator = singleUpdateValidator(applicationId, actionDesc = "preview",
      CannotUpdatePreview(s"preview $applicationId"))

    collection.updateOne(query, progressStatusBSON).toFuture() map validator
  }

  override def findTestForNotification(notificationType: NotificationTestType): Future[Option[TestResultNotification]] = {
    val query = Try{ notificationType match {
      case s: SuccessTestType if s.applicationRoutes.isEmpty =>
        Document("$and" -> BsonArray(
          Document("applicationStatus" -> s.appStatus.toBson),
          Document(s"progress-status.${s.notificationProgress}" -> Document("$ne" -> true))
        ))
      case s: SuccessTestType if s.applicationRoutes.nonEmpty =>
        Document("$and" -> BsonArray(
          Document("applicationStatus" -> s.appStatus.toBson),
          Document(s"progress-status.${s.notificationProgress}" -> Document("$ne" -> true)),
          Document("applicationRoute" -> Document("$in" -> Codecs.toBson(s.applicationRoutes)))
        ))
      case f: FailedTestType =>
        Document("$and" -> BsonArray(
          Document("applicationStatus" -> f.appStatus.toBson),
          Document(s"progress-status.${f.notificationProgress}" -> Document("$ne" -> true)),
          Document(s"progress-status.${f.receiveStatus}" -> true)
        ))
      case unknown => throw new RuntimeException(s"Unsupported NotificationTestType: $unknown")
    }}

    for {
      query <- Future.fromTry(query)
      result <- selectOneRandom[TestResultNotification](query)( doc => TestResultNotification.fromBson(doc), global )
    } yield result
  }

  override def findTestForSdipFsNotification(notificationType: NotificationTestTypeSdipFs): Future[Option[TestResultSdipFsNotification]] = {
    val query = Document("$and" -> BsonArray(
      Document("applicationRoute" -> notificationType.applicationRoute.toBson),
      Document(s"progress-status.${notificationType.progressStatus}" -> true),
      Document(s"progress-status.${notificationType.notificationProgress}" -> Document("$ne" -> true))
    ))

    selectOneRandom[TestResultSdipFsNotification](query)( doc => TestResultSdipFsNotification.fromBson(doc), global )
  }

  //scalastyle:off method.length
  override def getApplicationsToFix(issue: FixBatch): Future[Seq[Candidate]] = {
    issue.fix match {
      case PassToPhase2 =>
        val query = Document("$and" -> BsonArray(
          Document("applicationStatus" -> ApplicationStatus.PHASE1_TESTS.toBson),
          Document(s"progress-status.${ProgressStatuses.PHASE1_TESTS_PASSED}" -> true),
          Document(s"progress-status.${ProgressStatuses.PHASE2_TESTS_INVITED}" -> true)
        ))

        selectRandom[Candidate](query, issue.batchSize)(doc => Codecs.fromBson[Candidate](doc.toBsonDocument()), global)
      case PassToPhase1TestPassed =>
        val query = Document("$and" -> BsonArray(
          Document("applicationStatus" -> ApplicationStatus.PHASE1_TESTS.toBson),
          Document(s"progress-status.${ProgressStatuses.PHASE1_TESTS_PASSED}" -> true),
          Document(s"progress-status.${ProgressStatuses.PHASE2_TESTS_INVITED}" -> Document("$ne" -> true))
        ))

        selectRandom[Candidate](query, issue.batchSize)(doc => Codecs.fromBson[Candidate](doc.toBsonDocument()), global)
      case ResetPhase1TestInvitedSubmitted =>
        val query = Document("$and" -> BsonArray(
          Document("applicationStatus" -> ApplicationStatus.SUBMITTED.toBson),
          Document(s"progress-status.${ProgressStatuses.PHASE1_TESTS_INVITED}" -> true)
        ))

        selectRandom[Candidate](query, issue.batchSize)(doc => Codecs.fromBson[Candidate](doc.toBsonDocument()), global)
      case AddMissingPhase2ResultReceived =>
        val query = Document("$and" -> BsonArray(
          Document("applicationStatus" -> ApplicationStatus.PHASE2_TESTS.toBson),
          Document(s"progress-status.${ProgressStatuses.PHASE2_TESTS_RESULTS_READY}" -> true),
          Document(s"progress-status.${ProgressStatuses.PHASE2_TESTS_RESULTS_RECEIVED}" -> Document("$ne" -> true)),
          Document(s"testGroups.PHASE2.tests" ->
            Document("$elemMatch" -> Document(
              "usedForResults" -> true, "testResult" -> Document("$exists" -> true)
            ))
          )
        ))

        selectRandom[Candidate](query, issue.batchSize)(doc => Codecs.fromBson[Candidate](doc.toBsonDocument()), global)
    }
  }//scalastyle:on

  override def fix(application: Candidate, issue: FixBatch): Future[Option[Candidate]] = {
    issue.fix match {
      case PassToPhase2 =>
        val query = Document("$and" -> BsonArray(
          Document("applicationId" -> application.applicationId),
          Document("applicationStatus" -> ApplicationStatus.PHASE1_TESTS.toBson),
          Document(s"progress-status.${ProgressStatuses.PHASE1_TESTS_PASSED}" -> true),
          Document(s"progress-status.${ProgressStatuses.PHASE2_TESTS_INVITED}" -> true)
        ))
        val updateOp = Document("$set" -> Document("applicationStatus" -> ApplicationStatus.PHASE2_TESTS.toBson))

        collection.updateOne(query, updateOp).toFuture().flatMap { _ =>
          collection.find[BsonDocument](Document("applicationId" -> application.applicationId)).headOption().map { _.map { doc =>
            Codecs.fromBson[Candidate](doc)
          }}
        }
      case PassToPhase1TestPassed =>
        val query = Document("$and" -> BsonArray(
          Document("applicationId" -> application.applicationId),
          Document("applicationStatus" -> ApplicationStatus.PHASE1_TESTS.toBson),
          Document(s"progress-status.${ProgressStatuses.PHASE1_TESTS_PASSED}" -> true),
          Document(s"progress-status.${ProgressStatuses.PHASE2_TESTS_INVITED}" -> Document("$ne" -> true))
        ))
        val updateOp = Document("$set" -> Document("applicationStatus" -> ApplicationStatus.PHASE1_TESTS_PASSED.toBson))

        collection.updateOne(query, updateOp).toFuture().flatMap { _ =>
          collection.find[BsonDocument](Document("applicationId" -> application.applicationId)).headOption().map { _.map { doc =>
            Codecs.fromBson[Candidate](doc)
          }}
        }
      case ResetPhase1TestInvitedSubmitted =>
        val query = Document("$and" -> BsonArray(
          Document("applicationId" -> application.applicationId),
          Document("applicationStatus" -> ApplicationStatus.SUBMITTED.toBson),
          Document(s"progress-status.${ProgressStatuses.PHASE1_TESTS_INVITED}" -> true)
        ))
        val updateOp = Document("$unset" ->
          Document(
            s"progress-status.${ProgressStatuses.PHASE1_TESTS_INVITED}" -> "",
            s"progress-status-timestamp.${ProgressStatuses.PHASE1_TESTS_INVITED}" -> "",
            "testGroups" -> ""
          )
        )

        collection.updateOne(query, updateOp).toFuture().flatMap { _ =>
          collection.find[BsonDocument](Document("applicationId" -> application.applicationId)).headOption().map { _.map { doc =>
            Codecs.fromBson[Candidate](doc)
          }}
        }
      case AddMissingPhase2ResultReceived =>
        val query = Document("$and" -> BsonArray(
          Document("applicationId" -> application.applicationId),
          Document("applicationStatus" -> ApplicationStatus.PHASE2_TESTS.toBson),
          Document(s"progress-status.${ProgressStatuses.PHASE2_TESTS_RESULTS_READY}" -> true),
          Document(s"progress-status.${ProgressStatuses.PHASE2_TESTS_RESULTS_RECEIVED}" -> Document("$ne" -> true))
        ))
        val updateOp = Document("$set" ->
          Document(
            s"progress-status.${ProgressStatuses.PHASE2_TESTS_RESULTS_RECEIVED}" -> true,
            s"progress-status-timestamp.${ProgressStatuses.PHASE2_TESTS_RESULTS_RECEIVED}" -> dateTimeToBson(DateTime.now())
          ))

        collection.updateOne(query, updateOp).toFuture().flatMap { _ =>
          collection.find[BsonDocument](Document("applicationId" -> application.applicationId)).headOption().map { _.map { doc =>
            Codecs.fromBson[Candidate](doc)
          }}
        }
    }
  }

  override def fixDataByRemovingETray(appId: String): Future[Unit] = {
    import ProgressStatuses._

    val query = Document(
      "applicationId" -> appId,
      "applicationStatus" -> ApplicationStatus.PHASE2_TESTS.toBson
    )

    val updateOp =
      Document(
        "$set" -> Document("applicationStatus" -> ApplicationStatus.PHASE1_TESTS_PASSED.toBson),
        "$unset" -> Document(
          s"progress-status.${PHASE2_TESTS_INVITED.key}" -> "",
          s"progress-status.${PHASE2_TESTS_STARTED.key}" -> "",
          s"progress-status.${PHASE2_TESTS_FIRST_REMINDER.key}" -> "",
          s"progress-status.${PHASE2_TESTS_SECOND_REMINDER.key}" -> "",
          s"progress-status.${PHASE2_TESTS_COMPLETED.key}" -> "",
          s"progress-status.${PHASE2_TESTS_EXPIRED.key}" -> "",
          s"progress-status.${PHASE2_TESTS_RESULTS_RECEIVED.key}" -> "",
          s"progress-status.${PHASE2_TESTS_PASSED.key}" -> "",
          s"progress-status.${PHASE2_TESTS_FAILED.key}" -> "",
          s"progress-status.${PHASE2_TESTS_FAILED_NOTIFIED.key}" -> "",
          s"progress-status-timestamp.${PHASE2_TESTS_INVITED.key}" -> "",
          s"progress-status-timestamp.${PHASE2_TESTS_STARTED.key}" -> "",
          s"progress-status-timestamp.${PHASE2_TESTS_FIRST_REMINDER.key}" -> "",
          s"progress-status-timestamp.${PHASE2_TESTS_SECOND_REMINDER.key}" -> "",
          s"progress-status-timestamp.${PHASE2_TESTS_COMPLETED.key}" -> "",
          s"progress-status-timestamp.${PHASE2_TESTS_EXPIRED.key}" -> "",
          s"progress-status-timestamp.${PHASE2_TESTS_RESULTS_RECEIVED.key}" -> "",
          s"progress-status-timestamp.${PHASE2_TESTS_PASSED.key}" -> "",
          s"progress-status-timestamp.${PHASE2_TESTS_FAILED.key}" -> "",
          s"progress-status-timestamp.${PHASE2_TESTS_FAILED_NOTIFIED.key}" -> "",
          s"testGroups.PHASE2" -> ""
        )
      )

    collection.updateOne(query, updateOp).toFuture().map(_ => ())
  }

  override def fixDataByRemovingVideoInterviewFailed(appId: String): Future[Unit] = {
    import ProgressStatuses._

    val query = Document("$and" ->
      BsonArray(
        Document("applicationId" -> appId),
        Document("applicationStatus" -> ApplicationStatus.PHASE3_TESTS_FAILED.toBson)
      ))

    val updateOp = Document(
      "$set" -> Document("applicationStatus" -> ApplicationStatus.PHASE3_TESTS.toBson),
      "$unset" -> Document(
        s"progress-status.${PHASE3_TESTS_FAILED.key}" -> "",
        s"progress-status.${PHASE3_TESTS_FAILED_NOTIFIED.key}" -> "",
        s"progress-status-timestamp.${PHASE3_TESTS_FAILED.key}" -> "",
        s"progress-status-timestamp.${PHASE3_TESTS_FAILED_NOTIFIED.key}" -> "",
        s"testGroups.PHASE3.evaluation" -> ""
      )
    )

    collection.updateOne(query, updateOp).toFuture().map(_ => ())
  }

  def fixDataByRemovingProgressStatus(appId: String, progressStatus: String): Future[Unit] = {
    val query = Document(
      "applicationId" -> appId,
      s"progress-status.$progressStatus" -> true
    )
    val updateOp = Document(
      "$unset" -> Document(
        s"progress-status.$progressStatus" -> "",
        s"progress-status-timestamp.$progressStatus" -> ""
      )
    )

    collection.updateOne(query, updateOp).toFuture().map(_ => ())
  }

  private[application] def isNonSubmittedStatus(progress: ProgressResponse): Boolean = {
    val isNotSubmitted = !progress.submitted
    val isNotWithdrawn = !progress.withdrawn
    isNotWithdrawn && isNotSubmitted
  }

  override def confirmAdjustments(applicationId: String, data: Adjustments): Future[Unit] = {
    val query = Document("applicationId" -> applicationId)

    val resetExerciseAdjustmentsBSON = Document("$unset" -> Document(
      "assistance-details.etray" -> "",
      "assistance-details.video" -> ""
    ))

    val adjustmentsConfirmationBSON = Document("$set" -> Document(
      "assistance-details.typeOfAdjustments" -> data.adjustments.getOrElse(List.empty[String]),
      "assistance-details.adjustmentsConfirmed" -> true,
      "assistance-details.etray" -> Codecs.toBson(data.etray),
      "assistance-details.video" -> Codecs.toBson(data.video)
    ))

    val resetValidator = singleUpdateValidator(applicationId, actionDesc = "reset adjustments")
    val adjustmentValidator = singleUpdateValidator(applicationId, actionDesc = "update adjustments")

    collection.updateOne(query, resetExerciseAdjustmentsBSON).toFuture().map(resetValidator).flatMap { _ =>
      collection.updateOne(query, adjustmentsConfirmationBSON).toFuture() map adjustmentValidator
    }
  }

  override def findAdjustments(applicationId: String): Future[Option[Adjustments]] = {
    val query = Document("$and" -> BsonArray(
      Document("applicationId" -> applicationId),
      Document("assistance-details" -> Document("$exists" -> true))
    ))
    val projection = Projections.include("assistance-details")

    collection.find[Document](query).projection(projection).headOption().map { docOpt =>
      docOpt.map { document =>
        val rootOpt = document.get("assistance-details")
        val adjustmentList = rootOpt.flatMap( bson => Try(Codecs.fromBson[List[String]](bson.asDocument().get("typeOfAdjustments"))).toOption )
        val adjustmentsConfirmed = rootOpt.flatMap( bson => Try(bson.asDocument.get("adjustmentsConfirmed").asBoolean().getValue).toOption )
        val etray = rootOpt.flatMap( bson => Try(Codecs.fromBson[AdjustmentDetail](bson.asDocument().get("etray")) ).toOption )
        val video = rootOpt.flatMap( bson => Try(Codecs.fromBson[AdjustmentDetail](bson.asDocument().get("video")) ).toOption )
        Adjustments(adjustmentList, adjustmentsConfirmed, etray, video)
      }
    }
  }

  // Note that this should be successful as long as we match an existing document even if that document
  // does not actually have the adjustmentsComment stored within it
  def removeAdjustmentsComment(applicationId: String): Future[Unit] = {
    val query = Document("applicationId" -> applicationId)

    val removeBSON = Document("$unset" -> Document(
      "assistance-details.adjustmentsComment" -> ""
    ))

    val validator = singleUpdateValidator(applicationId,
      actionDesc = "remove adjustments comment",
      error = CannotRemoveAdjustmentsComment(applicationId))

    collection.updateOne(query, removeBSON).toFuture() map validator
  }

  def updateAdjustmentsComment(applicationId: String, adjustmentsComment: AdjustmentsComment): Future[Unit] = {
    val query = Document("applicationId" -> applicationId)

    val updateBSON = Document("$set" -> Document(
      "assistance-details.adjustmentsComment" -> adjustmentsComment.comment
    ))

    val validator = singleUpdateValidator(applicationId,
      actionDesc = "save adjustments comment",
      error = CannotUpdateAdjustmentsComment(applicationId))

    collection.updateOne(query, updateBSON).toFuture() map validator
  }

  def findAdjustmentsComment(applicationId: String): Future[AdjustmentsComment] = {
    val query = Document("applicationId" -> applicationId)
    val projection = Projections.include("assistance-details")

    collection.find[Document](query).projection(projection).headOption().map {
      case Some(document) =>
        val root = document.get("assistance-details").map(_.asDocument())
        root match {
          case Some(doc) =>
            val comment = Try(doc.get("adjustmentsComment").asString().getValue).getOrElse(throw AdjustmentsCommentNotFound(applicationId))
            AdjustmentsComment(comment)
          case None => throw AdjustmentsCommentNotFound(applicationId)
        }
      case None =>
        throw ApplicationNotFound(s"No application found when looking for adjustments comment for $applicationId")
    }
  }

  def gisByApplication(applicationId: String): Future[Boolean] = {
    val query = Document("applicationId" -> applicationId)
    val projection = Projections.include("assistance-details.guaranteedInterview")

    collection.find[Document](query).projection(projection).headOption().map {
      _.exists { doc =>
        val assistanceDetailsRoot = doc.get("assistance-details").map(_.asDocument()).get
        Try(assistanceDetailsRoot.get("guaranteedInterview").asBoolean().getValue).getOrElse(false)
      }
    }
  }

  def updateStatus(applicationId: String, applicationStatus: ApplicationStatus): Future[Unit] = {
    val query = Document("applicationId" -> applicationId)
    val validator = singleUpdateValidator(applicationId, actionDesc = "updating application status")

    collection.updateOne(query, Document("$set" -> applicationStatusBSON(applicationStatus))).toFuture() map validator
  }

  def updateApplicationStatusOnly(applicationId: String, applicationStatus: ApplicationStatus): Future[Unit] = {
    val query = Document("applicationId" -> applicationId)
    val updateOp = Document("$set" -> Document("applicationStatus" -> applicationStatus.toBson))
    val validator = singleUpdateValidator(applicationId, actionDesc = "updating application status")

    collection.updateOne(query, updateOp).toFuture() map validator
  }

  override def updateSubmissionDeadline(applicationId: String, newDeadline: DateTime): Future[Unit] = {
    val query = Document("applicationId" -> applicationId)
    val validator = singleUpdateValidator(applicationId, actionDesc = "updating submission deadline")

    import uk.gov.hmrc.mongo.play.json.formats.MongoJodaFormats.Implicits._
    collection.updateOne(query, Document("$set" -> Document("submissionDeadline" -> Codecs.toBson(newDeadline)))).toFuture() map validator
  }

  override def getOnlineTestApplication(appId: String): Future[Option[OnlineTestApplication]] = {
    val query = Document("applicationId" -> appId)
    collection.find[Document](query).headOption() map {
      _.map(bsonDocToOnlineTestApplication)
    }
  }

  override def addProgressStatusAndUpdateAppStatus(applicationId: String, progressStatus: ProgressStatuses.ProgressStatus): Future[Unit] = {
    val query = Document("applicationId" -> applicationId)
    val validator = singleUpdateValidator(applicationId, actionDesc = "updating progress and app status")

    collection.updateOne(query, Document("$set" ->
      applicationStatusBSON(progressStatus))
    ).toFuture() map validator
  }

  override def removeProgressStatuses(applicationId: String, progressStatuses: List[ProgressStatuses.ProgressStatus]): Future[Unit] = {
    require(progressStatuses.nonEmpty, "Progress statuses to remove cannot be empty")

    val query = Document("applicationId" -> applicationId)

    val statusesToUnset = progressStatuses.map { progressStatus =>
      Map(
        s"progress-status.$progressStatus" -> "",
        s"progress-status-dates.$progressStatus" -> "", // TODO: mongo looks like an out-of-date key!!
        s"progress-status-timestamp.$progressStatus" -> ""
      )
    }

    // Fold the list of maps into one map
    val foldedStatuses = statusesToUnset.foldLeft(Map.empty[String, String])((acc, v) => acc ++ v)
    val update = Document("$unset" -> Document(foldedStatuses))

    val validator = singleUpdateValidator(applicationId, actionDesc = "removing progress statuses")
    collection.updateOne(query, update).toFuture() map validator
  }

  override def updateApplicationRoute(appId: String, appRoute:ApplicationRoute, newAppRoute: ApplicationRoute): Future[Unit] = {
    val query = Document("$and" -> BsonArray(
      Document("applicationId" -> appId),
      applicationRouteCriteria(appRoute)
    ))

    val updateAppRoute = Document("$set" -> Document(
      "applicationRoute" -> newAppRoute.toBson
    ))

    val validator = singleUpdateValidator(appId, actionDesc = "updating application route")
    collection.updateOne(query, updateAppRoute).toFuture() map validator
  }

  override def archive(appId: String, originalUserId: String, userIdToArchiveWith: String,
                       frameworkId: String, appRoute: ApplicationRoute): Future[Unit] = {
    val query = Document("$and" -> BsonArray(
      Document("applicationId" -> appId),
      applicationRouteCriteria(appRoute)
    ))

    val updateWithArchiveUserId = Document("$set" ->
      (Document("originalUserId" -> originalUserId, "userId" -> userIdToArchiveWith)
        ++ applicationStatusBSON(ProgressStatuses.APPLICATION_ARCHIVED))
    )

    val validator = singleUpdateValidator(appId, actionDesc = "archiving application")
    collection.updateOne(query, updateWithArchiveUserId).toFuture() map validator
  }

  override def findAllocatedApplications(applicationIds: List[String]): Future[CandidatesEligibleForEventResponse] = {
    val query = Document("applicationId" -> Document("$in" -> applicationIds))
    val projection = Projections.include(
      "applicationId",
      "personal-details.firstName",
      "personal-details.lastName",
      "assistance-details.needsSupportAtVenue",
      "assistance-details.needsSupportForOnlineAssessment",
      "progress-status-timestamp",
      "fsac-indicator",
      "testGroups.FSB.scoresAndFeedback"
    )

    collection.find[Document](query).projection(projection).toFuture()
      .map { _.map { doc => bsonDocToCandidatesEligibleForEvent(doc) }.toList
      }.flatMap { result =>
      Future.successful(CandidatesEligibleForEventResponse(result, -1))
    }
  }

  private def countDocuments(query: Document) = {
    collection.find(query).toFuture()
      .map( _.size )
  }

  override def findCandidatesEligibleForEventAllocation(locations: List[String],
                                                        eventType: EventType,
                                                        schemeId: Option[SchemeId]
                                                       ): Future[CandidatesEligibleForEventResponse] = {
    logger.info("Finding candidates eligible for event allocation with " +
      s"maxNumberOfCandidates = ${appConfig.eventsConfig.maxNumberOfCandidates}")
    val appStatus = eventType.applicationStatus
    val status = EventProgressStatuses.get(appStatus)
    val awaitingAllocation = status.awaitingAllocation.key
    val confirmedAllocation = status.allocationConfirmed.key
    val unconfirmedAllocation = status.allocationUnconfirmed.key
    val fsacConditions = Document("fsac-indicator.assessmentCentre" -> Document("$in" -> locations))
    val fsbConditions = schemeId.map { s => isFirstResidualPreference(s) }
    val query = Document("$and" -> BsonArray(
      Document("applicationStatus" -> appStatus.toBson),
      if (eventType == EventType.FSAC) fsacConditions else fsbConditions,
      Document(s"progress-status.$awaitingAllocation" -> true),
      Document(s"progress-status.$confirmedAllocation" -> Document("$exists" -> false)),
      Document(s"progress-status.$unconfirmedAllocation" -> Document("$exists" -> false))
    ))

    countDocuments(query).flatMap { count =>
      if (count == 0) {
        Future.successful(CandidatesEligibleForEventResponse(List.empty, 0))
      } else {
        val projection = Projections.include(
          "applicationId",
          "personal-details.firstName",
          "personal-details.lastName",
          "assistance-details.needsSupportAtVenue",
          "assistance-details.needsSupportForOnlineAssessment",
          "progress-status-timestamp",
          "fsac-indicator"
        )

        // Eligible candidates should be sorted based on when they passed PHASE 3
        val sort = sortAsc(s"progress-status-timestamp.${ApplicationStatus.PHASE3_TESTS_PASSED}")

        collection.find[Document](query).projection(projection).sort(sort).limit(appConfig.eventsConfig.maxNumberOfCandidates).toFuture()
          .map { _.map { doc => bsonDocToCandidatesEligibleForEvent(doc) }.toList
          }.flatMap { result =>
          Future.successful(CandidatesEligibleForEventResponse(result, count))
        }
      }
    }
  }

  override def resetApplicationAllocationStatus(applicationId: String, eventType: EventType): Future[Unit] = {
    replaceAllocationStatus(applicationId, EventProgressStatuses.get(eventType.applicationStatus).awaitingAllocation)
  }

  override def setFailedToAttendAssessmentStatus(applicationId: String, eventType: EventType): Future[Unit] = {
    replaceAllocationStatus(applicationId, EventProgressStatuses.get(eventType.applicationStatus).failedToAttend)
  }

  import ProgressStatuses._

  private val progressStatuses = Map(
    ASSESSMENT_CENTRE -> List(
      ASSESSMENT_CENTRE_ALLOCATION_CONFIRMED,
      ASSESSMENT_CENTRE_ALLOCATION_UNCONFIRMED,
      ASSESSMENT_CENTRE_AWAITING_ALLOCATION,
      ASSESSMENT_CENTRE_FAILED_TO_ATTEND),
    FSB -> List(
      FSB_ALLOCATION_CONFIRMED,
      FSB_ALLOCATION_UNCONFIRMED,
      FSB_AWAITING_ALLOCATION,
      FSB_FAILED_TO_ATTEND
    )
  )

  private def replaceAllocationStatus(applicationId: String, newStatus: ProgressStatuses.ProgressStatus) = {
    val query = Document("applicationId" -> applicationId)
    val statusesToRemove = progressStatuses(newStatus.applicationStatus)
      .filter(_ != newStatus).map(p => s"progress-status.${p.key}" -> "")

    val updateQuery = Document(
      "$unset" -> Document(statusesToRemove),
      "$set" -> Document(s"progress-status.${newStatus.key}" -> true)
    )
    collection.updateOne(query, updateQuery).toFuture().map(_ => ())
  }

  private def bsonDocToCandidatesEligibleForEvent(doc: Document) = {
    val applicationId = doc.get("applicationId").get.asString().getValue
    val personalDetailsRoot = doc.get("personal-details").map(_.asDocument()).get
    val firstName = personalDetailsRoot.get("firstName").asString().getValue
    val lastName = personalDetailsRoot.get("lastName").asString().getValue

    val assistanceDetailsRoot = doc.get("assistance-details").map(_.asDocument()).get
    val needsSupportAtVenue = Try(assistanceDetailsRoot.get("needsSupportAtVenue").asBoolean().getValue).getOrElse(false)
    val needsSupportForOnlineTests = Try(assistanceDetailsRoot.get("needsSupportForOnlineAssessment").asBoolean().getValue).getOrElse(false)

    val needsAdjustment = needsSupportAtVenue || needsSupportForOnlineTests

    import uk.gov.hmrc.mongo.play.json.formats.MongoJodaFormats.Implicits._
    val dateReadyOpt = doc.get("progress-status-timestamp").map{ _.asDocument().get(ApplicationStatus.PHASE3_TESTS_PASSED) }.flatMap {
      bson => Try(Codecs.fromBson[DateTime](bson)).toOption
    }

    val fsacIndicator = Codecs.fromBson[model.persisted.FSACIndicator](doc.get("fsac-indicator").get)

    val fsbOpt = Try{
      doc.get("testGroups").map( _.asDocument().get("FSB").asDocument() )
    }.toOption.flatten

    val scoresAndFeedbackOpt = fsbOpt.flatMap( fsb => Try(Codecs.fromBson[ScoresAndFeedback](fsb.get("scoresAndFeedback"))).toOption )

    val fsbScoresAndFeedbackSubmitted = scoresAndFeedbackOpt.isDefined

    CandidateEligibleForEvent(
      applicationId,
      firstName,
      lastName,
      needsAdjustment,
      fsbScoresAndFeedbackSubmitted,
      model.FSACIndicator(fsacIndicator),
      dateReadyOpt.getOrElse(DateTime.now()))
  }

  private def applicationRouteCriteria(appRoute: ApplicationRoute) = appRoute match {
    case ApplicationRoute.Faststream =>
      Document("$or" -> BsonArray(
        Document("applicationRoute" -> appRoute.toBson),
        Document("applicationRoute" -> Document("$exists" -> false))
      ))
    case _ => Document("applicationRoute" -> appRoute.toBson)
  }

  override def getApplicationRoute(applicationId: String): Future[ApplicationRoute] = {
    def error = throw ApplicationNotFound(s"No application found for $applicationId")
    val predicate = Document("applicationId" -> applicationId)
    val projection = Projections.include("applicationRoute")
    collection.find[Document](predicate).projection(projection).headOption().map {
      _.map { doc =>
        Codecs.fromBson[ApplicationRoute](doc.get("applicationRoute").getOrElse(error))
      }.getOrElse(error)
    }
  }

  override def getLatestProgressStatuses: Future[Seq[String]] = {

    import uk.gov.hmrc.mongo.play.json.formats.MongoJodaFormats.Implicits._

    import scala.collection.JavaConverters._

    val query = Document.empty
    val projection = Projections.include("progress-status-timestamp")
    collection.find[Document](query).projection(projection).toFuture.map {
      _.map { doc =>
        doc.get("progress-status-timestamp").map { timestamps =>
          val convertedTimestamps = timestamps.asDocument().entrySet().asScala.toSet
          convertedTimestamps.map { element =>
            element.getKey -> Codecs.fromBson[DateTime](element.getValue)
          }.toList
        }.getOrElse(Nil).sortBy(tup => tup._2).reverse.head._1
      }
    }
  }

  override def countByStatus(applicationStatus: ApplicationStatus): Future[Long] = {
    val query = Document("applicationStatus" -> applicationStatus.toString)
    collection.countDocuments(query).head
  }

  override def getProgressStatusTimestamps(applicationId: String): Future[List[(String, DateTime)]] = {
    val query = Document("applicationId" -> applicationId)
    val projection = Projections.include("progress-status-timestamp")

    import uk.gov.hmrc.mongo.play.json.formats.MongoJodaFormats.Implicits._

    import scala.collection.JavaConverters._

    collection.find[Document](query).projection(projection).headOption.map {
      case Some(doc) =>
        doc.get("progress-status-timestamp").map { timestamps =>
          val convertedTimestamps = timestamps.asDocument().entrySet().asScala.toSet
          convertedTimestamps.map { element =>
            element.getKey -> Codecs.fromBson[DateTime](element.getValue)
          }.toList
        }.getOrElse(Nil)
      case _ => Nil
    }
  }

  override def updateCurrentSchemeStatus(applicationId: String, results: Seq[SchemeEvaluationResult]): Future[Unit] = {
    val query = Document("applicationId" -> applicationId)
    val updateBSON = Document("$set" -> currentSchemeStatusBSON(results))

    val validator = singleUpdateValidator(applicationId, actionDesc = s"Saving currentSchemeStatus for $applicationId")
    collection.updateOne(query, updateBSON).toFuture() map validator
  }

  override def removeCurrentSchemeStatus(applicationId: String): Future[Unit] = {
    val query = Document("applicationId" -> applicationId)
    val update = Document("$unset" -> Document(s"currentSchemeStatus" -> ""))

    val validator = singleUpdateValidator(applicationId, actionDesc = s"removing current scheme status for $applicationId")
    collection.updateOne(query, update).toFuture().map(validator)
  }

  def findEligibleForJobOfferCandidatesWithFsbStatus: Future[Seq[String]] = {
    val query = Document("$and" -> BsonArray(
      Document("applicationStatus" -> Document("$eq" -> FSB.toString)),
      Document(s"progress-status.${ELIGIBLE_FOR_JOB_OFFER.toString}" -> Document("$exists" -> true))
    ))
    val projection = Projections.include("applicationId")

    collection.find[Document](query).projection(projection).toFuture().map { docList =>
      docList.map { doc =>
        doc.get("applicationId").get.asString().getValue
      }
    }
  }

  override def listCollections: Future[Seq[String]] = {
    mongo.database.listCollectionNames.toFuture
  }

  override def removeCollection(name: String): Future[Either[Exception, Unit]] = {
    val collectionExists = for {
      collections <- listCollections
    } yield collections.contains(name)

    collectionExists.flatMap { exists =>
      if (exists) {
        mongo.database.getCollection(name).drop.toFuture.map ( _ => Right(()) )
      } else {
        Future.successful(Left( new Exception(s"Collection not found: $name")))
      }
    }
  }

  override def removeCandidate(applicationId: String): Future[Unit] = {
    val query = Document("applicationId" -> applicationId)
    val validator = singleRemovalValidator(applicationId, actionDesc = s"removing candidate $applicationId")
    collection.deleteOne(query).toFuture().map(validator)
  }
}


text:
import json
from datetime import datetime

from utils import format_value
from web.w import UpdatesHandler

def process(conn, cursor, current, message, data):
    model = message.get('model')
    if model not in ('sensor_ht', 'weather.v1'):
        return False

    save = False
    if message.get('cmd') == 'report':
        if current:
            save = True
        if 'temperature' in data:
            current['temperature'] = data['temperature']
        if 'humidity' in data:
            current['humidity'] = data['humidity']
    else:
        current = data
    if not save:
        return False

    query = "INSERT INTO ht(sid, temperature, humidity, dt) VALUES (%s, %s, %s, %s)"
    cursor.execute(query, (message['sid'], current['temperature'], current['humidity'], datetime.now().isoformat()))
    conn.commit()

    data = {
        'device': 'sensor_ht',
        'sid': message['sid'],
        'temperature': format_value(current['temperature']),
        'humidity': format_value(current['humidity'])
    }
    UpdatesHandler.send_updates(data)

    return True

redpajama_set_name: RedPajamaGithub


text:
Rails.application.routes.draw do
  get  'contact' => 'contact#new'
  post 'contact' => 'contact#create'

  get '/feed' => 'posts#feed', :as => :feed, :format => 'atom'
  get '/ksevelyar' => 'pages#show', :as => 'show', :id => 'ksevelyar'

  namespace :admin do
    resources :comments
    resources :posts
  end

  resources :sessions, only: %i[new create destroy]

  resources :pages do
    resources :images
    resources :attachments
  end

  resources :posts, path: '' do
    resources :comments
    resources :images
    resources :attachments

    get 'page-:page', action: :index, on: :collection
  end

  resources :tags, only: :show do
    get ':id/page-:page', action: :show, on: :collection
  end

  root to: 'posts#index'
end


text:
OkComputer::Engine.routes.draw do
  root to: "ok_computer#show", defaults: {check: "default"}, via: [:get, :options]
  match "/all" => "ok_computer#index", via: [:get, :options]
  match "/:check" => "ok_computer#show", via: [:get, :options]
end


text:
import React from "react";
const moment = require('moment')

const FORMAT = "dddd, MMMM Do YYYY, h:mm:ss a"
export default class FormatedDate extends React.Component {
  render() {
    let format = this.props.format || FORMAT
    return (
      <span>
        {moment(this.props.date).format(format)}
      </span>
    )
  }
}


text:
<?php

/**
 * sfSimpleCMSSlot filter form.
 *
 * @package    ##PROJECT_NAME##
 * @subpackage filter
 * @author     ##AUTHOR_NAME##
 */
class sfSimpleCMSSlotFormFilter extends BasesfSimpleCMSSlotFormFilter
{
  public function configure()
  {
  }
}


text:
package mil.nga.geopackage.tiles.matrix;

import java.sql.SQLException;

import org.junit.Test;

import mil.nga.geopackage.ImportGeoPackageTestCase;

/**
 * Test Tile Matrix from an imported database
 *
 * @author osbornb
 */
public class TileMatrixImportTest extends ImportGeoPackageTestCase {

	/**
	 * Constructor
	 */
	public TileMatrixImportTest() {

	}

	/**
	 * Test reading
	 *
	 * @throws SQLException
	 */
	@Test
	public void testRead() throws SQLException {

		TileMatrixUtils.testRead(geoPackage, null);

	}

	/**
	 * Test updating
	 *
	 * @throws SQLException
	 */
	@Test
	public void testUpdate() throws SQLException {

		TileMatrixUtils.testUpdate(geoPackage);

	}

	/**
	 * Test creating
	 *
	 * @throws SQLException
	 */
	@Test
	public void testCreate() throws SQLException {

		TileMatrixUtils.testCreate(geoPackage);

	}

	/**
	 * Test deleting
	 *
	 * @throws SQLException
	 */
	@Test
	public void testDelete() throws SQLException {

		TileMatrixUtils.testDelete(geoPackage);

	}

}


text:
Kozmic CI
=========

Documentation is available on [Read the Docs](https://kozmic-ci.readthedocs.io/en/latest/index.html).


text:
#pragma once

#include <string>
#include <type_traits>

#include <sqlpp17/core/type_traits.h>

namespace sqlpp
{
  template <typename Expr>
  struct embrace_t
  {
    const Expr& _expr;
  };

  template <typename Context, typename Expr>
  [[nodiscard]] auto to_sql_string(Context& context, const embrace_t<Expr>& t)
  {
    return std::string{"("} + to_sql_string(context, t._expr) + ")";
  }

  template <typename Expr>
  constexpr decltype(auto) embrace(const Expr& expr)
  {
    if constexpr (requires_braces_v<Expr>)
    {
      return embrace_t<Expr>{expr};
    }
    else
    {
      return expr;
    }
  }
}  // namespace sqlpp


text:
package org.eclipse.rdf4j.benchmark.rio.impl;

import org.eclipse.rdf4j.benchmark.rio.ParserBenchmark;
import org.eclipse.rdf4j.rio.RDFParser;
import org.eclipse.rdf4j.rio.jsonld.JSONLDParser;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

/**
 * @author Tomas Kovachev t.kovachev1996@gmail.com
 */
public class JSONLDParserBenchmark extends ParserBenchmark {

	@Override
	public RDFParser getParser() {
		return new JSONLDParser();
	}

	public static void main(String[] args) throws RunnerException {
		Options opt = new OptionsBuilder()
				.include(JSONLDParserBenchmark.class.getSimpleName())
				.build();
		new Runner(opt).run();
	}
}


text:
namespace AMD
{
    class ShaderCache;
}
namespace TiledLighting11
{
    struct GuiState;
    struct Scene;
    class CommonUtil;
    class LightUtil;
}

namespace TiledLighting11
{
    typedef void (*UpdateCameraCallback)( const DirectX::XMMATRIX& mViewProj );

    class RSMRenderer
    {
    public:

        RSMRenderer();
        ~RSMRenderer();

        void SetCallbacks( UpdateCameraCallback cameraCallback );

        void AddShadersToCache( AMD::ShaderCache *pShaderCache );

        // Various hook functions
        void OnCreateDevice( ID3D11Device* pd3dDevice );
        void OnDestroyDevice();
        HRESULT OnResizedSwapChain( ID3D11Device* pd3dDevice, const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc );
        void OnReleasingSwapChain();

        void RenderSpotRSMs( int NumSpotLights, const GuiState& CurrentGuiState, const Scene& Scene, const CommonUtil& CommonUtil );
        void RenderPointRSMs( int NumPointLights, const GuiState& CurrentGuiState, const Scene& Scene, const CommonUtil& CommonUtil );
        void GenerateVPLs( int NumSpotLights, int NumPointLights, const LightUtil& LightUtil );

        ID3D11ShaderResourceView* GetSpotDepthSRV() { return m_SpotAtlas.m_pDepthSRV; }
        ID3D11ShaderResourceView* GetSpotNormalSRV() { return m_SpotAtlas.m_pNormalSRV; }
        ID3D11ShaderResourceView* GetSpotDiffuseSRV() { return m_SpotAtlas.m_pDiffuseSRV; }

        ID3D11ShaderResourceView* GetPointDepthSRV() { return m_PointAtlas.m_pDepthSRV; }
        ID3D11ShaderResourceView* GetPointNormalSRV() { return m_PointAtlas.m_pNormalSRV; }
        ID3D11ShaderResourceView* GetPointDiffuseSRV() { return m_PointAtlas.m_pDiffuseSRV; }

        ID3D11ShaderResourceView * const * GetVPLBufferCenterAndRadiusSRVParam() const { return &m_pVPLBufferCenterAndRadiusSRV; }
        ID3D11ShaderResourceView * const * GetVPLBufferDataSRVParam() const { return &m_pVPLBufferDataSRV; }

        int ReadbackNumVPLs();

    private:
        void RenderRSMScene( const GuiState& CurrentGuiState, const Scene& Scene, const CommonUtil& CommonUtil );

    private:

        UpdateCameraCallback        m_CameraCallback;

        struct GBufferAtlas
        {
            void Release()
            {
                SAFE_RELEASE( m_pDepthSRV );
                SAFE_RELEASE( m_pDepthDSV );
                SAFE_RELEASE( m_pDepthTexture );

                SAFE_RELEASE( m_pNormalRTV );
                SAFE_RELEASE( m_pNormalSRV );
                SAFE_RELEASE( m_pNormalTexture );

                SAFE_RELEASE( m_pDiffuseRTV );
                SAFE_RELEASE( m_pDiffuseSRV );
                SAFE_RELEASE( m_pDiffuseTexture );
            }

            ID3D11Texture2D*            m_pDepthTexture;
            ID3D11DepthStencilView*     m_pDepthDSV;
            ID3D11ShaderResourceView*   m_pDepthSRV;

            ID3D11Texture2D*            m_pNormalTexture;
            ID3D11RenderTargetView*     m_pNormalRTV;
            ID3D11ShaderResourceView*   m_pNormalSRV;

            ID3D11Texture2D*            m_pDiffuseTexture;
            ID3D11RenderTargetView*     m_pDiffuseRTV;
            ID3D11ShaderResourceView*   m_pDiffuseSRV;
        };

        GBufferAtlas                m_SpotAtlas;
        GBufferAtlas                m_PointAtlas;

        ID3D11Buffer*               m_pVPLBufferCenterAndRadius;
        ID3D11ShaderResourceView*   m_pVPLBufferCenterAndRadiusSRV;
        ID3D11UnorderedAccessView*  m_pVPLBufferCenterAndRadiusUAV;

        ID3D11Buffer*               m_pVPLBufferData;
        ID3D11ShaderResourceView*   m_pVPLBufferDataSRV;
        ID3D11UnorderedAccessView*  m_pVPLBufferDataUAV;

        ID3D11Buffer*               m_pSpotInvViewProjBuffer;
        ID3D11ShaderResourceView*   m_pSpotInvViewProjBufferSRV;

        ID3D11Buffer*               m_pPointInvViewProjBuffer;
        ID3D11ShaderResourceView*   m_pPointInvViewProjBufferSRV;

        ID3D11Buffer*               m_pNumVPLsConstantBuffer;
        ID3D11Buffer*               m_pCPUReadbackConstantBuffer;

        ID3D11VertexShader*         m_pRSMVS;
        ID3D11PixelShader*          m_pRSMPS;
        ID3D11InputLayout*          m_pRSMLayout;

        ID3D11ComputeShader*        m_pGenerateSpotVPLsCS;
        ID3D11ComputeShader*        m_pGeneratePointVPLsCS;
    };

} // namespace TiledLighting11

//--------------------------------------------------------------------------------------
// EOF
//--------------------------------------------------------------------------------------


text:
#include "lexicalAnalyzer.h"
#include <stdlib.h>
#include "../DLang/D_DEFINE_NON_RESERVED_WORDS.h"
#include "../utils/operatorParser.h"
#include "lexicalHelper.h"
#include "../errorManager/errorManager.h"
#include <string.h>

extern lexicalAnalyzer *global_la;

/**
 * Saves memory for the operators and calls the parser to fill the data.
 *
 * */
void initOperators(listOfOperators **mList, char *pathToOperators) {
    *mList = (listOfOperators *) malloc(sizeof(listOfOperators));
    (*mList)->list = NULL;
    parseOperators(mList, pathToOperators);
}

/**
 *
 * Resets the list of operators to say that all of them are possible again.
 *
 * */
void resetListOfOperators(listOfOperators **mList) {
    int i;
    for (i = 0; i < (*mList)->length; i++) {
        (*mList)->list[i].isPossible = 1;
    }
    (*mList)->startPosition = 0;
    (*mList)->numPossible = (*mList)->length;
}

/**
 * Initializes the lexical analyzer and the operators
 *
 * rs and st need to be init already.
 *
 * */
void initLexicalAnalyzer(lexicalAnalyzer **la, readerSystem *rs, symbolTable *st, char *pathToOperators) {
    *la = (lexicalAnalyzer *) malloc(sizeof(lexicalAnalyzer));
    global_la = *la;                                            //We update the pointer to the lexical analyzer in case we need it in error management.
    (*la)->mReaderSystem = rs;
    (*la)->mSymbolTable = st;
    (*la)->mListOfOperators = NULL;
    initOperators(&(*la)->mListOfOperators, pathToOperators);   //And we initialize the list of operators from the file.
}

/**
 * Deletes the list of operators
 *
 * */
void deleteOperators(listOfOperators **mList) {
    if ((*mList)->list != NULL)
        free((*mList)->list);
    if (*mList != NULL)
        free(*mList);
}

/**
 * Deletes the whole lexical analyzer
 *
 * */
void deleteLexicalAnalyzer(lexicalAnalyzer **la) {
    deleteOperators(&(*la)->mListOfOperators);
    free(*la);
}

/**
 * Detects if a char can be part of an operator for a given position.
 *
 * If the char read is valid for that operator we return TRUE
 *
 * If the operator has only X elements and we are checking position X+1 then we return TRUE
 * because the operator has already been read and detected as valid, we need to discard any other options.
 *
 * */
short detectOperator(operator *anOperator, int position, char c) {
    return anOperator->length <= position || (anOperator->str[position] == c);;
}

/**
 * This function checks what operators are still possible for the character that has been read.
 *
 * Before this version, this iterator through all the list but now when we find the first operator
 * we just mark that as the next position to start. In the next call we start over there and we read
 * only the amount of operators that start with the first character for the operator read.
 *
 * What this means is that the first time we would have to check for all the operators on the list but
 * after that iteration we will do only 3 or 4 iterations per call.
 *
 * */
short checkAllOperators(listOfOperators *mList, int position, char c) {
    int i;
    int numOfPosOp = 0;                                                     //Number of possible operators found
    //int maxLength = 0;            //Use this in case we wanted to check the max length found for a possible operator.
    int first = 1;

    int limit = (*mList).startPosition + (*mList).numPossible;

    for (i = (*mList).startPosition; i < limit; i++) {
        if ((*mList).list[i].isPossible == 1) {                             //If that operator is still possible
            if (detectOperator(&(*mList).list[i], position, c) == 1) {      //If that operator is still valid
                if ((*mList).list[i].length >= position) {
                    /*
                     * If we find a possible operator for the first time then we start looking there the next time.
                     *
                     *
                     * */
                    if (first && position == 0) {
                        mList->startPosition = i;
                        first = 0;
                    }
                    //maxLength = (*mList).list[i].length;                  //This is the longest operator
                    numOfPosOp++;
                } else if ((*mList).list[i].length < position) {
                    (*mList).list[i].isPossible = 0;                        //We have found a longer operator
                }
            } else {
                (*mList).list[i].isPossible = 0;
            }
        }
    }

    if (numOfPosOp > 0 && position == 0) {
        (*mList).numPossible = numOfPosOp;
    }

    if (numOfPosOp == 0) {
        //We don't have any operator that is possible with this data.
        return 0;
    } else if (numOfPosOp == 1) {
        //We have found one operator possible
        return 1;
    } else if (numOfPosOp > 1) {
        //Various operators are possible
        return 2;
    } else {
        manageFatalError(ERR_BAD_OPERATOR,
                         "Possible operators are negative, this should never be possible, how did you get here?\n\tTell me if you can contact me, I can't put my name in here :)");
        return -1;
    }
}

/**
 * Returns the only operator that is possible at the moment.
 *
 * Or the first one it finds but this is only called if there is only one.
 *
 * */
int getOnlyPossibleOperator(listOfOperators *mList) {
    int i;
    for (i = 0; i < (*mList).length; i++) {
        if ((*mList).list[i].isPossible == 1) {
            return (*mList).list[i].lexicalComponent;
        }
    }
    return -1;
}

/**
 * We read another character from the only operator possible.
 *
 * Returning FALSE when all the string has been read.
 *
 * */
int readAllFromOperator(listOfOperators *mList, int position, char c) {

    int i;

    for (i = 0; i < (*mList).length; i++) {
        if ((*mList).list[i].isPossible == 1) {                             //If that operator is still possible
            if (detectOperator(&(*mList).list[i], position, c) == 1) {      //For the only valid operator
                return ((*mList).list[i].str[position] == c);
            }
        }
    }
    return -1;
}

//Define this in order to send the lexeme to the syntactical analyzer
#define SEND_DEBUG_DATA

/**
 * Returns the next lexical component found by the lexical analyzer.
 *
 * */
lexemeComponentPackage getNextLexicalComponent(lexicalAnalyzer *la) {

    readerSystem *rs = la->mReaderSystem;

    //int i = 0;
    char c = 0;
    int lexComp;

    lexemeComponentPackage lcp;
    lcp.lexeme = NULL;

    for (;;) {
        for (;;) {

            c = getNextChar(rs);                    //We get a character

            readEmptyData(la, &c);                  //After we read non useful data

            if (c == EOF) {                         //If we have found the end of file we end the search.
                lcp.lexicalComponent = END_OF_FILE;
                return lcp;                         //Return End Of File here
            }

            //-------------------------------------------------CHECKING FOR COMMENTS--------------------------------------------------

            short checkedForComment = 0;

            if (couldBeComment(c)) {                            //We could be at the beginning of a comment
                c = getNextChar(rs);
                checkedForComment = 1;
                if (isBegOfNestedComment(c)) {                  //We are at the beginning of a nested comment
                    c = getNextChar(rs);
                    readUntilEndOfNestedComment(la, &c);        //We read until we have finished the nested comment
                    char *lex = getCurrentLex(rs);              //We get the string that corresponds to the comment
                    returnChar(rs);
#ifdef PRINT_LEXEM
                    printf("Detected comment: [%s]\n", lex);    //Print it
#endif
                    free(lex);                                  //And here we free it.
                    break;
                    //return NESTED_COMMENT;
                } else if (isBegOfBlockComment(c)) {            //We are at the beginning of a block comment
                    c = getNextChar(rs);

                    if (isBegOfBlockComment(c)) {               //If we find another '*' then this could be a documentation comment.
                        c = getNextChar(rs);
                        if (couldBeComment(c)) {                //If we find a '/' then the comment is a normal block comment like this "/**/"
                            c = getNextChar(rs);
                            char *lex = getCurrentLex(rs);
                            returnChar(rs);
#ifdef PRINT_LEXEM
                            printf("Detected comment: [%s]\n", lex);
#endif
                            free(lex);
                            break;
                            //return BLOCK_COMMENT;
                        } else {                                //Else it is a documentation comment
                            readUntilEndOfBlockComment(la, &c); //We read until the end of the comment
                            char *lex = getCurrentLex(rs);
                            returnChar(rs);
#ifdef PRINT_LEXEM
                            printf("Detected documentation comment: [%s]\n", lex);
#endif
#ifdef SEND_DEBUG_DATA
                            lcp.lexeme = lex;
                            sprintf(lcp.strLexicalComponent, "DOCUMENTATION_COMMENT");
#else
                            free(lex);
#endif

                            lcp.lexicalComponent = DOCUMENTATION_COMMENT;       //And return a different lexical component.
                            return lcp;

                        }

                    }
                    readUntilEndOfBlockComment(la, &c);         //If we end up here is just a normal block comment with something inside it.
                    char *lex = getCurrentLex(rs);              //We just to the same as above to get the lexeme, print it and return the lexical component.
                    returnChar(rs);
#ifdef PRINT_LEXEM
                    printf("Detected comment: [%s]\n", lex);
#endif
                    free(lex);
                    break;
                    //return BLOCK_COMMENT;
                } else if (isBegOfLineComment(c)) {             //If we have found "//" then this is a line comment
                    c = getNextChar(rs);
                    readUntilEndOfLineComment(la, &c);          //We read until the next line
                    char *lex = getCurrentLex(rs);              //And repeat the steps above to return the correct lexical component.
                    returnChar(rs);
#ifdef PRINT_LEXEM
                    printf("Detected comment: [%s]\n", lex);
#endif
                    free(lex);
                    break;
                    //return LINE_COMMENT;
                }
                returnChar(rs);
            }

            //---------------------------------CHECKING FOR SPECIAL CASE OF FLOAT STARTING WITH A DOT---------------------------------

            short checkedForFloat = 0;
            if (couldBeFloat(c)) {
                c = getNextChar(rs);

                if (isPartOfNumber(c)) {
                    while (isPartOfNumber(c)) {
                        c = getNextChar(rs);
                    }

                    if (couldBeSciNo(c)) {                      //Now we know we have either a float or an integer but we could find some scientific notation.
                        c = getNextChar(rs);
                        if (isPlusMinus(c)) {                   //The e or E can be followed by a plus or minus.
                            c = getNextChar(rs);
                        }
                        if (c == EOF) {//ERROR CHECKING FOR END OF FILE
                            manageFatalErrorWithLine(ERR_UNEXPECTED_EOF, "EOF found after incomplete definition of a number in scientific notation",
                                                     la->mReaderSystem->currentLine,
                                                     la->mReaderSystem->currentPosition);
                        }

                        if (!isPartOfNumber(c)) {
                            manageNonFatalErrorWithLine(ERR_BAD_SCI_NO, "Found incomplete definition of a scientific notation number",
                                                        la->mReaderSystem->currentLine,
                                                        la->mReaderSystem->currentPosition);
                            char *lex = getCurrentLex(rs);
                            returnChar(rs);
#ifdef PRINT_LEXEM
                            printf("Error in: [%s]\n", lex);
#endif
#ifdef SEND_DEBUG_DATA
                            lcp.lexeme = lex;
                            sprintf(lcp.strLexicalComponent, "COMPONENT_ERROR");
#else
                            free(lex);
#endif
                            lcp.lexicalComponent = COMPONENT_ERROR;
                            return lcp;

                        }
                        while (isPartOfNumber(c)) {             //And after that we can only have an array of numbers
                            c = getNextChar(rs);
                        }
                    }

                    char *lex = getCurrentLex(rs);
                    returnChar(rs);
#ifdef PRINT_LEXEM
                    printf("Detected lexem: [%s]\n", lex);
#endif
                    lcp.lexicalComponent = FLOAT_LITERAL;
#ifdef SEND_DEBUG_DATA
                    lcp.lexeme = lex;
                    sprintf(lcp.strLexicalComponent, "FLOAT_LITERAL");
#else
                    free(lex);
#endif
                    return lcp;

                } else {
                    checkedForFloat = 1;
                }
            }

            //-------------------------------------------------CHECKING FOR OPERATORS--------------------------------------------------

            int chkResult;
            if (checkedForComment) {
                chkResult = checkAllOperators(la->mListOfOperators, 0, '/');            //If the character can be the beginning of an operator
            } else if (checkedForFloat) {
                chkResult = checkAllOperators(la->mListOfOperators, 0, '.');            //If the character can be the beginning of an operator
            } else {
                chkResult = checkAllOperators(la->mListOfOperators, 0, c);              //If the character can be the beginning of an operator
            }
            if (chkResult != 0) {
                int position = 1;
                if (!checkedForFloat)                                                   //If we have checked for a floating number with a point at the beginning
                    c = getNextChar(rs);                                                //We dont need this next character, we have already read it
                chkResult = checkAllOperators(la->mListOfOperators, position, c);       //We test if the next char can be the next part of the operator
                while (chkResult != 1) {                                                //And we test while we don't have only one operator
                    if (chkResult == 0) {                                               //If we have no operators possible then there is an error

                        manageNonFatalErrorWithLine(ERR_BAD_OPERATOR, "Found an error when parsing an operator", la->mReaderSystem->currentLine,
                                                    la->mReaderSystem->currentPosition);
                        char *lex = getCurrentLex(rs);
                        returnChar(rs);
#ifdef PRINT_LEXEM
                        printf("Detected lexem: [%s]\n", lex);
#endif
#ifdef SEND_DEBUG_DATA
                        lcp.lexeme = lex;
                        sprintf(lcp.strLexicalComponent, "OPE_ERROR");
#else
                        free(lex);
#endif
                        resetListOfOperators(&(la->mListOfOperators));

                        lcp.lexicalComponent = OPE_ERROR;
                        return lcp;

                    } else if (chkResult == 2) {                                    //If we have more than two operators possible we need to check more
                        c = getNextChar(rs);
                        position++;
                        chkResult = checkAllOperators(la->mListOfOperators, position, c);
                    }
                }
                //Here we already know the operator since it's only one option left

                //But we need to read all of it's characters.
                while (readAllFromOperator(la->mListOfOperators, position, c)) {    //So we keep reading and checking
                    position++;
                    c = getNextChar(rs);                                            //And getting the remaining characters
                }
                lexComp = getOnlyPossibleOperator(la->mListOfOperators);            //We get the lexical component of the only operator possible
                char *lex = getCurrentLex(rs);                                      //Get the lexeme
                returnChar(
                        rs);                                                        //And return the last chat that was used for checking the end of the operator
#ifdef PRINT_LEXEM
                printf("Detected lexem: [%s]\n", lex);                              //Print it
#endif
#ifdef SEND_DEBUG_DATA
                lcp.lexeme = lex;
                sprintf(lcp.strLexicalComponent, "OPERATOR: %s", lex);
                //We don't print the name because we can't know the string here without too much hustle.
#else
                free(lex);
#endif                                                       //Free it
                resetListOfOperators(
                        &(la->mListOfOperators));                                   //Reset the operator list to make them all possible again for future iterations.
                lcp.lexicalComponent = lexComp;                                     //And return the lexical component.
                return lcp;
            } else {
                resetListOfOperators(&(la->mListOfOperators));                      //We need to reset the list of operators to set them all to possible again.
            }

            //-------------------------------------------------CHECKING FOR STRING LITERALS--------------------------------------------------

            if (couldBeStrLit(c)) {                                                 //If this can be a string literal
                c = getNextChar(rs);
                if (c == EOF) {//ERROR CHECKING FOR END OF FILE
                    manageFatalErrorWithLine(ERR_UNEXPECTED_EOF, "EOF found inside a string", la->mReaderSystem->currentLine,
                                             la->mReaderSystem->currentPosition);
                }

                if (isNewLine(c)) {//ERROR CHECKING FOR NEWLINE
                    manageNonFatalErrorWithLine(ERR_JUMP_LINE_IN_LITERAL, "Newline character found inside a string", la->mReaderSystem->currentLine,
                                                la->mReaderSystem->currentPosition);
                }

                while (!isEndOfString(c)) {                                         //While we don't find the closing character
                    if (isScapeCharacter(c)) {                                      //If we find an scape character
                        c = getNextChar(rs);                                        //The we get the next character to that and ignore it
                        if (c == EOF) {//ERROR CHECKING FOR END OF FILE
                            manageFatalErrorWithLine(ERR_UNEXPECTED_EOF, "EOF found inside a string", la->mReaderSystem->currentLine,
                                                     la->mReaderSystem->currentPosition);

                        }
                    }
                    c = getNextChar(rs);                                            //We keep getting characters
                    if (c == EOF) {//ERROR CHECKING FOR END OF FILE
                        manageFatalErrorWithLine(ERR_UNEXPECTED_EOF, "EOF found inside a string", la->mReaderSystem->currentLine,
                                                 la->mReaderSystem->currentPosition);
                    }
                    if (isNewLine(c)) {//ERROR CHECKING FOR NEWLINE
                        manageNonFatalErrorWithLine(ERR_JUMP_LINE_IN_LITERAL, "Newline character found inside a string", la->mReaderSystem->currentLine,
                                                    la->mReaderSystem->currentPosition);
                    }
                }
                c = getNextChar(rs);
                char *lex = getCurrentLex(rs);                                      //Get the lexeme
                returnChar(rs);
#ifdef PRINT_LEXEM
                printf("Detected lexem: [%s]\n", lex);                              //Print it
#endif
#ifdef SEND_DEBUG_DATA
                lcp.lexeme = lex;
                sprintf(lcp.strLexicalComponent, "STRING_LITERAL");

#else
                free(lex);
#endif
                lcp.lexicalComponent = STRING_LITERAL;
                return lcp;

            }

            //-------------------------------------------------CHECKING FOR ALPHANUMERIC STRINGS--------------------------------------------------

            if (couldBeIdent(c)) {                                                  //If this can be an alphanumeric string
                while (isPartOfIdent(c)) {                                          //We get all the characters that can still be part of it
                    c = getNextChar(rs);
                }
                char *lex = getCurrentLex(rs);                                      //We get the lexeme
                returnChar(rs);
#ifdef PRINT_LEXEM
                printf("Detected lexem: [%s]\n", lex);                              //And print it
#endif

                symbolData *sd = searchLex(la->mSymbolTable, lex);
                if (sd == NULL) {                                                   //We have found an identifier that was not on the table
                    sd = (symbolData *) malloc(sizeof(symbolData));
                    sd->lexicalComponent = IDENTIFIER;
                    sd->lexeme = lex;
                    addLex(&(la->mSymbolTable), lex, sd);
#ifdef SEND_DEBUG_DATA
                    lcp.lexeme = lex;
                    sprintf(lcp.strLexicalComponent, "IDENTIFIER");
#else
                    free(lex);
#endif

                } else {                                                            //There was already a reserved word or an identifier in the table.
                    if (sd->lexicalComponent == IDENTIFIER) {                       //Identical identifier in the symbol table already
                        /**
                         * We could use the code below if we wanted to allow duplicates on the symbol table
                         * taking care of the level.
                         *
                         * */
                        /*sd = (symbolData *) malloc(sizeof(symbolData));
                        sd->lexicalComponent = IDENTIFIER;
                        addLex(&(la->mSymbolTable), lex, sd);                       //We add it again, currently we will have repeated elements that are identical.*/

#ifdef SEND_DEBUG_DATA
                        lcp.lexeme = sd->lexeme;
                        free(lex);
                        sprintf(lcp.strLexicalComponent, "IDENTIFIER");
#else
                        free(lex);                                                  //The lexeme needs to be deleted since it is not going to the table.
#endif

                    } else {                                                        //If it is a reserved word
#ifdef SEND_DEBUG_DATA
                        lcp.lexeme = lex;
                        sprintf(lcp.strLexicalComponent, "RESERVED_WORD: %s", lex);
#else
                        free(lex);
#endif

                    }
                    lcp.lexicalComponent = (sd->lexicalComponent);
                    return lcp;
                }

                lcp.lexicalComponent = IDENTIFIER;
                return lcp;
            }

            //-------------------------------------------------CHECKING FOR NUMERIC LITERALS--------------------------------------------------

            if (couldBeNumber(c)) {                                             //If this is the start of a number
                char cAux = c;
                c = getNextChar(rs);
                if (isBinFirst(cAux) && isBinPrefix(c)) {                       //We have read a binary number
                    c = getNextChar(rs);
                    if (c == EOF) {//ERROR CHECKING FOR END OF FILE
                        manageFatalErrorWithLine(ERR_UNEXPECTED_EOF, "EOF found after incomplete definition of a binary number", la->mReaderSystem->currentLine,
                                                 la->mReaderSystem->currentPosition);
                    }
                    if (!isPartOfBinary(c)) {                                   //If it does not have a 0 or 1 after the definition then there is an error
                        manageNonFatalErrorWithLine(ERR_BAD_BINARY, "Found incomplete definition of a binary number", la->mReaderSystem->currentLine,
                                                    la->mReaderSystem->currentPosition);
                        char *lex = getCurrentLex(rs);
                        returnChar(rs);
#ifdef PRINT_LEXEM
                        printf("Error in: [%s]\n", lex);
#endif
#ifdef SEND_DEBUG_DATA
                        lcp.lexeme = lex;
                        sprintf(lcp.strLexicalComponent, "COMPONENT_ERROR");
#else
                        free(lex);
#endif
                        lcp.lexicalComponent = COMPONENT_ERROR;
                        return lcp;
                    }
                    while (isPartOfBinary(c)) {                                 //We read all 0s and 1s
                        c = getNextChar(rs);
                    }
                    char *lex = getCurrentLex(rs);                              //And get the lexeme
                    returnChar(rs);
#ifdef PRINT_LEXEM
                    printf("Detected lexem: [%s]\n", lex);
#endif
#ifdef SEND_DEBUG_DATA
                    lcp.lexeme = lex;
                    sprintf(lcp.strLexicalComponent, "BINARY_LITERAL");
#else
                    free(lex);
#endif
                    lcp.lexicalComponent = BINARY_LITERAL;
                    return lcp;

                } else if (!canBeInNumber(c)) {                 //If the number has a length of one then it is an integer
                    char *lex = getCurrentLex(rs);
                    returnChar(rs);
#ifdef PRINT_LEXEM
                    printf("Detected lexem: [%s]\n", lex);
#endif
                    lcp.lexicalComponent = INTEGER_LITERAL;
#ifdef SEND_DEBUG_DATA
                    lcp.lexeme = lex;
                    sprintf(lcp.strLexicalComponent, "INTEGER_LITERAL");
#else
                    free(lex);
#endif
                    return lcp;

                } else if (canBeInNumber(c)) {                  //The number is not binary and is longer than 2 characters
                    int type = INTEGER_LITERAL;

#ifdef SEND_DEBUG_DATA
                    sprintf(lcp.strLexicalComponent, "INTEGER_LITERAL");
#endif

                    while (isPartOfNumber(c)) {                 //We read an array of numbers
                        c = getNextChar(rs);
                    }                                           //We have read the whole string of numbers but we could have a float or scientific notation.
                    if (couldBeFloat(c)) {                      //We check if the number has a point.
                        c = getNextChar(rs);                    //If this is a float we need to keep looking for all the numbers after the point
                        while (isPartOfNumber(c)) {
                            c = getNextChar(rs);
                        }
                        type = FLOAT_LITERAL;
#ifdef SEND_DEBUG_DATA
                        sprintf(lcp.strLexicalComponent, "FLOAT_LITERAL");
#endif
                    }

                    if (couldBeSciNo(c)) {                      //Now we know we have either a float or an integer but we could find some scientific notation.
                        c = getNextChar(rs);
                        if (isPlusMinus(c)) {                   //The e or E can be followed by a plus or minus.
                            c = getNextChar(rs);
                        }
                        if (c == EOF) {//ERROR CHECKING FOR END OF FILE
                            manageFatalErrorWithLine(ERR_UNEXPECTED_EOF, "EOF found after incomplete definition of a number in scientific notation",
                                                     la->mReaderSystem->currentLine,
                                                     la->mReaderSystem->currentPosition);
                        }

                        if (!isPartOfNumber(c)) {
                            manageNonFatalErrorWithLine(ERR_BAD_SCI_NO, "Found incomplete definition of a scientific notation number",
                                                        la->mReaderSystem->currentLine,
                                                        la->mReaderSystem->currentPosition);
                            char *lex = getCurrentLex(rs);
                            returnChar(rs);
#ifdef PRINT_LEXEM
                            printf("Error in: [%s]\n", lex);
#endif
#ifdef SEND_DEBUG_DATA
                            lcp.lexeme = lex;
                            sprintf(lcp.strLexicalComponent, "COMPONENT_ERROR");
#else
                            free(lex);
#endif
                            lcp.lexicalComponent = COMPONENT_ERROR;
                            return lcp;

                        }

                        while (isPartOfNumber(c)) {             //And after that we can only have an array of numbers
                            c = getNextChar(rs);
                        }
                        type = FLOAT_LITERAL;
#ifdef SEND_DEBUG_DATA
                        sprintf(lcp.strLexicalComponent, "FLOAT_LITERAL");
#endif

                    }

                    char *lex = getCurrentLex(rs);
                    returnChar(rs);
#ifdef PRINT_LEXEM
                    printf("Detected lexem: [%s]\n", lex);
#endif
                    lcp.lexicalComponent = type;
#ifdef SEND_DEBUG_DATA
                    lcp.lexeme = lex;
#else
                    free(lex);
#endif
                    return lcp;

                }
            }//End of number checking

            //Could continue here in order to check more things

        }
    }
}


text:
<?php

class Google_Service_Bigquery_JobListJobs extends Google_Model
{
  protected $internal_gapi_mappings = array(
        "userEmail" => "user_email",
  );
  protected $configurationType = 'Google_Service_Bigquery_JobConfiguration';
  protected $configurationDataType = '';
  protected $errorResultType = 'Google_Service_Bigquery_ErrorProto';
  protected $errorResultDataType = '';
  public $id;
  protected $jobReferenceType = 'Google_Service_Bigquery_JobReference';
  protected $jobReferenceDataType = '';
  public $kind;
  public $state;
  protected $statisticsType = 'Google_Service_Bigquery_JobStatistics';
  protected $statisticsDataType = '';
  protected $statusType = 'Google_Service_Bigquery_JobStatus';
  protected $statusDataType = '';
  public $userEmail;

  public function setConfiguration(Google_Service_Bigquery_JobConfiguration $configuration)
  {
    $this->configuration = $configuration;
  }
  public function getConfiguration()
  {
    return $this->configuration;
  }
  public function setErrorResult(Google_Service_Bigquery_ErrorProto $errorResult)
  {
    $this->errorResult = $errorResult;
  }
  public function getErrorResult()
  {
    return $this->errorResult;
  }
  public function setId($id)
  {
    $this->id = $id;
  }
  public function getId()
  {
    return $this->id;
  }
  public function setJobReference(Google_Service_Bigquery_JobReference $jobReference)
  {
    $this->jobReference = $jobReference;
  }
  public function getJobReference()
  {
    return $this->jobReference;
  }
  public function setKind($kind)
  {
    $this->kind = $kind;
  }
  public function getKind()
  {
    return $this->kind;
  }
  public function setState($state)
  {
    $this->state = $state;
  }
  public function getState()
  {
    return $this->state;
  }
  public function setStatistics(Google_Service_Bigquery_JobStatistics $statistics)
  {
    $this->statistics = $statistics;
  }
  public function getStatistics()
  {
    return $this->statistics;
  }
  public function setStatus(Google_Service_Bigquery_JobStatus $status)
  {
    $this->status = $status;
  }
  public function getStatus()
  {
    return $this->status;
  }
  public function setUserEmail($userEmail)
  {
    $this->userEmail = $userEmail;
  }
  public function getUserEmail()
  {
    return $this->userEmail;
  }
}