#-*-confg: utf-8 -*-
from datetime import datetime
import math
import os
import sys

class BinaryFile:

	def  __init__(self, name):
		self.fd = open(name, 'r+b')

	def __getitem__(self, pos):
		if isinstance(pos, int):
			self.fd.seek(pos)
			return self.fd.read(1)
		else:
			if pos.stop < pos.start:
				pos.start, pos.stop = pos.stop, pos.start
			self.fd.seek(pos.start)
			return self.fd.read(pos.stop - pos.start)

	def __setitem__(self, pos, value):
		self.fd.seek(pos)
		self.fd.write(value)

	def b2i(self, pos, nbytes):
		return int.from_bytes(self[pos:pos+nbytes], byteorder='little')

	def close(self):
		self.fd.close()


class BootRecord:

    def __init__(self, fd):
        if not isinstance(fd, BinaryFile):
        	raise TypeError('`fd` must be a `BinaryFile` instance.')
        self.sigla = fd[0:4]
        self.version = fd.b2i(4, 2)
        self.bytes_per_block = fd.b2i(6, 2)
        self.block_num = fd.b2i(8, 4)
        self.desloc_first_block = fd.b2i(12, 4)
        self.void_blocks_list = fd.b2i(16, 4)
        self.root_block = fd.b2i(20, 4)

