/*

	Copyright (c) 2020, 2023, The Unified Company.

*/

import tools	 		from './tools.js';

import querySQL	 		from './querySQL.js';

import document	 		from './document.js';

import DATATYPE 		from './datatype.js';

import path				from 'path';

import sqlite			from './sqlite.js';

import sqlite3			from 'better-sqlite3';


class databaseManager{

	database;

	type			= "update";

	sqlite 			= new sqlite();

	collisionID		= 0;

	serverID 		= 0;


	async connect() {

		if( !document.doctype ) {

			this.sqlite.setup();

			this.database 		= this.sqlite.db;

		}

	}

	createTimeUUID() {

		const timestamp 	= Date.now();

		++this.collisionID;

		var padddedCollisionID = String( this.collisionID % 9 ).padStart( 2, '0' )

		var TimeUUID = parseFloat( timestamp + "" + this.serverID + "" + padddedCollisionID );

		return TimeUUID;

	}

	query( query, execute = true ) {

		if( query.type == "insert" ) {

			query.setValue( "id", this.createTimeUUID() );

		}

		if( execute && !query.root ) {
			
			query.root = true;

		}
		
		if( !query.name ) {

			query.name = query.table;

		}


		var queryString = this.parseQuery( query );

		//console.log("query", query);

		if( execute ) {
		
			return this.executeQuery( queryString, query );
			
		}
		
		return queryString;

	}
	
	parseFilter( query ) {

		var searchable = query.filter.search;

		
	
		if( searchable ) {

			//console.log("query.filter", query, searchable);

			return this.parseSearch( searchable, query, 0 );

		} else {

			return false;

		}

	}

	tabLevel( level ) {

		var tabs = "";

		for (var i = 0; i < level; i++) {

			tabs += "\t";

		}

		return tabs;

	}

	parseSearchOperator( a, b, searchable, query, level ) {

		//console.log("parseSearchOperator", searchable);

		var tabs 			= this.tabLevel( level ) + "	";

		var subEnd 			= "";

		var begin 			= "\n" + tabs;

		var end				= tabs + "\n";


		switch( searchable.operator.replaceAll(/\s/g,'')) {

			case "LIKE" :

				b = "'%" + tools.htmlEntities( b ) + "%'";

			break;

			case "=" :

				b = "'" + tools.htmlEntities( b ) + "'";

				

			break;

			case "OR":

				searchable.operator 	= "	" + tabs + searchable.operator + "";

				subEnd = tabs;

			break;

			case "AND":

				searchable.operator 	= "\n	" + tabs + searchable.operator + "\n";

				subEnd = tabs;

			break;

			default:

		}


		return begin + "( " + a + " " + searchable.operator + " " + b + subEnd +  " ) " + end;

	}

	parsePath( searchable, filterAddress, query ) {

		var path 			= searchable.path;
		
		var parts 			= path.split("/");

		var columnName 		= parts.pop();

		var filterAddress	= parts.join("/") + "/";

		this.findMatchingQuery( filterAddress, query, searchable, path );

	}

	validateSearchables( a  ) {

		if( typeof a == "string" ) {

			return true;

		}


		if( a.operator ) {

			return true;

		}

		if( !a.queryName ) {

			return false;

		}

		return true;

	}

	parseSearch( searchable, query, level ) {

		if( searchable.operator ) {

			level++;


			var a = this.parseSearch( searchable.a, query, level );

			var b = this.parseSearch( searchable.b, query, level );

/*
			var invalidA = this.validateSearchables( searchable.a )

			var invalidB = this.validateSearchables( searchable.b )

			if( !invalidA ) {


				return false;

			}

			if( !invalidB ) {

				return false;

			}

			if( !a && typeof a != "string" ) {

				return b;

			}

			if( !b && typeof b != "string" ) {

				return a;

			}
*/
			return this.parseSearchOperator( a, b, searchable, query, level );

		}

		if( searchable.path ) {

			var filterAddress = "";

			this.parsePath( searchable, filterAddress, query );

			if( !searchable.queryName ) {

				//return false;

			}
			
		}


		if( typeof searchable == "string" ) {

			return searchable;

		}

		if( searchable.queryName ) {

			return searchable.queryName;

		}


	}

