/*
 * crud.js - module to provide CRUD db capabilities
 */

/*jslint         node    : true, continue : true,
  devel  : true, indent  : 2,    maxerr   : 50,
  newcap : true, nomen   : true, plusplus : true,
  regexp : true, sloppy  : true, vars     : false,
  white  : true
*/
/*global */

// ------------ BEGIN MODULE SCOPE VARIABLES --------------
'use strict';
var
    authenticate, initialize, checkObjectSchema,
    getDBSchemaVersion, updateDBSchema, updateDBSchemaVersion, schemaCheck,
    booleanizeObject, ensureIndexes, prepareObjectForMongo,
    fixStringToDates, autocomplete, search, doSearch,
    checkType, errResponse, constructObj, readObj,
    updateObj, upsertObj, destroyObj, destroyMany, db,
    jwt = require('jsonwebtoken'),
    async = require('async'),
    _ = require('underscore'),
    response = require('./response-codes.js'),
    hasher = require('./hash.js'),
    mongodb = require('mongodb'),
    mongoClient = mongodb.MongoClient,
    fsHandle = require('fs'),
    url = 'mongodb://localhost:27017/rockfish',
    DB_USER_NAME = 'user',
    Ajv = require('ajv'), //validator
    ajv = Ajv({
        allErrors: true,
        schemas: [
            require('./user.json'),
            require('./customer.json'),
            require('./contact.json'),
            require('./notification.json'),
            require('./site.json'),
            require('./purchase.json'),
            require('./incident.json'),
            require('./trial.json'),
            require('./license_templates.json')
        ]
    }),
    objTypeMap = {
        'user': {},
        'customer': {},
        'contact': {},
        'notification': {},
        'site': {},
        'purchase': {},
        'incident': {},
        'trial': {},
        'license_templates': {}
    },
    currentSchemaVersion = -1,
    REQUIRED_SCHEMA_VERSION = 1;



// ------------- END MODULE SCOPE VARIABLES ---------------

// ---------------- BEGIN UTILITY METHODS -----------------


booleanizeObject = function (obj) {
    var keys = Object.keys(obj);
    keys.forEach(function (key) {
        var value = obj[key];
        if (typeof value === 'string') {
            var lvalue = value.toLowerCase();
            if (lvalue === 'true') {
                obj[key] = true;
            } else if (lvalue === 'false') {
                obj[key] = false;
            }
        } else if (value !== null && typeof value === 'object') {
            booleanizeObject(obj[key]);
        }
    });
}




//This function exists to change the properties of the passed in object
//to values compatible with mongo database (primarily dates for now)
//
prepareObjectForMongo = function (obj) {
    if (Array.isArray(obj)) {
        for (var i = 0; i < obj.length; i++) {
            fixStringToDates(obj[i]);
        }
    } else {
        fixStringToDates(obj);
    }
    return obj;
}

//turn empty strings of date fields into null
//turn string date values into actual date objects
//
fixStringToDates = function (obj) {
    var keys = Object.keys(obj);
    keys.forEach(function (key) {
        if (key.endsWith('Date')) {
            var value = obj[key];
            if (value == '') {
                obj[key] = null;
            } else {
                obj[key] = new Date(value);
            }
        }
    });
}
//




checkObjectSchema = function (obj_type, obj_map, callback) {
    //Clean up quoted bools
    booleanizeObject(obj_map);

    // console.log('dat.js::checkObjectSchema -> obj_type: ' + obj_type);
    // console.log('dat.js::checkObjectSchema -> obj_map:');
    // console.dir(obj_map);

    var validate = ajv.getSchema("http://ayanova.com/schemas/" + obj_type + ".json#");
    var valid = validate(obj_map);
    if (!valid) {
        callback(validate.errors);
    } else {
        callback([]);
    }


};


//Take an error from Mongo and turn it into english for display in UI
errResponse = function (preface, err) {
    //return preface plus better message
    if (err.message) {
        return preface + '\n' + err.message;
    } else {
        return preface;
    }
}


