<commit_before>/**
 * Copyright 2016 <NAME>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
**/
package frmr.scyig.matching

import frmr.scyig.models._
import net.liftweb.common._
import net.liftweb.actor._

/**
 * The brains of this operation! The matching engine is responsible for generating fully realized
 * rounds using the suggesters and by ensuring the matching policies are enforced.
 *
 * @param participants All of the participants in the competition.
 * @param roundNumber The round number the matching engine is initilized for.
 * @param numberOfRooms The number of rooms available to the matching engine.
 * @param suggester The suggester that should be used for suggesting possible members.
 */
class MatchingEngine(
  initialParticipants: Seq[Participant],
  roundNumber: Int,
  numberOfRooms: Int,
  matchingPolicy: MatchingPolicy,
  suggester: (Seq[Participant])=>ParticipantSuggester = participants=>new RandomizedParticipantSuggester(participants)
) extends LiftActor with Loggable {
  val self = this
  var finalState: Option[MatchingEngineState] = None

  def buildMatch(state: MatchingEngineState): Unit = {
    val suggestions = state.suggester.suggestParticipants(state.currentlyBuildingRound)

    (state.currentlyBuildingRound, suggestions) match {
      case (None, suggestions) if suggestions.nonEmpty =>
        suggestions.headOption match {
          case Some(team: CompetingTeam) =>
            val updatedState = state.copy(
              currentlyBuildingRound = Some(MatchSeed(team)),
              remainingParticipants = state.remainingParticipants.filterNot(_ == suggestions.head)
            )

            self ! BuildMatch(updatedState)

          case unexpected =>
            self ! MatchingError(s"Unexpected suggestion from suggestion engine $unexpected")
        }

      case (None, _) =>
        self ! FinishMatching(state)

      case (Some(seed: MatchSeed), suggestions) if suggestions.nonEmpty =>
        val suggestedMember = suggestions.find(candidate => matchingPolicy.isValid(seed, candidate))

        suggestedMember match {
          case None =>
            self ! BuildMatch(
              state.copy(
                scheduledRounds = state.scheduledRounds ++ seed.toByes,
                currentlyBuildingRound = None
              ).withoutParticipant(seed.team)
            )

          case Some(nextTeam: CompetingTeam) =>
            self ! BuildMatch(
              state.copy(
                currentlyBuildingRound = Some(seed.withOtherTeam(nextTeam))
              ).withoutParticipant(nextTeam)
            )

          case Some(otherParticipant) =>
            self ! MatchingError(s"Received an unexpected participant suggestion. Expected CompetingTeam. Got: $otherParticipant")
        }

      case (Some(matchedTeams: MatchedTeams), suggestions) if suggestions.nonEmpty =>
        val suggestedMember = suggestions.find(candidate => matchingPolicy.isValid(matchedTeams, candidate))

        suggestedMember match {
          case None =>
            self ! BuildMatch(
              state.copy(
                scheduledRounds = state.scheduledRounds ++ matchedTeams.toByes,
                currentlyBuildingRound = None
              )
            )

          case Some(presidingJudge: PresidingJudge) =>
            self ! BuildMatch(
              state.copy(
                currentlyBuildingRound = Some(matchedTeams.withPresidingJudge(presidingJudge))
              ).withoutParticipant(presidingJudge)
            )

          case Some(otherParticipant) =>
            self ! MatchingError(s"Received an unexpected participant suggestion. Expected PresidingJudge. Got: $otherParticipant")
        }

      case (Some(teamsWithPresiding: MatchedTeamsWithPresidingJudge), suggestions) if suggestions.nonEmpty =>
        val suggestedMember = suggestions.find(candidate => matchingPolicy.isValid(teamsWithPresiding, candidate))

        suggestedMember match {
          case None =>
            self ! BuildMatch(
              state.copy(
                fullyMatchedRounds = state.fullyMatchedRounds :+ teamsWithPresiding.withScoringJudge(None),
                currentlyBuildingRound = None
              )
            )

          case scoringJudgeOpt @ Some(scoringJudge: ScoringJudge) =>
            self ! BuildMatch(
              state.copy(
                fullyMatchedRounds = state.fullyMatchedRounds :+ teamsWithPresiding.withScoringJudge(
                  scoringJudgeOpt.asInstanceOf[Option[ScoringJudge]]
                ),
                currentlyBuildingRound = None
              ).withoutParticipant(scoringJudge)
            )

          case Some(unexpected) =>
            self ! MatchingError(s"Received an unexpected participant suggestion. Expected ScoringJudge. Got: $unexpected")
        }

      case (Some(partialMatch: PartialRoundMatch), _) =>
        self ! BuildMatch(
          state.copy(
            scheduledRounds = state.scheduledRounds ++ partialMatch.toByes,
            currentlyBuildingRound = None
          )
        )
    }
  }

  def startMatching(): Unit = {
    val initialState = MatchingEngineState(
      remainingParticipants = initialParticipants,
      suggester = suggester(initialParticipants)
    )
    self ! BuildMatch(initialState)
  }

  def handleMatchingEvent(matchingEvent: MatchingEngineEvent): Unit = {
    matchingEvent match {
      case StartMatching =>
        logger.info("Starting matching.")
        startMatching()

      case BuildMatch(state) =>
        buildMatch(state)

      case FinishMatching(state) =>
        logger.info("Matching finished.")
        finalState = Some(state)

      case MatchingError(error) =>
        logger.error(s"Matching halted with error: $error")
    }
  }

  def handleMatchingQuery(matchingQuery: MatchingEngineQuery): Unit = {
    matchingQuery match {
      case QueryMatchingState =>
        reply(finalState)
    }<commit_msg>Restrict visibility of internal components.<commit_after>/**
 * Copyright 2016 <NAME>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
**/
package frmr.scyig.matching

import frmr.scyig.models._
import net.liftweb.common._
import net.liftweb.actor._

/**
 * The brains of this operation! The matching engine is responsible for generating fully realized
 * rounds using the suggesters and by ensuring the matching policies are enforced.
 *
 * @param participants All of the participants in the competition.
 * @param roundNumber The round number the matching engine is initilized for.
 * @param numberOfRooms The number of rooms available to the matching engine.
 * @param suggester The suggester that should be used for suggesting possible members.
 */
class MatchingEngine(
  initialParticipants: Seq[Participant],
  roundNumber: Int,
  numberOfRooms: Int,
  matchingPolicy: MatchingPolicy,
  suggester: (Seq[Participant])=>ParticipantSuggester = participants=>new RandomizedParticipantSuggester(participants)
) extends LiftActor with Loggable {
  private[matching] val self = this
  private[matching] var finalState: Option[MatchingEngineState] = None

  private[matching] def buildMatch(state: MatchingEngineState): Unit = {
    val suggestions = state.suggester.suggestParticipants(state.currentlyBuildingRound)

    (state.currentlyBuildingRound, suggestions) match {
      case (None, suggestions) if suggestions.nonEmpty =>
        suggestions.headOption match {
          case Some(team: CompetingTeam) =>
            val updatedState = state.copy(
              currentlyBuildingRound = Some(MatchSeed(team)),
              remainingParticipants = state.remainingParticipants.filterNot(_ == suggestions.head)
            )

            self ! BuildMatch(updatedState)

          case unexpected =>
            self ! MatchingError(s"Unexpected suggestion from suggestion engine $unexpected")
        }

      case (None, _) =>
        self ! FinishMatching(state)

      case (Some(seed: MatchSeed), suggestions) if suggestions.nonEmpty =>
        val suggestedMember = suggestions.find(candidate => matchingPolicy.isValid(seed, candidate))

        suggestedMember match {
          case None =>
            self ! BuildMatch(
              state.copy(
                scheduledRounds = state.scheduledRounds ++ seed.toByes,
                currentlyBuildingRound = None
              ).withoutParticipant(seed.team)
            )

          case Some(nextTeam: CompetingTeam) =>
            self ! BuildMatch(
              state.copy(
                currentlyBuildingRound = Some(seed.withOtherTeam(nextTeam))
              ).withoutParticipant(nextTeam)
            )

          case Some(otherParticipant) =>
            self ! MatchingError(s"Received an unexpected participant suggestion. Expected CompetingTeam. Got: $otherParticipant")
        }

      case (Some(matchedTeams: MatchedTeams), suggestions) if suggestions.nonEmpty =>
        val suggestedMember = suggestions.find(candidate => matchingPolicy.isValid(matchedTeams, candidate))

        suggestedMember match {
          case None =>
            self ! BuildMatch(
              state.copy(
                scheduledRounds = state.scheduledRounds ++ matchedTeams.toByes,
                currentlyBuildingRound = None
              )
            )

          case Some(presidingJudge: PresidingJudge) =>
            self ! BuildMatch(
              state.copy(
                currentlyBuildingRound = Some(matchedTeams.withPresidingJudge(presidingJudge))
              ).withoutParticipant(presidingJudge)
            )

          case Some(otherParticipant) =>
            self ! MatchingError(s"Received an unexpected participant suggestion. Expected PresidingJudge. Got: $otherParticipant")
        }

      case (Some(teamsWithPresiding: MatchedTeamsWithPresidingJudge), suggestions) if suggestions.nonEmpty =>
        val suggestedMember = suggestions.find(candidate => matchingPolicy.isValid(teamsWithPresiding, candidate))

        suggestedMember match {
          case None =>
            self ! BuildMatch(
              state.copy(
                fullyMatchedRounds = state.fullyMatchedRounds :+ teamsWithPresiding.withScoringJudge(None),
                currentlyBuildingRound = None
              )
            )

          case scoringJudgeOpt @ Some(scoringJudge: ScoringJudge) =>
            self ! BuildMatch(
              state.copy(
                fullyMatchedRounds = state.fullyMatchedRounds :+ teamsWithPresiding.withScoringJudge(
                  scoringJudgeOpt.asInstanceOf[Option[ScoringJudge]]
                ),
                currentlyBuildingRound = None
              ).withoutParticipant(scoringJudge)
            )

          case Some(unexpected) =>
            self ! MatchingError(s"Received an unexpected participant suggestion. Expected ScoringJudge. Got: $unexpected")
        }

      case (Some(partialMatch: PartialRoundMatch), _) =>
        self ! BuildMatch(
          state.copy(
            scheduledRounds = state.scheduledRounds ++ partialMatch.toByes,
            currentlyBuildingRound = None
          )
        )
    }
  }

  private[matching] def startMatching(): Unit = {
    val initialState = MatchingEngineState(
      remainingParticipants = initialParticipants,
      suggester = suggester(initialParticipants)
    )
    self ! BuildMatch(initialState)
  }

  private[matching] def handleMatchingEvent(matchingEvent: MatchingEngineEvent): Unit = {
    matchingEvent match {
      case StartMatching =>
        logger.info("Starting matching.")
        startMatching()

      case BuildMatch(state) =>
        buildMatch(state)

      case FinishMatching(state) =>
        logger.info("Matching finished.")
        finalState = Some(state)

      case MatchingError(error) =>
        logger.error(s"Matching halted with error: $error")
    }
  }

  private[matching] def handleMatchingQuery(matchingQuery: MatchingEngineQuery): Unit = {
    matchingQuery match {
      case QueryMatchingState =>
        reply(finalState)
    }
{'fraction_non_alphanumeric': 0.07808660933660934, 'fraction_numerical': 0.0012285012285012285, 'mean_word_length': 2.640301844605925, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3104880', 'n_tokens_mistral': 3360, 'n_tokens_neox': 3168, 'n_words': 858}