	findMatchingQueryJoins( query, filterAddress, searchable ) {

		for( var c = 0; c< query.joins.length; c++ ) {
			
			var join = query.joins[c];

			console.log("search join", join);

			//this.findMatchingQuery( filterAddress, join, searchable );

		}

	}

	findMatchingQueryUnions( query, filterAddress, searchable ) {

		for (var i = 0; i < query.union.length; i++) {

			var union = query.union[i];

			this.findMatchingQuery( filterAddress, union, searchable  );

		}

	}

	findMatchingQuery( filterAddress, query, searchable, path ) {

		//console.log( "query.filterAddress", query.filterAddress );

		//console.log( "searchable.path", filterAddress );

		var parts = path.split("/");

		//console.log("parts", parts);

		if( parts.length == 3 ) {

			parts.shift();

		}

		searchable.queryName = parts.join(".");


/*
		if( query.filterAddress == filterAddress ) {

			var parts = path.split("/");

			console.log("parts", parts);


			searchable.queryName = path.replace("./", query.name + ".");

		} else {


			searchable.queryName = path;

			console.log("filterAddress", filterAddress);

			console.log("filterAddress", searchable);

			var parts = filterAddress.split("/")

			//console.log("parts", parts);

			this.findMatchingQueryJoins( query, filterAddress, searchable );

			//this.findMatchingQueryUnions( query, filterAddress, searchable );

		}
*/
	}
	
	insertRow( table ) {
	
		var query = new querySQL( );
		
		query.type	= "insert";

		query.addObject( table );
		
		var collection	= table.getCollection();
		
		if( collection ) {
		
			query.setColumn( "joined", true ); // todo : fix : set boolean on setColumn
			
		} else {
			
			query.setColumn( "joined", false );
		
		}

		var row = this.query( query ); 

		return row.lastInsertRowid;
	
	}
	
	updateChildren( query ) {
	
		var objects = query.objects;
		
		for( var c = 0; c < objects.length; c++ ) {
		
			var object 		= objects[c];

			var values 		= new Array();
			
			var children 	= object.getChildren();

			for( var c = 0; c < children.length; c++ ) {
			
				var child = children[c];
				
				if( child.datatype ) {
				
					values.push( child.value );
					
				}
			}
			
		}
	
	}
	
	exec( queryString ) {
	
		return this.database.prepare( queryString ).all();
	
	}
	
	prepare( queryString ) {

		return this.database.prepare( queryString ).all();

	}

	performUpdateQuery( queryString, query ) {

		if( query.objects.length < 2 ) {

			var update = this.database.prepare( queryString );

			update.run( query.getValues() );
			
		}

		if( query.debug ) {

			console.log();
			console.log( queryString );
			console.log();
			console.log( query.getValues() );
			console.log();

		}

		if( query.objects.length > 1 ) {
		
			var id = this.updateChildren( query );

		} else {

			if( global.syncClient && query.sync ) {

				var id = query.getWhereByColumnName("id");

				global.syncClient.sendMessage( queryString, query, id );
			
			} 


		}

		//if( global.syncClient && query.sync ) {

		//	global.syncClient.sendMessage( queryString, query, output.lastInsertRowid );
			
		//} 

		// query.queryUpdateChildren();

	}

	performInsertQuery( queryString, query ) {

		var insert = this.database.prepare( queryString );

		if( query.debug ) {

			console.log( queryString );

		}

		var output 	= insert.run( query.getValues() );

		if( global.syncClient && query.sync) {

			var id = query.getWhereByColumnName("id");

			global.syncClient.sendMessage( queryString, query, output.lastInsertRowid );
			
		} 

		return output;

	}

	performDeleteQuery( queryString, query ) {

		if( query.debug ) {

			console.log(queryString );

		}

		var output 	= this.database.prepare( queryString ).run();

		if( global.syncClient && query.sync ) {

			global.syncClient.sendMessage( queryString, query, output.lastInsertRowid );
		
		} 

		return output;

	}

	executeQuery( queryString, query ) {

		console.log( queryString );
	
		switch( query.type.toLowerCase() ) {
		
			case "update":

				return this.performUpdateQuery( queryString, query );
				
			break;
			
			case "insert":

				return this.performInsertQuery( queryString, query );
			
			break;
			
			case "select":

				return this.database.prepare( queryString ).all();
			
			break;

			case "count":

				return this.database.prepare( queryString ).pluck().get();
			
			break;
			
			case "delete":

				return this.performDeleteQuery( queryString, query );

			break;
			
		}
	
	}