//Begin ensureIndexes
//
ensureIndexes = function (callback) {
    db.collection('user').createIndex('login', {
        unique: true
    }, function (err, res) {
        if (err) {
            return callback(err);
        }

        db.collection('customer').createIndex('name', {
            unique: true
        }, function (err, res) {
            if (err) {
                return callback(err);
            }

            //----
            //more here

            return callback();

            //----
        });

    });
}
//end ensureIndexes



//*************************************************************************
//***************************** SCHEMA ************************************
//*************************************************************************


schemaCheck = function (callback) {

    //------------
    //CHECK SCHEMA

    async.whilst(
        function () {
            return currentSchemaVersion < REQUIRED_SCHEMA_VERSION;
        },
        function (callback) {

            getDBSchemaVersion(function (err, v) {
                if (err) {
                    console.log("dat.js->Connect::Error reading schema version:");
                    console.log(err);
                    process.exit(1);
                    return callback(err);
                } else {
                    currentSchemaVersion = v;
                    if (v < REQUIRED_SCHEMA_VERSION) {
                        updateDBSchema(v, function (err) {
                            if (err) {
                                console.log("dat.js->Connect::Error updating schema version:");
                                callback(null, 'one', 'two');
                                console.log(err);
                                process.exit(1);
                                return callback(err);
                            } else {
                                //it updated successfully

                                //update schema version here...
                                //currentSchemaVersion++;

                                updateDBSchemaVersion(currentSchemaVersion + 1, function (err) {
                                    return callback(err);
                                });
                            }
                        })
                    } else {
                        return callback();
                    }
                }
            })


        },
        function (err) {
            if (err) {
                console.log("dat.js->Connect::Error during schema update:");
                console.log(err);
                process.exit(1);
            } else {
                console.log("Schema check completed");
            }
        }
    );
    //---------------------
}




//Begin getDBSchemaVersion  
//Returns current db schema 
//
getDBSchemaVersion = function (callback) {
    var col = db.collection('rfset');
    var startSchema = 0;
    col.findOne({}, function (err, rfset) {
        if (err) {
            return callback(err);
        }
        //is there a settings doc?
        if (rfset == null) {
            return callback(null, 0);
        } else {
            return callback(null, rfset.schemaVersion);
        }
    });
}
//end getDBSchemaVersion

//Begin updateDBSchemaVersion  
//
updateDBSchemaVersion = function (toVersion, callback) {

    var col = db.collection('rfset');

    col.findOne({}, function (err, rfset) {
        if (err) {
            return callback(err);
        }
        //is there a settings doc?
        if (rfset == null) {
            rfset = {
                schemaVersion: toVersion
            }
        }

        col.update({}, rfset, {
            upsert: true
        }, function (err, res) {
            if (!err) {
                //flag modules currentschemaversion variable
                currentSchemaVersion = toVersion;
                console.log("Schema updated to " + currentSchemaVersion);
            }
            callback(err);
        })
    });
};

//end updateDBSchema

//Begin updateDBSchema  
//
updateDBSchema = function (currentSchemaVersion, callback) {

    //
    //>>>>> SCHEMA 1
    if (currentSchemaVersion == 0) {
        console.log('>>>>> schema update 1 initiated...');

        db.collection('customer').find().toArray(function (err, custDocs) {
            async.eachSeries(custDocs, function (customerRecord, cbCustSeries) {
                db.collection('site').find({
                    'customer_id': customerRecord._id.toString()
                }).toArray(function (err2, siteDocs) {
                    async.eachSeries(siteDocs, function (siteRecord, cbSiteSeries) {
                        siteRecord.country = customerRecord.country;
                        siteRecord.stateProvince = customerRecord.stateProvince;
                        //save sitedoc
                        db.collection('site').findOneAndReplace({
                            '_id': siteRecord._id
                        }, siteRecord, function (err, ret) {
                            if (err) {
                                return cbSiteSeries(err);
                            } else {
                                console.log('updated customer: ' + customerRecord.name + ' & site: ' + siteRecord.name);
                                cbSiteSeries(); //callback to trigger the next site
                            }
                        });
                    }, function (err) {
                        cbCustSeries(err); //callback to trigger next customer
                    }); //end of async site series 
                });
            }, function (err) {
                callback(err);
            }); //async Customer series
        });
    }
    //<<<<< end schema update 1
    //




}; //end of function
//end updateDBSchema



