

#include <classConfiguration.h>

#include <stdlib.h>

#include <sqlite3.h>

#include <stdio.h>

#include <string.h>

#include <array.h>

#include <text.h>

#include "console.h"






class sqlite{


	sqlite3 		* db;

	sqlite3_stmt 	* res;

	char * value;

	char * selectedModel;


	void constructor( char * filePath ) 
	{
		this->value = "good";

		this.connect( filePath );

	}

	void connect( char * filePath ) {

		printf("\n\ntrying to connect.. %s\n\n", filePath);

		int rc = sqlite3_open( filePath, & this->db );

		if ( rc != SQLITE_OK ) {

			printf( "Cannot open database: %s\n", sqlite3_errmsg( this->db ) );

			sqlite3_close( this->db );

			console->error( ( char * ) sqlite3_errmsg( this->db ) );

			return;

		}

		rc = sqlite3_prepare_v2( this->db, "SELECT SQLITE_VERSION()", -1, & this->res, 0 );    

		if (rc != SQLITE_OK) {

			console->error( ( char * ) sqlite3_errmsg( this->db ) );
			
			sqlite3_close( this->db );

			return;

		}

		rc = sqlite3_step(this->res);

		/*if ( rc == SQLITE_ROW ) {

			printf( "%s\n", ( char * ) sqlite3_column_text( this->res, 0 ) );

		}

		printf( "Success \n");
*/
		//return 1;
		
	}

	void free() {

		sqlite3_finalize( this->res );

		sqlite3_close( this->db );

	}

	void selectModel( char * className ) {

		this->selectedModel = className;

		/*

		printf("select className: %s\n", className);

		int classIndex 			= getClassIndexByClassName( this->selectedModel );

		printf("class index:	%i\n", classIndex);


		int propertyIndex 		= getPropertyIndexByPropertyName( classIndex, "username" );

		int propertyCount		= getPropertyCountByClassIndex( classIndex );

		char ** propertyNames	= getPropertiesByClassIndex( classIndex );

		int  * propertyOffsets	= getPropertyOffsetsByClassIndex( classIndex );


		printf("propertyIndex: %i\n\n", propertyIndex);

		for (int i = 0; i < propertyCount; ++i)
		{

			char * propertyName = propertyNames[i];

			int propertyOffset	= propertyOffsets[i];


			printf("propertyName: %s offset: %i\n", propertyName, propertyOffset);
		}



		

		char * shortPointer		= malloc( sizeof( char * ) * 100000 );

		void * * voidArray		= malloc( sizeof( void * ) * 100000 );

		int  structByteSize;
		
		getArrayByClassIndex( 100000, voidArray, shortPointer, &structByteSize, 0 );
		
	*/
	}