	runQuery( queryString ) {

		return this.database.prepare( queryString ).all();

	}

	parseQueryItems( query, queryID ) {

		var items 			= Object.entries( query );

		var queryString 	= "";

		for( var c = 0; c < items.length; c++ ){
			
			var currentQuery 	= items[c];
			
			var name			= tools.getClassNameByEntry( currentQuery );
			var value			= tools.getObjectByEntry( currentQuery );

			queryString 		+= this.parseTerm( name, value, query, queryID );
			
		}

		queryString += "\n";

		return queryString;

	}

	parseQueryJoins( query ) {

		var queryString = "";

		for(var c = 0; c< query.joins.length; c++) {
			
			var join = query.joins[c];
			
			join.parent 	= query;
			join.queryID 	= c;
			
			queryString 	+= this.parseQuery( join, c );

		}

		return queryString;

	}

	parseQueryUnions( query, queryString ) {

		var parsedUnionQuerys = new Array();

		for (var i = 0; i < query.union.length; i++) {

			var union = query.union[i];

			union.root = true;

			parsedUnionQuerys[i] = this.query( union, false );
		

		}

		if( parsedUnionQuerys.length > 0 ) {

			return queryString =  "SELECT  * from ( " + parsedUnionQuerys.join(" \nUNION\n ") + ") ";

		} else {

			return queryString;

		}

	}

	finalizeItems( query, items, queryID, stackedQuery ) {

		var queryString = "";

		for( var c = 0; c < items.length; c++){
			
			var currentQuery 	= items[c];
			
			var name			= tools.getClassNameByEntry( currentQuery );

			var value			= tools.getObjectByEntry( currentQuery );


			queryString += this.parseStacked( name, value, query, queryID, stackedQuery );

		}	

		return queryString;
	}
	
	finalizeQuery( query, queryID ) {

		var queryString	= "";

		if( tools.getClassName( query ) == "querySQL" && query.root ) {

			var stackedQuery		= new Object();

			stackedQuery.where 		= new Array();

			stackedQuery.groups 	= new Array();

			stackedQuery.order 		= new Array();

			this.stackQuerys( query, stackedQuery );

			var items 			= Object.entries( stackedQuery );

			queryString			= this.finalizeItems( query, items, queryID, stackedQuery );

		}

		return queryString;
	}

	parseQuery( query, queryID = false ) {

		var queryString		= "";




		if( query.filter && query.filter.search ) {

			

			query.searchQuery 			= this.parseFilter( query );

			//console.log("query.searchQuery", query.getClassName(), query.searchQuery, query);

			if( tools.getClassName( query ) == "joinSQL" ) {

				query.parent.searchQuery 	= query.searchQuery;

			}

			

		}

		queryString 		+= this.parseQueryItems( query, queryID );

		queryString			+= this.parseQueryJoins( query );

		queryString			 = this.parseQueryUnions( query, queryString );

		queryString			+= this.finalizeQuery( query, queryID );
		
		return queryString;

	}

	stackOrder( query, stackedQuery ) {

		var order 		= query.order;

		for ( var i = 0; i < order.length; i++ ) {

			var orderStatement = new Object();

			orderStatement.query 		= query;

			orderStatement.columnName 	= order[ i ];

			stackedQuery.order.push( orderStatement );

		}

	}

	stackWhere( query, stackedQuery ) {

		var where 		= query.where;

		for (var i = 0; i < where.length; i++) {

			var whereStatements 	= where[i];

			whereStatements.query 	= query;

			stackedQuery.where.push( whereStatements );

		}

	}

	stackGroups( query, stackedQuery ) {

		var groups 		= query.groups;

		for (var i = 0; i < groups.length; i++) {

			var group 		= groups[i];

			group.query 	= query;

			stackedQuery.groups.push( group );

		}	

	}

	stackQuerys( query, stackedQuery ) {


		this.stackOrder( query, stackedQuery );

		this.stackWhere( query, stackedQuery );

		this.stackGroups( query, stackedQuery );


		if( query.limit ) {

			stackedQuery.limit 		= query.limit;

		}

		if( query.offset ) {

			stackedQuery.offset 	= query.offset;
			
		} 

		if( query.direction ) {

			stackedQuery.direction 	= query.direction;

		}


		for (var i = 0; i < query.joins.length; i++) {
			
			this.stackQuerys( query.joins[i], stackedQuery );

		}
		
	}
	