// ----------------- END UTILITY METHODS ------------------

// ---------------- BEGIN PUBLIC METHODS ------------------

//create minimum required data for new db
initialize = function (callback) {
    var col = db.collection(DB_USER_NAME);

    hasher.hashPassword("abraxis", function (err, hash, salt) {

        if (err) {
            return callback(err, null);
        }

        col.update({
            name: "Joyce Cardinal"
        }, {
                name: 'Joyce Cardinal',
                login: "joyce",
                password: hash,
                salt: salt
            }, {
                upsert: true
            }, function (err, result) {
                if (err) {
                    return callback(err, null);
                }
            })

        hasher.hashPassword("abraxis", function (err, hash, salt) {
            if (err) {
                return callback(err, null);
            }

            col.update({
                name: "John Cardinal"
            }, {
                    name: 'John Cardinal',
                    login: "john",
                    password: hash,
                    salt: salt
                }, {
                    upsert: true
                }, function (err, result) {
                    if (err) {
                        return callback(err, null);
                    }
                    return callback(err, {
                        ok: 1,
                        msg: 'DB initialized'
                    });
                })
        })
    })
}
//end of initialize

//Authenticate
authenticate = function (body, callback) {

    // console.log("***** authenticate->body is:");
    // console.dir(body);

    //validate there is enough here to work with
    //require a list name
    if (!body.login || !body.password) {
        return callback("Authentication failed. Missing data.", {
            error: 1
        });
    }

    //fetch user
    var col = db.collection(DB_USER_NAME);

    col.findOne({
        "login": body.login
    }, function (err, userdoc) {

        //      console.log("authenticate::INFINDONE->body is:");    
        // console.dir(body);

        if (err) {
            return callback(err, null);
        }

        //Got user check password
        if (!userdoc) {
            return callback("Authentication failed.", {
                error: 1
            });
        }

        //compute hash and see if it matches...
        hasher.hashPassword(body.password, userdoc.salt, function (err, hash, salt) {
            if (err) {
                return callback("Authentication failed.", {
                    error: 1,
                    msg: err
                });
            }

            if (hash == userdoc.password) {
                // if user is found and password is right
                // create a token
                var token = jwt.sign({
                    id: userdoc._id
                }, 'Nothing8utTheRain', {
                        //expiresIn: 300 // expires in 5 minutes in seconds
                        expiresIn: 86400 // expires in 24 hours in seconds
                        //expiresIn: 31536000 //expires in one year
                    });

                // return the information including token as JSON
                return callback(null, {
                    ok: 1,
                    token: token,
                    name: userdoc.name,
                    id: userdoc._id
                });
            } else {
                //failed login
                return callback("Authentication failed.", {
                    error: 1
                });
            }
        });
    });
} //end authenticate




//=========== BEGIN CRUD ====================


checkType = function (obj_type) {
    if (!objTypeMap[obj_type]) {
        return ({
            error: 1,
            msg: 'Object type "' + obj_type + '" is not supported.',
            error_detail: {}
        });
    }
    return null;
};

constructObj = function (obj_type, obj_map, callback) {
    var type_check_map = checkType(obj_type);
    if (type_check_map) {
        callback(type_check_map);
        return;
    }

    checkObjectSchema(
        obj_type, obj_map,
        function (error_list) {
            if (error_list.length === 0) {

                db.collection(obj_type).insertOne(
                    prepareObjectForMongo(obj_map),
                    function (inner_error, result_map) {
                        if (inner_error) {
                            callback({
                                error: 1,
                                msg: errResponse('Error inserting object in database', inner_error),
                                error_detail: inner_error
                            });
                        } else {
                            // console.log('dat.js::constructObj->Result object is:');
                            // console.log(result_map);
                            callback({
                                ok: 1,
                                insertedCount: result_map.insertedCount,
                                insertedId: result_map.insertedId
                            });
                        }
                    }
                );
            } else {
                callback({
                    error: 1,
                    msg: 'Document not valid:' + '\n' + JSON.stringify(error_list, null, 4),
                    error_detail: error_list
                });
            }
        }
    );
};




