var EXPORTED_SYMBOLS = ["YoonoStorageAsync"];

// Globals
const CI = Components.interfaces;
const CL = Components.classes;

/////
const YOONO_DIRSERVICE = Components.classes['@mozilla.org/file/directory_service;1'].getService(Components.interfaces.nsIProperties);
const CONSOLESERVICE = CL["@mozilla.org/consoleservice;1"].getService(CI.nsIConsoleService);

const PERMS_FILE      = 0655;
/////

var yoono = {};

function YoonoStorageAsync() {
  this.dataBase = null;

  this.dbFileName = "";
  this.dbLastBackupFileName = ""; // slightly useless so far
  this.lowPriorityRequestQueue = [] ; // so that we can handle priorities (chat request must not wait)
  this.highPriorityRequestQueue = [] ; // so that we can handle priorities (chat request must not wait)
  this.requestIsRunning = false;
  this._debugDb = false;
}

YoonoStorageAsync.prototype.init = function (y) {
  yoono = y;
}

/**
 * @param aStatement string for the statement, with placeholders ?1, ?2, etc
 * @param aValues array of arrays , each holding the values to bind to the placeholders for one execution of the statement
 */
YoonoStorageAsync.prototype.executeThreadSql = function(aRequestData) {
  var request = aRequestData.request;
  var datasets = aRequestData.datasets;
  var priority = aRequestData.priority;
  var callback = aRequestData.callback;
  this._debugDb = yoono.prefs.get("debug.db");
  
  // Init the database the first time it's needed
  var statusObject = {};
  var statement = null;
  statusObject.errorString = "";
  statusObject.errorCode = 0;
   if(!this.dataBase) {
    this.openStorage();   // is that a good idea ?
  }
  if(!this.dataBase) {
    statusObject.errorString = "Unable to open the database file";
    statusObject.errorCode = 14;
    yoono.log.error("Unable to execute : "+statement+"\nbecause DB is not open!");
    return [statusObject];
  }

  if(!datasets) datasets = [[]]; // all statements don't need values... But they need to be executed at least once...
  var nbDataSet = datasets.length; // always at least 1

  var statements = [];
  var startTime = 0, endTime = 0;

  if (this._debugDb) {
    startTime = new Date().getTime();
    yoono.log.debug("Executing SQL " + (priority?' high priority ':'') + nbDataSet + " times the request : " + request );
  }
  try {
    statement = this.dataBase.createStatement(request);
    if(datasets.length) {
      var params = statement.newBindingParamsArray();
      for(var idx=0; idx < datasets.length; idx ++) {
        var bp = params.newBindingParams();
        for(var jdx = 0 ; jdx < datasets[idx].length ; jdx ++) {
          var data = datasets[idx][jdx];
          bp.bindByIndex(jdx, data);
        }
        params.addParams(bp);
      }
      statement.bindParameters(params);
    }  
    statements.push(statement);
  } catch(e) {
    yoono.log.exception(e);
    try {
      statusObject.errorString = this.dataBase.lastErrorString;
      statusObject.errorCode = this.dataBase.lastError;
    } catch(e1) {
      statusObject.errorString = e1.message || e1;
      statusObject.errorCode = -1;
    }
    yoono.log.error(statusObject.errorString);
  }
  var _self = this;
  this.dataBase.executeAsync(statements, statements.length,  {
    _rows: [[]],
    _statusObject: {},
    _callback : callback,
    _dataBase: _self.dataBase,
    _debugDb: _self._debugDb,
    _startTime : startTime,
    handleResult: function(aResultSet) {
      for (let resultRow = aResultSet.getNextRow(); resultRow; resultRow = aResultSet.getNextRow()) {
        var row = [];

        for(let idx=0 ; idx < resultRow.numEntries; idx ++ ) {
          row.push(resultRow.getResultByIndex(idx));
        }
        this._rows[0].push(row);
      }
    },

    handleError: function(aError) {
      this._statusObject.errorString =  aError.message;
      this._statusObject.errorCode = -1;
    },

    handleCompletion: function(aReason) {
      if (aReason == Components.interfaces.mozIStorageStatementCallback.REASON_FINISHED) {
        this._statusObject.errorString =  "";
        this._statusObject.errorCode = 0;
      } else {
        if(this._dataBase.lastError) {
          this._statusObject.errorString =  this._dataBase.lastErrorString ;
          this._statusObject.errorCode = this._dataBase.lastError;
        } else {
          this._statusObject.errorString =  "A DB error occured";
          this._statusObject.errorCode = aReason;
        }
      }
      this._statusObject.lastInsertRowID = this._dataBase.lastInsertRowID
      if (this._debugDb) {
        endTime = new Date().getTime();
        yoono.log.debug("Executing SQL took : "+ (endTime - this._startTime)+"\n");
      }
      if (this._callback && (typeof this._callback.callback == "function")) {
        this._rows.unshift(this._statusObject);
        this._callback.callback(this._rows);
      }
    }
  });
}