	void createTable() {

		char * errorMessage = 0;

		char *sql = "DROP TABLE IF EXISTS user;" 
		            "CREATE TABLE user( id INT, username TEXT, userlevel INT, hash TEXT );";

		int rc = sqlite3_exec(this->db, sql, 0, 0, &errorMessage);

		if ( rc != SQLITE_OK ) {
		    
		    printf( "SQL error: %s\n", errorMessage );
		    
		    sqlite3_free( errorMessage );

		    sqlite3_close(this->db);
		    
		    
		} 

	}

/*
	// Fast but unsave -> sql injection
	void addRowsFast( array * insertArray ) {

		array * copy	= insertArray;

		int classIndex 			= getClassIndexByClassName( this->selectedModel );

		text * query			= new text( "" );

		query->append( "INSERT INTO " );

		query->append( this->selectedModel );


		sqlite3_stmt * result;


		char * * propertyNames	= getPropertiesByClassIndex( classIndex );

		int propertyCount		= getPropertyCountByClassIndex( classIndex );

		int  * propertyOffsets	= getPropertyOffsetsByClassIndex( classIndex );

		int * datatypeIndices	= getPropertyDatatypeIndexesByClassIndex( classIndex );



		query->append( " ( " );

		for (int propertyIndex = 0; propertyIndex < propertyCount; ++propertyIndex)
		{

			char * propertyName 		= propertyNames[propertyIndex];

			int datatype				= datatypeIndices[ propertyIndex ];

			if( datatype > 0 ) {

				// join table because this is an class
				continue;
				
			}

			if( propertyIndex > 0 ) {

				query->append( ", " );

			}

			query->append( propertyName );

		}

		query->append( " ) " );

		query->append(" VALUES " );



		int insertItemCount	= copy->total;

		char * voidArray	= ( char * ) copy->items;

		//sqlite3_exec(result, "BEGIN TRANSACTION;", NULL, NULL, NULL);

		for (int i = 0; i < insertItemCount; ++i)
		{
			char * pointer 		= copy->get( i );



			if( i > 0 ) {

				query->append( ", " );

			}

			query->append( "( " );

			for (int propertyIndex = 0; propertyIndex < propertyCount; ++propertyIndex)
			{

				char * propertyName 		= propertyNames[propertyIndex];

				int datatype				= datatypeIndices[ propertyIndex ];

				if( datatype > 0 ) {

					// join table because this is an class
					continue;
					
				}

				int propertyOffset			= propertyOffsets[propertyIndex];

				int propertyDatatypeIndex	= getPropertyDatatypeIndex( datatypeIndices, propertyIndex );
				
				if( propertyIndex > 0 ) {

					query->append( ", " );

				}
	
				if( propertyDatatypeIndex == -5 ) {

					int value	= *( int * )(pointer + propertyOffset);

					char * textNumber = malloc( sizeof( char ) * 20 );

					// Boost Spirit.Karma is faster
					sprintf( textNumber, "%d", value );

					query->append( textNumber );

				} else if( propertyDatatypeIndex == -3 ) {

					char * columnValueCopy		= ( char * ) malloc( 8 );

					char * value				= ( char * ) ( pointer + propertyOffset );

					strncpy( &columnValueCopy, value, 8 );

					query->append( "'" );

					query->append( columnValueCopy );

					query->append( "'" );


				}

			}

			query->append( " )" );

		}

		//sqlite3_exec(result, "END TRANSACTION;", NULL, NULL, NULL);
		sqlite3_prepare_v2( this->db, query->buffer, -1, &result, 0 );

		sqlite3_step( result );

		sqlite3_finalize( result );

		//printf("insert query: %s\n", query->buffer);

	}
*/







	void addRows( array * insertArray ) {

		array * copy	= insertArray;

		int classIndex 			= getClassIndexByClassName( this->selectedModel );

		text * query			= new text( "" );

		query->append( "INSERT INTO " );

		query->append( this->selectedModel );


		sqlite3_stmt * result;


		char * * propertyNames	= getPropertiesByClassIndex( classIndex );

		int propertyCount		= getPropertyCountByClassIndex( classIndex );

		int  * propertyOffsets	= getPropertyOffsetsByClassIndex( classIndex );

		int * datatypeIndices	= getPropertyDatatypeIndexesByClassIndex( classIndex );



		query->append( " ( " );

		for (int propertyIndex = 0; propertyIndex < propertyCount; ++propertyIndex)
		{

			char * propertyName 		= propertyNames[propertyIndex];

			int datatype				= datatypeIndices[ propertyIndex ];

			if( datatype > 0 ) {

				// join table because this is an class
				continue;
				
			}

			if( propertyIndex > 0 ) {

				query->append( ", " );

			}

			query->append( propertyName );

		}

		query->append( " ) " );

		query->append( " VALUES ( " );

		for (int propertyIndex = 0; propertyIndex < propertyCount; ++propertyIndex)
		{

			int datatype				= datatypeIndices[ propertyIndex ];

			if( datatype > 0 ) {

				// join table because this is an class
				continue;
				
			}

			if( propertyIndex > 0 ) {

				query->append( ", " );

			}

			query->append( "?" );

		}

		query->append(" ) " );



		sqlite3_prepare_v2( this->db, query->value, -1, &result, 0 );

		sqlite3_exec(this->db, this->selectedModel, NULL, NULL, 0);

		sqlite3_exec(this->db, "PRAGMA synchronous = OFF", NULL, NULL, 0);

		sqlite3_exec(this->db, "PRAGMA journal_mode = MEMORY", NULL, NULL, 0);


		int insertItemCount		= copy->total;

		char * voidArray		= ( char * ) copy->items;

		//sqlite3_exec(result, "BEGIN TRANSACTION;", NULL, NULL, NULL);

		for (int i = 0; i < insertItemCount; ++i)
		{
			char * pointer 		= copy->get( i );

			this->updateRow( result, propertyCount, propertyNames, propertyOffsets, datatypeIndices, pointer );

			sqlite3_step( result );

			sqlite3_reset( result );

		}

		//sqlite3_exec(result, "END TRANSACTION;", NULL, NULL, NULL);

		sqlite3_finalize( result );

		//printf("insert query: %s\n", query->buffer);

	}