	parseTerm( name, value, query, queryID ){
	
		var queryString = "";
	
		switch( name.toLowerCase() ) {
		
			case "type":
				
				queryString += this.parseType( value, query, queryID );

			break;

			case "columns":

				queryString += this.parseColumns( value, query.type, query );

			break;
			
		
			case "table":

				queryString += this.parseTableStatement( value, query );
				
			break;

			case "values":

				queryString += this.parseValues( query );

			break;

			case "groupby":
			
				queryString += this.parseGroupByStatement( value, query );

			break;

			case "count":
			
			
			break;

		}
		
		return queryString;
		
	}

	parseGroupByStatement( value, query ){

		var groups 				= value;

		var groupStatements 	= new Array();

		for (var i = 0; i < groups.length; i++) {

			var currentGroup = groups[i];

			var queryString = currentGroup.query.name + "." + currentGroup.columnName;

			groupStatements.push( queryString );

		}

		if( groupStatements.length > 0 ) {
		
			return " group by " + groupStatements.join(",");

		} else {

			return "";

		}

	}

	parseStacked( name, value, query, queryID, stackedQuery ){
	
		var queryString = "";
	
		switch( name.toLowerCase() ) {
		
			case "where":
			
				queryString		+= this.parseWhereStatements( value, query );

			break;

			case "groups":

				queryString 	+= this.parseGroupByStatement( value, query );

			break;

			case "order":
				
				queryString 	+= this.parseOrderBy( value, query, stackedQuery );
				
			break;

			case "limit":
			
				queryString 	+= this.parseLimit( value  );

			break;

			case "offset":
			
				queryString 	+= this.parseOffset( value  );

			break;
					
		}
		
		return queryString;

	}



	parseEndTerm( name, value, query, queryID ){
	
		var queryString = "";
	
		switch( name.toLowerCase() ) {
		
			case "order":
				
				queryString += this.parseOrderBy( value, query );
				
			break;
			
		}
		
		return queryString;

	}

	parseLimit( limit ) {

		var queryString = "";

		if( limit ) {

			console.log("limits", limit );
		
			queryString += " LIMIT " + parseInt( limit );
			

		}

		return queryString;

	}

	parseOffset( offset ) {

		var queryString = "";

		if( offset ) {

			queryString += " OFFSET " + parseInt( offset );

		}

		return queryString;

	}

	createOrderParts( orderStatements, value ) {

		var orderParts 		= new Array();

		for ( var i = 0; i < orderStatements.length; i++ ) {

			var orderStatement 	= orderStatements[i];

			var columnName 		= orderStatement.columnName;

			var query 			= orderStatement.query;

			orderParts.push( tools.htmlEntities( columnName ) );

		}

		return orderParts;

	}

	parseOrderBy( orderStatements, query, stackedQuery ) {

		var orderParts		= this.createOrderParts( orderStatements );

		var queryString 	= "";

		if( orderParts.length > 0 ) {

			queryString += " ORDER BY " + orderParts.join(",");

			//console.log("parsing order by:", stackedQuery );

			if( query.direction ) {

				//if( stackedQuery.direction.toLowerCase() == "asc" || stackedQuery.direction.toLowerCase() == "desc" ) {

					queryString += " " + query.direction + " ";

				//}

			} else {


			/*
			if( stackedQuery.direction ) {

				//if( stackedQuery.direction.toLowerCase() == "asc" || stackedQuery.direction.toLowerCase() == "desc" ) {

					queryString += " " + stackedQuery.direction + " ";

				//}

			}
*/

			}

		}

		return queryString;

	}

	getQuotationTerm( where ) {

		var quotation	= "";

		if( where.quotation ) {

			if( where.type == "LIKE" ) {

				var quotation	= "'%"

			} else {

				var quotation	= "'"

			}

		}

		if( typeof where.value == "boolean" ) {

			var quotation	= ""

			if( where.value ) {

				//where.value == 1;

			} else {

				//where.value == 0;
			}

		}

		if( where.value == "false" ) {

			var quotation	= ""

			//where.value == 0;

		}

		return quotation;

	}

