<commit_before>package lila.game

import scala.util.Random

import chess.format.Forsyth
import chess.{ Color, Variant, Status }
import org.joda.time.DateTime
import play.api.libs.json._
import play.modules.reactivemongo.json.BSONFormats.toJSON
import play.modules.reactivemongo.json.ImplicitBSONHandlers.JsObjectWriter
import reactivemongo.bson.{ BSONDocument, BSONArray, BSONBinary, BSONInteger }

import lila.common.PimpedJson._
import lila.db.api._
import lila.db.BSON.BSONJodaDateTimeHandler
import lila.db.ByteArray
import lila.db.Implicits._
import lila.user.User

object GameRepo {

  import tube.gameTube

  type ID = String

  import Game._
  import Game.{ BSONFields => F }

  def game(gameId: ID): Fu[Option[Game]] = $find byId gameId

  def games(gameIds: Seq[ID]): Fu[List[Game]] = $find byOrderedIds gameIds

  def finished(gameId: ID): Fu[Option[Game]] =
    $find.one($select(gameId) ++ Query.finished)

  def player(gameId: ID, color: Color): Fu[Option[Player]] =
    $find byId gameId map2 { (game: Game) => game player color }

  def player(gameId: ID, playerId: ID): Fu[Option[Player]] =
    $find byId gameId map { gameOption =>
      gameOption flatMap { _ player playerId }
    }

  def player(playerRef: PlayerRef): Fu[Option[Player]] =
    player(playerRef.gameId, playerRef.playerId)

  def pov(gameId: ID, color: Color): Fu[Option[Pov]] =
    $find byId gameId map2 { (game: Game) => Pov(game, game player color) }

  def pov(gameId: ID, color: String): Fu[Option[Pov]] =
    Color(color) ?? (pov(gameId, _))

  def pov(playerRef: PlayerRef): Fu[Option[Pov]] =
    $find byId playerRef.gameId map { gameOption =>
      gameOption flatMap { game =>
        game player playerRef.playerId map { Pov(game, _) }
      }
    }

  def pov(fullId: ID): Fu[Option[Pov]] = pov(PlayerRef(fullId))

  def pov(ref: PovRef): Fu[Option[Pov]] = pov(ref.gameId, ref.color)

  def remove(id: ID) = $remove($select(id))

  def recentByUser(userId: String): Fu[List[Game]] = $find(
    $query(Query user userId) sort Query.sortCreated
  )

  def recentPovsByUser(user: User, nb: Int): Fu[List[Pov]] = $find(
    $query(Query user user) sort Query.sortCreated, nb
  ) map { _.flatMap(g => Pov(g, user)) }

  def chronologicalFinishedByUser(userId: String): Fu[List[Game]] = $find(
    $query(Query.finished ++ Query.rated ++ Query.user(userId)) sort ($sort asc F.createdAt)
  )

  def unrate(gameId: String) =
    $update($select(gameId), BSONDocument("$unset" -> BSONDocument(
      F.rated -> true,
      s"${F.whitePlayer}.${Player.BSONFields.ratingDiff}" -> true,
      s"${F.blackPlayer}.${Player.BSONFields.ratingDiff}" -> true
    )))

  def save(progress: Progress): Funit =
    GameDiff(progress.origin, progress.game) |> {
      case (Nil, Nil) => funit
      case (sets, unsets) => gameTube.coll.update(
        $select(progress.origin.id),
        nonEmptyMod("$set", BSONDocument(sets)) ++ nonEmptyMod("$unset", BSONDocument(unsets))
      ).void
    }

  private def nonEmptyMod(mod: String, doc: BSONDocument) =
    if (doc.isEmpty) BSONDocument() else BSONDocument(mod -> doc)

  def setRatingDiffs(id: ID, white: Int, black: Int) =
    $update($select(id), BSONDocument("$set" -> BSONDocument(
      s"${F.whitePlayer}.${Player.BSONFields.ratingDiff}" -> BSONInteger(white),
      s"${F.blackPlayer}.${Player.BSONFields.ratingDiff}" -> BSONInteger(black))))

