class DirectoryService(Database):
    DATABASE = "sql.db"

    def __init__(self, port, replication):
        super(DirectoryService, self).__init__()
        self.port = port
        self.replication = replication
        self._create_tables()
        self.server = Server(port, self._handler)

    def _insert_server(self, sock, master, host, port):
        exists = self._server_exists(host, port)
        if exists:
            return True
        else:
            self.execute_sql("insert into servers (master, host, port) values (?, ?, ?)", (master, host, port), exclusive=True)
            return True

    def _delete_server(self, sock, host, port):
        self.execute_sql("delete from servers where host=? and port=?", (host, port))

    def _list_servers(self, sock):
        # return (pickle.dumps(self.fetch_sql("select * from servers", ())), "PADDING")
         pass

    def _server_exists(self, host, port):
        return len(self.fetch_sql("select * from servers where host=? and port=?", (host, port))) != 0

    def _select_random_server(self):
        return self.fetch_sql("select * from servers where id >= (abs(random()) % (SELECT max(id) FROM servers));", ())

    def _select_server_by_id(self, id):
        return self.fetch_sql("select * from servers where id=?", (str(id),))

    def _select_server_master(self, host, port):
        return self.fetch_sql("select id from servers where host=? and port=?", (host, port))

    def _find_directory_host(self, file_name):
        return self.fetch_sql("select * from directories where file=?", (file_name,))

    def _insert_directory_host(self, directory, file_name, server_id):
        self.execute_sql("insert into directories (directory, file, server_id) values (?, ?, ?)",
                         (directory, file_name, str(server_id)))

    def _create_tables(self):
        conn = sqlite3.connect(self.DATABASE)
        cur = conn.cursor()
        cur.executescript("""
                create table if not exists servers(
                    id INTEGER PRIMARY KEY ASC,
                    master INTEGER,
                    host TEXT,
                    port TEXT
                );
                create table if not exists directories(
                    id INTEGER PRIMARY KEY ASC,
                    directory TEXT,
                    file TEXT,
                    server_id TEXT
                );""")

    def _handler(self, sock):
        try:
            data = sock.recv(1024)
            msg = data.decode('utf-8')
            msg = msg.replace('$', ' ')
            if 'KILL_SERVICE' in msg:
                exit()

            elif msg[:6] == "INSERT":
                temp = msg.split()
                ip_port = temp[1].split(':')
                master = temp[2]
                self._insert_server(sock, master, ip_port[0], ip_port[1])
                self.server.respond(sock, response200())

            elif msg[:4] == "DELETE":
                temp = msg.split()
                ip_port = temp[1].split(':')
                self._delete_server(sock, ip_port[0], ip_port[1])
                self.server.respond(sock, response200())

            elif msg[:3] == "GET":
                temp = msg.split()
                file_name = temp[1]
                row = self._select_random_server()
                file_dir = self._find_directory_host(file_name)
                pdb.set_trace()
                if file_dir is not None:
                    response = response200() + " " + row[0][2] + ":" + row[0][3] + " " + file_dir[0][1] + " " + str(file_dir[0][0])
                    self.server.respond(sock, response)
                else:
                    self.server.respond(sock, response404())

            elif msg[:4] == "LIST":
                # TODO not complete, for replication
                temp = msg.split()
                #self._mkdir_handler(sock, temp[1])

            elif msg[:3] == "ADD":
                temp = msg.split()
                ip_port = temp[1].split(':')
                server_ids = self._select_server_master(ip_port[0], ip_port[1])
                server_id = server_ids[0]
                self._insert_directory_host(temp[2], temp[3], server_id[0])
            else:
                self.server.error(sock, 0, responseErrParse())
        except sock.error as e:
            err = e.args[0]
            self.server.error(sock, 0, e.args[1])
class FileService(object):
    """docstring for FileService"""

    def __init__(self, port, ds_host, ds_port):
        super(FileService, self).__init__()
        self.dir = ''
        self.port = port
        self.ds_host = ds_host
        self.ds_port = ds_port
        self.server = Server(port, self._handler)

    def announce_server(self):
        sock = self.server.send_as_client(self.ds_host, self.ds_port, "INSERT " + self.server.host + ":" + str(self.port) + " " + "1")
        data = sock.recv(1024)
        if data.decode('utf-8') != response200():
            print("Error with Directory Server: ", data)
        sock.shutdown(socket.SHUT_WR)
        sock.close()
        #TODO Add to allow exsting files or remove

    def _open_handler(self, conn, path):
        filename = self.dir + path
        if not os.path.isfile(filename):
            self.server.error(conn, 404, response404())
        else:
            f = open(filename, "rb")
            pdb.set_trace()
            self.server.send_file(conn, f)
            conn.shutdown(socket.SHUT_WR)
            conn.close()

    def _create_handler(self, conn, path):
        file_path = os.path.dirname(path)
        if not os.path.isdir(file_path):
            self.server.error(conn, 405, response405())
        else:
            self.server.respond(conn, response200())
            exists = os.path.isfile(self.dir + path)
            f = open(self.dir + path, "wb")
            conn.recv_file(f)
            f.close()
            self.server.respond(conn, response200())
            # TODO: send to other replication managers
            # if not exists:
            #

    def _mkdir_handler(self, conn, path):
        newdir = str(self.dir + path.strip('/'))
        basedir = os.path.dirname(newdir)
        if not os.path.isdir(basedir):
            self.server.error(conn, 404, response404())
        else:
            try:
                os.makedirs(newdir)
                self.server.respond(conn, response200())
                # self._advertise_buffer.add(path.strip('/'), ObjectBuffer.Type.directory)
                # TODO: send to replication manager
            except Exception as e:
                if e.errno != errno.EEXIST:
                    raise
                else:
                    conn.send()

    def _delete_handler(self, conn, path):
        pdb.set_trace()
        obj = str(self.dir + path).rstrip('/')
        if os.path.isdir(obj):
            shutil.rmtree(obj)
            self.server.respond(conn, response200())
            # self._advertise_buffer.add(path, ObjectBuffer.Type.deleteDirectory)
        elif os.path.isfile(obj):
            os.remove(obj)
            self.server.respond(conn, response200())
            # self._advertise_buffer.add(path, ObjectBuffer.Type.deleteFile)
        else:
            self.server.error(conn, 404, "not found!")

    def _handler(self, sock):
        LF = "\n"
        try:
            data = sock.recv(1024)
            msg = data.decode('utf-8')
            msg = msg.replace('$', ' ')
            if 'KILL_SERVICE' in msg:
                exit()
            elif msg[:4] == "HELO":
                response = ["IP:" + self.server.host, "Port:" + str(self.port), "StudentID:" + self.server.studentId]
                fullResponse = msg + LF.join(response)
                print(response)
                sock.send(fullResponse.encode('utf-8'))
            elif msg[:4] == "OPEN":
                temp = msg.split()
                self._open_handler(sock, temp[1])
            elif msg[:6] == "CREATE":
                temp = msg.split()
                self._create_handler(sock, temp[1])
            elif msg[:5] == "MKDIR":
                temp = msg.split()
                self._mkdir_handler(sock, temp[1])
            elif msg[:6] == "DELETE":
                temp = msg.split()
                self._delete_handler(sock, temp[1])
            else:
                self.server.error(sock, 0, responseErrParse())
        except sock.error as e:
            err = e.args[0]
            self.server.error(sock, 0, e.args[1])
class LockService(Database):
    """
    LockService provdes functionality to lock access to a particular file on the fileservers. 
    Every service that writes to file uses the lockService, every read of a file checks if the particular file is locked.
    The lockservice provides rudimentary acknowledgments on all operations
    """

    DATABASE = "sql.db"
   
    def __init__(self, port):
        super(LockService, self).__init__()
        self.port = port
        self._create_tables()
        self.server = Server(port, self._handler)

    def _lock_file(self, file_name):
        is_locked = self._check_if_locked(file_name)
        if is_locked:
            return False
        insert = self.execute_sql("insert into locks (file_name) values (?)", (file_name,), exclusive=True)
        if insert:
            return True
        else:
            return False

    def _unlock_file(self, file_name):
        delete = self.execute_sql("delete from locks where file_name = ?", (file_name,), exclusive=True)
        if delete:
            return True
        else:
            return False

    def _check_if_locked(self, file_name):
        data = self.fetch_sql("select * from locks where file_name=?", (file_name,))
        if file_name in data:
            return True
        else:
            return False

    def _handler(self, sock):
        try:
            data = sock.recv(1024)
            msg = data.decode('utf-8')
            msg = msg.replace('$', ' ')
            if 'KILL_SERVICE' in msg:
                exit()

            elif msg[:4] == "LOCK":
                temp = msg.split()
                is_locked = self._unlock_file(temp[1])
                if is_locked:
                    self.server.respond(sock, response200())
                else:
                    self.server.respond(sock, response604())

            elif msg[:6] == "UNLOCK":
                temp = msg.split()
                is_unlocked = self._unlock_file( temp[1])
                if is_unlocked:
                    self.server.respond(sock, response200())
                else:
                    self.server.respond(sock, response604())

            elif msg[:5] == "CHECK":
                temp = msg.split()
                locked = self._check_if_locked(temp[1])
                if locked:
                    self.server.respond(sock, responseLocked())
                else:
                    self.server.respond(sock, responseUnlocked())
            else:
                self.server.error(sock, 0, responseErrParse())
        except sock.error as e:
            err = e.args[0]
            self.server.error(sock, 0, e.args[1])

    def _create_tables(self):
        conn = sqlite3.connect(self.DATABASE)
        cur = conn.cursor()
        cur.executescript("""
            create table if not exists locks(
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                file_name TEXT
            );""")