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

// base.a
#include <util/token.h>  // Genode::Token

// project
#include "BufIO.h"//class PickStr...

// lib-agms
#if 1
extern "C"
{
#	include "MatchUTF8.h"  // AGMS's feature-complete string matcher
}
#endif

// this
#include "SlotItem.h"


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



KeyValPair::KeyValPair(
			const char * _key,
			const char * _value
			)
:	key( _key )
	,value( _value )
{
	//Genode::log("kvp: value: <", value, ">");
}



//#pragma mark - boilerplate stuff -


SlotItem::SlotItem( Genode::Allocator & alloc )
:	fullPath()
	,memHeap( alloc )
{
}

SlotItem::~SlotItem()
{
	Clear();
}


void SlotItem::Clear()
{
	while( KeyValPair * pair = first() )
	{
		remove( pair );
		destroy( memHeap, pair );
	}
}


void SlotItem::SetFullpath( const char * newpath, unsigned str_len )
{
	PickStr path( newpath, str_len );
	
	fullPath = path.string();
	
	if( debug >= 2 )
		Genode::log( "  item.setpath <", fullPath, ">" );
}

const char * SlotItem::FullPath() const
{
	return fullPath.string();
}


bool SlotItem::HasAttr( const char * attrname ) const
{
	for( const KeyValPair * pair = first(); pair; pair = pair->next() )
		if( pair->key == attrname )
			return true;
	
	return false;
}

unsigned SlotItem::CountAttrs() const
{
	unsigned i = 0;
	
	for( const KeyValPair * pair = first(); pair; pair = pair->next() )
		i += 1;
	
	return i;
}


void SlotItem::SetAttr( const char * attrname, const char * val )//const void * buf, unsigned bufsize )
{
	for( KeyValPair * pair = first(); pair; pair = pair->next() )
	{
		if( pair->key == attrname )
		{
			//Genode::log( " set existing attr" );
			
			// set existing
			pair->value = val;
			
			return;
		}
	}
	
	// append new
//	Genode::Cstring val( (const char*)buf, bufsize );
	;
	insert( new (memHeap) KeyValPair(attrname, val) );//(const char*)buf) );//val) );
}

void SlotItem::DeleteAttr( const char * attrname )
{
	for( KeyValPair * pair = first(); pair; pair = pair->next() )
	{
		if( pair->key == attrname )
		{
			remove( pair );
			destroy( memHeap, pair );
			
			return;
		}
	}
	
	Genode::warning( "idx: SlotItem.DeleteAttr: attr not found" );
}



//#pragma mark - the big bad Predicate parsing and matching -


struct Scanner_policy_identifier_with_colon
{
	// Letters and underline characters are allowed anywhere in an identifier,
	// digits and colons must not appear at the beginning.
	static bool identifier_char(char c, unsigned i)
	{
		return
			Genode::is_letter(c)
			|| (c == '_')
			|| (i && c == ':')  // allow a colon, as in "Audio:Artist"
			|| (i && Genode::is_digit(c))
			;
	}
	static bool end_of_quote(const char *s)
	{
		return
			s[0] != '\\'
			&&
			s[1] == '\"'
			;
	}
};

struct Scanner_policy_operator
{
	static bool identifier_char(char c, unsigned i)
	{
		return  // allow "=="
			(i < 2)
			&& c == '='
			;
	}
	static bool end_of_quote(const char *s)
	{
		return
			s[0] != '\\'
			&&
			s[1] == '\"'
			;
	}
};



bool SlotItem::MatchesPredicate( const char * predicate ) const
{
	///ToDo: support recursive expressions like ((...) _&&_ (...))
	
	if( debug >= 2 )
		Genode::log( " Trying to match <", fullPath, "> against query predicate" );
	
	return recursParenthese( predicate );
}


bool SlotItem::recursParenthese( const char * cur_pos ) const
{
	// parse and handle e.g.
	//		(Audio:Artist=="Pink*Floyd")
	
	if( cur_pos[0] != '(' )
	{
		Genode::warning( "query predicate: expected opening bracket, but got this instead: ", cur_pos[0] );
		
		return false;
	}
	// Else:
	
	cur_pos += strlen( "(" );
	
	// Parse e.g. <Audio:Artist=="Pink*Floyd">
	//         or <Audio:Artist == "*[aA]*"> (whitespaces around operator)
	//
	char wanted_attr[64] = {};
	char ope[3] = {};
	char wanted_value[256] = {};
	{
		Genode::Token<Scanner_policy_identifier_with_colon>
			token( cur_pos );
		token.string( wanted_attr, sizeof(wanted_attr) );
		
		Genode::Token<Scanner_policy_operator>
			token2 = token.start() + token.len();//next();//( cur_pos );
		token2 = token2.eat_whitespace();
		token2.string( ope, sizeof(ope) );
		
		Genode::Token<Scanner_policy_operator> //policy doesnt matter, we want a string..
			token3 = token2.next();
		token3 = token3.eat_whitespace();
		token3.string( wanted_value, sizeof(wanted_value) );
		
		if( debug >= 2 )
			Genode::log( "  tokens <", (const char*)wanted_attr, "> <", (const char*)ope, "> <", (const char*)wanted_value, ">" );
		
		cur_pos = token3.start() + token3.len();
	}
	///ToDo: manage B_INT32 ? using eg this to test for integer values:
	//Genode::ascii_to( const char*, signed & )
	//Or better yet, Token support:
	// if( Token::NUMBER == token.type() )
	// ..
	
	if( cur_pos[0] != ')' )
	{
		Genode::warning( "query predicate: expected closing bracket, but got this instead: ", cur_pos[0] );
		
		return false;
	}
	
	StrValue actual_value;
	;
	if( getActualValue(wanted_attr, actual_value) )
	{
		if( applyOperator(ope, wanted_value, actual_value) )
		{
			if( debug >= 2 )
				Genode::log( "  ** Match **, adding this file to query results: ", fullPath );
			
			return true;
		}
		else
			// no match, go to next file
			return false;
	}
	// else:
	
	// if the attribute is missing, the file never matches, even if the predicate calls for 'empty value'
	return false;
}


