package xyz.driver.pdsuicommon.synchronization.db
import xyz.driver.pdsuicommon.synchronization.domain.FakeId
import xyz.driver.pdsuicommon.synchronization.utils.{FakeIdGen, Refiner}
import scala.annotation.tailrec
import scala.collection.breakOut
import scala.collection.immutable.SortedSet
object SlickDbDiff {
/**
* Calculates DB-actions to synchronize origEntities with draftEntities.
*/
def calc[DraftT, OrigT](origEntities: Iterable[OrigT], draftEntities: Iterable[DraftT])(
implicit draftFakeIdGen: FakeIdGen[DraftT],
origFakeIdGen: FakeIdGen[OrigT],
refiner: Refiner[DraftT, OrigT]): List[SlickDbAction[OrigT]] = {
val origMap: Map[FakeId, OrigT] = origEntities.map(x => origFakeIdGen(x) -> x)(breakOut)
val uniqueDraftEntities = SortedSet.newBuilder[DraftT](Ordering.by[DraftT, FakeId](draftFakeIdGen))
uniqueDraftEntities ++= draftEntities
loop(origMap, uniqueDraftEntities.result(), List.empty)
}
@tailrec private def loop[DraftT, OrigT](origEntitiesMap: Map[FakeId, OrigT],
draftEntities: Iterable[DraftT],
actions: List[SlickDbAction[OrigT]])(
implicit draftFakeIdGen: FakeIdGen[DraftT],
refiner: Refiner[DraftT, OrigT]): List[SlickDbAction[OrigT]] = {
draftEntities.headOption match {
case None =>
// The rest original entities are not a part of draft, so we will delete them
val toDelete: List[SlickDbAction[OrigT]] = origEntitiesMap.values.map(x => SlickDbAction.Delete(x))(breakOut)
actions ++ toDelete
case Some(currRaw) =>
val rawCore = draftFakeIdGen.getFor(currRaw)
val action: Option[SlickDbAction[OrigT]] = origEntitiesMap.get(rawCore) match {
// It is a new entity, because it doesn't exist among originals
case None => Some(SlickDbAction.Create(refiner.refine(currRaw)))
case Some(orig) =>
val draft = refiner.refresh(orig, currRaw)
if (draft == orig) None
else Some(SlickDbAction.Update(draft))
}
loop(origEntitiesMap - rawCore, draftEntities.tail, action.map(_ :: actions).getOrElse(actions))
}
}
}