from Abstract import *
import sgpem
                
## @brief This is the abstract class a user-defined policy
# should inherit from
#
# This class also exposes the method sort(), which can be
# used to easily sort the queue of ready process with a
# user-defined given compare function.
class CPUPolicy:
    ## @var Avoid instantiation of an abstract class.
    # @see Abstract.Metaclass
    __metaclass__ = Metaclass

    ## @brief Configure policy to initial values
    #
    # This is called just before a simulation starts, and is responsible
    # to define the parameters the policy wants to expose to the user.
    # For example, it may make the return value of is_preemptive configurable,
    # or register an integer value for a the time slice duration.
    #
    # Should be implemented with signature:
    # @code
    # def configure(self):
    #      # function body
    # @endcode
    #
    # @see sgpem::Policy::get_parameters()
    configure = AbstractMethod('configure')

    ## @brief Sort ready processes queue
    #
    # This method is called by the scheduler at each
    # step of the simulation to sort the ready
    # processes queue.
    #
    # Should be implemented with signature:
    # @code
    # def sort_queue(self, queue):
    #      # function body
    # @endcode
    #
    # @param queue The sgpem::ReadyQueue to be sorted.
    # Only some methods of it are implemented,
    # notably get_item_at(position),
    # swap(positionA, positionB) and size().
    #
    # @see Policy::Policy::sort()
    sort_queue = AbstractMethod('sort_queue')

    ## @brief Returns whether the policy wants to be preemptive,
    # other than by normal time slice termination
    #
    # See the return value for a complete explanation. Please
    # note how the word ``priority'' here has a general meaning:
    # it indicates every process than can bubble up the sorted
    # ready queue and come before another. So it's up to
    # Policy.sort_queue() to give it a precise meaning.
    # 
    # Should be implemented with signature:
    # @code
    # def is_preemptive(self):
    #      # function body
    # @endcode
    #
    # @return True If the policy declares it wants the running
    # process to be released if a process at higher priority
    # is put at the beginning of the ready processes queue
    # @return False If the policy always waits the end of the time
    # slice (or a process blocking/termination, of course) before
    # selecting a new running process, even if it has greater priority
    # than the current one
    is_preemptive = AbstractMethod('is_preemptive')

    ## @brief Returns how long is a time-slice for this policy
    #
    # A time sliced policy should return a positive integer value,
    # a policy which doesn't use slices should instead return -1.
    # You're encouraged to use a user-configurable parameter via
    # Policy.configure() if the policy is time-sliced, to ensure
    # greater flexibility.
    # 
    # Should be implemented with signature:
    # @code
    # def get_time_slice(self):
    #      # function body
    # @endcode
    #
    # FIXME: what happens for ``return 0''? The same as ``return 1''?
    #
    # @return -1 If the policy doesn't want to use time slices
    # @return 0+ To specify a time slice duration for this policy
    get_time_slice = AbstractMethod('get_time_slice')
    
    ## @brief Returns the PolicyParameters instance you can use in
    # Policy::Policy::configure()
    #
    # @return A sgpem::PolicyParameters instance
    def get_parameters(self):
        return sgpem.CPUPolicy.callback_get_policy().get_parameters()

    
    ## @brief This function implements an in-place stable sort
    # using directly ReadyQueue methods
    #
    # The compare parameter should be a user defined binary
    # function returning either True or False, defined in one
    # of the following ways:
    # @code
    # # As a lambda anonymous function (preferred)
    # # (x and y are two DynamicSchedulable objects)
    # cmpf = lambda x,y: x.someProperty() <= y.someProperty()
    #
    # # As a normal *global* function
    # def compare(a,b):
    #     return a.someProperty <= b.someProperty()
    # cmpf = compare
    # @endcode
    #
    # The call is then simply:
    # @code
    # def sort_queue() :
    #     # ...
    #     self.sort(queue, cmpf)
    # @endcode
    #
    # Since queue.sort() uses a in-place version of the 
    # quicksort algorithm, note the effect of using "<"
    # instead than "<=": quicksort wouldn't be stable anymore.
    # You have been warned. If your policy behaves strangely,
    # this may be the cause.
    #
    # @param self The object caller
    # @param queue The ReadyQueue to be sorted in place
    # @param cmpf The binary function to use to compare elements
    # @returns None
    def sort(self, queue, cmpf):
        self.__recursive_qsort(queue, 0, queue.size()-1, cmpf)


    ## @brief Recursive (private) call to perform quicksort on a
    # queue
    #
    # @param queue The queue to sort
    # @param a The initial element position of the slice
    # @param b The final element position of the slice
    # @param cmpf The user-defined compare function to employ
    # @returns None
    def __recursive_qsort(self, queue, a, b, cmpf):
        if(b>a):
            pivot = self.__partition(queue, a, b, cmpf)
            self.__recursive_qsort(queue, a, pivot-1, cmpf)
            self.__recursive_qsort(queue, pivot+1, b, cmpf)    


    ## @brief Recursive (private) call to partition a slice of the queue
    #
    # This private function (the name mangling should work)
    # naively sorts a partition of queue in place using just
    # its methods.
    #
    # Feel the love.
    #
    # @param queue The ReadyQueue to sort
    # @param a The partition starting element position in the queue
    # @param b The partition ending element position in the queue
    # @param cmpf The binary function to use for comparing two elements
    # @return The new pivot index
    def __partition(self, queue, a, b, cmpf):
        # takes pivot element:
        right = queue.get_item_at(b)
        i = a
        for j in range(a,b): # goes from a to b-1
            if cmpf(queue.get_item_at(j), right):
                # the C++ code should do nothing if i == j:
                queue.swap(i,j)
                i = i+1
        # puts pivot in place
        queue.swap(i,b)
        return i
