/*
 * copyright: 2013-2015
 * name : Francis Banyikwa
 * email: mhogomchungu@gmail.com
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <libsecret-1/libsecret/secret.h>

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

/*
 * The libsecret API we use stores information in key-value pairs.
 *
 * The API requires a key to give the key's associated value.
 *
 * How can we tell how many and what keys libsecret is holding for us?
 *
 * The API does not give a list of keys it is holding for us and we solve this
 * problem using two schemas.
 *
 * Schema "keyValues" holds user provided keys and their associated user provided values.
 *
 * Schema "keyID" holds unique numbers generated by the library and these numbers are associated
 * with user provided keys.
 *
 * We also store an entry in libsecret that keeps track of the number of entries
 * we have.
 *
 * In "keyID" schema,we get a list of all user provided keys we are managing by looping over these
 * unique numbers looking for their corresponding values.These values are user provided keys.
 *
 * Once we have all the keys,we use them in "keyValue" schema to get their corresponding values and
 * then we return all keys and their values in key-value pairs to the library user.
 *
 */

#define BUFFER_SIZE 32

static char  * _get_string_value_0( const SecretSchema * s,const char * key )
{
	return secret_password_lookup_sync( s,NULL,NULL,"string",key,NULL ) ;
}

static int _get_string_value( const SecretSchema * s,const char * key )
{
	char * c = _get_string_value_0( s,key ) ;
	int e ;

	if( c == NULL ){

		return -1 ;
	}else{
		e = atoi( c ) ;
		free( c ) ;
		return e ;
	}
}

static char * _get_integer_value_0( const SecretSchema * s,int key )
{
	return secret_password_lookup_sync( s,NULL,NULL,"integer",key,NULL ) ;
}

static int _get_integer_value( const SecretSchema * s,int key )
{
	char * c = _get_integer_value_0( s,key ) ;
	int e ;

	if( c == NULL ){

		return -1 ;
	}else{
		e = atoi( c ) ;
		free( c ) ;
		return e ;
	}
}

static int _set_integer_value( const SecretSchema * s,const char * name,const char * key,int value )
{
	return secret_password_store_sync( s,"default",name,key,NULL,NULL,"integer",value,NULL ) ;
}

static int _set_string_value( const SecretSchema * s,const char * name,const char * key,const char * value )
{
	return secret_password_store_sync( s,"default",name,key,NULL,NULL,"string",value,NULL ) ;
}

static int _number_of_entries_in_the_wallet( const SecretSchema * s )
{
	int e = _get_string_value( s,"lxqt_wallet_size" ) ;

	if( e == -1 ){
		return 0 ;
	}else{
		return e ;
	}
}

static int _clear_integer_value( const SecretSchema * s,int k )
{
	return secret_password_clear_sync( s,NULL,NULL,"integer",k,NULL ) ;
}

static int _clear_string_value( const SecretSchema * s,const char * k )
{
	return secret_password_clear_sync( s,NULL,NULL,"string",k,NULL ) ;
}

/*
 * This function is used to check if a wallet is open or not and force prompt to open it if it is not open.
 *
 * write to the wallet and the operation will succeed if the wallet is open.
 * If the wallet is not open,the operation will block while a user is prompted for a key to unlock it.
 * If the user fail to unlock it,the operation will fail.
 */
int lxqt_libsecret_wallet_is_open( const void * s )
{
	const SecretSchema * e = s ;
	return _set_string_value( e,e->name,"lxqt_wallet_open","lxqt_wallet_open" ) ;
}

char * lxqt_libsecret_get_value( const char * key,const void * s )
{
	return _get_string_value_0( s,key ) ;
}

void * lxqt_libsecret_create_schema( const char * schemaName,const char * type )
{
	SecretSchema * s = malloc( sizeof( SecretSchema ) ) ;

	memset( s,'\0',sizeof( SecretSchema ) ) ;

	s->name  = schemaName ;
	s->flags = SECRET_SCHEMA_NONE ;

	s->attributes[0].name = type ;

	if( strcmp( type,"string" ) == 0 ){

		s->attributes[0].type = SECRET_SCHEMA_ATTRIBUTE_STRING ;
	}else{
		s->attributes[0].type = SECRET_SCHEMA_ATTRIBUTE_INTEGER ;
	}

	s->attributes[1].name = "NULL" ;
	s->attributes[1].type = 0 ;

	return s ;
}