bool SlotItem::getActualValue(
			const StrName wanted_attr,
			StrValue & into_actual_value
			) const
{
	for( const KeyValPair * pair = first(); pair; pair = pair->next() )
	{
		if( debug >= 4 )
			Genode::log( "  ..trying <", pair->key, ">" );
		
		if( wanted_attr == pair->key )
		{
			// found !
			into_actual_value = pair->value;
			
			if( debug >= 3 )
				Genode::log( "  getActualValue <", wanted_attr, "> returns: ", into_actual_value );
			
			return true;
		}
	}
	
	if( debug >= 2 )
		Genode::log( "  getActualValue <", wanted_attr, "> did NOT find an attribute value..." );
	
	return false;
}


bool SlotItem::applyOperator(
			const StrOperator & ope,
			const StrValue & wanted_value,
			const StrValue & raw_actual_value
			) const
{
	if( debug >= 2 )
		Genode::log( "   applyOperator: test that <", wanted_value, "> <", ope, "> <", raw_actual_value, ">" );
	
	if( ope == "==" )
	{
		if( 0==Genode::strcmp(raw_actual_value.string(), "RTSC", strlen("RTSC")) )
			return matchString(
				raw_actual_value.string() + Genode::strlen( "RTSC" ),
				ope,
				wanted_value
				);
		else
			Genode::warning( " vfs_indexer: unknown attribute type/header: <", raw_actual_value, ">" );
	}
	else
		Genode::warning( " vfs_indexer: unknown predicate operator: <", ope, ">" );
	
	return false;
}



bool SlotItem::matchString(
			const StrValue & actual_value,
			const StrOperator & ope,
			const StrValue & wanted_value
			) const
{
	if( ope == "==" )
	{
		// Operator == somewhat does this (except it's more complex, with wildcards and such):
		//		return wanted_unpacked == actual_value;
		
		char wanted_unpacked[256] = {};
		
		// eg: int count = unpack_string( "\"Pink \\\" Floyd\"", out_buf, out_len );
		// eg: count == 0 when calling unpack_string( "Floyd" ); -- in that case, copy the raw string
		int len = Genode::unpack_string( wanted_value.string(), wanted_unpacked, sizeof(wanted_unpacked) );
		if( len <= 0 )
			Genode::copy_cstring( wanted_unpacked, wanted_value.string(), sizeof(wanted_unpacked) );
		
		if( debug >= 4 )
			Genode::log( "    Unpacked <", (const char*)wanted_unpacked, "> from poss. quotted string <", wanted_value, "> vs actual value <", actual_value, ">" );  // e.g.: UNPACKED 10 bytes: <Pink*Floyd> from <"Pink*Floyd">
		
#if 1
		auto valid = IsValidUTF8Pattern( wanted_unpacked, NULL );
		if( false == valid )
			Genode::warning( "bquery: ", valid ? "valid" : "INVALID", " pattern: ", (const char*)wanted_unpacked, " versus: ", actual_value );///
		
		auto res = MatchUTF8e(
			wanted_unpacked,
			actual_value.string()
			);
		
		// !
		return MATCH_VALID == res;
		
		///ToDo: do we want to also get inspiration from AGMS for parenthese parsing and the like ? (for Lightning-style complex queries)
		/// or maybe this ? :
		// https://git.haiku-os.org/haiku/tree/src/add-ons/kernel/file_systems/shared/QueryParserUtils.cpp
		// (compareKeys())
		// or (!)
		// https://git.haiku-os.org/haiku/tree/src/add-ons/kernel/file_systems/bfs/Query.cpp
#endif
		
#if 0
		// kinda strcmp(), but with basic "*" wildcard handling:
		//
		unsigned act = 0;
		for( int w = 0; w < len; w++ )
		{
			if( wanted_unpacked[w] == actual_value.string()[act] )
			{
				act += 1;
				
				continue;
			}
			else if( '*' == wanted_unpacked[w] )
			{
				if( debug >= 2 )
					Genode::log( " wildcard case" );
				
				// advance in <actual_value>, looking for first letter that matches the one after the "*" wildcard:
				w += 1;
				const char wanted = wanted_unpacked[w];
				if( '\0' == wanted )
					// the star comes last, meaning we're e.g. matching "Pink*" versus "PinkFoo", which always matches:
					return true;
				
				for(  ; ; act += 1 )
				{
					if( act >= actual_value.length() )
						return false;
					
					if( wanted == actual_value.string()[act] )
						break;
				}
				
				// for() exited with a match, so we do "++" on both buffers:
				act += 1;
				// continue outer for(), including "w++"
			}
			else  // wanted_unpacked[w] != actual_value.string()[act]
				return false;
		}
#endif
		
		return true;
	}
	
	return false;
}

