Implement Once behavior with the new scheme. Refactor in the process
[aquarium] / src / main / scala / gr / grnet / aquarium / computation / TimeslotComputations.scala
1 /*
2  * Copyright 2011-2012 GRNET S.A. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or
5  * without modification, are permitted provided that the following
6  * conditions are met:
7  *
8  *   1. Redistributions of source code must retain the above
9  *      copyright notice, this list of conditions and the following
10  *      disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above
13  *      copyright notice, this list of conditions and the following
14  *      disclaimer in the documentation and/or other materials
15  *      provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  * The views and conclusions contained in the software and
31  * documentation are those of the authors and should not be
32  * interpreted as representing official policies, either expressed
33  * or implied, of GRNET S.A.
34  */
35
36 package gr.grnet.aquarium.computation
37
38 import collection.immutable.SortedMap
39 import gr.grnet.aquarium.util.Loggable
40 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
41 import gr.grnet.aquarium.policy._
42 import collection.immutable
43 import gr.grnet.aquarium.policy.EffectiveUnitPrice
44 import gr.grnet.aquarium.charging.Chargeslot
45
46 /**
47  * Methods for converting accounting events to wallet entries.
48  *
49  * @author Georgios Gousios <gousiosg@gmail.com>
50  * @author Christos KK Loverdos <loverdos@gmail.com>
51  */
52 object TimeslotComputations extends Loggable {
53
54   /**
55    * Breaks a reference timeslot (e.g. billing period) according to policies and agreements.
56    *
57    * @param referenceTimeslot
58    * @param policyTimeslots
59    * @param agreementTimeslots
60    * @return
61    */
62   protected
63   def splitTimeslotByPoliciesAndAgreements(
64       referenceTimeslot: Timeslot,
65       policyTimeslots: List[Timeslot],
66       agreementTimeslots: List[Timeslot]
67   ): List[Timeslot] = {
68
69     // Align policy and agreement validity timeslots to the referenceTimeslot
70     val alignedPolicyTimeslots = referenceTimeslot.align(policyTimeslots)
71     val alignedAgreementTimeslots = referenceTimeslot.align(agreementTimeslots)
72
73     val result = alignTimeslots(alignedPolicyTimeslots, alignedAgreementTimeslots)
74
75     result
76   }
77
78   /**
79    * Given a reference timeslot, we have to break it up to a series of timeslots where a particular
80    * algorithm and price unit is in effect.
81    *
82    */
83   protected
84   def resolveEffectiveUnitPrices(
85       alignedTimeslot: Timeslot,
86       policy: PolicyModel,
87       agreement: UserAgreementModel,
88       effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable
89   ): SortedMap[Timeslot, Double] = {
90
91     // Note that most of the code is taken from calcChangeChunks()
92     val ret = resolveEffectiveUnitPricesForTimeslot(alignedTimeslot, policy, agreement, effectivePriceTableSelector)
93     ret map {case (t,p) => (t,p.unitPrice)}
94   }
95
96   def computeInitialChargeslots(
97       referenceTimeslot: Timeslot,
98       policyByTimeslot: SortedMap[Timeslot, PolicyModel],
99       agreementByTimeslot: SortedMap[Timeslot, UserAgreementModel],
100       effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable
101   ): List[Chargeslot] = {
102
103     val policyTimeslots = policyByTimeslot.keySet
104     val agreementTimeslots = agreementByTimeslot.keySet
105
106     def getPolicyWithin(ts: Timeslot): PolicyModel = {
107       policyByTimeslot.find(_._1.contains(ts)).get._2
108     }
109     def getAgreementWithin(ts: Timeslot): UserAgreementModel = {
110       agreementByTimeslot.find(_._1.contains(ts)).get._2
111     }
112
113     // 1. Round ONE: split time according to overlapping policies and agreements.
114     //val alignedTimeslots = List(referenceTimeslot) //splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList, Just(clog))
115     val alignedTimeslots = splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList)
116
117     // 2. Round TWO: Use the aligned timeslots of Round ONE to produce even more
118     //    fine-grained timeslots according to applicable algorithms.
119     //    Then pack the info into charge slots.
120     //    clog.begin("ROUND 2")
121     val allChargeslots = for {
122       alignedTimeslot <- alignedTimeslots
123     } yield {
124       //val policy = policyByTimeslot.valuesIterator.next()//getPolicyWithin(alignedTimeslot)
125       val policy = getPolicyWithin(alignedTimeslot)
126       //      clog.debug("dslPolicy = %s", dslPolicy)
127       //val userAgreement = agreementByTimeslot.valuesIterator.next()//getAgreementWithin(alignedTimeslot)
128       val userAgreement = getAgreementWithin(alignedTimeslot)
129
130       val unitPriceByTimeslot = resolveEffectiveUnitPrices(
131         alignedTimeslot,
132         policy,
133         userAgreement,
134         effectivePriceTableSelector
135       )
136
137       // Now, the timeslots must be the same
138       val finegrainedTimeslots = unitPriceByTimeslot.keySet
139
140       val chargeslots = for (finegrainedTimeslot ← finegrainedTimeslots) yield {
141         Chargeslot(
142           finegrainedTimeslot.from.getTime,
143           finegrainedTimeslot.to.getTime,
144           unitPriceByTimeslot(finegrainedTimeslot)
145         )
146       }
147
148       chargeslots.toList
149     }
150
151     val result = allChargeslots.flatten
152
153     result
154   }
155
156   /**
157    * Given two lists of timeslots, produce a list which contains the
158    * set of timeslot slices, as those are defined by
159    * timeslot overlaps.
160    *
161    * For example, given the timeslots a and b below, split them as shown.
162    *
163    * a = |****************|
164    * ^                ^
165    * a.from            a.to
166    * b = |*********|
167    * ^         ^
168    * b.from     b.to
169    *
170    * result: List(Timeslot(a.from, b.to), Timeslot(b.to, a.to))
171    */
172   private[computation] def alignTimeslots(a: List[Timeslot],
173                                     b: List[Timeslot]): List[Timeslot] = {
174
175     def safeTail(foo: List[Timeslot]) = foo match {
176       case Nil => List()
177       case x :: Nil => List()
178       case x :: rest => rest
179     }
180
181     if(a.isEmpty) return b
182     if(b.isEmpty) return a
183
184     assert(a.head.from == b.head.from)
185
186     if(a.head.endsAfter(b.head)) {
187       val slice = a.head.slice(b.head.to)
188       slice.head :: alignTimeslots(slice.last :: a.tail, safeTail(b))
189     } else if(b.head.endsAfter(a.head)) {
190       val slice = b.head.slice(a.head.to)
191       slice.head :: alignTimeslots(safeTail(a), slice.last :: b.tail)
192     } else {
193       a.head :: alignTimeslots(safeTail(a), safeTail(b))
194     }
195   }
196
197     type PriceMap =  immutable.SortedMap[Timeslot, EffectiveUnitPrice]
198     private type PriceList = List[EffectiveUnitPrice]
199     private def emptyMap = immutable.SortedMap[Timeslot,EffectiveUnitPrice]()
200
201     /**
202      * Resolves the effective price list for each chunk of the
203      * provided timeslot and returns it as a Map
204      */
205     private[this] def resolveEffectiveUnitPricesForTimeslot(
206         alignedTimeslot: Timeslot,
207         policy: PolicyModel,
208         agreement: UserAgreementModel,
209         effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable
210     ): PriceMap = {
211
212       val fullPriceTable = agreement.computeFullPriceTable(policy)
213       val effectivePriceTable = effectivePriceTableSelector(fullPriceTable)
214
215       resolveEffective(alignedTimeslot, effectivePriceTable.priceOverrides)
216       //immutable.SortedMap(alignedTimeslot -> effectivePriceTable.priceOverrides.head)
217     }
218
219     private def printPriceList(p: PriceList) : Unit = {
220       Console.err.println("BEGIN PRICE LIST")
221       for { p1 <- p } Console.err.println(p1)
222       Console.err.println("END PRICE LIST")
223     }
224
225     private def printPriceMap(m: PriceMap) = {
226       Console.err.println("BEGIN PRICE MAP")
227       for { (t,p) <- m.toList } Console.err.println("Timeslot " + t + "\t\t" + p)
228       Console.err.println("END PRICE MAP")
229     }
230
231     private def resolveEffective(alignedTimeslot: Timeslot,p:PriceList): PriceMap = {
232       //Console.err.println("\n\nInput timeslot: " + alignedTimeslot + "\n\n")
233       //printPriceList(p)
234       val ret =  resolveEffective3(alignedTimeslot,p) //HERE
235       //printPriceMap(ret)
236       ret
237     }
238
239
240     private def resolveEffective3(alignedTimeslot: Timeslot, effectiveUnitPrices: PriceList): PriceMap =
241       effectiveUnitPrices match {
242         case Nil =>
243           emptyMap
244         case hd::tl =>
245           val (satisfied,notSatisfied) = hd splitTimeslot alignedTimeslot
246           val satisfiedMap = satisfied.foldLeft (emptyMap)  {(map,t) =>
247           //Console.err.println("Adding timeslot" + t +
248           // " for policy " + policy.name)
249             map + ((t,hd))
250           }
251           val notSatisfiedMap = notSatisfied.foldLeft (emptyMap) {(map,t) =>
252             val otherMap = resolveEffective3(t,tl)
253             //Console.err.println("Residual timeslot: " + t)
254             val ret = map ++ otherMap
255             ret
256           }
257           val ret = satisfiedMap ++ notSatisfiedMap
258           ret
259       }
260 }