readObj = function (obj_type, find_map, options, callback) {
    var type_check_map = checkType(obj_type);
    if (type_check_map) {
        callback(type_check_map);
        return;
    } 
    /* // Find records sort by 'a', skip 1, limit 2 records
            // Sort can be a single name, array, associate array or ordered hash
            collection.find({}, {'skip':1, 'limit':1, 'sort':'a'}).toArray(function(err, docs) {    */


    db.collection(obj_type).find(find_map, options).toArray(
        function (inner_error, map_list) {
            if (inner_error) {
                callback({
                    error: 1,
                    msg: errResponse('Error reading object in database', inner_error),
                    error_detail: inner_error
                });
            } else {
                if (options && options.sort) {
                    var sortField, sortDir;

                    //normalize if it's in an array
                    if (options.sort[0].constructor === Array) {
                        sortField = options.sort[0][0];
                        sortDir = options.sort[0][1];

                    } else {

                        sortField = options.sort[0];
                        sortDir = options.sort[1];
                    }


                    //due to mongo bug with sorting it's just easiest to post sort the results
                    var ret = _.sortBy(map_list, function (item) {

                        //if it's a string then lowercase it otherwise just return it
                        if (typeof item[sortField] == "string") {
                            return item[sortField].toLowerCase()
                        } else {
                            return item[sortField];
                        }


                    });

                    if (sortDir === 'desc') {
                        ret = ret.reverse();
                    }

                    callback(ret);
                } else {
                    callback(map_list);
                }

            }
        }
    );
};





updateObj = function (obj_type, find_map, set_map, callback) {
    var type_check_map = checkType(obj_type);
    if (type_check_map) {
        callback(type_check_map);
        return;
    }

    checkObjectSchema(
        obj_type, set_map,
        function (error_list) {
            if (error_list.length === 0) {

                db.collection(obj_type).updateMany(
                    find_map, {
                        $set: prepareObjectForMongo(set_map)
                    },
                    null,
                    function (inner_error, update_count) {
                        if (inner_error) {
                            callback({
                                error: 1,
                                msg: errResponse('Error updating object in database', inner_error),
                                error_detail: inner_error
                            });
                        } else {
                            callback({
                                update_count: update_count
                            });
                        }
                    }
                );
            } else {
                callback({
                    error: 1,
                    msg: 'Document not valid: ' + '\n' + JSON.stringify(error_list, null, 4),
                    error_detail: error_list
                });
            }
        }
    );
};

//========================================================================
//UPSERT
upsertObj = function (obj_type, set_map, callback) {
    var type_check_map = checkType(obj_type);
    if (type_check_map) {
        callback(type_check_map);
        return;
    }

    checkObjectSchema(
        obj_type, set_map,
        function (error_list) {
            if (error_list.length === 0) {

                db.collection(obj_type).update(
                    {}, {
                        $set: prepareObjectForMongo(set_map)
                    },
                    { upsert: true },
                    function (inner_error, update_count) {
                        if (inner_error) {
                            callback({
                                error: 1,
                                msg: errResponse('Error upserting object in database', inner_error),
                                error_detail: inner_error
                            });
                        } else {
                            callback({
                                update_count: update_count
                            });
                        }
                    }
                );
            } else {
                callback({
                    error: 1,
                    msg: 'Document not valid: ' + '\n' + JSON.stringify(error_list, null, 4),
                    error_detail: error_list
                });
            }
        }
    );
};


