<commit_before>package lila.study

import com.github.blemale.scaffeine.AsyncLoadingCache
import play.api.libs.json._
import reactivemongo.api.bson._
import scala.concurrent.duration._

import chess.Color
import chess.format.pgn.Tags
import chess.format.{ FEN, Uci }

import BSONHandlers._
import JsonView._
import lila.common.config.MaxPerPage
import lila.common.paginator.{ Paginator, PaginatorJson }
import lila.db.dsl._
import lila.db.paginator.MapReduceAdapter
import lila.game.BSONHandlers.FENBSONHandler

final class StudyMultiBoard(
    runCommand: lila.db.RunCommand,
    chapterRepo: ChapterRepo,
    cacheApi: lila.memo.CacheApi
)(implicit ec: scala.concurrent.ExecutionContext) {
  private val maxPerPage = MaxPerPage(9)

  import StudyMultiBoard._
  import handlers._

  def json(studyId: Study.Id, page: Int, playing: Boolean): Fu[JsObject] = {
    if (page == 1 && !playing) firstPageCache.get(studyId)
    else fetch(studyId, page, playing)
  } map { PaginatorJson(_) }

  private val firstPageCache: AsyncLoadingCache[Study.Id, Paginator[ChapterPreview]] =
    cacheApi.scaffeine
      .refreshAfterWrite(4 seconds)
      .expireAfterAccess(10 minutes)
      .buildAsyncFuture[Study.Id, Paginator[ChapterPreview]] { fetch(_, 1, playing = false) }

  private def fetch(studyId: Study.Id, page: Int, playing: Boolean): Fu[Paginator[ChapterPreview]] = {

    val selector = $doc("studyId" -> studyId) ++ playing.??(playingSelector)

    /* If players are found in the tags,
     * return the last mainline node.
     * Else, return the root node without its children.
     */
    Paginator(
      adapter = new MapReduceAdapter[ChapterPreview](
        collection = chapterRepo.coll,
        selector = selector,
        sort = $sort asc "order",
        runCommand = runCommand,
        command = $doc(
          "map"    -> """var node = this.root, child, tagPrefixes = ['White','Black','Result'], result = {name:this.name,orientation:this.setup.orientation,tags:this.tags.filter(t => tagPrefixes.find(p => t.startsWith(p)))};
if (result.tags.lengthIs > 1) { while(child = node.n[0]) { node = child }; }
result.fen = node.f;
result.uci = node.u;
emit(this._id, result)""",
          "reduce" -> """function() {}""",
          "jsMode" -> true
        )
      )(previewBSONReader, ec),
      currentPage = page,
      maxPerPage = maxPerPage
    )
  }

  private val playingSelector = $doc("tags" -> "Result:*", "root.n.0" $exists true)

  private object handlers {

    implicit val previewBSONReader = new BSONDocumentReader[ChapterPreview] {
      def readDocument(result: BSONDocument) =
        for {
          value <- result.getAsTry[List[Bdoc]]("value")
          doc   <- value.headOption toTry "No mapReduce value?!"
          tags     = doc.getAsOpt[Tags]("tags")
          lastMove = doc.getAsOpt[Uci]("uci")
        } yield ChapterPreview(
          id = result.getAsOpt[Chapter.Id]("_id") err "Preview missing id",
          name = doc.getAsOpt[Chapter.Name]("name") err "Preview missing name",<commit_msg>fix study multiboard mongodb function<commit_after>package lila.study

import com.github.blemale.scaffeine.AsyncLoadingCache
import play.api.libs.json._
import reactivemongo.api.bson._
import scala.concurrent.duration._

import chess.Color
import chess.format.pgn.Tags
import chess.format.{ FEN, Uci }

import BSONHandlers._
import JsonView._
import lila.common.config.MaxPerPage
import lila.common.paginator.{ Paginator, PaginatorJson }
import lila.db.dsl._
import lila.db.paginator.MapReduceAdapter
import lila.game.BSONHandlers.FENBSONHandler

final class StudyMultiBoard(
    runCommand: lila.db.RunCommand,
    chapterRepo: ChapterRepo,
    cacheApi: lila.memo.CacheApi
)(implicit ec: scala.concurrent.ExecutionContext) {
  private val maxPerPage = MaxPerPage(9)

  import StudyMultiBoard._
  import handlers._

  def json(studyId: Study.Id, page: Int, playing: Boolean): Fu[JsObject] = {
    if (page == 1 && !playing) firstPageCache.get(studyId)
    else fetch(studyId, page, playing)
  } map { PaginatorJson(_) }

  private val firstPageCache: AsyncLoadingCache[Study.Id, Paginator[ChapterPreview]] =
    cacheApi.scaffeine
      .refreshAfterWrite(4 seconds)
      .expireAfterAccess(10 minutes)
      .buildAsyncFuture[Study.Id, Paginator[ChapterPreview]] { fetch(_, 1, playing = false) }

  private def fetch(studyId: Study.Id, page: Int, playing: Boolean): Fu[Paginator[ChapterPreview]] = {

    val selector = $doc("studyId" -> studyId) ++ playing.??(playingSelector)

    /* If players are found in the tags,
     * return the last mainline node.
     * Else, return the root node without its children.
     */
    Paginator(
      adapter = new MapReduceAdapter[ChapterPreview](
        collection = chapterRepo.coll,
        selector = selector,
        sort = $sort asc "order",
        runCommand = runCommand,
        command = $doc(
          "map"    -> """var node = this.root, child, tagPrefixes = ['White','Black','Result'], result = {name:this.name,orientation:this.setup.orientation,tags:this.tags.filter(t => tagPrefixes.find(p => t.startsWith(p)))};
if (result.tags.length > 1) { while(child = node.n[0]) { node = child }; }
result.fen = node.f;
result.uci = node.u;
emit(this._id, result)""",
          "reduce" -> """function() {}""",
          "jsMode" -> true
        )
      )(previewBSONReader, ec),
      currentPage = page,
      maxPerPage = maxPerPage
    )
  }

  private val playingSelector = $doc("tags" -> "Result:*", "root.n.0" $exists true)

  private object handlers {

    implicit val previewBSONReader = new BSONDocumentReader[ChapterPreview] {
      def readDocument(result: BSONDocument) =
        for {
          value <- result.getAsTry[List[Bdoc]]("value")
          doc   <- value.headOption toTry "No mapReduce value?!"
          tags     = doc.getAsOpt[Tags]("tags")
          lastMove = doc.getAsOpt[Uci]("uci")
        } yield ChapterPreview(
          id = result.getAsOpt[Chapter.Id]("_id") err "Preview missing id",
          name = doc.getAsOpt[Chapter.Name]("name") err "Preview missing name",
{'fraction_non_alphanumeric': 0.12807881773399016, 'fraction_numerical': 0.002955665024630542, 'mean_word_length': 3.4492330168005845, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 19, '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': '4219785', 'n_tokens_mistral': 1974, 'n_tokens_neox': 1892, 'n_words': 441}