/*
 * Copyright 2022, ttcoder
 * All rights reserved. Distributed under the terms of the MIT license.
 */

// libroot.so
#include <stdio.h>
#include <syscalls.h>  // _kern_read(), _kern_open()

// this
//#include <storage/Directory.h>
#include <storage/Entry.h>  // entry_ref
#include <storage/Path.h>
#include <storage/Query.h>
//#include <storage/Volume.h>
#include <support/String.h>


/**************/
static int debug = 1;



//#pragma mark - class BQuery -


void BQuery::fetchAll()
{
	if( debug >= 2 )
		printf( "bquery.fetchAll() with predicate <%s>\n", fPredicate );
	
	// read all entries in one go (everything is ready at once on the vfs_indexer.so's side anyway)
	char buf[128] = {};  // set e.g. "buf[3]" for stress testing
	for( ;; )
	{
		auto res = _kern_read( fQueryFd, -1, buf, sizeof(buf) );
		if( res <= 0 )
			break;
		entList.Write( buf, res );
		//printf( "  ..collected %ld bytes\n", res );
	}
	
	// T+
	entList.Seek( 0, SEEK_SET );
}


status_t BQuery::getRef( entry_ref * ref, const struct dirent & entry )
{
	status_t error = 0;
	
/*
	struct stat st = {};
	auto res = _kern_read_stat(-1, "/boot", false, &st, sizeof(st) );
	printf("kern read stat res: %d  device: %ld  dir: %lu\n", res, st.st_dev, st.st_ino);
	*ref = entry_ref( st.st_dev, st.st_ino, "mpeg_file" );
	return B_OK;// /mpeg_file;
*/
	// Genode libc's "struct stat" has a different layout
	// i.e. same problem as in BDirectory, except more difficult:
	// - in Haiku, struct dirent/LongDirEntry contains the inode of the parent dir:  ino_t d_pino; /* parent inode (only for queries) */
	// - in FreeBSD, it does not..!
	// Yet GetNextDirents(), below, uses the FreeBSD dirent struct, and passes it to us.
	// Fortunately, we shortcut the whole dilemma by abusing the <d_name> field,
	// setting it to the whole path instead of the leafname (!), seems to work well.
	ref->device = fDevice;
//	ref->directory = entry.d_fileno;
	
	//printf(" set <%s> in GetNextRef()\n", entry.d_name);
	BString p;
	{
///ToDo: hardcoded SetTo "/boot": fix later ...
		p.SetTo( "/boot" );
		
		if( entry.d_name[0] != '/' )
			p += "/";
		p += entry.d_name;  // e.g. "/boot/Music/thewall.mp3"
	}
	error = ref->set_name( p );
	
	return error;
}


int32 BQuery::getDirents( struct dirent * buffer, size_t length, int32 count )
{
	//puts(" GetNextDirents ........");
	//printf("Position: %ld of %lu\n", entList.Position(), entList.BufferLength());
	
	///later: test that <length> is sufficient for the strcpy() below
	
	if( (size_t)entList.Position() >= entList.BufferLength() )
		//
		return 0;
		//
	
	// T0
	auto current = static_cast<const char*>(entList.Buffer()) + entList.Position();
	const char * zero = (const char*)memchr(
		current,
		'\0',
		entList.BufferLength() - entList.Position()
		);
	if( zero && current[0] )
	{
		// T0
		strcpy( buffer->d_name, current );  // e.g. "/Music/TheWall.mp3"
		//printf(" strcpy <%s>\n", current);
		
		// T+
		entList.Seek(
			zero + sizeof('\0') - static_cast<const char*>( entList.Buffer() ),
			SEEK_SET
			);
	}
	else
	{
		puts( "*** BQuery: missing ending nul, or immediately occuring ending nul (empty name)" );///
		entList.Seek( 0, SEEK_END );  // !
		
		return 0;
	}
	
#if 0
	BString leafname;
	BPath parent;
	{
		BPath for_leaf( buf );  // e.g. "/Music/TheWall.mp3"
		leafname = for_leaf.Leaf();  // e.g. "TheWall.mp3"
		
		BPath p;
		for_leaf.GetParent( &p );  // e.g. "/Music"
	parent.SetTo( "/boot" );///
		if( p.Path() != BString("/") )  // don't append "/" slash as that results in a NULL path for some reason
			parent.Append( p.Path() );  // e.g. "/boot/Music"
		
		printf( " <%s> <%s> <%s>\n", for_leaf.Leaf(), p.Path(), parent.Path() );
	}
	
	struct stat st = {};
	if( parent.Path() )
	{
		auto res = _kern_read_stat(-1, parent.Path(), false, &st, sizeof(st) );
		printf("  kern read stat res: %d  device: %ld  dir: %lu\n", res, st.st_dev, st.st_ino);
	}
	// abuse the "fileno" field, assign it with parent dir's inode (instead of the found entry's inode):
	buffer->d_fileno = st.st_ino;
	buffer->d_pdev = st.st_dev;
	strcpy( buffer->d_name, leafname );  // e.g. "TheWall.mp3"
#else
	// abuse the d_name field, fill it with the FULL PATH:
//	if( res > 0 )
///		strcpy( buffer->d_name, buf );  // e.g. "/Music/TheWall.mp3"
#endif
	
	return 1;
}



//#pragma mark - C-style libroot functions -


extern "C"
int _kern_open_query(
	dev_t device,
	const char * parsed_predicate,
	size_t pred_length,
	uint32 flags,
	port_id port,
	int32 token
	)
{
	// USED BY
	// - BQuery::Fetch()
	
	BPath protocol;
#if 0
	///ToDo: hardcoded "/boot": I need to fix BVolume.GetRootDirectory() before I can work cleanly like this:
	BDirectory root;
	BVolume v( device );
	v.GetRootDirectory( &root );
	protocol.SetTo( &root );
#else
	// hardcode for now....:
	protocol.SetTo( "/boot" );
#endif
	protocol.Append( "_BQuery_support" ); ///ToDo: #include "_GeShared_xattr_defines.h" after moving constant there?
	protocol.Append( parsed_predicate );///ToDo: mangle "/" (slashes) ; use BString.Escape() ?
	
	if( debug )
		printf( "bquery: querying <%s>\n", protocol.Path());
	
#if 0
	// opendir() based implementation (before we moved on to plain-file listings):
	
	// nah don't use opendir(), otherwise entries get located inside the VIRTUAL query dir, instead of inside their real location!
		e.g.
			[init -> basic-layers] aka: /boot/_BQuery_support/(Audio:Artist=="Pink*Floyd")/song.ogg (oops !)
	BDirectory dir( protocol.Path() );
	BEntry ent;
	Genode::warning("Read Query Entries");
	while( B_OK == dir.GetNextEntry(&ent) )
	{
		Genode::warning( "__Query Reading yields path <", ent.Name(), ">" );
		BPath p( &ent );
		Genode::warning( "aka: ", p.Path());
	}

#else
	
	// plain-file listing:
	// forward to _kern_open() (the one BFile uses)
	return
		_kern_open( -1, protocol.Path(), B_READ_ONLY, 0 );//DEFFILEMODE & ~__gUmask );
/*
	BFile file( protocol.Path(), B_READ_ONLY );
	struct stat st = {};
	file.GetStat( &st );
	{
		void * results = calloc( st.st_size, 1 );
		auto res = file.Read( results, st.st_size );
		
		Genode::log( "query.read retrieved ", res, " bytes of total ", st.st_size );
		Genode::log( "buf: ", (const char*)results );
		//..
		
		free( results );
	}
	
	return -1;
	*/
#endif
}

