/*
*				libreevent - jsondb.js
*
*	Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
*			https://janishutz.com, development@janishutz.com
*
*
*/

const fs = require( 'fs' );
const path = require( 'path' );

class JSONDB {
    constructor () {
        this.db = {};
        this.dbIndex = { 'libreevent_temp': 0, 'libreevent_admin': 0, 'libreevent_orders': 0, 'libreevent_users': 0, 'libreevent_processing_orders': 0 };
        this.isSaving = false;
        this.awaitingSaving = true;
    }

    connect () {
        let data = {};
        try {
            JSON.parse( fs.readFileSync( path.join( __dirname + '/../../data/db.json' ) ) );
        } catch ( err ) {
            console.error( '[ JSON-DB ] CRITICAL INITIALIZATION FAILURE!' + err );
            throw ( 'JSONDB failed to start!' );
        }
        this.db = data[ 'db' ] ?? { 'libreevent_temp': {}, 'libreevent_admin': {}, 'libreevent_orders': {}, 'libreevent_users': {}, 'libreevent_processing_orders': {} };
        this.dbIndex = data[ 'index' ] ?? { 'libreevent_temp': 0, 'libreevent_admin': 0, 'libreevent_orders': 0, 'libreevent_users': 0, 'libreevent_processing_orders': 0 };
        this.db[ 'libreevent_temp' ] = {};
        this.saveToDisk();
        console.log( '[ JSON-DB ] Database initialized successfully' );
        return 'connection';
    }

    async saveToDisk () {
        if ( !this.isSaving ) {
            this.awaitingSaving = false;
            this.save();
        } else {
            this.awaitingSaving = true;
        }
    }

    save () {
        fs.writeFile( path.join( __dirname + '/../../data/db.json' ), JSON.stringify( { 'db': this.db, 'index': this.dbIndex } ), ( err ) => {
            if ( err ) console.error( '[ JSON-DB ] An error occurred during saving: ' + err );
            this.isSaving = false;
            if ( this.awaitingSaving ) {
                this.saveToDisk();
            }
        } );
    }

    async resetDB () {
        this.db = { 'libreevent_temp': {}, 'libreevent_admin': {}, 'libreevent_orders': {}, 'libreevent_users': {}, 'libreevent_processing_orders': {} };
        this.dbIndex = { 'libreevent_temp': 0, 'libreevent_admin': 0, 'libreevent_orders': 0, 'libreevent_users': 0, 'libreevent_processing_orders': 0 };
        fs.writeFile( path.join( __dirname + '/../../data/db.json' ), JSON.stringify( { 'db': this.db, 'index': this.dbIndex } ) );
    }

    async setupDB () {
        this.db = { 'libreevent_temp': {}, 'libreevent_admin': {}, 'libreevent_orders': {}, 'libreevent_users': {}, 'libreevent_processing_orders': {} };
        this.dbIndex = { 'libreevent_temp': 0, 'libreevent_admin': 0, 'libreevent_orders': 0, 'libreevent_users': 0, 'libreevent_processing_orders': 0 };
        fs.writeFile( path.join( __dirname + '/../../data/db.json' ), JSON.stringify( { 'db': this.db, 'index': this.dbIndex } ) );
    }

    query ( operation, table ) {
        return new Promise( ( resolve, reject ) => {
            /* 
                Possible operation.command values (all need the table argument of the method call):
                    - getAllData: no additional instructions needed

                    - getFilteredData: 
                        - operation.property (the column to search for the value), 
                        - operation.searchQuery (the value to search for [will be sanitised by method])

                    - InnerJoin (Select values that match in both tables):
                        - operation.property (the column to search for the value),
                        - operation.searchQuery (the value to search for [will be sanitised by method])
                        - operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
                        - operation.secondTable (The second table to perform Join operation with)
                        - operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)

                    - LeftJoin (Select values in first table and return all corresponding values of second table): 
                        - operation.property (the column to search for the value), 
                        - operation.searchQuery (the value to search for [will be sanitised by method])
                        - operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
                        - operation.secondTable (The second table to perform Join operation with)
                        - operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)

                    - RightJoin (Select values in second table and return all corresponding values of first table): 
                        - operation.property (the column to search for the value), 
                        - operation.searchQuery (the value to search for [will be sanitised by method])
                        - operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
                        - operation.secondTable (The second table to perform Join operation with)
                        - operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)

                    - addData:
                        - operation.data (key-value pair with all data as values and column to insert into as key)
                     
                    - updateData:
                        - operation.newValues (a object with keys being the column and value being the value to be inserted into that column, values are being
                            sanitised by the function)
                        - operation.property (the column to search for the value), 
                        - operation.searchQuery (the value to search for [will be sanitised by method])

                    - checkDataAvailability:
                        - operation.property (the column to search for the value), 
                        - operation.searchQuery (the value to search for [will be sanitised by method])
            */
            
            if ( operation.command === 'getAllData' ) {
                let ret = [];
                for ( let entry in this.db[ table ] ) {
                    ret.push( this.db[ table ][ entry ] );
                }
                resolve( ret );
            } else if ( operation.command === 'getFilteredData' || operation.command === 'checkDataAvailability' ) {
                let ret = [];
                for ( let entry in this.db[ table ] ) {
                    if ( this.db[ table ][ entry ][ operation.property ] == operation.searchQuery ) {
                        ret.push( this.db[ table ][ entry ] );
                    }
                }
                resolve( ret );
            } else if ( operation.command === 'addData' ) {
                this.dbIndex[ table ] += 1;
                this.db[ table ][ this.dbIndex[ table ] ] = operation.data;
                this.saveToDisk();
                resolve( true );
            } else if ( operation.command === 'updateData' ) {
                if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
                else {
                    for ( let entry in this.db[ table ] ) {
                        if ( this.db[ table ][ entry ][ operation.property ] == operation.searchQuery ) {
                            for ( let changed in operation.newValues ) {
                                this.db[ table ][ entry ][ changed ] = operation.newValues[ changed ];
                            }
                        }
                    }
                }
                this.saveToDisk();
                resolve( true );
            } else if ( operation.command === 'deleteData' ) {
                if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
                else {
                    for ( let entry in this.db[ table ] ) {
                        if ( this.db[ table ][ entry ][ operation.property ] == operation.searchQuery ) {
                            delete this.db[ table ][ entry ];
                        }
                    }
                }
                this.saveToDisk();
                resolve( true );
            } else if ( operation.command === 'InnerJoin' ) {
                // TODO: Finish those when actually needed
            } else if ( operation.command === 'LeftJoin' ) {
                //
            } else if ( operation.command === 'RightJoin' ) {
                //
            }
        } );
    }
}

module.exports = JSONDB;