<commit_before>package lila.puzzle

import scala.concurrent.duration._
import scala.util.Random

import lila.db.dsl._
import lila.user.User

private[puzzle] final class Selector(
    puzzleColl: Coll,
    api: PuzzleApi,
    anonMinRating: Int) {

  private def popularSelector(mate: Boolean) = $doc(
    Puzzle.BSONFields.voteSum $gt mate.fold(anonMinRating, 0))

  private def mateSelector(mate: Boolean) = $doc("mate" -> mate)

  private def difficultyDecay(difficulty: Int) = difficulty match {
    case 1 => -200
    case 3 => +200
    case _ => 0
  }

  private val toleranceMax = 1000

  val anonSkipMax = 5000

  def apply(me: Option[User], difficulty: Int): Fu[Option[Puzzle]] = {
    lila.mon.puzzle.selector.count()
    val isMate = scala.util.Random.nextBoolean
    me match {
      case None =>
        puzzleColl.find(popularSelector(isMate) ++ mateSelector(isMate))
          .skip(Random nextInt anonSkipMax)
          .uno[Puzzle]
      case Some(user) =>
        val head = api.head.find(user)
        head flatMap {
          case Some(PuzzleHead(_, Some(c), _)) => api.puzzle.find(c)
          case _ =>
            val isLearn = scala.util.Random.nextInt(5) == 0
            val next = if (isLearn) api.learning.nextPuzzle(user) flatMap {
              case None => newPuzzleForUser(user, isMate, difficulty)
              case p => fuccess(p)
            }
            else newPuzzleForUser(user, isMate, difficulty)
            next flatMap {
              case Some(p) => api.head.add(PuzzleHead(user.id, Some(p.id), none))
              case _ => fuccess(none) 
            }
            next
        }
    }
  }.mon(_.puzzle.selector.time)

  private def toleranceStepFor(rating: Int) =
    math.abs(1500 - rating) match {
      case d if d >= 500 => 300
      case d if d >= 300 => 250
      case d             => 200
    }

  private def newPuzzleForUser(user: User, isMate: Boolean, difficulty: Int): Fu[Option[Puzzle]] = {
    val rating = user.perfs.puzzle.intRating min 2300 max 900
    val step = toleranceStepFor(rating)
    api.head.find(user) flatMap {
      case Some(PuzzleHead(_, _, Some(l))) => tryRange(rating, step, step, difficultyDecay(difficulty), l, 100, 100, isMate)
      case _ =>                               tryRange(rating, step, step, difficultyDecay(difficulty), 0, 100, 100, isMate)
    }
  }

  private def tryRange(rating: Int, tolerance: Int, step: Int, decay: Int, last: PuzzleId, idRange: Int, idStep: Int, isMate: Boolean): Fu[Option[Puzzle]] =
    puzzleColl.find(mateSelector(isMate) ++ $doc(
      Puzzle.BSONFields.id $gt
        last $lt
        (last + idRange),
      Puzzle.BSONFields.rating $gt
        (rating - tolerance + decay) $lt
        (rating + tolerance + decay)
    )).sort($sort desc Puzzle.BSONFields.voteSum)
      .uno[Puzzle] flatMap {
        case None if (tolerance + step) <= toleranceMax =>
          tryRange(rating, tolerance + step, step, decay, last, idRange + idStep, idStep, isMate)
        case res => fuccess(res)
      }
}<commit_msg>make puzzle selector less ugly<commit_after>package lila.puzzle

import scala.concurrent.duration._
import scala.util.Random

import lila.db.dsl._
import lila.user.User

private[puzzle] final class Selector(
    puzzleColl: Coll,
    api: PuzzleApi,
    anonMinRating: Int) {

  private def popularSelector(mate: Boolean) = $doc(
    Puzzle.BSONFields.voteSum $gt mate.fold(anonMinRating, 0))

  private def mateSelector(mate: Boolean) = $doc("mate" -> mate)

  private def difficultyDecay(difficulty: Int) = difficulty match {
    case 1 => -200
    case 3 => +200
    case _ => 0
  }

  private val toleranceMax = 1000

  val anonSkipMax = 5000

  def apply(me: Option[User], difficulty: Int): Fu[Option[Puzzle]] = {
    lila.mon.puzzle.selector.count()
    val isMate = scala.util.Random.nextBoolean
    me match {
      case None =>
        puzzleColl.find(popularSelector(isMate) ++ mateSelector(isMate))
          .skip(Random nextInt anonSkipMax)
          .uno[Puzzle]
      case Some(user) =>
        val head = api.head.find(user)
        head flatMap {
          case Some(PuzzleHead(_, Some(c), _)) => api.puzzle.find(c)
          case _ =>
            val isLearn = scala.util.Random.nextInt(5) == 0
            val next = if (isLearn) api.learning.nextPuzzle(user) flatMap {
              case None => newPuzzleForUser(user, isMate, difficulty)
              case p => fuccess(p)
            }
            else newPuzzleForUser(user, isMate, difficulty)
            next flatMap {
              case Some(p) => api.head.add(PuzzleHead(user.id, Some(p.id), none))
              case _ => fuccess(none) 
            }
            next
        }
    }
  }.mon(_.puzzle.selector.time)

  private def toleranceStepFor(rating: Int) =
    math.abs(1500 - rating) match {
      case d if d >= 500 => 300
      case d if d >= 300 => 250
      case d             => 200
    }

  private def newPuzzleForUser(user: User, isMate: Boolean, difficulty: Int): Fu[Option[Puzzle]] = {
    val rating = user.perfs.puzzle.intRating min 2300 max 900
    val step = toleranceStepFor(rating)
    api.head.find(user) flatMap {
      oph => tryRange(rating, step, step, difficultyDecay(difficulty), oph match {
          case Some(PuzzleHead(_, _, Some(l))) => l
          case _ => 0
        }, 100, 100, isMate)
    }
  }

  private def tryRange(rating: Int, tolerance: Int, step: Int, decay: Int, last: PuzzleId, idRange: Int, idStep: Int, isMate: Boolean): Fu[Option[Puzzle]] =
    puzzleColl.find(mateSelector(isMate) ++ $doc(
      Puzzle.BSONFields.id $gt
        last $lt
        (last + idRange),
      Puzzle.BSONFields.rating $gt
        (rating - tolerance + decay) $lt
        (rating + tolerance + decay)
    )).sort($sort desc Puzzle.BSONFields.voteSum)
      .uno[Puzzle] flatMap {
        case None if (tolerance + step) <= toleranceMax =>
          tryRange(rating, tolerance + step, step, decay, last, idRange + idStep, idStep, isMate)
        case res => fuccess(res)
      }
}
{'fraction_non_alphanumeric': 0.11974755024082379, 'fraction_numerical': 0.018601561202458063, 'mean_word_length': 2.46889400921659, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 46, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2123665', 'n_tokens_mistral': 2106, 'n_tokens_neox': 1910, 'n_words': 488}