<commit_before>package lila.tournament

import org.joda.time.DateTime
import org.joda.time.format.ISODateTimeFormat
import play.api.libs.json._
import scala.concurrent.duration._

import lila.common.{ Lang, LightUser }
import lila.game.{ GameRepo, LightPov, Game }
import lila.hub.tournamentTeam._
import lila.quote.Quote.quoteWriter
import lila.rating.PerfType
import lila.socket.Socket.SocketVersion
import lila.user.User

final class JsonView(
    lightUserApi: lila.user.LightUserApi,
    cached: Cached,
    statsApi: TournamentStatsApi,
    shieldApi: TournamentShieldApi,
    asyncCache: lila.memo.AsyncCache.Builder,
    verify: Condition.Verify,
    duelStore: DuelStore,
    pause: Pause,
    startedSinceSeconds: Int => Boolean
) {

  import JsonView._

  private case class CachableData(
      duels: JsArray,
      featured: Option[JsObject],
      podium: Option[JsArray],
      next: Option[JsObject]
  )

  def apply(
    tour: Tournament,
    page: Option[Int],
    me: Option[User],
    getUserTeamIds: User => Fu[TeamIdList],
    playerInfoExt: Option[PlayerInfoExt],
    socketVersion: Option[SocketVersion],
    partial: Boolean,
    lang: Lang
  ): Fu[JsObject] = for {
    data <- cachableData get tour.id
    myInfo <- me ?? { myInfo(tour, _) }
    pauseDelay = me flatMap { u => pause.remainingDelay(u.id, tour) }
    full = !partial
    stand <- (myInfo, page) match {
      case (_, Some(p)) => standing(tour, p)
      case (Some(i), _) => standing(tour, i.page)
      case _ => standing(tour, 1)
    }
    playerInfoJson <- playerInfoExt ?? { pie =>
      playerInfoExtended(pie).map(_.some)
    }
    verdicts <- full ?? {
      me match {
        case None => fuccess(tour.conditions.accepted.some)
        case Some(user) if myInfo.isDefined => fuccess(tour.conditions.accepted.some)
        case Some(user) => verify(tour.conditions, user, getUserTeamIds) map some
      }
    }
    stats <- statsApi(tour)
    shieldOwner <- full.?? { shieldApi currentOwner tour }
  } yield Json.obj(
    "nbPlayers" -> tour.nbPlayers,
    "duels" -> data.duels,
    "standing" -> stand
  )
    .add("isStarted" -> tour.isStarted)
    .add("isFinished" -> tour.isFinished)
    .add("isRecentlyFinished" -> tour.isRecentlyFinished)
    .add("secondsToFinish" -> tour.isStarted.option(tour.secondsToFinish))
    .add("secondsToStart" -> tour.isCreated.option(tour.secondsToStart))
    .add("me" -> myInfo.map(myInfoJson(me, pauseDelay)))
    .add("featured" -> data.featured)
    .add("podium" -> data.podium)
    .add("playerInfo" -> playerInfoJson)
    .add("pairingsClosed" -> tour.pairingsClosed)
    .add("stats" -> stats)
    .add("next" -> data.next)
    .add("socketVersion" -> socketVersion.map(_.value)) ++ full.?? {
      Json.obj(
        "id" -> tour.id,
        "createdBy" -> tour.createdBy,
        "startsAt" -> formatDate(tour.startsAt),
        "system" -> tour.system.toString.toLowerCase,
        "fullName" -> tour.fullName,
        "minutes" -> tour.minutes,
        "perf" -> full.option(tour.perfType),
        "clock" -> full.option(tour.clock),
        "variant" -> full.option(tour.variant.key)
      ).add("spotlight" -> tour.spotlight)
        .add("berserkable" -> tour.berserkable)
        .add("position" -> full.option(tour.position).filterNot(_.initial).map(positionJson))
        .add("verdicts" -> verdicts.map(Condition.JSONHandlers.verdictsFor(_, lang)))
        .add("schedule" -> tour.schedule.map(scheduleJson))
        .add("private" -> tour.isPrivate)
        .add("quote" -> tour.isCreated.option(lila.quote.Quote.one(tour.id)))
        .add("defender" -> shieldOwner.map(_.value))
        .add("greatPlayer" -> GreatPlayer.wikiUrl(tour.name).map { url =>
          Json.obj("name" -> tour.name, "url" -> url)
        })
    }

  def standing(tour: Tournament, page: Int): Fu[JsObject] =
    if (page == 1) firstPageCache get tour.id
    else computeStanding(tour, page)

  def clearCache(id: String): Unit = {
    firstPageCache invalidate id
    cachableData invalidate id
  }

  def myInfo(tour: Tournament, me: User): Fu[Option[MyInfo]] =
    PlayerRepo.find(tour.id, me.id) flatMap {
      _ ?? { player =>
        fetchCurrentGameId(tour, me) flatMap { gameId =>
          getOrGuessRank(tour, player) map { rank =>
            MyInfo(rank + 1, player.withdraw, gameId).some
          }
        }
      }
    }

  // if the user is not yet in the cached ranking,
  // guess its rank based on other players scores in the DB
  private def getOrGuessRank(tour: Tournament, player: Player): Fu[Int] =
    cached ranking tour flatMap {
      _ get player.userId match {
        case Some(rank) => fuccess(rank)
        case None => PlayerRepo.computeRankOf(player)
      }
    }

  def playerInfoExtended(info: PlayerInfoExt): Fu[JsObject] = for {
    ranking <- cached ranking info.tour
    sheet <- cached.sheet(info.tour, info.user.id)
  } yield info match {
    case PlayerInfoExt(tour, user, player, povs) =>
      val isPlaying = povs.headOption.??(_.game.playable)
      val povScores: List[(LightPov, Option[Score])] = povs zip {
        (isPlaying ?? List(none[Score])) ::: sheet.scores.map(some)
      }
      Json.obj(
        "player" -> Json.obj(
          "id" -> user.id,
          "name" -> user.username,
          "rating" -> player.rating,
          "ratingDiff" -> 0, // # temp mobile app BC - remove me #TODO
          "score" -> player.score,
          "fire" -> player.fire,
          "nb" -> sheetNbs(user.id, sheet)
        ).add("title" -> user.title)
          .add("performance" -> player.performanceOption)
          .add("rank" -> ranking.get(user.id).map(1+))
          .add("provisional" -> player.provisional)
          .add("withdraw" -> player.withdraw),
        "pairings" -> povScores.map {
          case (pov, score) => Json.obj(
            "id" -> pov.gameId,
            "color" -> pov.color.name,
            "op" -> gameUserJson(pov.opponent.userId, pov.opponent.rating),<commit_msg>{master} remove temporary mobile app BC<commit_after>package lila.tournament

import org.joda.time.DateTime
import org.joda.time.format.ISODateTimeFormat
import play.api.libs.json._
import scala.concurrent.duration._

import lila.common.{ Lang, LightUser }
import lila.game.{ GameRepo, LightPov, Game }
import lila.hub.tournamentTeam._
import lila.quote.Quote.quoteWriter
import lila.rating.PerfType
import lila.socket.Socket.SocketVersion
import lila.user.User

final class JsonView(
    lightUserApi: lila.user.LightUserApi,
    cached: Cached,
    statsApi: TournamentStatsApi,
    shieldApi: TournamentShieldApi,
    asyncCache: lila.memo.AsyncCache.Builder,
    verify: Condition.Verify,
    duelStore: DuelStore,
    pause: Pause,
    startedSinceSeconds: Int => Boolean
) {

  import JsonView._

  private case class CachableData(
      duels: JsArray,
      featured: Option[JsObject],
      podium: Option[JsArray],
      next: Option[JsObject]
  )

  def apply(
    tour: Tournament,
    page: Option[Int],
    me: Option[User],
    getUserTeamIds: User => Fu[TeamIdList],
    playerInfoExt: Option[PlayerInfoExt],
    socketVersion: Option[SocketVersion],
    partial: Boolean,
    lang: Lang
  ): Fu[JsObject] = for {
    data <- cachableData get tour.id
    myInfo <- me ?? { myInfo(tour, _) }
    pauseDelay = me flatMap { u => pause.remainingDelay(u.id, tour) }
    full = !partial
    stand <- (myInfo, page) match {
      case (_, Some(p)) => standing(tour, p)
      case (Some(i), _) => standing(tour, i.page)
      case _ => standing(tour, 1)
    }
    playerInfoJson <- playerInfoExt ?? { pie =>
      playerInfoExtended(pie).map(_.some)
    }
    verdicts <- full ?? {
      me match {
        case None => fuccess(tour.conditions.accepted.some)
        case Some(user) if myInfo.isDefined => fuccess(tour.conditions.accepted.some)
        case Some(user) => verify(tour.conditions, user, getUserTeamIds) map some
      }
    }
    stats <- statsApi(tour)
    shieldOwner <- full.?? { shieldApi currentOwner tour }
  } yield Json.obj(
    "nbPlayers" -> tour.nbPlayers,
    "duels" -> data.duels,
    "standing" -> stand
  )
    .add("isStarted" -> tour.isStarted)
    .add("isFinished" -> tour.isFinished)
    .add("isRecentlyFinished" -> tour.isRecentlyFinished)
    .add("secondsToFinish" -> tour.isStarted.option(tour.secondsToFinish))
    .add("secondsToStart" -> tour.isCreated.option(tour.secondsToStart))
    .add("me" -> myInfo.map(myInfoJson(me, pauseDelay)))
    .add("featured" -> data.featured)
    .add("podium" -> data.podium)
    .add("playerInfo" -> playerInfoJson)
    .add("pairingsClosed" -> tour.pairingsClosed)
    .add("stats" -> stats)
    .add("next" -> data.next)
    .add("socketVersion" -> socketVersion.map(_.value)) ++ full.?? {
      Json.obj(
        "id" -> tour.id,
        "createdBy" -> tour.createdBy,
        "startsAt" -> formatDate(tour.startsAt),
        "system" -> tour.system.toString.toLowerCase,
        "fullName" -> tour.fullName,
        "minutes" -> tour.minutes,
        "perf" -> full.option(tour.perfType),
        "clock" -> full.option(tour.clock),
        "variant" -> full.option(tour.variant.key)
      ).add("spotlight" -> tour.spotlight)
        .add("berserkable" -> tour.berserkable)
        .add("position" -> full.option(tour.position).filterNot(_.initial).map(positionJson))
        .add("verdicts" -> verdicts.map(Condition.JSONHandlers.verdictsFor(_, lang)))
        .add("schedule" -> tour.schedule.map(scheduleJson))
        .add("private" -> tour.isPrivate)
        .add("quote" -> tour.isCreated.option(lila.quote.Quote.one(tour.id)))
        .add("defender" -> shieldOwner.map(_.value))
        .add("greatPlayer" -> GreatPlayer.wikiUrl(tour.name).map { url =>
          Json.obj("name" -> tour.name, "url" -> url)
        })
    }

  def standing(tour: Tournament, page: Int): Fu[JsObject] =
    if (page == 1) firstPageCache get tour.id
    else computeStanding(tour, page)

  def clearCache(id: String): Unit = {
    firstPageCache invalidate id
    cachableData invalidate id
  }

  def myInfo(tour: Tournament, me: User): Fu[Option[MyInfo]] =
    PlayerRepo.find(tour.id, me.id) flatMap {
      _ ?? { player =>
        fetchCurrentGameId(tour, me) flatMap { gameId =>
          getOrGuessRank(tour, player) map { rank =>
            MyInfo(rank + 1, player.withdraw, gameId).some
          }
        }
      }
    }

  // if the user is not yet in the cached ranking,
  // guess its rank based on other players scores in the DB
  private def getOrGuessRank(tour: Tournament, player: Player): Fu[Int] =
    cached ranking tour flatMap {
      _ get player.userId match {
        case Some(rank) => fuccess(rank)
        case None => PlayerRepo.computeRankOf(player)
      }
    }

  def playerInfoExtended(info: PlayerInfoExt): Fu[JsObject] = for {
    ranking <- cached ranking info.tour
    sheet <- cached.sheet(info.tour, info.user.id)
  } yield info match {
    case PlayerInfoExt(tour, user, player, povs) =>
      val isPlaying = povs.headOption.??(_.game.playable)
      val povScores: List[(LightPov, Option[Score])] = povs zip {
        (isPlaying ?? List(none[Score])) ::: sheet.scores.map(some)
      }
      Json.obj(
        "player" -> Json.obj(
          "id" -> user.id,
          "name" -> user.username,
          "rating" -> player.rating,
          "score" -> player.score,
          "fire" -> player.fire,
          "nb" -> sheetNbs(user.id, sheet)
        ).add("title" -> user.title)
          .add("performance" -> player.performanceOption)
          .add("rank" -> ranking.get(user.id).map(1+))
          .add("provisional" -> player.provisional)
          .add("withdraw" -> player.withdraw),
        "pairings" -> povScores.map {
          case (pov, score) => Json.obj(
            "id" -> pov.gameId,
            "color" -> pov.color.name,
            "op" -> gameUserJson(pov.opponent.userId, pov.opponent.rating),
{'fraction_non_alphanumeric': 0.14734724666834298, 'fraction_numerical': 0.0007543374402816193, 'mean_word_length': 2.6952616909259834, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 144, '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': '5514970', 'n_tokens_mistral': 3993, 'n_tokens_neox': 3867, 'n_words': 781}