static int StopApplier (TRI_replication_applier_t* applier, 
                        bool resetError) {
  TRI_replication_applier_state_t* state;

  state = &applier->_state;

  if (! state->_active) {
    return TRI_ERROR_INTERNAL;
  }
  
  state->_active = false;

  SetTerminateFlag(applier, true);

  TRI_SetProgressReplicationApplier(applier, "applier stopped", false);
 
  if (resetError) { 
    if (state->_lastError._msg != NULL) {
      TRI_FreeString(TRI_CORE_MEM_ZONE, state->_lastError._msg);
      state->_lastError._msg = NULL;
    }

    state->_lastError._code = TRI_ERROR_NO_ERROR;
  
    TRI_GetTimeStampReplication(state->_lastError._time, sizeof(state->_lastError._time) - 1);
  }
  
  TRI_LockCondition(&applier->_runStateChangeCondition);
  TRI_SignalCondition(&applier->_runStateChangeCondition);
  TRI_UnlockCondition(&applier->_runStateChangeCondition);

  return TRI_ERROR_NO_ERROR;
}
int TRI_StopReplicationApplier (TRI_replication_applier_t* applier,
                                bool resetError) {
  int res;

  res = TRI_ERROR_NO_ERROR;
  
  LOG_TRACE("requesting replication applier stop");
 
  TRI_WriteLockReadWriteLock(&applier->_statusLock);

  if (! applier->_state._active) {
    TRI_WriteUnlockReadWriteLock(&applier->_statusLock);

    return res;
  }

  res = StopApplier(applier, resetError);
  TRI_WriteUnlockReadWriteLock(&applier->_statusLock);
 
  // join the thread without the status lock (otherwise it would probably not join) 
  if (res == TRI_ERROR_NO_ERROR) {
    res = TRI_JoinThread(&applier->_thread);
  }
  else {
    // keep original error code
    TRI_JoinThread(&applier->_thread);
  }
  
  SetTerminateFlag(applier, false);

  LOG_INFO("stopped replication applier for database '%s'",
           applier->_databaseName);
  
  return res;
}
static int StartApplier (TRI_replication_applier_t* applier,
                         TRI_voc_tick_t initialTick,
                         bool useTick) {
  TRI_replication_applier_state_t* state;
  void* fetcher;

  state = &applier->_state;

  if (state->_active) {
    return TRI_ERROR_INTERNAL;
  }

  if (applier->_configuration._endpoint == NULL) {
    return SetError(applier, TRI_ERROR_REPLICATION_INVALID_APPLIER_CONFIGURATION, "no endpoint configured");
  }
  
  if (applier->_configuration._database == NULL) {
    return SetError(applier, TRI_ERROR_REPLICATION_INVALID_APPLIER_CONFIGURATION, "no database configured");
  }
  
  fetcher = (void*) TRI_CreateContinuousSyncerReplication(applier->_vocbase, 
                                                          &applier->_configuration,
                                                          initialTick,
                                                          useTick);

  if (fetcher == NULL) {
    return TRI_ERROR_OUT_OF_MEMORY;
  }
  
 
  // reset error 
  if (state->_lastError._msg != NULL) {
    TRI_FreeString(TRI_CORE_MEM_ZONE, state->_lastError._msg);
    state->_lastError._msg = NULL;
  }

  state->_lastError._code = TRI_ERROR_NO_ERROR;
  
  TRI_GetTimeStampReplication(state->_lastError._time, sizeof(state->_lastError._time) - 1);

  
  SetTerminateFlag(applier, false); 
  state->_active = true;
  
  TRI_InitThread(&applier->_thread);

  if (! TRI_StartThread(&applier->_thread, "[applier]", ApplyThread, fetcher)) {
    TRI_DeleteContinuousSyncerReplication(fetcher);

    return TRI_ERROR_INTERNAL;
  }

  LOG_INFO("started replication applier for database '%s'",
           applier->_databaseName);

  return TRI_ERROR_NO_ERROR;
}
static int StartApplier (TRI_replication_applier_t* applier,
                         TRI_voc_tick_t initialTick,
                         bool useTick) {
  TRI_replication_applier_state_t* state = &applier->_state;

  if (state->_active) {
    return TRI_ERROR_INTERNAL;
  }

  if (applier->_configuration._endpoint == nullptr) {
    return SetError(applier, TRI_ERROR_REPLICATION_INVALID_APPLIER_CONFIGURATION, "no endpoint configured");
  }

  if (applier->_configuration._database == nullptr) {
    return SetError(applier, TRI_ERROR_REPLICATION_INVALID_APPLIER_CONFIGURATION, "no database configured");
  }
  
  // TODO: prevent restart of the applier with a tick after a shutdown

  auto fetcher = new triagens::arango::ContinuousSyncer(applier->_server,
                                                        applier->_vocbase,
                                                        &applier->_configuration,
                                                        initialTick,
                                                        useTick);

  if (fetcher == nullptr) {
    return TRI_ERROR_OUT_OF_MEMORY;
  }

  // reset error
  if (state->_lastError._msg != nullptr) {
    TRI_FreeString(TRI_CORE_MEM_ZONE, state->_lastError._msg);
    state->_lastError._msg = nullptr;
  }

  state->_lastError._code = TRI_ERROR_NO_ERROR;

  TRI_GetTimeStampReplication(state->_lastError._time, sizeof(state->_lastError._time) - 1);


  SetTerminateFlag(applier, false);
  state->_active = true;

  TRI_InitThread(&applier->_thread);

  if (! TRI_StartThread(&applier->_thread, nullptr, "[applier]", ApplyThread, static_cast<void*>(fetcher))) {
    delete fetcher;

    return TRI_ERROR_INTERNAL;
  }

  LOG_INFO("started replication applier for database '%s'",
           applier->_databaseName);

  return TRI_ERROR_NO_ERROR;
}
TRI_replication_applier_t* TRI_CreateReplicationApplier (TRI_vocbase_t* vocbase) {
  TRI_replication_applier_t* applier;
  int res;

  applier = TRI_Allocate(TRI_CORE_MEM_ZONE, sizeof(TRI_replication_applier_t), false);

  if (applier == NULL) {
    return NULL;
  }
  
  TRI_InitConfigurationReplicationApplier(&applier->_configuration);
  TRI_InitStateReplicationApplier(&applier->_state);

  res = LoadConfiguration(vocbase, &applier->_configuration);
  
  if (res != TRI_ERROR_NO_ERROR && 
      res != TRI_ERROR_FILE_NOT_FOUND) {
    TRI_set_errno(res);
    TRI_DestroyStateReplicationApplier(&applier->_state);
    TRI_DestroyConfigurationReplicationApplier(&applier->_configuration);
    TRI_Free(TRI_CORE_MEM_ZONE, applier);

    return NULL;
  }

  res = TRI_LoadStateReplicationApplier(vocbase, &applier->_state);

  if (res != TRI_ERROR_NO_ERROR && 
      res != TRI_ERROR_FILE_NOT_FOUND) {
    TRI_set_errno(res);
    TRI_DestroyStateReplicationApplier(&applier->_state);
    TRI_DestroyConfigurationReplicationApplier(&applier->_configuration);
    TRI_Free(TRI_CORE_MEM_ZONE, applier);

    return NULL;
  }
  
  TRI_InitReadWriteLock(&applier->_statusLock);
  TRI_InitSpin(&applier->_threadLock);
  TRI_InitCondition(&applier->_runStateChangeCondition);

  applier->_vocbase      = vocbase;
  applier->_databaseName = TRI_DuplicateStringZ(TRI_CORE_MEM_ZONE, vocbase->_name);
  
  SetTerminateFlag(applier, false); 

  assert(applier->_databaseName != NULL);

  TRI_SetProgressReplicationApplier(applier, "applier created", false);
  
  return applier;
}
int TRI_ShutdownReplicationApplier (TRI_replication_applier_t* applier) {
  if (applier == nullptr) {
    return TRI_ERROR_NO_ERROR;
  }

  LOG_TRACE("requesting replication applier shutdown");

  if (applier->_vocbase->_type == TRI_VOCBASE_TYPE_COORDINATOR) {
    return TRI_ERROR_CLUSTER_UNSUPPORTED;
  }

  TRI_WriteLockReadWriteLock(&applier->_statusLock);

  if (! applier->_state._active) {
    TRI_WriteUnlockReadWriteLock(&applier->_statusLock);

    return TRI_ERROR_NO_ERROR;
  }

  int res = StopApplier(applier, true);

  TRI_WriteUnlockReadWriteLock(&applier->_statusLock);

  // join the thread without the status lock (otherwise it would probably not join)
  if (res == TRI_ERROR_NO_ERROR) {
    res = TRI_JoinThread(&applier->_thread);
  }
  else {
    // stop the thread but keep original error code
    int res2 = TRI_JoinThread(&applier->_thread);

    if (res2 != TRI_ERROR_NO_ERROR) {
      LOG_ERROR("could not join replication applier for database '%s': %s",
                applier->_databaseName,
                TRI_errno_string(res2));
    }
  }

  SetTerminateFlag(applier, false);
  
  TRI_WriteLockReadWriteLock(&applier->_statusLock);
  // really abort all ongoing transactions
  applier->abortRunningRemoteTransactions();

  TRI_WriteUnlockReadWriteLock(&applier->_statusLock);

  LOG_INFO("stopped replication applier for database '%s'",
           applier->_databaseName);

  return res;
}
TRI_replication_applier_t* TRI_CreateReplicationApplier (TRI_server_t* server,
                                                         TRI_vocbase_t* vocbase) {
  TRI_replication_applier_t* applier = new TRI_replication_applier_t(server, vocbase);

  if (applier == nullptr) {
    return nullptr;
  }
  
  TRI_InitConfigurationReplicationApplier(&applier->_configuration);
  TRI_InitStateReplicationApplier(&applier->_state);

  if (vocbase->_type == TRI_VOCBASE_TYPE_NORMAL) {
    int res = LoadConfiguration(vocbase, &applier->_configuration);

    if (res != TRI_ERROR_NO_ERROR &&
        res != TRI_ERROR_FILE_NOT_FOUND) {
      TRI_set_errno(res);
      TRI_DestroyStateReplicationApplier(&applier->_state);
      TRI_DestroyConfigurationReplicationApplier(&applier->_configuration);
      delete applier;

      return nullptr;
    }

    res = TRI_LoadStateReplicationApplier(vocbase, &applier->_state);

    if (res != TRI_ERROR_NO_ERROR &&
        res != TRI_ERROR_FILE_NOT_FOUND) {
      TRI_set_errno(res);
      TRI_DestroyStateReplicationApplier(&applier->_state);
      TRI_DestroyConfigurationReplicationApplier(&applier->_configuration);
      delete applier;

      return nullptr;
    }
  }

  SetTerminateFlag(applier, false);

  TRI_ASSERT(applier->_databaseName != nullptr);

  TRI_SetProgressReplicationApplier(applier, "applier created", false);

  return applier;
}