# Created By Kumail Raza
# on 8-03-2017

import numpy as np
import unittest		#python unit test library
import hashlib      # to calculate hash (md5)

class restraunt:

    def __init__(self, open=1100, close = 2200, max_time = 30):
        self.__opentime = open
        self.__closetime = close
        self.__maxtime = max_time
        self.capacity={"extra_large":12, "large":6, "medium":4, "small":2}
        self.__booktable = None
    def set_tables(self, extra_large=0, large=0, medium=0, small=0):
        self.tables={"extra_large":extra_large, "large":large, "medium":medium, "small":small}
        if self.__booktable == None:
            self.__booktable = np.ones((30,sum(self.tables.values()),24))
            self.__booktable[:,:,0:self.__opentime/100] = 0
            self.__booktable[:, :, self.__closetime/100: 24] = 0

    def book_table(self, time, day, people, duration=2, username="root", password="admin", db=None):
        temp = people
        while(people< 2*temp):
            temp2 = people
            people += people%2 #This makes people even. Now we treat it as a coin change problem
            table =np.copy(self.__booktable)
            if people <=6:
                duration = 2
            if people >6 and people <= 12:
                if np.sum(table[day,0,time:time+duration]) == duration:
                    table[day, 0, time:time + duration] = 0
                    #print "RESERVING ONE EXTRA LARGE TABLE FOR "+str(duration) +" hours from "+ str(time) +" on day " + str(day)
                    people = people-12
            if people >4 :
                for a in range(0, self.tables["large"]):
                    if np.sum(table[day,self.tables["extra_large"]+a,time:time+duration]) == duration and people >4:
                        table[day,self.tables["extra_large"]+a,time:time+duration] = 0
                        #print "RESERVING ONE LARGE TABLE FOR " + str(duration) + " hours from " + str(
                            # time) + " on day " + str(day)
                        people = people-6
            if people > 2 :
                for a in range(0, self.tables["medium"]):
                    if people >2 and np.sum(table[day, self.tables["extra_large"]+self.tables["large"]+a, time:time+duration]) == duration:
                        table[day, self.tables["extra_large"]+self.tables["large"]+a, time:time+duration]=0
                        people = people-4
            if people > 0 :
               for a in range(0, self.tables["small"]):
                   if people > 0 and np.sum(table[day, self.tables["medium"]+ self.tables["extra_large"]+self.tables["large"]+a, time:time+duration]) == duration:
                       table[day, self.tables["medium"]+self.tables["extra_large"]+self.tables["large"]+a, time:time+duration]=0
                       people = people-2

            if people <=0:
                people = temp2
                people += people % 2  # This makes people even. Now we treat it as a coin change problem
                table = self.__booktable
                if people <= 6:
                    duration = 2
                if people > 6 and people <= 12:
                    if np.sum(table[day, 0, time:time + duration]) == duration:
                        table[day, 0, time:time + duration] = 0
                        print "RESERVING ONE EXTRA LARGE TABLE FOR " + str(duration) + " hours from " + str(
                            time) + " on day " + str(day)
                        if db != None:
                            db.add_booking(username, 0, str(duration) + " hours from " + str(time) + " on day " + str(day))

                        people = people - 12
                if people > 4:
                    for a in range(0, self.tables["large"]):
                        if np.sum(table[day, self.tables["extra_large"] + a, time:time + duration]) == duration and people > 4:
                            table[day, self.tables["extra_large"] + a, time:time + duration] = 0
                            print "RESERVING ONE LARGE TABLE FOR " + str(duration) + " hours from " + str(
                                time) + " on day " + str(day)
                            if db != None:
                                db.add_booking(username, self.tables["extra_large"]+a,
                                               str(duration) + " hours from " + str(time) + " on day " + str(day))
                            people = people - 6
                if people > 2:
                    for a in range(0, self.tables["medium"]):
                        if people > 2 and np.sum(table[day, self.tables["extra_large"] + self.tables["large"] + a,
                                                 time:time + duration]) == duration:
                            table[day, self.tables["extra_large"] + self.tables["large"] + a, time:time + duration] = 0
                            if db != None:
                                db.add_booking(username, self.tables["extra_large"]+ self.tables["large"] + a,
                                               str(duration) + " hours from " + str(time) + " on day " + str(day))
                            people = people - 4
                if people > 0:
                    for a in range(0, self.tables["small"]):
                        if people > 0 and np.sum(
                                table[day, self.tables["medium"] + self.tables["extra_large"] + self.tables["large"] + a,
                                time:time + duration]) == duration:
                            table[day, self.tables["medium"] + self.tables["extra_large"] + self.tables["large"] + a,
                            time:time + duration] = 0
                            if db != None:
                                db.add_booking(username, self.tables["medium"] + self.tables["extra_large"] + self.tables["large"] + a,
                                               str(duration) + " hours from " + str(time) + " on day " + str(day))
                            people = people - 2
                if people <= 0:
                    return True
            people = temp2+people


            return False
        return False

    def get_table(self):
        return self.__booktable

# ************ MY SQL WRAPPER FUNCTIONS **********