//====================================================
//DESTROY
destroyObj = function (obj_type, find_map, callback) {
    var type_check_map = checkType(obj_type);
    if (type_check_map) {
        callback(type_check_map);
        return;
    }

    db.collection(obj_type).deleteOne(find_map,
        function (inner_error, delete_count) {
            if (inner_error) {
                callback({
                    error: 1,
                    msg: errResponse('Error deleting one object in database', inner_error),
                    error_detail: inner_error
                });
            } else {
                callback({
                    delete_count: delete_count
                });
            }
        }
    );
};


destroyMany = function (obj_type, find_map, callback) {
    var type_check_map = checkType(obj_type);
    if (type_check_map) {
        callback(type_check_map);
        return;
    }

    db.collection(obj_type).deleteMany(find_map,
        function (inner_error, delete_count) {
            if (inner_error) {
                callback({
                    error: 1,
                    msg: errResponse('Error inserting object in database', inner_error),
                    error_detail: inner_error
                });
            } else {
                callback({
                    delete_count: delete_count
                });
            }
        }
    );


};




autocomplete = function (q, callback) {
    //support for autocomplete jquery plugin:
    //https://github.com/devbridge/jQuery-Autocomplete

    //Must return data in one of two formats 
    // {
    //     // Query is not required as of version 1.2.5
    //     "query": "Unit",
    //     "suggestions": [
    //         { "value": "United Arab Emirates", "data": "AE" },
    //         { "value": "United Kingdom",       "data": "UK" },
    //         { "value": "United States",        "data": "US" }
    //     ]
    // }

    // {
    //     "query": "Unit",
    //     "suggestions": ["United Arab Emirates", "United Kingdom", "United States"]
    // }

    //http://stackoverflow.com/questions/3305561/how-do-i-query-mongodb-with-like

    var ret = [];
    var re = new RegExp(q.query, 'i');


    //could be a collection.field or a single token
    if (q.acget.indexOf('.') > -1) {
        //It's a collection and name to search in
        var res = q.acget.split('.');
        var colName = res[0];
        var fldName = res[1];
        var qq = {};
        qq[fldName] = re;
        var rr = {};
        rr[fldName] = 1;

        //get all unique fields in db that match
        // db.collection(colName).distinct( fldName, qq,rr )
        db.collection(colName).find(qq, rr).toArray(
            function (inner_error, map_list) {
                if (inner_error) {
                    return callback({
                        error: 1,
                        msg: errResponse('Error reading object in database', inner_error),
                        error_detail: inner_error
                    });
                } else {
                    for (var i = 0; i < map_list.length; i++) {
                        ret.push(map_list[i][fldName]);
                    }

                    return callback({
                        suggestions: _.uniq(ret)
                    });
                }
            }
        );


    } else {
        //It's a token
        switch (q.acget) {
            case 'email':
                //get all unique emails in db that match
                db.collection('contact').find({
                    'email': re
                }, {
                        'email': 1
                    }).toArray(
                    function (inner_error, map_list) {
                        if (inner_error) {
                            return callback({
                                error: 1,
                                msg: errResponse('Error reading object in database', inner_error),
                                error_detail: inner_error
                            });
                        } else {
                            for (var i = 0; i < map_list.length; i++) {
                                ret.push(map_list[i].email);
                            }

                            return callback({
                                suggestions: _.uniq(ret)
                            });
                        }
                    }
                    );

                break;

            case 'purchaseName':
                //get all unique names in db that match
                db.collection('purchase').find({
                    'name': re
                }, {
                        'name': 1
                    }).toArray(
                    function (inner_error, map_list) {
                        if (inner_error) {
                            return callback({
                                error: 1,
                                msg: errResponse('Error reading object in database', inner_error),
                                error_detail: inner_error
                            });
                        } else {
                            for (var i = 0; i < map_list.length; i++) {
                                ret.push(map_list[i].name);
                            }

                            return callback({
                                suggestions: _.uniq(ret)
                            });
                        }
                    }
                    );

                break;

        }
    }
};