	parseWhereStatement( where, ANDStatements, ORStatements ) {

		var subQuery 		= where.query;

		var tableName 		= subQuery.name + ".";

		var quotation 		= this.getQuotationTerm( where );

		var quotationEnd 	= tools.reverseString( quotation );


		if( where.type != "" ) {

			var whereString = "		" + tableName + where.columnName + " " + where.type + " " +   quotation + where.value + quotationEnd;

			if( !where.or ) {

				ANDStatements.push( whereString );
			
			} else {

				ORStatements.push( whereString );

			}

		}

	}

	
	parseWhereStatements( value, query ) {

		var queryString = "";
	
		if( value.length > 0 ) {
		
			queryString += "	where\n "
			
		}
		
		var ANDStatements 		= new Array();

		var ORStatements 		= new Array();


		for( var b = 0; b < value.length;b++ ){

			var where 	= value[ b ];

			this.parseWhereStatement( where, ANDStatements, ORStatements );

		}
	
		if( ORStatements.length > 0 ) {

			ANDStatements.push( "(" + ORStatements.join("\n	OR\n") + ")" ); // Custom for the logical operators

		}

		queryString += ANDStatements.join("\n	AND\n");

		//console.log( "query.searchQuery", query.searchQuery);

		if( query.searchQuery ) {

			queryString += "\n	AND\n		" + query.searchQuery;

		}

		return queryString;

	}

	stackJoinIDS( query ) {

		var select_join_id = "";

		for( var c = 0; c < query.joins.length; c++ ) {
			
			var join = query.joins[c];

			if( c == 0 ) {

				select_join_id += "," + query.table + ".id as join_id";

			} else {

				select_join_id += "," + query.table + ".id as join_id_" + c;

			}

		}

		return select_join_id;

	}

	parseColumns( value, queryType, query ) {
				
		if( queryType == "select" ) {
		
			if( value.length == 0 ) {

				var select_join_id	= this.stackJoinIDS( query );

				return " * " + select_join_id + " ";
			
			} else {
			
				return " " + value.join(", ");
			
			}
		}
		
		return "";

	}
	
	
	parseTableStatement( value, query ) {

		var type = query.type;
	
		if( type == "select" ) {
		
			 var select = " From " + value;

			 if( query.name ) {

			 	select += " AS " + query.name + " "; 

			 }

			 return select;
		
		}

		if( type != "join" ) {
		
			return value;
		
		}
		
		return "";
		
	}
	
	parseInsertValues( query, values ) {

		var valueString 	= new Array( values.length ).fill("?").join(", ");
		
		var columnNames		= query.getColumnNames();
		
		return "(" + columnNames + ") VALUES (" + valueString + ")";

	}

	parseUpdateValues( values ) {

		var updateArray = new Array();
	
		for( var c = 0; c < values.length;c++ ) {
		
			var column 		= values[c];
		
			updateArray[c] 	= column.name + " = ?";
			
		}
		
		if( values.length == 0 ) {
		
			return "";
		
		}
		
		return " SET " + updateArray.join(", ");

	}

	parseValues( query ) {
	
		var values 		= query.values;

		var type		= query.type.toLowerCase();

		switch( type ) {

			case "insert":

				return this.parseInsertValues( query, values );

			break;

			case "update":

				return this.parseUpdateValues( values );

			break;

			default:

				return "";

		}
		
	}
	
	parseType( value, query, queryID ) {

		switch( value.toLowerCase() ) {
		
			case "select":
			
				return value;
			
			break;

			case "count":
			
				return "select  count (*)  from ";
			
			break;
			
			case "insert":
			
				return "Insert into ";
			
			break;
			
			case "update":
			
				return "update ";
			
			break;
			
			case "delete":
			
				return "DELETE FROM ";
			
			break;
			
			case "join":
		
				if( query.parent ) {

					return this.queryJoin( query, "", queryID, query.parent );
				
				} 

			break;
			
		}

	}