class Command:
	
	def __init__(self, br, fd):
		if not isinstance(br, BootRecord):
			raise TypeError('`br` must be a `BootRecord` instance')
		if not isinstance(fd, BinaryFile):
			raise TypeError('`fd` must be a `BinaryFile` instance.')	
				
		self.bytes_per_block = br.bytes_per_block
		self.fd = fd
		self.br = br
	
	def __getFile(self, init_bloc_dir, name):
		size_dir = self.fd.b2i((init_bloc_dir)+18, 8)
		for i in range(init_bloc_dir + 40, init_bloc_dir + size_dir, 32):
			if(self.fd[i:i+18].decode("utf8") == name + ("\x00" *(18 - len(name)))):
				return self.fd.b2i(i-4, 4)

	def exportFile(self, dpath, opath):			
		opath = opath.split('/')
		
		if(len(opath) == 1): #é o root
			index_file = self.__getFile(self.br.root_block, opath[-1])
		else:
			addrs = self.__checaSubDir(opath, self.br.root_block, self.br.root_block)
			index_file = self.__getFile(addrs[0], opath[-1])

		size_file = self.fd.b2i(index_file + 18, 8)
		print("index: ", index_file, " size_file: ", size_file)
		f = open(dpath,"wb")
		f.write(self.fd[index_file + 26: index_file+size_file])
		f.close()


	def copyFile(self, opath, dpath):
		if os.path.isfile(opath):
			with open(opath, 'rb') as f:
				content = f.read()
		else:
			sys.exit('`%s` does not exist or is not a file.' % opath)
						
		dpath = dpath.split('/')
		
		if len(dpath[-1]) > 18:
			raise NameError('long names not supported yet. \
			                   Max name length is 18 chars.')
		file_size = len(content) + 26
		file_size_blocks = math.ceil(file_size/self.bytes_per_block)

		list_pointers = self.__findNFreeBlocks(file_size_blocks)
		
		
		
		

		#verifica se é o diretório raiz ou um sub diretório
		if(len(dpath) == 1): #é o root
			self.__checaNome(self.br.root_block, dpath[-1])
			self.__saveMetaDados(self.br.root_block, b"\00", file_size, list_pointers[0], dpath[-1])
		else:
			print("ta no else")
			addrs = self.__checaSubDir(dpath, self.br.root_block, self.br.root_block)
			self.__checaNome(addrs[0], dpath[-1])
			self.__saveMetaDados(addrs[0], b"\00", file_size, list_pointers[0], dpath[-1])

		self.__clearBlocks(list_pointers[0], list_pointers[1])

		self.fd[list_pointers[2]] = list_pointers[1].to_bytes(4, byteorder='little')
		self.fd[list_pointers[0]] = b"\00" * 18 + file_size.to_bytes(8, byteorder='little') + content
		self.fd[list_pointers[0]] = bytes(dpath[-1], encoding='utf8')

	def __checaNome(self, init_bloc_dir, name):
		size_dir = self.fd.b2i((init_bloc_dir)+18, 8)
		for i in range(init_bloc_dir + 40, init_bloc_dir + size_dir, 32):
			if(self.fd[i:i+18].decode("utf8") == name + ("\x00" *(18 - len(name)))):
				raise NameError("this name already exists")
		
	def __saveMetaDados(self, init_bloc_dir, flag, size, initial_block, name):
		size_dir = self.fd.b2i((init_bloc_dir)+18, 8)
		size_dir = math.ceil(size_dir/self.bytes_per_block)
		size_dir = size_dir * self.bytes_per_block

		for i in range(init_bloc_dir+90, init_bloc_dir + size_dir, 32):
			if(self.fd[i] == b'\00'):
				self.fd[i] = b'\00'*32
				self.fd[i] = b'\xff'
				self.fd[i+1] = flag
				self.fd[i+2] = size.to_bytes(8, byteorder='little')
				self.fd[i+10] = initial_block.to_bytes(4, byteorder='little')
				self.fd[i+14] =  bytes(name, encoding='utf8')
				break
		self.fd[init_bloc_dir + 18] = (self.fd.b2i((init_bloc_dir)+18, 8) + 32).to_bytes(8, byteorder='little')

	def __clearBlocks(self, init_block,final_block):
		for i in range(init_block, final_block):
			self.fd[i] = b'\00'

	def __findNFreeBlocks(self, n_blocks):
		bloco = self.fd.b2i(16, 4)
		cont_blocks = 1
		before_init_bloc = 16

		size_system = self.fd.b2i(6, 2) * self.fd.b2i(8, 4)
		while(cont_blocks < n_blocks):
			if(self.fd.b2i(bloco + self.bytes_per_block - 4, 4) == (bloco + self.bytes_per_block)):
				cont_blocks += 1
			else:
				before_init_bloc = bloco
				cont_blocks = 1

			bloco = self.fd.b2i(bloco + self.bytes_per_block - 4, 4)
			if(bloco >= size_system):
				raise MemoryError('Insufficient space on FOXX.')
		
		#retorno: primeiro bloco do arquivo, qual bloco termina, bloco que aponta para o primeiro bloco do arquivo
		return (bloco - ((n_blocks - 1) * self.bytes_per_block)) , self.fd.b2i(bloco + (self.bytes_per_block-4), 4), before_init_bloc 

	def mkdir(self, dpath): 
		DIR_SIZE = 90
		DIR_SIZE_BLOCKS = 1
		
		dpath = dpath.split('/')
		
		if len(dpath[-1]) > 18:
			raise NameError('long names not supported yet. \
			                   Max name length is 18 chars.')


		list_pointers = self.__findNFreeBlocks(DIR_SIZE_BLOCKS)
		

		
		endereco_pai = 0
		#verifica se é o diretório raiz ou um sub diretório
		if(len(dpath) == 1): #é o root
			self.__checaNome(self.br.root_block, dpath[-1])
			self.__saveMetaDados(self.br.root_block, b"\xFF", DIR_SIZE, list_pointers[0], dpath[-1])
			endereco_pai = self.fd.b2i(20, 4)
		else:
			
			addrs = self.__checaSubDir(dpath, self.br.root_block, self.br.root_block)
			self.__checaNome(addrs[0], dpath[-1])
			self.__saveMetaDados(addrs[0], b"\xFF", DIR_SIZE, list_pointers[0], dpath[-1])
			endereco_pai = addrs[1]

		self.__clearBlocks(list_pointers[0], list_pointers[1])

		self.fd[list_pointers[2]] = list_pointers[1].to_bytes(4, byteorder='little')
		self.fd[list_pointers[0]] = bytes(dpath[-1], encoding = "utf8")
		self.fd[list_pointers[0] + 18] = (90).to_bytes(8, byteorder='little')
		self.fd[list_pointers[0] + 26] = b'\xff'
		self.fd[list_pointers[0] + 27] = b'\xff'
		self.fd[list_pointers[0] + 28] = (90).to_bytes(8, byteorder='little')
		self.fd[list_pointers[0] + 36] = list_pointers[0].to_bytes(4, byteorder='little')
		self.fd[list_pointers[0] + 40] = b'.'
		self.fd[list_pointers[0] + 58] = b'\xff'
		self.fd[list_pointers[0] + 59] = b'\xff'
		self.fd[list_pointers[0] + 60] = self.fd.b2i(endereco_pai + 18, 8).to_bytes(8, byteorder='little')
		self.fd[list_pointers[0] + 68] = endereco_pai.to_bytes(4, byteorder='little')
		self.fd[list_pointers[0] + 72] = b'..'

	def __checaSubDir(self, dpath, addr, father_addr):
		if(len(dpath) == 1):
			return addr, father_addr

		size_dir = self.fd.b2i((addr)+18, 8)
		for i in range(addr + 40, addr + size_dir, 32):
			print("file system:", len(self.fd[i:i+18].decode("utf8")))
			print("path:", len(dpath[0]))
			if(self.fd[i:i+18].decode("utf8") == dpath[0] + ("\x00" *(18 - len(dpath[0])))):
				return self.__checaSubDir(dpath[1:], self.fd.b2i(i - 4, 4), addr)
				
		sys.exit("this directory not exists")

	def listFiles(self):
		addr_root = self.br.root_block
		print('root/')
		self.printFiles(addr_root)

	def printFiles(self, addr_dir, level = 1):
		size_dir = self.fd.b2i(addr_dir + 18, 8)
		for i in range(addr_dir + 90, addr_dir + size_dir, 32):
			if self.fd[i] == b'\xff': #apagado ou não
				if self.fd[i+1] == b'\xff': #dir
					print( level*'   '  + self.fd[i+14:i+32].decode("utf-8") + "/")
					level += 1
					self.printFiles(self.fd.b2i(i+10, 4), level)
					level -= 1
				else:
					print(level*'   ' + self.fd[i+14:i+32].decode("utf-8"))


