package lila.openingexplorer

case class Entry(sub: Map[Tuple2[RatingGroup, SpeedGroup], SubEntry]) {

  def subEntry(ratingGroup: RatingGroup, speedGroup: SpeedGroup): SubEntry =
    sub.getOrElse((ratingGroup, speedGroup), SubEntry.empty)

  def totalGames = sub.values.map(_.totalGames).sum

  def withGameRef(game: GameRef): Entry = {
    RatingGroup.find(game.averageRating) match {
      case Some(ratingGroup) =>
        copy(sub = sub + ((ratingGroup, game.speed) -> subEntry(ratingGroup, game.speed).withGameRef(game)))
      case None =>
        this  // rating too low
    }
  }

  def select(ratingGroups: List[RatingGroup], speedGroups: List[SpeedGroup]): SubEntry =
    // cross product
    (for {
      ratingGroup <- ratingGroups
      speedGroup <- speedGroups
    } yield subEntry(ratingGroup, speedGroup))
      .foldLeft(SubEntry.empty)((l, r) => l.combine(r))

  def selectAll = select(RatingGroup.all, SpeedGroup.all)

}

object Entry {

  def empty = Entry(Map.empty)

  def fromGameRef(game: GameRef) = Entry.empty.withGameRef(game)

}

package lila.openingexplorer

case class Entry(sub: Map[Tuple2[RatingGroup, SpeedGroup], SubEntry]) {

  def subEntry(ratingGroup: RatingGroup, speedGroup: SpeedGroup): SubEntry =
    sub.getOrElse((ratingGroup, speedGroup), SubEntry.empty)

  def totalGames = sub.values.map(_.totalGames).sum

  def maxPerWinnerAndGroup = sub.values.map(_.maxPerWinner).max

  def withGameRef(game: GameRef): Entry = {
    RatingGroup.find(game.averageRating) match {
      case Some(ratingGroup) =>
        copy(sub = sub + ((ratingGroup, game.speed) -> subEntry(ratingGroup, game.speed).withGameRef(game)))
      case None =>
        this  // rating too low
    }
  }

  def select(ratings: List[RatingGroup], speeds: List[SpeedGroup]): SubEntry =
    selectGroups(Entry.groups(ratings, speeds))

  def selectAll: SubEntry = selectGroups(Entry.allGroups)

  def selectGroups(groups: List[Tuple2[RatingGroup, SpeedGroup]]): SubEntry =
    groups.map((g) => subEntry(g._1, g._2)).foldLeft(SubEntry.empty)((l, r) => l.combine(r))

}

object Entry {

  def empty = Entry(Map.empty)

  def fromGameRef(game: GameRef) = Entry.empty.withGameRef(game)

  def groups(
      ratings: List[RatingGroup],
      speeds: List[SpeedGroup]): List[Tuple2[RatingGroup, SpeedGroup]] = {
    // cross product
    for {
      ratingGroup <- ratings
      speedGroup <- speeds
    } yield (ratingGroup, speedGroup)
  }

  val allGroups = groups(RatingGroup.all, SpeedGroup.all)

}

Refactor group cross product building
Refactor group cross product building