  // used by RatingFest
  def setRatingAndDiffs(id: ID, white: (Int, Int), black: (Int, Int)) =
    $update($select(id), BSONDocument("$set" -> BSONDocument(
      s"${F.whitePlayer}.${Player.BSONFields.rating}" -> BSONInteger(white._1),
      s"${F.blackPlayer}.${Player.BSONFields.rating}" -> BSONInteger(black._1),
      s"${F.whitePlayer}.${Player.BSONFields.ratingDiff}" -> BSONInteger(white._2),
      s"${F.blackPlayer}.${Player.BSONFields.ratingDiff}" -> BSONInteger(black._2))))

  def setUsers(id: ID, white: Option[(String, Int)], black: Option[(String, Int)]) =
    (white.isDefined || black.isDefined) ?? {
      $update($select(id), BSONDocument("$set" -> BSONDocument(
        s"${F.whitePlayer}.${Player.BSONFields.rating}" -> white.map(_._2).map(BSONInteger.apply),
        s"${F.blackPlayer}.${Player.BSONFields.rating}" -> black.map(_._2).map(BSONInteger.apply),
        F.playerUids -> lila.db.BSON.writer.listO(List(~white.map(_._1), ~black.map(_._1))),
        F.playingUids -> List(white.map(_._1), black.map(_._1)).flatten.distinct
      )))
    }

  def nowPlaying(user: User): Fu[List[Pov]] =
    $find(Query nowPlaying user.id) map {
      _ flatMap { Pov(_, user) } sortBy Pov.priority
    }

  def onePlaying(user: User): Fu[Option[Pov]] = nowPlaying(user) map (_.headOption)

  def setTv(id: ID) {
    $update.fieldUnchecked(id, F.tvAt, $date(DateTime.now))
  }

  def onTv(nb: Int): Fu[List[Game]] = $find($query(Json.obj(F.tvAt -> $exists(true))) sort $sort.desc(F.tvAt), nb)

  def setAnalysed(id: ID) {
    $update.fieldUnchecked(id, F.analysed, true)
  }

  private def selectAnalysed = Json.obj(F.analysed -> true)

  def isAnalysed(id: ID): Fu[Boolean] =
    $count.exists($select(id) ++ selectAnalysed)

  def filterAnalysed(ids: Seq[String]): Fu[Set[String]] =
    $primitive(($select byIds ids) ++ selectAnalysed, "_id")(_.asOpt[String]) map (_.toSet)

  def incBookmarks(id: ID, value: Int) =
    $update($select(id), $incBson(F.bookmarks -> value))

  def setHoldAlert(pov: Pov, mean: Int, sd: Int) = {
    import Player.holdAlertBSONHandler
    $update(
      $select(pov.gameId),
      BSONDocument(
        "$set" -> BSONDocument(
          s"p${pov.color.fold(0, 1)}.${Player.BSONFields.holdAlert}" ->
            Player.HoldAlert(ply = pov.game.turns, mean = mean, sd = sd)
        )
      )
    ).void
  }

  def finish(id: ID, winnerColor: Option[Color], winnerId: Option[String]) = $update(
    $select(id),
    nonEmptyMod("$set", BSONDocument(
      F.winnerId -> winnerId,
      F.winnerColor -> winnerColor.map(_.white)
    )) ++ BSONDocument("$unset" -> BSONDocument(
      F.positionHashes -> true,
      F.checkAt -> true,
      F.playingUids -> true,
      ("p0." + Player.BSONFields.lastDrawOffer) -> true,
      ("p1." + Player.BSONFields.lastDrawOffer) -> true,
      ("p0." + Player.BSONFields.isOfferingDraw) -> true,
      ("p1." + Player.BSONFields.isOfferingDraw) -> true,
      ("p0." + Player.BSONFields.isProposingTakeback) -> true,
      ("p1." + Player.BSONFields.isProposingTakeback) -> true
    ))
  )

  def findRandomStandardCheckmate(distribution: Int): Fu[Option[Game]] = $find.one(
    Query.mate ++ Json.obj("v" -> $exists(false)),
    _ sort Query.sortCreated skip (Random nextInt distribution)
  )

