<commit_before>package com.michalrus.nofatty.data

import java.util.UUID
import java.util.concurrent.atomic.AtomicReference

import scala.slick.driver.SQLiteDriver.simple._
import org.joda.time.{ LocalDate, DateTime }

import DB.discard

import scala.slick.jdbc.JdbcBackend

final case class NutritionalValue(kcal: Double, protein: Double, fat: Double, carbohydrate: Double, fiber: Double) {
  def +(that: NutritionalValue): NutritionalValue = NutritionalValue(
    this.kcal + that.kcal, this.protein + that.protein, this.fat + that.fat, this.carbohydrate + that.carbohydrate, this.fiber + that.fiber)
  def *(scale: Double): NutritionalValue = NutritionalValue(
    this.kcal * scale, this.protein * scale, this.fat * scale, this.carbohydrate * scale, this.fiber * scale)
}

object NutritionalValue {
  val Zero = NutritionalValue(0.0, 0.0, 0.0, 0.0, 0.0)
  val PerGrams = 100.0

  def weightedMean(xs: Seq[(NutritionalValue, Double)]): NutritionalValue = {
    val weightSum = xs.map(_._2).sum
    val nutrSum = sum(xs map (x ⇒ x._1 * x._2))
    nutrSum * (1.0 / weightSum)
  }

  def sum(xs: Seq[NutritionalValue]): NutritionalValue = xs.foldLeft(Zero)(_ + _)
}

sealed trait Product {
  def uuid: UUID
  def lastModified: DateTime
  def name: String
  def nutrition: NutritionalValue
}

final case class BasicProduct(uuid: UUID, lastModified: DateTime, name: String, nutrition: NutritionalValue,
                              kcalExpr: String, proteinExpr: String, fatExpr: String, carbohydrateExpr: String, fiberExpr: String) extends Product

final case class CompoundProduct(uuid: UUID, lastModified: DateTime, name: String, massReduction: Double,
                                 massPreExpr: String, massPostExpr: String, ingredients: Map[UUID, (Double, String)]) extends Product {
  lazy val nutrition: NutritionalValue = {
    val xs = ingredients flatMap { case (id, (grams, gramsExpr)) ⇒ Products find id map (p ⇒ (p.nutrition, grams)) }
    if (xs.isEmpty) NutritionalValue.Zero
    else NutritionalValue.weightedMean(xs.toSeq) * massReduction
  }
}

object Products {

  private[this] val memo = new AtomicReference[Map[UUID, Product]]({
    DB.db withSession { implicit session ⇒
      val basics: Seq[Product] = DB.basicProducts.run map {
        case (uuid, lastMod, name, kcalE, kcal, protE, prot, fatE, fat, carbE, carb, fibE, fib) ⇒
          BasicProduct(uuid, lastMod, name, NutritionalValue(kcal, prot, fat, carb, fib), kcalE, protE, fatE, carbE, fibE)
      }
      val compounds: Seq[Product] = DB.compoundProducts.run map {
        case (uuid, lastMod, name, massRed, massPreE, massPostE) ⇒
          val ings = DB.ingredients.filter(_.compoundProductID === uuid).run map { case (_, subprod, gramsE, grams) ⇒ (subprod, (grams, gramsE)) }
          CompoundProduct(uuid, lastMod, name, massRed, massPreE, massPostE, ings.toMap)
      }
      (basics ++ compounds).map(p ⇒ (p.uuid, p)).toMap
    }
  })

  def names: Map[String, UUID] = memo.get map { case (u, p) ⇒ (p.name, u) }

  def find(uuid: UUID): Option[Product] = memo.get.get(uuid)