/**
 *
 * @param aRequest
 * @param aDataSets
 * @param aCallback : object with property 'callback' holding the actuall callback function (that's because of IE)
 */
YoonoStorageAsync.prototype.executeAsyncSql = function(aRequest, aDataSets, aCallback, aHighPriority) {
  var req = {
    'request' : aRequest,
    'datasets' : aDataSets,
    'callback' : aCallback,
    'priority' : aHighPriority
  };

  if(aHighPriority) {
    this.highPriorityRequestQueue.push(req);
  } else {
    this.lowPriorityRequestQueue.push(req);
  }

  this.processNextInQueue();

}

YoonoStorageAsync.prototype.processNextInQueue = function() {
  var req = null;
  if(this.highPriorityRequestQueue.length) {
    if (this._debugDb) {
      yoono.log.debug("IN HIGH PRIORITY QUEUE : " + this.highPriorityRequestQueue.length);
    }
    req = this.highPriorityRequestQueue.shift();
  } else {
    if (this._debugDb) {
      yoono.log.debug("IN NORMAL PRIORITY QUEUE : " + this.lowPriorityRequestQueue.length);
    }
    req = this.lowPriorityRequestQueue.shift();
  }
  if(req) {
    this.executeThreadSql(req);
  }
}

YoonoStorageAsync.prototype.closeStorage = function() {
  if (!this.dataBase)
    return;
  var _self = this;
  this.dataBase.asyncClose({
    complete: function() {
      _self.database = null;
    }
  });
}

/**
 * Backup the DB before a schema migration.
 * If backup already exists, it's not overwritten
 * @param aVersion
 */
YoonoStorageAsync.prototype.backupDB = function(aVersion) {
  var storageService = CL["@mozilla.org/storage/service;1"].getService(CI.mozIStorageService);

  var dbFile = this.getDBFile(); // Make sure this.dbFileName is initialized
  var newNameElts = this.dbFileName.split('.');
  newNameElts[0] += '_' + aVersion + '.';
  var newFile = storageService.backupDatabaseFile(dbFile, newNameElts.join(''));
  this.dbLastBackupFileName = newFile.leafName;
  return(this.dbLastBackupFileName);
}

YoonoStorageAsync.prototype.openStorage = function() {
  if (this.dataBase)
    return;
  var dbFile = this.getDBFile();
  if(!dbFile.exists()) {
    this.createAndOpenStorage(dbFile);
  } else {
    this.openDBStorage(dbFile);
  }
}

YoonoStorageAsync.prototype.getCurrentDBFileName = function() {
  return(this.dbFileName);
}

YoonoStorageAsync.prototype.getDBFile = function() {
  var dbFile = YOONO_DIRSERVICE.get("ProfDS", CI.nsIFile)
  dbFile.append('yoono');
  var userCredentials = yoono.main.getUserCredential();
  var dbName = 'yoono_friends_db.sqlite';
  if(userCredentials) {
    if(userCredentials.anonymous) {
      dbName = userCredentials.userId + '_friends_db.sqlite';
    } else {
      dbName = yoono.main.escapeFileName(userCredentials.login) + '_friends_db.sqlite';
    }
  }
  dbFile.append(dbName);
  // Fix some weird bug in yoono desktop 6.2 -> 7 migration
  if (dbFile.exists() && dbFile.isDirectory())
    dbFile.remove(true);
  this.dbFileName = dbName;
  return(dbFile);
}

/**
 * This method closes the database, renames the current file to the new file, and reopens the database
 */
YoonoStorageAsync.prototype.switchDBStorage = function() {
  this.closeStorage();
  var currentFileName = this.getCurrentDBFileName();
  var filePath = YOONO_DIRSERVICE.get("ProfDS", CI.nsIFile)
  filePath.append('yoono');
  var currentFile = filePath.clone();
  var newFilePath = filePath.clone();
  currentFile.append(currentFileName);
  var aFile = this.getDBFile();
  var newFileName = this.getCurrentDBFileName(); // was updated by getDBFile()
  newFilePath.append(newFileName);
  if(newFilePath.exists()) {
    newFilePath.remove(false);
  }
  if(!currentFileName) {
    this.createAndOpenStorage(newFilePath);
  } else {
    currentFile.moveTo(filePath, newFileName);
    this.openDBStorage(aFile);
  }
}

YoonoStorageAsync.prototype.openDBStorage = function(aFile) {
  var storageService = CL["@mozilla.org/storage/service;1"].getService(CI.mozIStorageService);
  // Open sqlite DB in unshared mode for FTS3
  try {
    this.dataBase = storageService.openUnsharedDatabase(aFile);
  } catch(e) {
    yoono.log.exception(e);
  }
}

YoonoStorageAsync.prototype.createAndOpenStorage = function(aFile) {
  aFile.create(CI.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
  this.openDBStorage(aFile);
}