////////////////////////////
//perform a search, called by search method
//called with an object and callback
/*
options:

{
    query:'ardv',
    colName: 'customer',
    fldName: 'name',
    projection:{'id':1}

}

*/
doSearch = function (opt, callback) {
    var ret = [];
    var re = new RegExp(opt.query, 'i');

    var find = {};
    find[opt.fldName] = re;



    //TODO: make this a method to call with collection name and field name
    //must return at least this array of objects {customer_id:blah, site_id:blah, obj_type:incident, obj_id:57758a38842adaaf3d72a195 }
    //then uniquify by objid, group ret array by customer then site, then add display names for customer and site into each object (use caching as much as possible) 
    //after the fact and return as array.  No excerpt because it would be needlessly slow.




    db.collection(opt.colName).find(find, opt.projection).toArray(
        function (inner_error, map_list) {
            if (inner_error) {
                return callback({
                    error: 1,
                    msg: errResponse('Error reading object in database', inner_error),
                    error_detail: inner_error
                });
            } else {
                for (var i = 0; i < map_list.length; i++) {
                    var item = map_list[i];
                    item['obj'] = opt.colName;
                    if (opt.colName == 'customer') {
                        //only has _id, not customer_id
                        item['customer_id'] = item._id;
                    }
                    ret.push(item);
                }


                return callback(ret);
            }
        }
    );
}





search = function (q, callback) {
    //search for string fragment in selected areas of Rockfish

    var ret = [];


    doSearch({
        query: q,
        colName: 'customer',
        fldName: 'name',
        projection: { 'id': 1 }

    }, function (dat) {
        if (dat.error) {
            return callback(dat);
        } else {

            ret = ret.concat(dat);

            //-----------
            doSearch({
                query: q,
                colName: 'customer',
                fldName: 'notes',
                projection: { 'id': 1, 'name': 1 }

            }, function (dat) {
                if (dat.error) {
                    return callback(dat);
                } else {

                    ret = ret.concat(dat);

                    //-----------
                    doSearch({
                        query: q,
                        colName: 'purchase',
                        fldName: 'email',
                        projection: { 'customer_id': 1, 'site_id': 1 }

                    }, function (dat) {
                        if (dat.error) {
                            return callback(dat);
                        } else {

                            ret = ret.concat(dat);

                            //-----------
                            doSearch({
                                query: q,
                                colName: 'incident',
                                fldName: 'name',
                                projection: { 'customer_id': 1, 'site_id': 1 }

                            }, function (dat) {
                                if (dat.error) {
                                    return callback(dat);
                                } else {

                                    ret = ret.concat(dat);

                                    //-----------
                                    doSearch({
                                        query: q,
                                        colName: 'incident',
                                        fldName: 'notes',
                                        projection: { 'customer_id': 1, 'site_id': 1 }

                                    }, function (dat) {
                                        if (dat.error) {
                                            return callback(dat);
                                        } else {

                                            ret = ret.concat(dat);

                                            //-----------
                                            doSearch({
                                                query: q,
                                                colName: 'site',
                                                fldName: 'name',
                                                projection: { 'customer_id': 1 }

                                            }, function (dat) {
                                                if (dat.error) {
                                                    return callback(dat);
                                                } else {

                                                    ret = ret.concat(dat);

                                                    //-----------
                                                    doSearch({
                                                        query: q,
                                                        colName: 'site',
                                                        fldName: 'notes',
                                                        projection: { 'customer_id': 1 }

                                                    }, function (dat) {
                                                        if (dat.error) {
                                                            return callback(dat);
                                                        } else {

                                                            ret = ret.concat(dat);

                                                            //-----------
                                                            doSearch({
                                                                query: q,
                                                                colName: 'trial',
                                                                fldName: 'name',
                                                                projection: { 'customer_id': 1, 'site_id': 1 }

                                                            }, function (dat) {
                                                                if (dat.error) {
                                                                    return callback(dat);
                                                                } else {

                                                                    ret = ret.concat(dat);

                                                                    //-----------
                                                                    doSearch({
                                                                        query: q,
                                                                        colName: 'trial',
                                                                        fldName: 'notes',
                                                                        projection: { 'customer_id': 1, 'site_id': 1 }

                                                                    }, function (dat) {
                                                                        if (dat.error) {
                                                                            return callback(dat);
                                                                        } else {

                                                                            ret = ret.concat(dat);

                                                                            //***** RETURN  *****
                                                                            ret = _.uniq(ret, false, function (p) {
                                                                                return p._id + p.customer_id + p.obj;
                                                                            });

                                                                            ret = _.sortBy(ret, function (p) {
                                                                                return p.customer_id + p.obj;
                                                                            });

                                                                            //tag with names here

                                                                            db.collection('customer').find({}, {
                                                                                'name': 1
                                                                            }).toArray(
                                                                                function (inner_error, cnames) {
                                                                                    if (inner_error) {
                                                                                        return callback({
                                                                                            error: 1,
                                                                                            msg: errResponse('Error reading object in database', inner_error),
                                                                                            error_detail: inner_error
                                                                                        });
                                                                                    } else {
                                                                                        //convert all id's to string instead of bson objects for matching later
                                                                                        //can do this in mongo with a projection but this works
                                                                                        //and I'm ditching mongo so no need to learn more shit about it now
                                                                                        for (var i = 0; i < cnames.length; i++) {
                                                                                            cnames[i]._id = cnames[i]._id.toString();
                                                                                        }

                                                                                        for (var i = 0; i < ret.length; i++) {
                                                                                            //insert a name property in each object in ret with customer name from cnames
                                                                                            var cust = _.findWhere(cnames, { _id: ret[i].customer_id.toString() });
                                                                                            if (cust) {
                                                                                                ret[i]['name'] = cust.name;
                                                                                            }
                                                                                        }
                                                                                        return callback(ret);
                                                                                    }
                                                                                }
                                                                                );


                                                                            //***************************
                                                                        }

                                                                    })
                                                                    //------------
                                                                }

                                                            })
                                                            //------------
                                                        }

                                                    })
                                                    //------------
                                                }

                                            })
                                            //------------
                                        }

                                    })
                                    //------------
                                }

                            })
                            //------------
                        }

                    })
                    //------------
                }

            })
            //------------


        }

    })

}; //end of search function
////////////////////////////////////////////




