WIP: New state machine for message processing
[aquarium] / src / main / scala / gr / grnet / aquarium / charging / OnceChargingBehavior.scala
index 1a1213d..c392d13 100644 (file)
 
 package gr.grnet.aquarium.charging
 
-import gr.grnet.aquarium.event.model.resource.ResourceEventModel
-import gr.grnet.aquarium.AquariumException
+import gr.grnet.aquarium.Aquarium
+import gr.grnet.aquarium.charging.state.UserAgreementHistoryModel
+import gr.grnet.aquarium.computation.BillingMonthInfo
+import gr.grnet.aquarium.event.{CreditsModel, DetailsModel}
+import gr.grnet.aquarium.message.avro.gen.{UserStateMsg, WalletEntryMsg, ResourcesChargingStateMsg, ResourceTypeMsg, ResourceInstanceChargingStateMsg, ResourceEventMsg}
+import gr.grnet.aquarium.message.MessageConstants
 
 /**
  * A charging behavior for which resource events just carry a credit amount that will be added to the total one.
@@ -45,38 +49,93 @@ import gr.grnet.aquarium.AquariumException
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
-final class OnceChargingBehavior
-    extends ChargingBehavior(
-      ChargingBehaviorAliases.once,
-      Set(ChargingBehaviorNameInput, CurrentValueInput)) {
+final class OnceChargingBehavior extends ChargingBehaviorSkeleton(Nil) {
+  def computeCreditsToSubtract(
+      resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
+      oldCredits: CreditsModel.Type,
+      timeDeltaMillis: Long,
+      unitPrice: CreditsModel.Type
+  ): (CreditsModel.Type, String /* explanation */) = {
 
-  /**
-   * This is called when we have the very first event for a particular resource instance, and we want to know
-   * if it is billable or not.
-   */
-  def isBillableFirstEvent(event: ResourceEventModel) = {
-    true
-  }
+    val currentValue = CreditsModel.from(resourceInstanceChargingState.getCurrentValue)
+    // Always remember to multiply with the `unitPrice`, since it scales the credits, depending on
+    // the particular resource type tha applies.
+    val credits = CreditsModel.mul(currentValue, unitPrice)
+    val explanation = "Value(%s) * UnitPrice(%s)".format(currentValue, unitPrice)
 
-  def mustGenerateDummyFirstEvent = false // no need to
+    (credits, explanation)
+  }
 
-  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]) = {
-    oldAmount
+  def computeSelectorPath(
+      chargingBehaviorDetails: DetailsModel.Type,
+      resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
+      currentResourceEvent: ResourceEventMsg,
+      referenceFromMillis: Long,
+      referenceToMillis: Long,
+      totalCredits: CreditsModel.Type
+  ): List[String] = {
+    List(MessageConstants.DefaultSelectorKey)
   }
 
-  def getResourceInstanceInitialAmount = 0.0
+  override def processResourceEvent(
+      aquarium: Aquarium,
+      resourceEvent: ResourceEventMsg,
+      resourceType: ResourceTypeMsg,
+      billingMonthInfo: BillingMonthInfo,
+      resourcesChargingState: ResourcesChargingStateMsg,
+      userAgreementHistoryModel: UserAgreementHistoryModel,
+      userStateMsg: UserStateMsg,
+      walletEntryRecorder: WalletEntryMsg ⇒ Unit
+  ): (Int, Double) = {
+    // The credits are given in the value
+    // But we cannot just apply them, since we also need to take into account the unit price.
+    // Normally, the unit price is 1.0 but we have the flexibility to allow more stuff).
 
-  def supportsImplicitEvents = false
+    // 1. Ensure proper initial state per resource and per instance
+    ensureInitializedWorkingState(resourcesChargingState,resourceEvent)
 
-  def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = false
+    // 2. Fill in data from the new event
+    val stateOfResourceInstance = resourcesChargingState.getStateOfResourceInstance
+    val resourcesChargingStateDetails = resourcesChargingState.getDetails
+    val instanceID = resourceEvent.getInstanceID
+    val resourceInstanceChargingState = stateOfResourceInstance.get(instanceID)
+    fillWorkingResourceInstanceChargingStateFromEvent(resourceInstanceChargingState, resourceEvent)
 
-  def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, occurredMillis: Long) = {
-    throw new AquariumException("constructImplicitEndEventFor() Not compliant with %s".format(this))
+    computeWalletEntriesForNewEvent(
+      resourceEvent,
+      resourceType,
+      billingMonthInfo,
+      userStateMsg.getTotalCredits,
+      resourceEvent.getOccurredMillis,
+      resourceEvent.getOccurredMillis + 1, // single point in time
+      userAgreementHistoryModel.agreementByTimeslot,
+      resourcesChargingStateDetails,
+      resourceInstanceChargingState,
+      aquarium,
+      walletEntryRecorder
+    )
+  }
+
+  def initialChargingDetails = {
+    DetailsModel.make
+  }
+
+  def computeNewAccumulatingAmount(
+      resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
+      eventDetails: DetailsModel.Type
+  ): CreditsModel.Type = {
+    CreditsModel.from(resourceInstanceChargingState.getOldAccumulatingAmount)
   }
-}
 
-object OnceChargingBehavior {
-  private[this] final val TheOne = new OnceChargingBehavior
+  def createVirtualEventsForRealtimeComputation(
+      userID: String,
+      resourceTypeName: String,
+      resourceInstanceID: String,
+      eventOccurredMillis: Long,
+      resourceInstanceChargingState: ResourceInstanceChargingStateMsg
+  ): List[ResourceEventMsg] = {
 
-  def apply(): OnceChargingBehavior = TheOne
+    // We optimize and generate no virtual event
+    Nil
+  }
 }