__author__ = 'bailey'

from random import randrange

class Person:
    numMade = 0

    def __init__(self, name = "Anon", age = 0):
        self.name = name
        self.age = age
        self.id = Person.numMade
        Person.numMade += 1

    def getName(self):
        return self.name
    def getAge(self):
        return self.age
    def getID(self):
        return self.id

    def nameLessThan(self, other):
        return self.name < other.getName()
    def ageLessThan(self, other):
        return self.age < other.getAge()
    def idLessThan(self, other):
        return self.id < other.getID()

    def __str__(self):
        return "Person < " + self.name + " _ " + str(self.id) + " ... age = " + str(self.age) + " >"

class Chair:
    numMade = 0

    def __init__(self, p = None, n = None) :
        self.data = p ## intended to hold people
        self.next = n ## an 'arrow' on each chair intended to point to other chairs
        self.id = Chair.numMade
        Chair.numMade += 1

    def setData(self, p):
        self.data = p
    def setNext(self, n):
        self.next = n
    def getData(self):
        return self.data
    def getNext(self):
        return self.next
    def getID(self):
        return self.id

    def __str__(self):
        return "Chair (" + str(self.id) + ") = " + str(self.data) + " (---> " + repr(self.next) + ")"
    def __repr__(self):
        return str(self.id)


class Seating:
    def __init__(self):
        self.firstChair = None
        self.lastChair = None
        self.length = 0

    def getFirstChair(self):
        return self.firstChair
    def getLastChair(self):
        return self.lastChair

    def isEmpty(self):
        return self.length == 0

    def append(self, p):
        tempChair = Chair(p) ## thus making a chair and putting p in it
        if self.isEmpty():
            self.firstChair = tempChair
            self.lastChair = tempChair
        else:
            self.lastChair.setNext(tempChair)
            self.lastChair = self.lastChair.getNext() ## thus moving the label along by one
        self.length += 1

    def swapData(self, a, b): ## reaches in to swap the data within the chairs
        tempData = a.getData()
        a.setData(b.getData())
        b.setData(tempData)

    def sortContent(self): ## this sorts by moving the data of the chairs, not their arrows
        if self.isEmpty():
            return
        if self.length == 1:
            return
        i = self.firstChair ## about to start selection sort
        while i != self.lastChair:
            mini = i ## guess that the smallest is at the front
            j = i.getNext()
            while j != None: ## ie j is still pointing to a real chair
                if j.getData().nameLessThan(mini.getData()):
                    mini = j ## correcting our choice of min
                j = j.getNext() ## moving j along by one
            ## now to swap min and i's data
            self.swapData(mini, i)
            ##tempContent = i.getData()  ##i.setData(mini.getData())  ##mini.setData(tempContent)
            i = i.getNext() ## moving i along by one
        print(">>> all sorted!!") ## only here to help us realise that we're done.

    def merge(self, L1, L2): ## we imagine that L1 and L2 are two sorted 'seatings' of things
        if L1.isEmpty():
            return L2
        if L2.isEmpty():
            return L1
        L3 = Seating()
        i = L1.getFirstChair()
        j = L2.getFirstChair()
        while i != None and j != None: ## ie while both are in their respective 'seatings'
            if i.getData().ageLessThan(j.getData()):
                L3.append(i.getData())
                i = i.getNext()
            else:
                L3.append(j.getData())
                j = j.getNext()
        while i == None and j != None: ## ie have finished using L1
            L3.append(j.getData()) ## so append the rest of L2
            j = j.getNext()
        while i != None and j == None: ## ie have finished using L2
            L3.append(i.getData())
            i = i.getNext()
        return L3

    def sortMergy(self): ## this will apply mergesort to our 'seats'
        self.seats = self.localSortMergy(self).seats ## call the localSort to reset the seats

    def localSortMergy(self, S): ## this will apply mergesort to an input S seating
        if S.length <= 1:
            return S ## already sorted!!!!
        location = 0 ## to start off by breaking the seats into left and right hand halves
        halfway = S.length // 2
        leftHalf = Seating()
        rightHalf = Seating()
        while location < halfway:
            leftHalf.append(S.seats[location].getData())
            location += 1
        while location < S.length:
            rightHalf.append(S.seats[location].getData())
            location += 1
        sortedLeftHalf = self.localSortMergy(leftHalf) ## call localSort recursively on the LH
        sortedRightHalf = self.localSortMergy(rightHalf) ## ditto, but on the RH
        return self.merge(sortedLeftHalf, sortedRightHalf) ## return the merged version of LH and RH

    def radixSortAge(self):
        ## we'll do age as an example -- note that max value for age is < 999
        buckets = []
        tempList = []
        numDigits = 3 ## the max number of digits in the number
        base = 14 ## the number base we're using
        factor = base
        factors = [] ## to hold the various powers of the base
        factors.append(1) ## the first spot -- holds the 0th power of the base
        for digits in range(numDigits - 1):
            factors.append(factor)
            factor *= base
        for i in range(base):
            buckets.append([]) ## thus appending 10 empty buckets into the buckets list
        for seat in self.seats:
            tempList.append(seat.getData())

        for location in range(numDigits):
            for peep in tempList:
                buckets[(peep.getAge() // factors[location]) % base].append(peep)
            del tempList[:]
            for bucket in buckets:
                for peep in bucket:
                    tempList.append(peep) ## taking people out of the buckets in order
                del bucket[:] ## emptying the bucket
        loc = 0
        for seat in self.seats:
            seat.setData(tempList[loc])
            loc += 1

    def padding(self, myString, maxLength):
        if len(myString) == maxLength:
            return myString ## no need for any padding
        pads = maxLength - len(myString)
        for hole in range(pads):
            myString += " "
        return myString

    def radixSortName(self, maxLength):
        ## we'll assume that names have max length 10
        buckets = []
        tempList = []
        numChars = maxLength ## the max number of letters in the name
        chars = " abcdefghijklmnopqrstuvwxyz"
        for i in range(27): ## to allow for lc letters and a space
            buckets.append([]) ## thus appending 27 empty buckets into the buckets list
        seat = self.firstChair
        while seat != None
            tempList.append(seat.getData()) ## appending people
            seat = seat.getNext()

        for location in range(numChars):
            for peep in tempList:
                relevantChar = self.padding(peep.getName(), maxLength)[numChars - location - 1].lower()
                loc_in_chars = chars.index(relevantChar)
                buckets[ loc_in_chars ].append(peep)
            del tempList[:]
            for bucket in buckets:
                for peep in bucket:
                    tempList.append(peep) ## taking people out of the buckets in order
                del bucket[:] ## emptying the bucket
        loc = 0
        for seat in self.seats:
            seat.setData(tempList[loc])
            loc += 1

    def __str__(self):
        if self.isEmpty():
            return "there aren't any seats, so hence no people either :("
        temp = "The seating contains the following chairs ...\n"
        pod = self.firstChair
        while pod != None:
            temp += str(pod) + "\n"
            pod = pod.getNext()
        return temp

## testing area

briefNames = "QWERTYUIOPLKJHGFDSAZXCVBNM"
longNames = []

for j in range(30):
    temp = briefNames[ randrange(0 , len(briefNames))]
    for i in range(randrange(8)):
        temp += briefNames[randrange(0 , len(briefNames))].lower()
    longNames.append(temp)


s = Seating()
for let in range(len(briefNames)):
    age = randrange(10, 200)
    s.append(Person(longNames[let], age))
print(s)
s.radixSortName(4)
print("After sorting ..." + str(s))