	void updateRow( sqlite3_stmt * result, int propertyCount, char * * propertyNames, int  * propertyOffsets, int * datatypeIndices, char * pointer ) {

		for (int propertyIndex = 0; propertyIndex < propertyCount; ++propertyIndex)
		{

			char * propertyName 		= propertyNames[propertyIndex];

			int propertyOffset			= propertyOffsets[propertyIndex];

			int propertyDatatypeIndex	= getPropertyDatatypeIndex( datatypeIndices, propertyIndex );

			//printf("propertyName: %s offset: %i datatype: %i propertyIndex: %i\n", propertyName, propertyOffset, propertyDatatypeIndex, propertyIndex);

			if( propertyDatatypeIndex == -5 ) {

				int value	= *( int * )(pointer + propertyOffset);

				//printf("integer value: %i  %i\n", propertyIndex + 1, value);

				sqlite3_bind_int( result, propertyIndex + 1, value );

			} else if( propertyDatatypeIndex == -3 ) {

				//char * columnValueCopy		= ( char * ) malloc( 8 );

				uintptr_t * value				= ( uintptr_t * ) ( pointer + propertyOffset );

				//strncpy( &columnValueCopy, value, 8 );

				//printf("char * value: %i  %s\n", propertyIndex + 1, columnValueCopy);

				sqlite3_bind_text( result, propertyIndex + 1, (char *) *value, -1, SQLITE_TRANSIENT  );

			}

		}

		//printf("\n\n\n"); 

	}

	void update( void * row ) {

		int classIndex 				= getClassIndexByClassName( this->selectedModel );

		char ** propertyNames		= getPropertiesByClassIndex( classIndex );

		int propertyCount			= getPropertyCountByClassIndex( classIndex );

		int  * propertyOffsets		= getPropertyOffsetsByClassIndex( classIndex );

		int * datatypeIndices		= getPropertyDatatypeIndexesByClassIndex( classIndex );

		int propertyIdOfIndex		= -1;


		for ( int i = 0; i < propertyCount; ++i )
		{
			char * propertyName	= propertyNames[i];

			//printf("propertyName: %s\n", propertyName);

			if( strcmp( propertyName, "id" ) == 0 ) {

				propertyIdOfIndex = i;

				break;

			}
		}

		if( propertyIdOfIndex == -1 ) {

			printf("Class '%s' does not have an id field. ");

		}

		int idOffset		= propertyOffsets[ propertyIdOfIndex ];

		char * pointer		= row;

		int id				= *( pointer + idOffset );

		text * query		= new text( "" ); //"UPDATE user set username = '?' where id=?; "

		query->append( "UPDATE " );

		query->append( this->selectedModel );

		query->append( " SET " );

		int activePropertyCount = 0;

		// set propertyCount
		for (int propertyIndex = 0; propertyIndex < propertyCount; ++propertyIndex)
		{

			char * propertyName 		= propertyNames[propertyIndex];

			int datatypeIndex			= datatypeIndices[propertyIndex];

			// todo properties that are classes like "array"
			if( datatypeIndex > 0 ) {

				continue;

			}

			activePropertyCount++;

			if( propertyIndex > 0 ) {

				query->append( ", " );

			}

			query->append( propertyName );

			query->append( " = ? " );

		}

		query->append( " where id = ? " );

		sqlite3_stmt * result;

		sqlite3_prepare_v2( this->db, query->value, -1, &result, 0 );

		this->updateRow( result, propertyCount, propertyNames, propertyOffsets, datatypeIndices, pointer );

		sqlite3_bind_int( result, activePropertyCount + 1, id );

		//printf("propertyCount: %i  %i\n", propertyCount, id );

		sqlite3_step( result );

		sqlite3_finalize( result );

		//printf("update query: %s\n", query->buffer);

	}

	void addRow( void * row ) {

		int classIndex 			= getClassIndexByClassName( this->selectedModel );

		text * query			= new text( "" );

		query->append( "INSERT INTO " );

		query->append( this->selectedModel );


		sqlite3_stmt * result;


		char * * propertyNames	= getPropertiesByClassIndex( classIndex );

		int propertyCount		= getPropertyCountByClassIndex( classIndex );

		int  * propertyOffsets	= getPropertyOffsetsByClassIndex( classIndex );

		int * datatypeIndices	= getPropertyDatatypeIndexesByClassIndex( classIndex );

		char * pointer 			= row;


		query->append( " ( " );

		for (int propertyIndex = 0; propertyIndex < propertyCount; ++propertyIndex)
		{

			char * propertyName 		= propertyNames[ propertyIndex ];


			int datatype				= datatypeIndices[ propertyIndex ];

			if( datatype > 0 ) {

				// join table because this is an class
				continue;
				
			}


			if( propertyIndex > 0 ) {

				query->append( ", " );

			}

			query->append( propertyName );

		}

		query->append( " ) " );

		query->append( " VALUES ( " );

		for (int propertyIndex = 0; propertyIndex < propertyCount; ++propertyIndex)
		{

			int datatype				= datatypeIndices[ propertyIndex ];

			if( datatype > 0 ) {

				// set joined table id
				continue;
				
			}


			if( propertyIndex > 0 ) {

				query->append( ", " );

			}

			query->append( "?" );

		}

		query->append(" ) " );



		// {

			sqlite3_prepare_v2( this->db, query->value, -1, &result, 0 );

			this->updateRow( result, propertyCount, propertyNames, propertyOffsets, datatypeIndices, pointer );

			sqlite3_step( result );
			
		// }

		sqlite3_finalize( result );

		//printf("query: %s\n", query->buffer);

	}

	struct array * fetchRows( char * sql ) {

		sqlite3_stmt * result;

		int rc 					= sqlite3_prepare_v2( this->db, sql, -1, &result, 0 );

		if ( rc != SQLITE_OK ) {

			printf("Failed to execute statement: %s\n", sqlite3_errmsg( this->db ) );

		}

/*
		if ( rc != SQLITE_ROW ) {
		  
			printf("not rows found\n\n");

		}
*/
		int rowIndex 			= 0;


		//printf("select className: 	%s\n", this->selectedModel);

		int classIndex 			= getClassIndexByClassName( this->selectedModel );


		//printf("class index:		%i\n", classIndex);


		int propertyIndex 		= getPropertyIndexByPropertyName( classIndex, "username" );

		int propertyCount		= getPropertyCountByClassIndex( classIndex );

		char ** propertyNames	= getPropertiesByClassIndex( classIndex );

		int  * propertyOffsets	= getPropertyOffsetsByClassIndex( classIndex );

		int * datatypeIndices	= getPropertyDatatypeIndexesByClassIndex( classIndex );


		char * shortPointer		= malloc( sizeof( char * ) * 100000 );

		void * * voidArray		= malloc( sizeof( void * ) * 100000 );

		int  structByteSize;
		
		getArrayByClassIndex( 1000, voidArray, &structByteSize, classIndex );

		shortPointer = ( char * ) shortPointer;
 

		while ( sqlite3_step( result ) != SQLITE_DONE ) { 

			 //if( rowsCount == 0 ) {

				int pointerIndex 		= ( rowIndex * ( structByteSize ) );

				//==printf("pointerIndex: %i\n", pointerIndex);

				char * pointer 			= shortPointer + pointerIndex;

			 	int columnCount 		= sqlite3_data_count( result );

			 	//printf("columnCount: %i\n\n", columnCount);

			 	for (int i = 0; i < columnCount; ++i)
			 	{

					const char * columnName 	= sqlite3_column_name( result, i );

					const int propertyIndex 	= getPropertyIndexByPropertyName( classIndex, (char *)columnName );

					int propertyDatatypeIndex	= getPropertyDatatypeIndex( datatypeIndices, propertyIndex );

					int propertyOffset			= getPropertyOffsetByPropertyIndex( propertyOffsets, propertyIndex );

					//printf("sqlite3 column index: 	%i\n", i);

					//printf("sqlite3 columnName: 	%s\n", columnName);

					//printf("class property index: 	%i\n", propertyIndex);

					//printf("class property offset: 	%i\n", propertyOffset);

					if( propertyDatatypeIndex == -5 ) {

						const int columnValue 		= sqlite3_column_int( result, i );

	
						*( int * )( pointer + propertyOffset ) 	= columnValue; 

					} else if( propertyDatatypeIndex == -3 ) {

						const char * columnValue 	= sqlite3_column_text( result, i );

						char * columnValueCopy		=  malloc( strlen( columnValue ) );

						strncpy( columnValueCopy, columnValue, strlen( columnValue ) );

						//columnValueCopy[8] = "\0";


						// something wrong here.

						memcpy( pointer + propertyOffset, &columnValueCopy, 8 );

					}

					//printf("class property datatype: %i\n\n\n", propertyDatatypeIndex);

			 }


			voidArray[rowIndex] = pointer;

	//		printf("rowIndex: %i\n", rowIndex);

			rowIndex++;

		}

		struct array * rows = new array();

		rows->items		= voidArray;

		rows->total		= rowIndex;

		return rows;

	}

}