	queryJoin( join, queryString, joinID, parentTable ) {

		if( !parentTable.name ) {

			parentTable.name = parentTable.table;

		}

		var left 	= join.left;
		var right 	= join.right;
		
		if( !right.table ) {

			right.table	= parentTable.name;
		}

		if( !left.column ) {
		
			left.column	= "id";
		
		}

		var joinType = "INNER";

		if( join.joinType ) {

			joinType = join.joinType;

		}

		var table;

		if( join.table ) {

			table = join.table;

		} else {

			table = left.table;

		}

		if( left.table && left.column && right.table && right.column ) {

			var ON = " \n		ON " + left.table + "." + left.column + " = " + right.table + "." + right.column;

		} else {

			var ON = "";

		}

		var AS = " as " + join.name;

		return "	" + joinType + " JOIN " + table + AS + ON;

	}


	subQuery( join, queryString, name ) {
	
		var querys = join.querys;
		
		for(var c = 0; c<querys.length;c++) {
		
			var query = querys[c];
			
			query.table = name;
			
			query.parent = join;
			
			queryString += this.parseQuery( query );
		
		}

		return queryString;

	}
	
	serializeColumnCreate( columns ){
	
		if( !columns ) {
		
			return false;
			
		}

		return columns.map( function( column ){ 

			if( !column.constraints ) {

				column.constraints = new Array();

			}

			return column.name + " " + column.type + " " + column.constraints.join(" "); 


		} ).join(", ");
		
	}

	createColumnQuery( columnName, datatype = "TEXT", constaints = new Array() ) {

		return columnName + " " + datatype + " " + constaints.join(" ");

	}

	tableExists( name ) {

		var query = this.database.prepare( "SELECT name FROM sqlite_master WHERE type='table' AND name='" + name + "' " );

		return query.pluck().get();

	}

	createTable( name, columns, drop = false ) {
	
		var columnString = this.serializeColumnCreate( columns );
	
		if( this.type == "create" || drop ) {
	
			var sql = "DROP TABLE IF EXISTS " + name + ";";

			console.log( "New table created", name );

			this.database.prepare( sql ).run();
			
			console.log( sql );
			
		}

		if( columnString ) {

			sql = "CREATE TABLE IF NOT EXISTS " + name + " (" + columnString + ");";

		} else {


			sql = "CREATE TABLE IF NOT EXISTS " + name + " ( id INTEGER PRIMARY KEY );";
		}
		
		this.database.prepare( sql ).run();

		return true;

	}
	
	hasChanged( columnName, datatype, tableName ) {

		var search = this.findColumn( columnName, tableName );

		if( search.length == 0  ) {

			return true;

		} else {

			return false;

		}

	}

	addColumnSimple( columnName, datatype, tableName ) {

		var sql = "ALTER TABLE " + tableName + " ADD COLUMN " + columnName + " " + datatype;
		
		this.database.prepare( sql ).run();

	}

	addColumn( columnName, datatype, tableName, constaints = new Array() ) {
		
		var search = this.findColumn( columnName, tableName );
		
		if( search.length == 0  ) {

			console.log("");
			console.log("________________________________________________________________________");	
			console.log("");
			console.log(  "	New column added"  );
			console.log("");

			console.log("	table:			", tableName);
			console.log("	columnName:		", columnName);


			console.log( "	Constraints:		", constaints.join(" ") )
			console.log("");
			console.log("________________________________________________________________________");
		

	 		var sql = "ALTER TABLE " + tableName + " ADD COLUMN " + columnName + " " + datatype;
			
			this.registerColumn( columnName, tableName, datatype );

			this.database.prepare( sql ).run();

		}

	}
	
	registerColumn( columnName, tableName, datatype = DATATYPE.VARCHAR  ) {
	
		var query = new querySQL( );
		
		query.type	= "insert";
		
		query.table	= "tableColumns";
		
		query.setValue( "tableName", tableName );

		query.setValue( "columnName", columnName );

		query.setValue( "datatype", datatype );
		
		var result = this.query( query ); 
		
	}

	cleanTable() {

		//var query = new querySQL( );
		
		//query.type	= "delete";
		//query.table	= name;
		
		//query.find( "signed", false );
		
		//this.query( query ); 
		
	}
	
	findColumn( columnName, tableName ) {
	
		var query = new querySQL( );
		
		query.type	= "select";

		query.table	= "tableColumns";
		
		query.find( "tableName", tableName );

		query.find( "columnName", columnName );
		
		var result = this.query( query ); 
	
		return result;
	
	}
	
}

var db = new databaseManager()

db.connect();

export default db;


