/*
AcessOS EDI Interface
edi.c
*/
#include <acess.h>
#include <fs_devfs2.h>
#define	IMPLEMENTING_EDI	1
#include <edi/edi.h>

#define	VALIDATE_PTR(_ptr,_err)	do{if(!(_ptr))return _err; }while(0)
#define	GET_DATA(_ptr)	(Object)

#include "edi_io.inc.c"
#include "edi_int.inc.c"

// === STRUCTURES ===
typedef struct sAcessEdiDriver {
	devfs_driver	Driver;
	 int	FileCount;
	vfs_node	*Files;
	edi_object_metadata_t	*Objects;
	edi_initialization_t	Init;
	driver_finish_t	Finish;
	struct sAcessEdiDriver	*Next;
} tAcessEdiDriver;

// === PROTOTYPES ===
vfs_node *EDI_FS_ReadDir(vfs_node *Node, int Pos);
vfs_node *EDI_FS_FindDir(vfs_node *Node, char *Name);
 int	EDI_FS_CharRead(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Buffer);
 int	EDI_FS_CharWrite(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Buffer);
 int	EDI_FS_IOCtl(vfs_node *Node, int Id, void *Data);
data_pointer EDI_GetInternalClass(edi_string_t ClassName);


// === GLOBALS ===
tAcessEdiDriver	*gEdi_Drivers = NULL;
edi_class_declaration_t	*gcEdi_IntClasses[] = {
	&scEdi_Int_Class_IO, &scEdi_Int_Class_IRQ
};
#define	NUM_INT_CLASSES	(sizeof(gcEdi_IntClasses)/sizeof(gcEdi_IntClasses[0]))
char *csCharNumbers[] = {"c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9"};

// === CODE ===
/**
 * \fn int Edi_LoadDriver(char *Path)
 * \brief Load an EDI Driver from a file
 * \param Path	Path to driver
 * \return 0 on success, non zero on error
 */
