All Downloads are FREE. Search and download functionalities are using the official Maven repository.

no.kodeworks.kvarg.refs.package.scala Maven / Gradle / Ivy

There is a newer version: 0.7
Show newest version
package no.kodeworks.kvarg

import akka.actor.ActorRef
import akka.pattern.ask
import akka.util.Timeout
import cats.syntax.option._
import no.kodeworks.kvarg.message.{Get, GetReplyComplete, GetReplyPatch}
import no.kodeworks.kvarg.patch.Patch
import no.kodeworks.kvarg.util._
import shapeless.ops.hlist.{Fill, Length, LiftAll, Zip}
import shapeless.ops.record.{Keys, SelectAll, UnzipFields}
import shapeless.{HList, IsDistinctConstraint, LabelledGeneric, _}

import scala.collection.immutable
import scala.concurrent.{ExecutionContext, Future}
import scala.language.experimental.macros
import scala.language.{dynamics, existentials}

package object refs {
  trait Refs[Model] {
    def asMap: Map[Symbol, Option[Any]]
  }

  object Refs {
    def empty[Model] = new Refs[Model] {
      override val asMap = Map.empty
    }
  }

  sealed trait RefLookup[Model, Foreign <: HList] {
    def apply(p: Model)(implicit to: Timeout, ec: ExecutionContext): Future[Refs[Model]]

    def apply(pp: Patch[Model])(implicit to: Timeout, ec: ExecutionContext): Future[Refs[Model]]
  }

  object RefLookup {
    def apply[Model, Foreign <: HList](implicit mkRefLookup: MkRefLookup[Model, Foreign]): mkRefLookup.type = mkRefLookup

    def empty[Model, Foreign <: HList] = new RefLookup[Model, Foreign] {
      override def apply(p: Model)(implicit to: Timeout, ec: ExecutionContext): Future[Refs[Model]] =
        Future.successful(Refs.empty)

      override def apply(pp: Patch[Model])(implicit to: Timeout, ec: ExecutionContext): Future[Refs[Model]] =
        Future.successful(Refs.empty)
    }
  }

  sealed trait MkRefLookup[Model, Foreign <: HList] {
    def model: String

    def foreignKeys: Map[Symbol, Symbol]

    def apply(serviceLookup: Map[Symbol, ActorRef]): RefLookup[Model, Foreign]
  }

  implicit def mkRefLookup[
  Model
  , Foreign <: HList
  , ModelFields0 <: HList
  , ModelKeys0 <: HList
  , ForeignKeys0 <: HList
  , Overlap0 <: HList
  , ForeignValues0 <: HList
  , ForeignValueTypeables <: HList
  , ForeignValueToKeys0 <: HList
  ](implicit
    model0: Typeable[Model]
    , modelLG0: LabelledGeneric.Aux[Model, ModelFields0]
    , modelKeys0: Keys.Aux[ModelFields0, ModelKeys0]
    , foreignUnzip0: UnzipFields.Aux[Foreign, ForeignKeys0, ForeignValues0]
    , overlap0: SelectAll.Aux[ModelFields0, ForeignKeys0, Overlap0]
    , distinctForeignKeys0: IsDistinctConstraint[ForeignKeys0]
    , longOrOptionOfLong0: LongOrOptionOfLong[Overlap0]
    , foreignValueToKeys: ToKeys.Aux[ForeignValues0, ForeignValueToKeys0]
    , foreignValueTypeables: LiftAll.Aux[Typeable, ForeignValues0, ForeignValueTypeables]
    , foreignValueToKeysAreSymbols0: LUBConstraint[ForeignValueToKeys0, Symbol]
   )
  : MkRefLookup[Model, Foreign] =
    new MkRefLookup[Model, Foreign] {
      override val model = typeableToSimpleName(model0)

      override val foreignKeys =
        hlistToList[Symbol](foreignUnzip0.keys()).zip(
          hlistToList[Symbol](foreignValueToKeys())).toMap

      override def apply(serviceLookup: Map[Symbol, ActorRef]) = {
        foreignKeys.values.foreach(k => require(serviceLookup.contains(k)))

        def lookup(modelForeigns: Map[Symbol, (Long, Typeable[Any])])
                  (implicit to: Timeout, ec: ExecutionContext) = {
          Future.sequence(modelForeigns.map { case (k, (v, tt)) =>
            val svc = serviceLookup(foreignKeys(k))
            (svc ? Get[Any](v.some)(tt)).mapAll(k -> _.toOption)
          }).map(res =>
            new Refs[Model] {
              override val asMap = res.toMap.mapValues {
                case Some(GetReplyComplete(p)) => Some(p)
                case Some(GetReplyPatch(pp)) => Some(pp)
                case _ => None
              }
            }
          )
        }

        new RefLookup[Model, Foreign] {
          override def apply(p: Model)
                            (implicit to: Timeout, ec: ExecutionContext)
          : Future[Refs[Model]] = {
            val modelForeigns = transformLongOptions(hlistToList[Symbol](foreignUnzip0.keys()).zip(
              hlistToList[Any](overlap0(modelLG0.to(p))).zip(
                hlistToList[Typeable[Any]](foreignValueTypeables.instances)
              )))
            lookup(modelForeigns)
          }

          override def apply(pp: Patch[Model])(implicit to: Timeout, ec: ExecutionContext) = {
            val modelForeigns = transformLongOptions(hlistToList[Symbol](foreignUnzip0.keys()).zip(
              hlistToList[Typeable[Any]](foreignValueTypeables.instances)).flatMap{
              case (k, tt) =>
                pp.get(k).map(l => (k,(l, tt)))
            })
            lookup(modelForeigns)
          }
        }
      }
    }

  trait LongOrOptionOfLong[L <: HList] extends Serializable

  trait LongOrOptionOfLongLP {
    implicit def hlistInstance0[H, T <: HList](implicit bct: LongOrOptionOfLong[T], ev: H =:= Option[Long]) =
      new LongOrOptionOfLong[H :: T] {}
  }


  object LongOrOptionOfLong extends LongOrOptionOfLongLP {
    def apply[L <: HList](implicit lc: LongOrOptionOfLong[L]): LongOrOptionOfLong[L] = lc

    implicit def hnilInstance = new LongOrOptionOfLong[HNil] {}

    implicit def hlistInstance[H, T <: HList](implicit bct: LongOrOptionOfLong[T], ev: H =:= Long) =
      new LongOrOptionOfLong[H :: T] {}
  }


  trait ToKeys[L <: HList] extends DepFn0 with Serializable {
    type Out <: HList
  }

  object ToKeys {
    type Aux[L <: HList, Out0 <: HList] = ToKeys[L] {type Out = Out0}

    def apply[L <: HList](implicit toKeys: ToKeys[L]): toKeys.type = toKeys

    implicit def hnilToKeys[L <: HNil]: Aux[L, HNil] = new ToKeys[L] {
      type Out = HNil

      def apply(): Out = HNil
    }

    implicit def hlistToKeys[V, Rest <: HList]
    (implicit
     toKeysRest: ToKeys[Rest]
     , tt: Typeable[V]
    ): Aux[V :: Rest, Symbol :: toKeysRest.Out] = new ToKeys[V :: Rest] {
      type Out = Symbol :: toKeysRest.Out

      def apply(): Out =
        Symbol(typeableToSimpleName(tt)) :: toKeysRest()
    }
  }

  trait ToKeysNested[L <: HList] extends DepFn0 with Serializable {
    type Out <: HList
  }

  object ToKeysNested {
    type Aux[L <: HList, Out0 <: HList] = ToKeysNested[L] {type Out = Out0}

    def apply[L <: HList](implicit toKeysNested: ToKeysNested[L]): toKeysNested.type = toKeysNested

    implicit def hnilToKeysNested[L <: HNil]: Aux[L, HNil] = new ToKeysNested[L] {
      type Out = HNil

      override def apply(): Out = HNil
    }

    implicit def hlistToKeysNested[V <: HList, Rest <: HList]
    (implicit
     toKeys: ToKeys[V]
     , toKeysNestedRest: ToKeysNested[Rest]
    ): Aux[V :: Rest, toKeys.Out :: toKeysNestedRest.Out] = new ToKeysNested[V :: Rest] {
      override type Out = toKeys.Out :: toKeysNestedRest.Out

      def apply(): Out =
        toKeys() :: toKeysNestedRest()
    }
  }

  sealed trait ServiceLookup[Domains <: HList, Services] {
    def apply(services: Services): Map[Symbol, ActorRef]
  }

  object ServiceLookup {
    def apply[Domains <: HList, Services](implicit serviceLookup: ServiceLookup[Domains, Services]): serviceLookup.type = serviceLookup

    implicit def serviceLookup[
    Domains <: HList
    , Services
    , ServicesFields <: HList
    , ServicesKeys <: HList
    , ServicesValues <: HList
    , Len <: Nat
    , DomainsToKeys <: HList
    , DomainServices <: HList
    , FlatDomainServices <: HList
    ]
    (implicit
     domainsAreHLists: LUBConstraint[Domains, HList]
     , servicesFields: LabelledGeneric.Aux[Services, ServicesFields]
     , servicesUnzip: UnzipFields.Aux[ServicesFields, ServicesKeys, ServicesValues]
     , servicesKeysAreSymbols: LUBConstraint[ServicesKeys, Symbol]
     , servicesAreActorRefs: LUBConstraint[ServicesValues, ActorRef]
     , servicesLength: Length.Aux[ServicesFields, Len]
     , domainsLength: Length.Aux[Domains, Len]
     , domainToKeys: ToKeysNested.Aux[Domains, DomainsToKeys]
     , domainServices: Zip.Aux[DomainsToKeys :: ServicesValues :: HNil, DomainServices]
    )
    : ServiceLookup[Domains, Services] = {
      new ServiceLookup[Domains, Services] {
        override def apply(services: Services) = {
          val servicesValues = servicesUnzip.values(servicesFields.to(services))
          val domainServices0 = domainServices(domainToKeys() :: servicesValues :: HNil)
          hlistToList[(HList, ActorRef)](domainServices0).flatMap {
            case (keys, service) => hlistToList[Symbol](keys).map(_ -> service)
          }.toMap
        }
      }
    }
  }

  private def transformLongOptions(mf: Seq[(Symbol, (Any, Typeable[Any]))]): Map[Symbol, (Long, Typeable[Any])] =
    mf.flatMap {
      case (k, (l: Long, tt)) => (k, (l, tt)).some
      case (k, (Some(l: Long), tt)) => (k, (l, tt)).some
      case (_, (None, _)) => None
    }.toMap

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy