import struct

from .rpc import RPC

#
# Author: Hegusung
#

# NFS: 100003
# Procedures:
# - NULL: 0
# - GETATTR: 1
# - LOOKUP: 3
# - ACCESS: 4
# - READLINK: 5
# - READ: 6
# - READDIRPLUS: 17

class NFSAccessError(Exception):
    pass

class NFS(RPC):
    program = 100003
    program_version = 3

    def null(self):
        procedure = 0 # Null

        super(NFS, self).request(self.program, self.program_version, procedure)

        # no exception raised
        return True

    def lookup(self, dir_handle, file_folder, auth=None):
        if type(dir_handle) != bytes:
            raise Exception("file_id should be bytes")

        procedure = 3 # Lookup

        data = struct.pack('!L', len(dir_handle))
        data += dir_handle
        data += b'\x00'*((4-len(dir_handle) % 4)%4)

        data += struct.pack('!L', len(file_folder))
        data += file_folder.encode()
        data += b'\x00'*((4-len(file_folder) % 4)%4)

        data = super(NFS, self).request(self.program, self.program_version, procedure, data=data, auth=auth)

        nfs_status = struct.unpack('!L', data[:4])[0]
        data = data[4:]

        if nfs_status != 0:
            raise NFSAccessError("Error: %d" % nfs_status)

        file_handle_len = struct.unpack("!L", data[:4])[0]
        data = data[4:]

        file_handle = data[:file_handle_len]
        data = data[file_handle_len:]
        data = data[(4-file_handle_len % 4)%4:]

        value_follows = data[:4]
        data = data[4:]

        if value_follows == b'\x00\x00\x00\x01':
            attributes = data[:84]
            data = data[84:]

            (file_type, mode, ulink, uid, gid, file_size) = struct.unpack('!LLLLLL', attributes[:24])
            # File types:
            # 1: Regular file
            # 2: Directory
            # 5: Symbolic link
        else:
            file_type = None
            file_size = None

        return {
            "file_handle": file_handle,
            "file_type": file_type,
            "file_size": file_size,
        }

    def read(self, file_handle, auth=None, offset=0, chunk_count=1024*1024):
        if type(file_handle) != bytes:
            raise Exception("file_id should be bytes")

        procedure = 6 # Read

        data = struct.pack('!L', len(file_handle))
        data += file_handle
        data += b'\x00'*((4-len(file_handle) % 4)%4)
        data += struct.pack('!QL', offset, chunk_count)

        data = super(NFS, self).request(self.program, self.program_version, procedure, data=data, auth=auth)

        nfs_status = struct.unpack('!L', data[:4])[0]
        data = data[4:]

        if nfs_status != 0:
            raise NFSAccessError("Error: %d" % nfs_status)

        value_follows = data[:4]
        data = data[4:]

        if value_follows == b'\x00\x00\x00\x01':
            attributes = data[:84]
            data = data[84:]

            (file_type, mode, ulink, uid, gid, file_size) = struct.unpack('!LLLLLL', attributes[:24])
            # File types:
            # 1: Regular file
            # 2: Directory
            # 5: Symbolic link
        else:
            file_type = None
            file_size = None

        (count, EOF) = struct.unpack('!LL', data[:8])
        data = data[8:]

        file_len = struct.unpack("!L", data[:4])[0]
        data = data[4:]
        file_data = data[:file_len]
        data = data[file_len:]
        data = data[(4-file_len % 4)%4:]

        if len(file_data) != count:
            raise Exception("File size mismatch")

        if EOF == 0:
            file_data += self.read(file_handle, auth=auth, offset=offset+len(file_data))

        return file_data

    def readdirplus(self, dir_handle, cookie=0, auth=None):
        # file_id should by bytes
        if type(dir_handle) != bytes:
            raise Exception("file_id should be bytes")

        procedure = 17 # Export

        dircount = 4096
        maxcount = dircount*8

        data = struct.pack('!L', len(dir_handle))
        data += dir_handle
        data += struct.pack('!Q', cookie)
        data += struct.pack('!QLL', 0, dircount, maxcount)

        data = super(NFS, self).request(self.program, self.program_version, procedure, data=data, auth=auth)

        nfs_status = struct.unpack('!L', data[:4])[0]
        data = data[4:]

        if nfs_status != 0:
            raise NFSAccessError("Error: %d" % nfs_status)

        dir_attributes = data[:88]
        data = data[88:]

        opaque_data = data[:8]
        data = data[8:]

        contents = []
        last_cookie = 0

        value_follows = data[:4]
        data = data[4:]
        while value_follows == b'\x00\x00\x00\x01':
            file_id = struct.unpack("!Q", data[:8])[0]
            data = data[8:]

            name_len = struct.unpack("!L", data[:4])[0]
            data = data[4:]

            name = data[:name_len].decode()
            data = data[name_len:]
            data = data[(4-name_len % 4)%4:]

            cookie = struct.unpack("!Q", data[:8])[0]
            last_cookie = cookie
            data = data[8:]

            value_follows = data[:4]
            data = data[4:]

            if value_follows == b'\x00\x00\x00\x01':
                attributes = data[:84]
                data = data[84:]

                (file_type, mode, ulink, uid, gid, _, file_size) = struct.unpack('!LLLLLLL', attributes[:28])
                # File types:
                # 1: Regular file
                # 2: Directory
                # 5: Symbolic link
            else:
                file_type = None
                file_size = None

            handle_value_follows = data[:4]
            data = data[4:]

            if handle_value_follows == b'\x00\x00\x00\x01':
                len_file_handle = struct.unpack('!L', data[:4])[0]
                data = data[4:]
                file_handle = data[:len_file_handle]
                data = data[len_file_handle:]
            else:
                file_handle = None

            contents.append({
                "name": name,
                "file_type": file_type,
                "cookie": cookie,
                "file_id": file_id,
                "file_handle": file_handle,
                "file_size": file_size,
            })

            value_follows = data[:4]
            data = data[4:]

        EOF = data[:4]

        if EOF == b'\x00\x00\x00\x00':
            self.readdirplus(dir_handle, cookie=last_cookie, auth=auth)

        return contents