def format(fd, device_size, bytes_per_block=512):
    if not isinstance(fd, BinaryFile):
        raise TypeError('`fd` must be a `BinaryFile` instance.')

    if math.log(bytes_per_block, 2) - int(math.log(bytes_per_block, 2)) != 0.0:
        raise ValueError('`bytes_per_block` must be base 2.')

    block_in_device = math.ceil(device_size / bytes_per_block)
    #tamanho do root
    max_nfile = (device_size - 1024)/512
    size_root_block = math.ceil((max_nfile * 32)/512)   
    size_root_byte = size_root_block * bytes_per_block

    fd[0] = b'\00' * device_size
    #começa o Boot Record
    fd[0] = b'FOXX'
    fd[4] = (0).to_bytes(2, byteorder='little')
    fd[6] = bytes_per_block.to_bytes(2, byteorder='little')
    fd[8] = block_in_device.to_bytes(4, byteorder='little')
    
    #desnecessário
    fd[12] = bytes_per_block.to_bytes(4, byteorder='little')
    
    fd[16] = int(size_root_byte + bytes_per_block).to_bytes(4, byteorder='little')
    fd[20] = bytes_per_block.to_bytes(4, byteorder='little')

    #começa o diretório raiz
    fd[512] = b"Root"
    fd[530] = (90).to_bytes(8, byteorder='little')
    fd[538] = b'\xff'
    fd[539] = b'\xff'
    fd[540] = (90).to_bytes(8, byteorder='little')
    fd[548] = (512).to_bytes(4, byteorder='little')
    fd[552] = b'.'

    #criando a lista ligada de espaços vazios
    for i in range(size_root_byte + (2*bytes_per_block)-4, device_size, bytes_per_block):
        fd[i] = (i+4).to_bytes(4, byteorder='little')



if __name__ == '__main__':
	b = BinaryFile('test.img')
	br = BootRecord(b)
	foxx_commands = Command(br, b)
	
	if len(sys.argv) == 1:
		sys.exit('Whats does the FOXX say?')
		
	if 'format_foxx' == sys.argv[1]:
		size_bytes = int(sys.argv[2]) * 512
		format(b, size_bytes) #1048576
	elif "import_foxx" == sys.argv[1]:
		foxx_commands.copyFile(sys.argv[2], sys.argv[3])
	elif 'export_foxx' == sys.argv[1]:
		foxx_commands.exportFile(sys.argv[2], sys.argv[3])
	elif 'mkdir_foxx' == sys.argv[1]:
		if len(sys.argv) == 2:
			sys.exit('directory name not specified.')
		else:
			foxx_commands.mkdir(sys.argv[2])
	elif 'ls_foxx' == sys.argv[1]:
		foxx_commands.listFiles()
	elif 'rm_foxx' == sys.argv[1]:
		foxx_commands.remove(sys.argv[2])
	else:
		sys.exit('command `%s` not found.' % sys.argv[1])
		
	b.close()