  def insertDenormalized(game: Game, ratedCheck: Boolean = true): Funit = {
    val g2 = if (ratedCheck && game.rated && game.userIds.distinct.size != 2)
      game.copy(mode = chess.Mode.Casual)
    else game
    val userIds = game.userIds.distinct
    val bson = (gameTube.handler write g2) ++ BSONDocument(
      F.initialFen -> g2.variant.exotic.option(Forsyth >> g2.toChess),
      F.checkAt -> (!game.isPgnImport).option(DateTime.now.plusHours(game.hasClock.fold(1, 24))),
      F.playingUids -> userIds.nonEmpty.option(userIds)
    )
    $insert bson bson
  }

  def setCheckAt(g: Game, at: DateTime) =
    $update($select(g.id), BSONDocument("$set" -> BSONDocument(F.checkAt -> at)))

  def unsetCheckAt(g: Game) =
    $update($select(g.id), BSONDocument("$unset" -> BSONDocument(F.checkAt -> true)))

  // used to make a compound sparse index
  def setImportCreatedAt(g: Game) =
    $update($select(g.id), BSONDocument(
      "$set" -> BSONDocument("pgni.ca" -> g.createdAt)
    ))

  def saveNext(game: Game, nextId: ID): Funit = $update(
    $select(game.id),
    $set(F.next -> nextId) ++
      $unset("p0." + Player.BSONFields.isOfferingRematch, "p1." + Player.BSONFields.isOfferingRematch)
  )