  private[this] def uncheckedDelete(uuid: UUID)(implicit session: JdbcBackend.Session): Unit = {
    discard { DB.basicProducts.filter(_.uuid === uuid).delete }<commit_msg>Check for potential cycles in the graph of Products<commit_after>package com.michalrus.nofatty.data

import java.util.UUID
import java.util.concurrent.atomic.AtomicReference

import scala.slick.driver.SQLiteDriver.simple._
import org.joda.time.{ LocalDate, DateTime }

import DB.discard

import scala.slick.jdbc.JdbcBackend

final case class NutritionalValue(kcal: Double, protein: Double, fat: Double, carbohydrate: Double, fiber: Double) {
  def +(that: NutritionalValue): NutritionalValue = NutritionalValue(
    this.kcal + that.kcal, this.protein + that.protein, this.fat + that.fat, this.carbohydrate + that.carbohydrate, this.fiber + that.fiber)
  def *(scale: Double): NutritionalValue = NutritionalValue(
    this.kcal * scale, this.protein * scale, this.fat * scale, this.carbohydrate * scale, this.fiber * scale)
}

object NutritionalValue {
  val Zero = NutritionalValue(0.0, 0.0, 0.0, 0.0, 0.0)
  val PerGrams = 100.0

  def weightedMean(xs: Seq[(NutritionalValue, Double)]): NutritionalValue = {
    val weightSum = xs.map(_._2).sum
    val nutrSum = sum(xs map (x ⇒ x._1 * x._2))
    nutrSum * (1.0 / weightSum)
  }

  def sum(xs: Seq[NutritionalValue]): NutritionalValue = xs.foldLeft(Zero)(_ + _)
}

sealed trait Product {
  def uuid: UUID
  def lastModified: DateTime
  def name: String
  def nutrition: NutritionalValue
}

final case class BasicProduct(uuid: UUID, lastModified: DateTime, name: String, nutrition: NutritionalValue,
                              kcalExpr: String, proteinExpr: String, fatExpr: String, carbohydrateExpr: String, fiberExpr: String) extends Product

final case class CompoundProduct(uuid: UUID, lastModified: DateTime, name: String, massReduction: Double,
                                 massPreExpr: String, massPostExpr: String, ingredients: Map[UUID, (Double, String)]) extends Product {
  lazy val nutrition: NutritionalValue = {
    val xs = ingredients flatMap { case (id, (grams, gramsExpr)) ⇒ Products find id map (p ⇒ (p.nutrition, grams)) }
    if (xs.isEmpty) NutritionalValue.Zero
    else NutritionalValue.weightedMean(xs.toSeq) * massReduction
  }
  /** Checks for potential cycles in the graph of Products */
  def couldContain(subproduct: UUID): Boolean =
    Products.find(subproduct) match {
      case Some(sub: BasicProduct)    ⇒ true
      case Some(sub: CompoundProduct) ⇒ !(sub ingredientsContain this.uuid)
      case None                       ⇒ false
    }
  def ingredientsContain(subproduct: UUID): Boolean =
    uuid == subproduct ||
      (ingredients contains subproduct) ||
      (ingredients.keySet flatMap Products.find collect { case cp: CompoundProduct ⇒ cp } exists (_ ingredientsContain subproduct))
}

object Products {

  private[this] val memo = new AtomicReference[Map[UUID, Product]]({
    DB.db withSession { implicit session ⇒
      val basics: Seq[Product] = DB.basicProducts.run map {
        case (uuid, lastMod, name, kcalE, kcal, protE, prot, fatE, fat, carbE, carb, fibE, fib) ⇒
          BasicProduct(uuid, lastMod, name, NutritionalValue(kcal, prot, fat, carb, fib), kcalE, protE, fatE, carbE, fibE)
      }
      val compounds: Seq[Product] = DB.compoundProducts.run map {
        case (uuid, lastMod, name, massRed, massPreE, massPostE) ⇒
          val ings = DB.ingredients.filter(_.compoundProductID === uuid).run map { case (_, subprod, gramsE, grams) ⇒ (subprod, (grams, gramsE)) }
          CompoundProduct(uuid, lastMod, name, massRed, massPreE, massPostE, ings.toMap)
      }
      (basics ++ compounds).map(p ⇒ (p.uuid, p)).toMap
    }
  })

  def names: Map[String, UUID] = memo.get map { case (u, p) ⇒ (p.name, u) }

  def find(uuid: UUID): Option[Product] = memo.get.get(uuid)

  private[this] def uncheckedDelete(uuid: UUID)(implicit session: JdbcBackend.Session): Unit = {
    discard { DB.basicProducts.filter(_.uuid === uuid).delete }
{'fraction_non_alphanumeric': 0.11883281646461799, 'fraction_numerical': 0.0053566394135889485, 'mean_word_length': 3.755361930294906, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, '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': '2365975', 'n_tokens_mistral': 2414, 'n_tokens_neox': 2295, 'n_words': 644}