import MySQLdb
class database_connection:

    def __init__(self, address, uname, password, dbname ):
        self.address = address
        self.username = uname
        self.password = password
        self.dbname = dbname
    def connect(self):
        self.db = MySQLdb.connect(self.address, self.username, self.password, self.dbname)
        self.cursor = self.db.cursor()
    def execute(self, username, password, query):
        self.cursor.execute(query)
        self.db.commit()
    def authenticate(self, username, password, type= "staff"):
        self.cursor.execute("SELECT pass_hash from "+type+" where username = '"+username+"'")
        results = self.cursor.fetchall()
        if results:
            if results[0][0] ==  hashlib.md5(password).hexdigest():
                return True
            else:
                return False
        return False

    def log(self, msg, user, message_type="log"):
        from datetime import datetime
        print "INSERT INTO "+message_type+" (user, msg, time) VALUES ('"+user+"','"+msg+"','"+str(8)+"')"
        self.cursor.execute("INSERT INTO "+message_type+" (user, msg, time) VALUES ('"+user+"','"+msg+"','"+str(datetime.now())+"')")
        self.db.commit()

    def add_user(self, username, password):
        self.cursor.execute("INSERT INTO staff (username, pass_hash) VALUES ('"+username+"','"+self.__compute_hash(password)+"')")
        self.log("Adding User", username)
        self.db.commit()

    def authenticate_user(self, user, password):
        self.log("Authenticating USER", user)
        return self.authenticate(user, password, "user")

    def __compute_hash(self, password):
        return hashlib.md5(password).hexdigest()
    def add_booking(self, username, table_id, timing):
        query = "INSERT INTO bookings(username, table_id, time) VALUES('"+username+"', "+str(table_id)+", '"+timing+"')"
        self.log("Book log "+timing, username)
        print query
        self.cursor.execute(query)
        self.db.commit()


# ********** UNIT TESTS ***********

class TestReservationSystem(unittest.TestCase):

    def test_reserve_twelve(self):
        db =  database_connection("127.0.0.1", "root", "root", "restaurant_system")
        db.connect()
        myRestraunt =  restraunt()
        myRestraunt.set_tables(1, 3, 8, 4)
        self.assertTrue(myRestraunt.book_table(12,0,12,3, "yagami", db=db))
        self.assertTrue(myRestraunt.book_table(12, 0, 12, 3, "yagami", db=db))
        self.assertFalse(myRestraunt.book_table(4, 0, 12, 5, "yagami", db=db)) #Restraunt close at 4
        self.assertFalse(myRestraunt.book_table(21,0,12,2, "yagami", db=db))   #Should be false since book time exceel close time

    def test_reserve_six(self):
        myRestraunt =  restraunt()
        myRestraunt.set_tables(1, 3, 8, 4)
        self.assertTrue(myRestraunt.book_table(12,0,6,2))
        self.assertTrue(myRestraunt.book_table(12, 4, 6, 4))
        self.assertTrue(myRestraunt.book_table(12, 4, 6, 4))
        self.assertTrue(myRestraunt.book_table(12, 4, 6, 4))
        self.assertTrue(myRestraunt.book_table(12, 4, 6, 4))       #Because only 3 large tables
    def test_reserve_4(self):
        myRestraunt =  restraunt()
        myRestraunt.set_tables(1, 3, 8, 4)
        self.assertTrue(myRestraunt.book_table(12, 0, 4, 2))
        self.assertTrue(myRestraunt.book_table(12, 4, 4, 4))
        self.assertFalse(myRestraunt.book_table(21,4,4,4))

    def test_reserve_2(self):
        myRestraunt =  restraunt()
        myRestraunt.set_tables(1, 3, 8, 4)
        self.assertTrue(myRestraunt.book_table(12, 0, 2, 2))
        self.assertTrue(myRestraunt.book_table(12, 4, 2, 4))
        self.assertFalse(myRestraunt.book_table(21,4,2,4))

    def test_book_after_close(self):
        myRestraunt =  restraunt()
        myRestraunt.set_tables(1, 3, 8, 4)
        self.assertFalse(myRestraunt.book_table(23, 0, 12, 2))
        self.assertFalse(myRestraunt.book_table(21, 4, 6, 4))
        self.assertFalse(myRestraunt.book_table(4, 4, 4, 4))

    def book_random(self):
        myRestraunt =  restraunt()
        myRestraunt.set_tables(1, 3, 8, 4)
        self.assertFalse(myRestraunt.book_table(23, 0, 12, 2))
        self.assertFalse(myRestraunt.book_table(21, 4, 6, 4))
        self.assertFalse(myRestraunt.book_table(4, 4, 4, 4))

    def test_authentication(self):
        myDb =  database_connection("127.0.0.1", "root", "root", "restaurant_system")
        myDb.connect()
        #myDb.add_user("boss", "1234")
        self.assertTrue(myDb.authenticate("root", "admin"))
        self.assertFalse(myDb.authenticate("boss","123"))
        self.assertFalse(myDb.authenticate("cook1","cooke"))

    def test_addUser(self):
        myDb = database_connection("127.0.01", "root", "root", "restaurant_system")
        myDb.connect()
        self.assertEqual(None, myDb.add_user("admin", "admin"))

if __name__ == '__main__':
    unittest.main()