  def initialFen(gameId: ID): Fu[Option[String]] =
    $primitive.one($select(gameId), F.initialFen)(_.asOpt[String]) flatMap {
      case None => fuccess(none)
      case Some(fen) => Forsyth fixCastles fen match {
        case None                        => $update($select(gameId), $unset(F.initialFen)) inject none
        case Some(fixed) if fen == fixed => fuccess(fixed.some)<commit_msg>don't show challenges in playing games<commit_after>package lila.game

import scala.util.Random

import chess.format.Forsyth
import chess.{ Color, Variant, Status }
import org.joda.time.DateTime
import play.api.libs.json._
import play.modules.reactivemongo.json.BSONFormats.toJSON
import play.modules.reactivemongo.json.ImplicitBSONHandlers.JsObjectWriter
import reactivemongo.bson.{ BSONDocument, BSONArray, BSONBinary, BSONInteger }

import lila.common.PimpedJson._
import lila.db.api._
import lila.db.BSON.BSONJodaDateTimeHandler
import lila.db.ByteArray
import lila.db.Implicits._
import lila.user.User

object GameRepo {

  import tube.gameTube

  type ID = String

  import Game._
  import Game.{ BSONFields => F }

  def game(gameId: ID): Fu[Option[Game]] = $find byId gameId

  def games(gameIds: Seq[ID]): Fu[List[Game]] = $find byOrderedIds gameIds

  def finished(gameId: ID): Fu[Option[Game]] =
    $find.one($select(gameId) ++ Query.finished)

  def player(gameId: ID, color: Color): Fu[Option[Player]] =
    $find byId gameId map2 { (game: Game) => game player color }

  def player(gameId: ID, playerId: ID): Fu[Option[Player]] =
    $find byId gameId map { gameOption =>
      gameOption flatMap { _ player playerId }
    }

  def player(playerRef: PlayerRef): Fu[Option[Player]] =
    player(playerRef.gameId, playerRef.playerId)

  def pov(gameId: ID, color: Color): Fu[Option[Pov]] =
    $find byId gameId map2 { (game: Game) => Pov(game, game player color) }

  def pov(gameId: ID, color: String): Fu[Option[Pov]] =
    Color(color) ?? (pov(gameId, _))

  def pov(playerRef: PlayerRef): Fu[Option[Pov]] =
    $find byId playerRef.gameId map { gameOption =>
      gameOption flatMap { game =>
        game player playerRef.playerId map { Pov(game, _) }
      }
    }

  def pov(fullId: ID): Fu[Option[Pov]] = pov(PlayerRef(fullId))

  def pov(ref: PovRef): Fu[Option[Pov]] = pov(ref.gameId, ref.color)

  def remove(id: ID) = $remove($select(id))

  def recentByUser(userId: String): Fu[List[Game]] = $find(
    $query(Query user userId) sort Query.sortCreated
  )

  def recentPovsByUser(user: User, nb: Int): Fu[List[Pov]] = $find(
    $query(Query user user) sort Query.sortCreated, nb
  ) map { _.flatMap(g => Pov(g, user)) }

  def chronologicalFinishedByUser(userId: String): Fu[List[Game]] = $find(
    $query(Query.finished ++ Query.rated ++ Query.user(userId)) sort ($sort asc F.createdAt)
  )

  def unrate(gameId: String) =
    $update($select(gameId), BSONDocument("$unset" -> BSONDocument(
      F.rated -> true,
      s"${F.whitePlayer}.${Player.BSONFields.ratingDiff}" -> true,
      s"${F.blackPlayer}.${Player.BSONFields.ratingDiff}" -> true
    )))

  def save(progress: Progress): Funit =
    GameDiff(progress.origin, progress.game) |> {
      case (Nil, Nil) => funit
      case (sets, unsets) => gameTube.coll.update(
        $select(progress.origin.id),
        nonEmptyMod("$set", BSONDocument(sets)) ++ nonEmptyMod("$unset", BSONDocument(unsets))
      ).void
    }

  private def nonEmptyMod(mod: String, doc: BSONDocument) =
    if (doc.isEmpty) BSONDocument() else BSONDocument(mod -> doc)

  def setRatingDiffs(id: ID, white: Int, black: Int) =
    $update($select(id), BSONDocument("$set" -> BSONDocument(
      s"${F.whitePlayer}.${Player.BSONFields.ratingDiff}" -> BSONInteger(white),
      s"${F.blackPlayer}.${Player.BSONFields.ratingDiff}" -> BSONInteger(black))))

  // used by RatingFest
  def setRatingAndDiffs(id: ID, white: (Int, Int), black: (Int, Int)) =
    $update($select(id), BSONDocument("$set" -> BSONDocument(
      s"${F.whitePlayer}.${Player.BSONFields.rating}" -> BSONInteger(white._1),
      s"${F.blackPlayer}.${Player.BSONFields.rating}" -> BSONInteger(black._1),
      s"${F.whitePlayer}.${Player.BSONFields.ratingDiff}" -> BSONInteger(white._2),
      s"${F.blackPlayer}.${Player.BSONFields.ratingDiff}" -> BSONInteger(black._2))))

  def setUsers(id: ID, white: Option[(String, Int)], black: Option[(String, Int)]) =
    (white.isDefined || black.isDefined) ?? {
      $update($select(id), BSONDocument("$set" -> BSONDocument(
        s"${F.whitePlayer}.${Player.BSONFields.rating}" -> white.map(_._2).map(BSONInteger.apply),
        s"${F.blackPlayer}.${Player.BSONFields.rating}" -> black.map(_._2).map(BSONInteger.apply),
        F.playerUids -> lila.db.BSON.writer.listO(List(~white.map(_._1), ~black.map(_._1))),
        F.playingUids -> List(white.map(_._1), black.map(_._1)).flatten.distinct
      )))
    }

  def nowPlaying(user: User): Fu[List[Pov]] =
    $find(Query nowPlaying user.id) map {
      _ flatMap { Pov(_, user) } sortBy Pov.priority
    }

  def onePlaying(user: User): Fu[Option[Pov]] = nowPlaying(user) map (_.headOption)

  def setTv(id: ID) {
    $update.fieldUnchecked(id, F.tvAt, $date(DateTime.now))
  }

  def onTv(nb: Int): Fu[List[Game]] = $find($query(Json.obj(F.tvAt -> $exists(true))) sort $sort.desc(F.tvAt), nb)

  def setAnalysed(id: ID) {
    $update.fieldUnchecked(id, F.analysed, true)
  }

  private def selectAnalysed = Json.obj(F.analysed -> true)

  def isAnalysed(id: ID): Fu[Boolean] =
    $count.exists($select(id) ++ selectAnalysed)

  def filterAnalysed(ids: Seq[String]): Fu[Set[String]] =
    $primitive(($select byIds ids) ++ selectAnalysed, "_id")(_.asOpt[String]) map (_.toSet)

  def incBookmarks(id: ID, value: Int) =
    $update($select(id), $incBson(F.bookmarks -> value))

  def setHoldAlert(pov: Pov, mean: Int, sd: Int) = {
    import Player.holdAlertBSONHandler
    $update(
      $select(pov.gameId),
      BSONDocument(
        "$set" -> BSONDocument(
          s"p${pov.color.fold(0, 1)}.${Player.BSONFields.holdAlert}" ->
            Player.HoldAlert(ply = pov.game.turns, mean = mean, sd = sd)
        )
      )
    ).void
  }

  def finish(id: ID, winnerColor: Option[Color], winnerId: Option[String]) = $update(
    $select(id),
    nonEmptyMod("$set", BSONDocument(
      F.winnerId -> winnerId,
      F.winnerColor -> winnerColor.map(_.white)
    )) ++ BSONDocument("$unset" -> BSONDocument(
      F.positionHashes -> true,
      F.checkAt -> true,
      F.playingUids -> true,
      ("p0." + Player.BSONFields.lastDrawOffer) -> true,
      ("p1." + Player.BSONFields.lastDrawOffer) -> true,
      ("p0." + Player.BSONFields.isOfferingDraw) -> true,
      ("p1." + Player.BSONFields.isOfferingDraw) -> true,
      ("p0." + Player.BSONFields.isProposingTakeback) -> true,
      ("p1." + Player.BSONFields.isProposingTakeback) -> true
    ))
  )

  def findRandomStandardCheckmate(distribution: Int): Fu[Option[Game]] = $find.one(
    Query.mate ++ Json.obj("v" -> $exists(false)),
    _ sort Query.sortCreated skip (Random nextInt distribution)
  )

  def insertDenormalized(g: Game, ratedCheck: Boolean = true): Funit = {
    val g2 = if (ratedCheck && g.rated && g.userIds.distinct.size != 2)
      g.copy(mode = chess.Mode.Casual)
    else g
    val userIds = g2.userIds.distinct
    val bson = (gameTube.handler write g2) ++ BSONDocument(
      F.initialFen -> g2.variant.exotic.option(Forsyth >> g2.toChess),
      F.checkAt -> (!g2.isPgnImport).option(DateTime.now.plusHours(g2.hasClock.fold(1, 24))),
      F.playingUids -> (g2.started && userIds.nonEmpty).option(userIds)
    )
    $insert bson bson
  }

  def setCheckAt(g: Game, at: DateTime) =
    $update($select(g.id), BSONDocument("$set" -> BSONDocument(F.checkAt -> at)))

  def unsetCheckAt(g: Game) =
    $update($select(g.id), BSONDocument("$unset" -> BSONDocument(F.checkAt -> true)))

  // used to make a compound sparse index
  def setImportCreatedAt(g: Game) =
    $update($select(g.id), BSONDocument(
      "$set" -> BSONDocument("pgni.ca" -> g.createdAt)
    ))

  def saveNext(game: Game, nextId: ID): Funit = $update(
    $select(game.id),
    $set(F.next -> nextId) ++
      $unset("p0." + Player.BSONFields.isOfferingRematch, "p1." + Player.BSONFields.isOfferingRematch)
  )

  def initialFen(gameId: ID): Fu[Option[String]] =
    $primitive.one($select(gameId), F.initialFen)(_.asOpt[String]) flatMap {
      case None => fuccess(none)
      case Some(fen) => Forsyth fixCastles fen match {
        case None                        => $update($select(gameId), $unset(F.initialFen)) inject none
        case Some(fixed) if fen == fixed => fuccess(fixed.some)
{'fraction_non_alphanumeric': 0.16614306428226197, 'fraction_numerical': 0.0038666022232962784, 'mean_word_length': 3.6484133670317327, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 127, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '3087838', 'n_tokens_mistral': 5972, 'n_tokens_neox': 5854, 'n_words': 1137}