int Edi_LoadDriver(char *Path)
{
	driver_init_t	init;
	driver_finish_t	finish;
	Uint	iDriverBase;
	tAcessEdiDriver	*info;
	 int	i, j;
	 int	devfsId;
	edi_class_declaration_t	*classes;
	
	//LogF("Edi_LoadDriver: (Path='%s')\n", Path);
	
	// Load Driver
	iDriverBase = Binary_LoadKernel(Path);
	if(!iDriverBase)	return -1;
	
	//LogF(" Edi_LoadDriver: Base = 0x%x\n", iDriverBase);
	
	// Get Functions
	if(
		!Binary_GetSymbolBin(iDriverBase, "driver_init", (Uint*)&init)
	||	!Binary_GetSymbolBin(iDriverBase, "driver_finish", (Uint*)&finish) )
	{
		WarningEx("EDI", "Driver %s does not provide both `driver_init` and `driver_finish`\n", Path);
		Binary_Unload(iDriverBase);
		return -2;
	}
	
	// Allocate Driver Information
	info = malloc( sizeof(tAcessEdiDriver) );
	info->Finish = finish;
	
	// Initialise Driver
	info->Init = init( 0, NULL );	// TODO: Implement Resources
	
	//LogF(" Edi_LoadDriver: Name '%s'\n", info->Init.driver_name);
	//LogF(" Edi_LoadDriver: %i Classes\n", info->Init.num_driver_classes);
	
	// Count mappable classes
	classes = info->Init.driver_classes;
	info->FileCount = 0;
	info->Objects = NULL;
	for( i = 0, j = 0; i < info->Init.num_driver_classes; i++ )
	{
		if( strncmp(classes[i].name, "EDI-CHARACTER-DEVICE", 32) == 0 )
		{
			data_pointer	*obj;
			// Initialise Object Instances
			for( ; (obj = classes[i].constructor()); j++ ) {
				//LogF(" Edi_LoadDriver: %i - Constructed '%s'\n", j, classes[i].name);
				info->FileCount ++;
				info->Objects = realloc(info->Objects, sizeof(*info->Objects)*info->FileCount);
				info->Objects[j].object = obj;
				info->Objects[j].object_class = &classes[i];
			}
		}
		else
			LogF(" Edi_LoadDriver: %i - %s\n", i, classes[i].name);
	}
	
	if(info->FileCount)
	{
		 int	iNumChar = 0;
		// Create VFS Nodes
		info->Files = (vfs_node *) malloc( info->FileCount * sizeof(vfs_node) );
		memsetda(info->Files, 0, info->FileCount * sizeof(vfs_node) / 4);
		j = 0;
		for( j = 0; j < info->FileCount; j++ )
		{
			classes = info->Objects[j].object_class;
			if( strncmp(classes->name, "EDI-CHARACTER-DEVICE", 32) == 0 )
			{
				//LogF(" Edi_LoadDriver: %i - %s\n", j, csCharNumbers[iNumChar]);
				info->Files[j].name = csCharNumbers[iNumChar];
				info->Files[j].nameLength = (iNumChar < 10 ? 1 : 2);
				info->Files[j].mode = 0666;	// RW-RW-RW-
				info->Files[j].inode = (Uint) &info->Objects[j];
				info->Files[j].read = EDI_FS_CharRead;
				info->Files[j].write = EDI_FS_CharWrite;
				info->Files[j].ctime =
					info->Files[j].mtime =
					info->Files[j].atime = now();
				
				iNumChar ++;
				continue;
			}
		}
		
		// Create DevFS Driver
		info->Driver.ioctl = EDI_FS_IOCtl;
		memsetda(&info->Driver.rootNode, 0, sizeof(vfs_node) / 4);
		info->Driver.rootNode.name = info->Init.driver_name;
		info->Driver.rootNode.nameLength = strlen( info->Init.driver_name );
		info->Driver.rootNode.length = info->FileCount;
		info->Driver.rootNode.mode = 0111;	// --X--X--X
		info->Driver.rootNode.flags = VFS_FFLAG_DIRECTORY;
		info->Driver.rootNode.inode = (Uint)info;
		info->Driver.rootNode.readdir = EDI_FS_ReadDir;
		info->Driver.rootNode.finddir = EDI_FS_FindDir;
		
		// Register
		devfsId = dev_addDevice( &info->Driver );
		if(devfsId == -1) {
			free(info->Files);	// Free Files
			info->Finish();	// Clean up driver
			free(info);		// Free info structure
			Binary_Unload(iDriverBase);	// Unload library
			return -3;	// Return error
		}
		
		// Save DevFS ID
		// - DevFS Requires the ID of the driver to be in vfs_node->impl
		info->Driver.rootNode.impl = devfsId;
		for( j = 0; j < info->FileCount; j ++ ) {
			info->Files[j].impl = devfsId;
		}
	}
	
	// Append to loaded list;
	LOCK();
	info->Next = gEdi_Drivers;
	gEdi_Drivers = info;
	UNLOCK();
	
	//LogF("Edi_LoadDriver: RETURN 0\n");
	LogF("[EDI ] Loaded Driver '%s' (%s)\n", info->Init.driver_name, Path);
	return 0;
}

// --- Filesystem Interaction ---
/**
 * \fn vfs_node *EDI_FS_ReadDir(vfs_node *Node, int Pos)
 * \brief Read from a drivers class list
 * \param Node	Driver's Root Node
 * \param Pos	Index of file to get
 */
vfs_node *EDI_FS_ReadDir(vfs_node *Node, int Pos)
{
	tAcessEdiDriver	*info;
	
	// Sanity Check
	if(!Node)	return NULL;
	
	// Get Information Structure
	info = (void *) Node->inode;
	if(!info)	return NULL;
	
	// Check Position
	if(Pos < 0)	return NULL;
	if(Pos >= info->FileCount)	return NULL;
	
	return &info->Files[Pos];
}

/**
 * \fn vfs_node *EDI_FS_FindDir(vfs_node *Node, char *Name)
 * \brief Find a named file in a driver
 * \param Node	Driver's Root Node
 * \param Name	Name of file to find
 */