static gboolean _password_store_sync( const char * key,
				      const char * value,
				      const void * kv,
				      const void * id,
				      int ws )
{
	const SecretSchema * keyValues = kv ;
	const SecretSchema * keyID     = id ;

	int i = 0 ;
	int k ;

	char wallet_size[ BUFFER_SIZE ] ;

	const char * walletLabel = keyValues->name ;

	snprintf( wallet_size,BUFFER_SIZE,"%d",ws ) ;

	_set_string_value( keyValues,walletLabel,wallet_size,"lxqt_wallet_size" ) ;

	while( i < ws ){

		k = _get_integer_value( keyID,i ) ;

		if( k == -1 ){

			if( _set_integer_value( keyID,walletLabel,key,i ) ){

				if( _set_string_value( keyValues,walletLabel,value,key ) ){

					return TRUE ;
				}else{
					_clear_integer_value( keyID,i ) ;
					return FALSE ;
				}
			}else{
				return FALSE ;
			}
		}else{
			i++ ;
		}
	}

	return FALSE ;
}

gboolean lxqt_libsecret_password_store_sync( const char * key,
						  const char * value,
						  const void * keyValues,
						  const void * keyID )
{
	int j ;

	if( lxqt_libsecret_wallet_is_open( keyValues ) ){

		j = _get_string_value( keyValues,"lxqt_wallet_size" ) ;

		if( j == -1 ){

			return _password_store_sync( key,value,keyValues,keyID,1 ) ;
		}else{
			return _password_store_sync( key,value,keyValues,keyID,j + 1 ) ;
		}
	}else{
		return FALSE ;
	}
}

static gboolean _exceeded_limit( int k )
{
	/*
	 * We dont expect to manage 10000 entries and getting this far most likely means we are
	 * stuck in an endless loop
	 */
	return k == 10000 ;
}

gboolean lxqt_libsecret_clear_sync( const char * key,const void * kv,const void * id )
{
	const SecretSchema * keyValues = kv ;
	const SecretSchema * keyID     = id ;

	int i = 0 ;
	int k = 0 ;
	int e ;

	char * c ;
	char wallet_size[ BUFFER_SIZE ] ;
	const char * walletLabel = keyValues->name ;

	int j = _number_of_entries_in_the_wallet( keyValues ) ;

	if( lxqt_libsecret_wallet_is_open( keyValues ) ){

		while( i <= j ){

			c = _get_integer_value_0( keyID,k ) ;

			if( c != NULL ){

				e = strcmp( c,key ) ;

				free( c ) ;

				if( e == 0 ){

					_clear_integer_value( keyID,k ) ;

					e = _get_string_value( keyValues,"lxqt_wallet_size" ) ;
					snprintf( wallet_size,BUFFER_SIZE,"%d",e - 1 ) ;

					_set_string_value( keyValues,walletLabel,wallet_size,"lxqt_wallet_size" ) ;

					_clear_string_value( keyValues,key ) ;

					return TRUE ;
				}else{
					i++ ;
					k++ ;
				}
			}else{
				k++ ;

				if( _exceeded_limit( k ) ){

				    break ;
				}
			}
		}
	}

	return FALSE ;
}

char ** lxqt_secret_get_all_keys( const void * kv,const void * id,size_t * count )
{
	const SecretSchema * keyValues = kv ;
	const SecretSchema * keyID     = id ;

	int k = 0 ;
	int i = 0 ;
	int j ;

	char ** c = NULL ;
	char * e  = NULL ;

	*count = 0 ;

	if( lxqt_libsecret_wallet_is_open( keyValues ) ){

		j = _number_of_entries_in_the_wallet( keyValues ) ;
		c = malloc( sizeof( char * ) * j ) ;

		if( c != NULL ){

			while( i < j ){

				e = _get_integer_value_0( keyID,k ) ;

				if( e != NULL ){

					*( c + i ) = e ;
					*count += 1 ;
					i++ ;
					k++ ;
				}else{
					k++ ;

					if( _exceeded_limit( k ) ){

					    break ;
					}
				}
			}
		}
	}

	return c ;
}

int lxqt_libsecret_wallet_size( const void * s )
{
	return _number_of_entries_in_the_wallet( s ) ;
}