//----------- END CRUD ----------------------



module.exports = {
    authenticate: authenticate,
    initialize: initialize,
    makeMongoId: mongodb.ObjectID,
    checkType: checkType,
    construct: constructObj,
    read: readObj,
    update: updateObj,
    upsert: upsertObj,
    destroy: destroyObj,
    destroyMany: destroyMany,
    autocomplete: autocomplete,
    search: search,
    booleanizeObject: booleanizeObject,
    fixStringToDates: fixStringToDates
};
// ----------------- END PUBLIC METHODS -----------------

// ------------- BEGIN MODULE INITIALIZATION --------------


mongoClient.connect(url, function (err, _db) {
    if (err) {
        console.log('*** ERROR CONNECTING TO MONGODB ***');
        console.log(err);
    } else {
        console.log('** Connected to MongoDB **');
        db = _db;
        ensureIndexes(function (err) {
            if (err) {
                console.log("dat.js::connect->Error ensuring indexes on startup:");
                console.log(err);
            } else {
                schemaCheck(function (err) {
                    if (err) {
                        console.log("dat.js::connect->Error during schemaCheck on startup:");
                        console.log(err);
                    }
                })
            }
        })
    }
});

// load schemas into memory (objTypeMap)
(function () {
    // var schema_name, schema_path;
    // for (schema_name in objTypeMap) {
    //   if (objTypeMap.hasOwnProperty(schema_name)) {
    //     schema_path = __dirname + '/' + schema_name + '.json';
    //     loadSchema(schema_name, schema_path);
    //   }
    // }
}());
// -------------- END MODULE INITIALIZATION ---------------