var fs = require("fs");
fcf.module({
  name: "fcf:NFSQL/NDetails/NConnections/FileConnection.js",
  dependencies: ["fcf:NFSQL/NDetails/Errors.js", "fcf:NFSQL/NDetails/NConnections/MemoryConnection.js"],
  module: function(Errors, MemoryConnection) {
    var NConnections = fcf.prepareObject(fcf, "NFSQL.NDetails.NConnections");

    fcf.addException("ERROR_FILEDB_WRITE_FILE",   "Error writing file '${{file}}$': ${{error}}$");
    fcf.addException("ERROR_FILEDB_READ_FILE",    "Error reading file '${{file}}$': ${{error}}$");
    fcf.addException("ERROR_FILEDB_FORMAT_FILE",  "Error format file '${{file}}$'");


    NConnections.FileConnection = function(a_options) {
      var self = this;
      MemoryConnection.call(this, a_options);
      this._lastModifyTime = "";
      this._actions    = new fcf.Actions();

      var parentSetTable = this.setTable;
      this.setTable = function(a_projection, a_table, a_cb) {
        this._actions.then(function(a_res, a_act) {
          parentSetTable.call(self, a_projection, a_table, function(){
            self._writeFile(function(a_error){
              a_act.complete();
              if (a_cb)
                a_cb(a_error);
            });
          });
        });
      }

      var parentProcessSelect = this._processSelect;
      this._processSelect = function(a_queryObject, a_options, a_cb){
        this._actions.then(function(a_res, a_act) {
          self._prepareFile(function(a_error){
            if (a_error) {
              a_act.complete();
              if (a_cb)
                a_cb(a_error);
              return;
            }
            parentProcessSelect.call(self, a_queryObject, a_options, function(a_error, a_records){
              a_act.complete();
              a_cb(a_error, a_records);
            });
          });
        });
      }

      var parentProcessInsert = this._processInsert;
      this._processInsert = function(a_queryObject, a_options, a_cb){
        this._actions.then(function(a_res, a_act) {
          self._prepareFile(function(a_error){
            if (a_error) {
              a_act.complete();
              if (a_cb)
                a_cb(a_error);
              return;
            }
            parentProcessInsert.call(self, a_queryObject, a_options, function(a_error, a_rows){
              if (a_error) {
                a_act.complete();
                if (a_cb)
                  a_cb(a_error);
                return;
              }
              self._writeFile(function(a_error){
                a_act.complete();
                if (a_cb)
                  a_cb(a_error, a_rows);
              });
            });
          });
        });
      }

      var parentProcessUpdate = this._processUpdate;
      this._processUpdate = function(a_queryObject, a_options, a_cb){
        this._actions.then(function(a_res, a_act) {
          self._prepareFile(function(a_error){
            if (a_error) {
              a_act.complete();
              if (a_cb)
                a_cb(a_error);
              return;
            }
            parentProcessUpdate.call(self, a_queryObject, a_options, function(a_error){
              if (a_error) {
                a_act.complete();
                if (a_cb)
                  a_cb(a_error);
                return;
              }
              self._writeFile(function(a_error){
                a_act.complete();
                if (a_cb)
                  a_cb(a_error);
              });
            });
          });
        });
      }

      var parentProcessDelete = this._processDelete;
      this._processDelete = function(a_queryObject, a_options, a_cb){
        this._actions.then(function(a_res, a_act) {
          self._prepareFile(function(a_error){
            if (a_error) {
              a_act.complete();
              if (a_cb)
                a_cb(a_error);
              return;
            }
            parentProcessDelete.call(self, a_queryObject, a_options, function(a_error){
              if (a_error) {
                a_act.complete();
                if (a_cb)
                  a_cb(a_error);
                return;
              }
              self._writeFile(function(a_error){
                a_act.complete();
                if (a_cb)
                  a_cb(a_error);
              });
            });
          });
        });
      }

      this._prepareFile = function(a_cb){
        let context = fcf.getContext();
        fs.stat(a_options.file, function(a_error, stats){
          fcf.setContext(context);
          if (a_error && a_error.code == "ENOENT") {
            self._writeFile(function(a_error){
              if (a_cb)
                a_cb(a_error);
            });
          } else {
            if (self._lastModifyTime != stats.mtime.toString()) {
              self._lastModifyTime = stats.mtime.toString();
              fs.readFile(a_options.file, 'utf8', function(a_error, a_data) {
                fcf.setContext(context);
                if(a_error) {
                  if (a_cb)
                    a_cb(new fcf.Exception("ERROR_FILEDB_READ_FILE", {file: a_options.file, error: a_error.message}));
                  return;
                }
                var dataObject = undefined;
                try {
                  dataObject = JSON.parse(a_data);
                } catch(e){
                  if (a_cb)
                    a_cb(new fcf.Exception("ERROR_FILEDB_FORMAT_FILE", {file: a_options.file}));
                  return;
                }
                self._keys      = typeof dataObject.keys == "object"      ? dataObject.keys : {};
                self._tables    = typeof dataObject.tables == "object"    ? dataObject.tables : {};
                self._keysQueue = typeof dataObject.keysQueue == "object" ? dataObject.keysQueue : {};
                if (a_cb)
                  a_cb();
              });
            } else {
              if (a_cb)
                a_cb();
            }
          }
        });
      }

      this._writeFile = function(a_cb){
        let fileData = {
          keys: this._keys,
          tables: this._tables,
          keysQueue: this._keysQueue,
        };
        let context = fcf.getContext();
        fs.writeFile(a_options.file, JSON.stringify(fileData), function(a_error) {
          fcf.setContext(context);
          if(a_error) {
            if (a_cb)
              a_cb(new fcf.Exception("ERROR_FILEDB_WRITE_FILE", {file: a_options.file, error: a_error.message}));
            return;
          }
          if (a_cb)
            a_cb();
        });
      }

      //this._writeFile(function(){});
    }

    return NConnections.FileConnection;
  }
});