vfs_node *EDI_FS_FindDir(vfs_node *Node, char *Name)
{
	tAcessEdiDriver	*info;
	 int	i;
	
	// Sanity Check
	if(!Node)	return NULL;
	if(!Name)	return NULL;
	
	// Get Information Structure
	info = (void *) Node->inode;
	if(!info)	return NULL;
	
	for( i = 0; i < info->FileCount; i++ )
	{
		if(strcmp(info->Files[i].name, Name) == 0)
			return &info->Files[i];
	}
	
	return NULL;
}

/**
 * \fn int EDI_FS_CharRead(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
 * \brief Read from an EDI Character Device
 * \param Node	File Node
 * \param Offset	Offset into file (ignored)
 * \param Length	Number of characters to read
 * \param Buffer	Destination for data
 * \return	Number of characters read
 */
int EDI_FS_CharRead(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
{
	edi_object_metadata_t	*meta;
	edi_class_declaration_t	*class;
	
	// Sanity Check
	if(!Node || !Buffer)	return 0;
	if(Length <= 0)	return 0;
	// Get Object Metadata
	meta = (void *) Node->inode;
	if(!meta)	return 0;
	
	// Get Class
	class = meta->object_class;
	if(!class)	return 0;
	
	// Read from object
	if( ((tAnyFunction) class->methods[0].code)( meta->object, Buffer, Length ))
		return Length;

	return 0;
}

/**
 * \fn int EDI_FS_CharWrite(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
 * \brief Write to an EDI Character Device
 * \param Node	File Node
 * \param Offset	Offset into file (ignored)
 * \param Length	Number of characters to write
 * \param Buffer	Source for data
 * \return	Number of characters written
 */
int EDI_FS_CharWrite(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
{
	edi_object_metadata_t	*meta;
	edi_class_declaration_t	*class;
	
	// Sanity Check
	if(!Node || !Buffer)	return 0;
	if(Length <= 0)	return 0;
	// Get Object Metadata
	meta = (void *) Node->inode;
	if(!meta)	return 0;
	
	// Get Class
	class = meta->object_class;
	if(!class)	return 0;
	
	// Write to object
	if( ((tAnyFunction) class->methods[1].code)( meta->object, Buffer, Length ))
		return Length;

	return 0;
}

/**
 * \fn int EDI_FS_IOCtl(vfs_node *Node, int Id, void *Data)
 * \brief Perfom an IOCtl call on the object
 */
int EDI_FS_IOCtl(vfs_node *Node, int Id, void *Data)
{
	return 0;
}

// --- EDI Functions ---
/**
 * \fn data_pointer EDI_GetDefinedClass(edi_string_t ClassName)
 * \brief Gets the structure of a driver defined class
 * \param ClassName	Name of class to find
 * \return Class definition or NULL
 */
data_pointer EDI_GetDefinedClass(edi_string_t ClassName)
{
	 int	i;
	tAcessEdiDriver	*drv;
	edi_class_declaration_t	*classes;
	
	for(drv = gEdi_Drivers;
		drv;
		drv = drv->Next	)
	{
		classes = drv->Init.driver_classes;
		for( i = 0; i < drv->Init.num_driver_classes; i++ )
		{
			if( strncmp(classes[i].name, ClassName, 32) == 0 )
				return &classes[i];
		}
	}
	return NULL;
}

/**
 * \fn int32_t EDI_CheckClassExistence(edi_string_t ClassName)
 * \brief Checks if a class exists
 * \param ClassName	Name of class
 * \return 1 if the class exists, -1 otherwise
 */
int32_t EDI_CheckClassExistence(edi_string_t ClassName)
{
	//LogF("check_class_existence: (ClassName='%s')\n", ClassName);
	if(EDI_GetInternalClass(ClassName))
		return 1;
		
	if(EDI_GetDefinedClass(ClassName))	// Driver Defined
		return 1;
	
	return -1;
}

/**
 * \fn edi_object_metadata_t EDI_ConstructObject(edi_string_t ClassName)
 * \brief Construct an instance of an class (an object)
 * \param ClassName	Name of the class to construct
 */
edi_object_metadata_t EDI_ConstructObject(edi_string_t ClassName)
{
	edi_object_metadata_t	ret = {0, 0};
	edi_class_declaration_t	*class;
	
	//LogF("EDI_ConstructObject: (ClassName='%s')\n", ClassName);
	
	// Get class definition
	if( !(class = EDI_GetInternalClass(ClassName)) )	// Internal
		if( !(class = EDI_GetDefinedClass(ClassName)) )	// Driver Defined
			return ret;		// Return ERROR
	
	// Initialise
	ret.object = class->constructor();
	if( !ret.object )
		return ret;	// Return ERROR
	
	// Set declaration pointer
	ret.object_class = class;
	
	//LogF("EDI_ConstructObject: RETURN {0x%x,0x%x}\n", ret.object, ret.object_class);
	return ret;
}

/**
 * \fn void EDI_DestroyObject(edi_object_metadata_t Object)
 * \brief Destroy an instance of a class
 * \param Object	Object to destroy
 */
void EDI_DestroyObject(edi_object_metadata_t Object)
{
	if( !Object.object )	return;
	if( !Object.object_class )	return;
	
	((edi_class_declaration_t*)(Object.object_class))->destructor( &Object );
}

/**
 * \fn function_pointer EDI_GetMethodByName(data_pointer ObjectClass, edi_string_t MethodName)
 * \brief Get a method of a class by it's name
 * \param ObjectClass	Pointer to a ::edi_object_metadata_t of the object
 * \param MethodName	Name of the desired method
 * \return Function address or NULL
 */
function_pointer EDI_GetMethodByName(data_pointer ObjectClass, edi_string_t MethodName)
{
	edi_class_declaration_t	*dec = ObjectClass;
	 int	i;
	
	//LogF("get_method_by_name: (ObjectClass=0x%x, MethodName='%s')\n", ObjectClass, MethodName);
	
	if(!ObjectClass)	return NULL;
	
	for(i = 0; i < dec->num_methods; i++)
	{
		if(strncmp(MethodName, dec->methods[i].name, 32) == 0)
			return dec->methods[i].code;
	}
	return NULL;
}

#if 0
function_pointer get_method_by_declaration(data_pointer Class, edi_function_declaration_t Declaration);
#endif

/**
 * \fn edi_string_ptr_t EDI_GetClassParent(edi_string_t ClassName)
 * \brief Get the parent of the named class
 * \todo Implement
 */
edi_string_ptr_t EDI_GetClassParent(edi_string_t ClassName)
{
	WarningEx("EDI", "`get_class_parent` is unimplemented");
	return NULL;
}

/**
 * \fn data_pointer EDI_GetInternalClass(edi_string_t ClassName)
 * \brief Get a builtin class
 * \param ClassName	Name of class to find
 * \return Pointer to the ::edi_class_declaration_t of the class
 */
data_pointer EDI_GetInternalClass(edi_string_t ClassName)
{
	 int	i;
	//LogF("get_internal_class: (ClassName='%s')\n", ClassName);
	for( i = 0; i < NUM_INT_CLASSES; i++ )
	{
		if( strncmp( gcEdi_IntClasses[i]->name, ClassName, 32 ) == 0 ) {
			return gcEdi_IntClasses[i];
		}
	}
	//LogF("get_internal_class: RETURN NULL\n");
	return NULL;
}

/**
 * \fn edi_string_ptr_t EDI_GetObjectClass(data_pointer Object)
 * \brief Get the name of the object of \a Object
 * \param Object	Object to get name of
 * \return Pointer to the class name
 */
edi_string_ptr_t EDI_GetObjectClass(data_pointer ObjectClass)
{
	edi_object_metadata_t	*Metadata = ObjectClass;
	// Sanity Check
	if(!ObjectClass)	return NULL;
	if(!(edi_class_declaration_t*) Metadata->object_class)	return NULL;
	
	// Return Class Name
	return ((edi_class_declaration_t*) Metadata->object_class)->name;
}

// === EXPORTS ===
EXPORTAS(EDI_CheckClassExistence, check_class_existence);
EXPORTAS(EDI_ConstructObject, construct_object);
EXPORTAS(EDI_DestroyObject, destroy_object);
EXPORTAS(EDI_GetMethodByName, get_method_by_name);
EXPORTAS(EDI_GetClassParent, get_class_parent);
EXPORTAS(EDI_GetInternalClass, get_internal_class);
EXPORTAS(EDI_GetObjectClass, get_object_class);
