#include <SQLiteCpp/Exception.h>
#include <uploadmanager.h>
#include <siaapi.h>
#include <eventsystem.h>
#include <siadriveconfig.h>
#include <filepath.h>

using namespace Sia::Api;

// Event Notifications
class UploadManagerStarted :
  public CEvent
{
public:
  UploadManagerStarted()
  {

  }

public:
  virtual ~UploadManagerStarted()
  {
  }

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName();
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new UploadManagerStarted());
  }

  virtual SString GetEventName() const override
  {
    return "UploadManagerStarted";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() }
    };
  }
};

class UploadManagerStopped :
  public CEvent
{
public:
  UploadManagerStopped()
  {

  }

public:
  virtual ~UploadManagerStopped()
  {
  }

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName();
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new UploadManagerStopped());
  }

  virtual SString GetEventName() const override
  {
    return "UploadManagerStopped";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() }
    };
  }
};

class FileAddedToQueue :
  public CEvent
{
public:
  FileAddedToQueue(const SString& siaPath, const SString& filePath) :
    _siaPath(siaPath),
    _filePath(filePath)
  {

  }

public:
  virtual ~FileAddedToQueue()
  {
  }

private:
  const SString _siaPath;
  const SString _filePath;

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() + 
      "|SP|" + _siaPath + 
      "|FP|" + _filePath;
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new FileAddedToQueue(_siaPath, _filePath));
  }

  virtual SString GetEventName() const override
  {
    return "FileAddedToQueue";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "sia_path", _siaPath },
      { "file_path", _filePath }
    };
  }
};

class ExternallyRemovedFileDetected :
  public CEvent
{
public:
  ExternallyRemovedFileDetected(const SString& siaPath, const SString& filePath) :
    _siaPath(siaPath),
    _filePath(filePath)
  {

  }

public:
  virtual ~ExternallyRemovedFileDetected()
  {
  }

private:
  const SString _siaPath;
  const SString _filePath;

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() + 
      "|SP|" + _siaPath + 
      "|FP|" + _filePath;
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new ExternallyRemovedFileDetected(_siaPath, _filePath));
  }

  virtual SString GetEventName() const override
  {
    return "ExternallyRemovedFileDetected";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "sia_path", _siaPath },
      { "file_path", _filePath }
    };
  }
};

class RenameFileFailed :
  public CEvent
{
public:
  RenameFileFailed(const SString& siaPath, const SString& newSiaPath, const SString& reason) :
    _siaPath(siaPath),
    _newSiaPath(newSiaPath),
    _reason(reason)
  {

  }

public:
  virtual ~RenameFileFailed()
  {
  }

private:
  const SString _siaPath;
  const SString _newSiaPath;
  const SString _reason;

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() +
      "|SP|" + _siaPath +
      "|NSP|" + _newSiaPath +
      "|RES|" + _reason;
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new RenameFileFailed(_siaPath, _newSiaPath, _reason));
  }

  virtual SString GetEventName() const override
  {
    return "RenameFileFailed";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "sia_path", _siaPath },
      { "new_sia_path", _newSiaPath },
      { "reason", _reason }
    };
  }
};

class RenameFolderFailed :
  public CEvent
{
public:
  RenameFolderFailed(const SString& siaPath, const SString& newSiaPath, const SString& reason) :
    _siaPath(siaPath),
    _newSiaPath(newSiaPath),
    _reason(reason)
  {

  }

public:
  virtual ~RenameFolderFailed()
  {
  }

private:
  const SString _siaPath;
  const SString _newSiaPath;
  const SString _reason;

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() +
      "|SP|" + _siaPath +
      "|NSP|" + _newSiaPath +
      "|RES|" + _reason;
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new RenameFolderFailed(_siaPath, _newSiaPath, _reason));
  }

  virtual SString GetEventName() const override
  {
    return "RenameFolderFailed";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "sia_path", _siaPath },
      { "new_sia_path", _newSiaPath },
      { "reason", _reason }
    };
  }
};

class UploadToSiaStarted :
  public CEvent
{
public:
  UploadToSiaStarted(const SString& siaPath, const SString& filePath) :
    _siaPath(siaPath),
    _filePath(filePath)
  {

  }

public:
  virtual ~UploadToSiaStarted()
  {
  }

private:
  const SString _siaPath;
  const SString _filePath;

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() + 
      "|SP|" + _siaPath + 
      "|FP|" + _filePath;
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new UploadToSiaStarted(_siaPath, _filePath));
  }

  virtual SString GetEventName() const override
  {
    return "UploadToSiaStarted";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "sia_path", _siaPath },
      { "file_path", _filePath }
    };
  }
};

class UploadToSiaComplete :
  public CEvent
{
public:
  UploadToSiaComplete(const SString& siaPath, const SString& filePath) :
    _siaPath(siaPath),
    _filePath(filePath)
  {

  }

public:
  virtual ~UploadToSiaComplete()
  {
  }

private:
  const SString _siaPath;
  const SString _filePath;

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() + 
      "|SP|" + _siaPath + 
      "|FP|" + _filePath;
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new UploadToSiaComplete(_siaPath, _filePath));
  }

  virtual SString GetEventName() const override
  {
    return "UploadToSiaComplete";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "sia_path", _siaPath },
      { "file_path", _filePath }
    };
  }
};

class FileRemoveQueued :
  public CEvent
{
public:
  FileRemoveQueued(const SString& siaPath, const SString& filePath) :
    _siaPath(siaPath),
    _filePath(filePath)
  {

  }

public:
  virtual ~FileRemoveQueued()
  {
  }

private:
  const SString _siaPath;
  const SString _filePath;

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() + 
      "|SP|" + _siaPath + 
      "|FP|" + _filePath;
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new FileRemoveQueued(_siaPath, _filePath));
  }

  virtual SString GetEventName() const override
  {
    return "FileRemoveQueued";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "sia_path", _siaPath },
      { "file_path", _filePath }
    };
  }
};

class FileRemovedFromSia :
  public CEvent
{
public:
  FileRemovedFromSia(const SString& siaPath, const SString& filePath) :
    _siaPath(siaPath),
    _filePath(filePath)
  {

  }

public:
  virtual ~FileRemovedFromSia()
  {
  }

private:
  const SString _siaPath;
  const SString _filePath;

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() + 
      "|SP|" + _siaPath + 
      "|FP|" + _filePath;
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new FileRemovedFromSia(_siaPath, _filePath));
  }

  virtual SString GetEventName() const override
  {
    return "FileRemovedFromSia";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "sia_path", _siaPath },
      { "file_path", _filePath }
    };
  }
};

class FileRemovedFromDatabase :
  public CEvent
{
public:
  FileRemovedFromDatabase(const SString& siaPath, const SString& filePath) :
    _siaPath(siaPath),
    _filePath(filePath)
  {

  }

public:
  virtual ~FileRemovedFromDatabase()
  {
  }

private:
  const SString _siaPath;
  const SString _filePath;

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() + 
      "|SP|" + _siaPath + 
      "|FP|" + _filePath;
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new FileRemovedFromDatabase(_siaPath, _filePath));
  }

  virtual SString GetEventName() const override
  {
    return "FileRemovedFromDatabase";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "sia_path", _siaPath },
      { "file_path", _filePath }
    };
  }
};

class FailedToRemoveFileFromSia :
  public CEvent
{
public:
  FailedToRemoveFileFromSia(const SString& siaPath, const SString& filePath, const SiaCurlError& curlError) :
    _siaPath(siaPath),
    _filePath(filePath),
    _curlError(curlError)
  {

  }

public:
  virtual ~FailedToRemoveFileFromSia()
  {
  }

private:
  const SString _siaPath;
  const SString _filePath;
  const SiaCurlError _curlError;

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() +
      "|SP|" + _siaPath +
      "|FP|" + _filePath +
      "|CERR|" + _curlError.GetReason();
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new FailedToRemoveFileFromSia(_siaPath, _filePath, _curlError));
  }

  virtual SString GetEventName() const override
  {
    return "FailedToRemoveFileFromSia";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "sia_path", _siaPath },
      { "file_path", _filePath },
      { "curl_error", _curlError.GetReason() }
    };
  }
};

class ModifyUploadStatusFailed :
  public CEvent
{
public:
  ModifyUploadStatusFailed(const SString& siaPath, const SString& filePath, const UploadStatus& uploadStatus, const SString& errorMsg) :
    _siaPath(siaPath),
    _filePath(filePath),
    _uploadStatus(uploadStatus),
    _errorMsg(errorMsg)
  {

  }

public:
  virtual ~ModifyUploadStatusFailed()
  {
  }

private:
  const SString _siaPath;
  const SString _filePath;
  const UploadStatus _uploadStatus;
  const SString _errorMsg;

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() + 
      "|SP|" + _siaPath + 
      "|FP|" + _filePath + 
      "|ST|" + CUploadManager::UploadStatusToString(_uploadStatus) + 
      "|MSG|" + _errorMsg;
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new ModifyUploadStatusFailed(_siaPath, _filePath, _uploadStatus, _errorMsg));
  }

  virtual SString GetEventName() const override
  {
    return "ModifyUploadStatusFailed";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "sia_path", _siaPath },
      { "file_path", _filePath },
      { "status", CUploadManager::UploadStatusToString(_uploadStatus) },
      { "message", _errorMsg}
    };
  }
};

class DatabaseInsertFailed :
  public CEvent
{
public:
  DatabaseInsertFailed(const SString& siaPath, const SString& filePath, const SString& errorMessage) :
    _siaPath(siaPath),
    _filePath(filePath),
    _errorMsg(errorMessage)
  {

  }

public:
  virtual ~DatabaseInsertFailed()
  {
  }

private:
  const SString _siaPath;
  const SString _filePath;
  const SString _errorMsg;

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() + 
      "|SP|" + _siaPath + 
      "|FP|" + _filePath + 
      "|MSG|" + _errorMsg;
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new DatabaseInsertFailed(_siaPath, _filePath, _errorMsg));
  }

  virtual SString GetEventName() const override
  {
    return "DatabaseInsertFailed";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "sia_path", _siaPath },
      { "file_path", _filePath },
      { "message", _errorMsg }
    };
  }
};

class DatabaseDeleteFailed :
  public CEvent
{
public:
  DatabaseDeleteFailed(const SString& siaPath, const SString& filePath, const SString& errorMessage) :
    _siaPath(siaPath),
    _filePath(filePath),
    _errorMsg(errorMessage)
  {

  }

public:
  virtual ~DatabaseDeleteFailed()
  {
  }

private:
  const SString _siaPath;
  const SString _filePath;
  const SString _errorMsg;

public:
  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() + 
      "|SP|" + _siaPath + 
      "|FP|" + _filePath + 
      "|MSG|" + _errorMsg;
  }

  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new DatabaseDeleteFailed(_siaPath, _filePath, _errorMsg));
  }

  virtual SString GetEventName() const override
  {
    return "DatabaseDeleteFailed";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "sia_path", _siaPath },
      { "file_path", _filePath },
      { "message", _errorMsg }
    };
  }
};

class SourceFileNotFound :
  public CEvent
{
public:
  SourceFileNotFound(const SString& siaPath, const SString& filePath) :
    _siaPath(siaPath),
    _filePath(filePath)
  {

  }

public:
  virtual ~SourceFileNotFound()
  {
  }

private:
  const SString _siaPath;
  const SString _filePath;

public:
  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new SourceFileNotFound(_siaPath, _filePath));
  }

  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() + 
      "|SP|" + _siaPath + 
      "|FP|" + _filePath;
  }

  virtual SString GetEventName() const override
  {
    return "SourceFileNotFound";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "sia_path", _siaPath },
      { "file_path", _filePath }
    };
  }
};

class DatabaseExceptionOccurred :
  public CEvent
{
public:
  DatabaseExceptionOccurred(const SString& duringOperation, const SQLite::Exception& exception) :
    _duringOperation(duringOperation),
    _exception(exception)
  {

  }

public:
  virtual ~DatabaseExceptionOccurred()
  {
  }

private:
  const SString _duringOperation;
  const SQLite::Exception _exception;

public:
  virtual std::shared_ptr<CEvent> Clone() const override
  {
    return std::shared_ptr<CEvent>(new DatabaseExceptionOccurred(_duringOperation, _exception));
  }

  virtual SString GetSingleLineMessage() const override
  {
    return GetEventName() +
      "|MSG|" + SString(_exception.what()) + 
      "|OPR|" + _duringOperation;
  }

  virtual SString GetEventName() const override
  {
    return "DatabaseExceptionOccurred";
  }

  virtual json GetEventJson() const override
  {
    return
    {
      { "event", GetEventName() },
      { "message", _exception.getErrorStr() },
      { "operation", _duringOperation }
    };
  }
};

#define TABLE_CREATE L"create table if not exists %s (%s);"
#define UPLOAD_TABLE L"upload_table"
#define UPLOAD_TABLE_COLUMNS L"id integer primary key autoincrement, sia_path text unique not null, file_path text unique not null, status integer not null, modified_time integer not null"
#define QUERY_STATUS "select * from upload_table where sia_path=@sia_path order by id desc limit 1;"
#define QUERY_UPLOADS "select * from upload_table order by sia_path desc;"
#define QUERY_UPLOADS_BY_STATUS "select * from upload_table where status=@status order by id desc limit 1;"
#define QUERY_UPLOADS_BY_2_STATUS "select * from upload_table where status=@status1 or status=@status2 order by sia_path desc;"
#define QUERY_UPLOAD_COUNT_BY_STATUS "select count(id) from upload_table where status=@status;"
#define QUERY_UPLOADS_BY_SIA_PATH "select * from upload_table where sia_path=@sia_path order by id desc limit 1;"
#define QUERY_UPLOADS_BY_SIA_PATH_AND_STATUS "select * from upload_table where sia_path=@sia_path and status=@status order by id desc limit 1;"
#define UPDATE_STATUS "update upload_table set status=@status where sia_path=@sia_path;"
#define UPDATE_SIAPATH_AND_FILEPATH "update upload_table set sia_path=@new_sia_path,file_path=@file_path where sia_path=@sia_path;"
#define INSERT_UPLOAD "insert into upload_table (sia_path, status, file_path, modified_time) values (@sia_path, @status, @file_path, @modified_time);"
#define DELETE_UPLOAD "delete from upload_table where sia_path=@sia_path;"

#define SET_STATUS(status, success_event, fail_event)\
bool statusUpdated = false;\
try\
{\
	SQLite::Statement update(_uploadDatabase, UPDATE_STATUS);\
	update.bind("@sia_path", SString::ToUtf8(siaPath).c_str());\
	update.bind("@status", static_cast<unsigned>(status));\
	if (update.exec() != 1)\
	{\
		CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(fail_event(siaPath, filePath, status, update.getErrorMsg())));\
	}\
	else\
	{\
		CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(success_event(siaPath, filePath)));\
		statusUpdated = true;\
	}\
}\
catch (SQLite::Exception e)\
{\
	CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred("SetStatus", e)));\
}

template <typename... Ts>
SString fmt(const SString &fmt, Ts... vs)
{
	size_t required = _snwprintf(nullptr, 0, fmt.str().c_str(), vs...);

	SString ret;
	ret.Resize(required);
	_snwprintf(&ret[0], required, fmt.str().c_str(), vs...);

	return ret;
}

static void CreateTableIfNotFound(SQLite::Database* database, const SString& tableName, const SString& columns)
{
	SString sqlCreate = fmt(TABLE_CREATE, &tableName[0], &columns[0]);
	database->exec(SString::ToUtf8(sqlCreate).c_str());
}

SString CUploadManager::UploadStatusToString(const UploadStatus& uploadStatus)
{
	switch (uploadStatus)
	{
	case UploadStatus::Complete:
		return L"Complete";

	case UploadStatus::NotFound:
		return L"Not Found";

	case UploadStatus::Queued:
		return L"Queued";

	case UploadStatus::Uploading:
		return L"Uploading";

	default:
		return L"!!Not Defined!!";
	}
}

CUploadManager::CUploadManager(CSiaDriveConfig* siaDriveConfig, const CSiaApi& siaApi) :
	CAutoThread(siaDriveConfig),
  _siaDriveConfig(siaDriveConfig),
  _siaApi(siaApi),
	_uploadDatabase(siaDriveConfig->GetRenter_UploadDbFilePath(), SQLite::OPEN_CREATE | SQLite::OPEN_READWRITE, 5000)
{
	CreateTableIfNotFound(&_uploadDatabase, UPLOAD_TABLE, UPLOAD_TABLE_COLUMNS);

	// Detect files that have been removed since last startup
  CleanUploadDatabase(siaDriveConfig);
	DeleteFilesRemovedFromSia(siaDriveConfig, true);

	// Begin normal processing
	StartAutoThread();

  CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(UploadManagerStarted()));
}

CUploadManager::~CUploadManager()
{
	// Stop all processing
	StopAutoThread();

  CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(UploadManagerStopped()));
}

void CUploadManager::UpdateDatabaseAfterRename(const SString& siaPath, const SString& newSiaPath)
{
  try
  {
    SQLite::Statement query(_uploadDatabase, QUERY_UPLOADS_BY_SIA_PATH);
    query.bind("@sia_path", SString::ToUtf8(siaPath).c_str());
    if (query.executeStep())
    {
      query.reset();

      // Update siapath and filepath
      const SString newFilePath = FilePath(GetSiaDriveConfig().GetCacheFolder(), newSiaPath);
      SQLite::Statement update(_uploadDatabase, UPDATE_SIAPATH_AND_FILEPATH);
      update.bind("@sia_path", SString::ToUtf8(siaPath).c_str());
      update.bind("@new_sia_path", SString::ToUtf8(newSiaPath).c_str());
	    update.bind("@file_path", SString::ToUtf8(newFilePath).c_str());
      update.exec();
    }
  }
  catch (SQLite::Exception e)
  {
    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred("Rename Folder", e)));
    CEventSystem::EventSystem.NotifyEvent(CreateSystemCriticalEvent(e.getErrorStr()));
  }
}

void CUploadManager::CleanUploadDatabase(CSiaDriveConfig* siaDriveConfig)
{
  std::vector<std::pair<SString, SString>> removeList;
  {
    SQLite::Statement query(_uploadDatabase, QUERY_UPLOADS);
    while (query.executeStep())
    {
      SString filePath = static_cast<const char*>(query.getColumn(query.getColumnIndex("file_path")));
      if (!FilePath(filePath).IsFile())
      {
        SString siaPath = static_cast<const char*>(query.getColumn(query.getColumnIndex("sia_path")));
        removeList.push_back({ siaPath, filePath });
      }
    }
  }

  for (const auto& item : removeList)
  {
    SString siaPath = item.first;
    SString filePath = item.second;
    SQLite::Statement del(_uploadDatabase, DELETE_UPLOAD);
    del.bind("@sia_path", SString::ToUtf8(siaPath).c_str());
    if (del.exec() >= 0)
    {
      CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(FileRemovedFromDatabase(siaPath, filePath)));
    }
    else
    {
      CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseDeleteFailed(siaPath, filePath, del.getErrorMsg())));
    }
  }
}

void CUploadManager::DeleteFilesRemovedFromSia(CSiaDriveConfig* siaDriveConfig, const bool& isStartup)
{
	CSiaFileTreePtr fileTree(new CSiaFileTree(siaDriveConfig));
	json result;
	SiaCurlError cerror = CSiaCurl(siaDriveConfig->GetHostConfig()).Get(L"/renter/files", result);
	if (ApiSuccess(cerror))
	{
		fileTree->BuildTree(result);
		auto fileList = fileTree->GetFileList();
    // TODO Implement this
	}
	else if (isStartup)
	{
		throw StartupException(L"Failed to get Sia files");
	}
}

void CUploadManager::HandleFileRemove(const SString& siaPath)
{
  FilePath removeFilePath(GetSiaDriveConfig().GetCacheFolder(), siaPath);

  json response;
  SiaCurlError cerror = CSiaCurl(GetSiaDriveConfig().GetHostConfig()).Post(SString(L"/renter/delete/") + siaPath, {}, response);
  if (ApiSuccess(cerror) || cerror.GetReason().Contains("no file known"))
  {
    SQLite::Statement del(_uploadDatabase, DELETE_UPLOAD);
    del.bind("@sia_path", SString::ToUtf8(siaPath).c_str());
    auto delCount = del.exec();
    if (delCount >= 0)
    {
      if (delCount != 0)
      {
        CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(FileRemovedFromSia(siaPath, removeFilePath)));
      }
    }
    else
    {
      CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseDeleteFailed(siaPath, removeFilePath, del.getErrorMsg())));
    }
  }
  else
  {
    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(FailedToRemoveFileFromSia(siaPath, removeFilePath, cerror)));
    CEventSystem::EventSystem.NotifyEvent(CreateSystemCriticalEvent(cerror.GetReason()));
  }

  _siaApi.GetRenter()->RefreshFileTree();
}

void CUploadManager::AutoThreadCallback(CSiaDriveConfig* siaDriveConfig)
{
	bool processNext = true;
  do
  {
    try
    {
      CSiaFileTreePtr fileTree;
      if (ApiSuccess(_siaApi.GetRenter()->GetFileTree(fileTree)))
      {
        try
        {
          std::shared_ptr<json> uploadFileList(new json);
          std::lock_guard<std::mutex> l(_uploadMutex);
          SQLite::Statement query(_uploadDatabase, QUERY_UPLOADS_BY_2_STATUS);
          query.bind("@status1", static_cast<unsigned>(UploadStatus::Queued));
          query.bind("@status2", static_cast<unsigned>(UploadStatus::Uploading));
          while (query.executeStep())
          {
            SString siaPath = static_cast<const char*>(query.getColumn(query.getColumnIndex("sia_path")));
            auto fileList = fileTree->GetFileList();
            auto it = std::find_if(fileList->begin(), fileList->end(), [&](const CSiaFilePtr& ptr)
            {
              return ptr->GetSiaPath() == siaPath;
            });

            uploadFileList->push_back({ siaPath, ((it == fileList->end()) ? 0 : (*it)->GetUploadProgress()) });
          }

          std::sort(uploadFileList->begin(), uploadFileList->end(), [](const json& a, const json& b)
          {
            return a[1].get<std::uint32_t>() > b[1].get<std::uint32_t>();
          });
          _uploadFileList = uploadFileList;
        }
        catch (const SQLite::Exception& e)
        {
          // error condition
          CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred("AutoThreadCallback(uploadStatus)", e)));
        }

        // Lock here - if file is modified again before previously queued upload is complete, delete it and 
        //	start again later
        bool complete = false;
        SString siaPath;
        SString filePath;
        {
          SQLite::Statement query(_uploadDatabase, QUERY_UPLOADS_BY_STATUS);
          query.bind("@status", static_cast<unsigned>(UploadStatus::Uploading));
          if (query.executeStep())
          {
            siaPath = static_cast<const char*>(query.getColumn(query.getColumnIndex("sia_path")));
            filePath = static_cast<const char*>(query.getColumn(query.getColumnIndex("file_path")));

            auto fileList = fileTree->GetFileList();
            auto it = std::find_if(fileList->begin(), fileList->end(), [&](const CSiaFilePtr& ptr)
            {
              return ptr->GetSiaPath() == siaPath;
            });

            // Removed by another client
            if (it == fileList->end())
            {
              HandleFileRemove(siaPath);
            }
            // Upload is complete
            else if ((*it)->GetAvailable())
            {
              complete = true;
            }
            // Upload still active, don't process another file
            else
            {
              SQLite::Statement count(_uploadDatabase, QUERY_UPLOAD_COUNT_BY_STATUS);
              count.bind("@status", static_cast<unsigned>(UploadStatus::Uploading));
              processNext = (count.executeStep() && (count.getColumn(0).getInt64() < _siaDriveConfig->GetMaxUploadCount()));
            }
          }
        }

        if (complete)
        {
          SET_STATUS(UploadStatus::Complete, UploadToSiaComplete, ModifyUploadStatusFailed)
          processNext = statusUpdated;
        }
      }
      else
      {
        // error condition - host down?
        processNext = false;
      }
    }
    catch (const SQLite::Exception& e)
    {
      // error condition - database not initialized (i.e. no table)?
      CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred("AutoThreadCallback(/renter/files)", e)));
      processNext = false;
    }

    if (processNext)
    {
      try
      {
        SString siaPath;
        SString filePath;
        bool uploading = false;

        std::lock_guard<std::mutex> l(_uploadMutex);
        {
          SQLite::Statement query(_uploadDatabase, QUERY_UPLOADS_BY_STATUS);
          query.bind("@status", static_cast<unsigned>(UploadStatus::Queued));
          if (query.executeStep())
          {
            siaPath = static_cast<const char*>(query.getColumn(query.getColumnIndex("sia_path")));
            filePath = static_cast<const char*>(query.getColumn(query.getColumnIndex("file_path")));
            if (FilePath(filePath).IsFile())
            {
              json response;
              SiaCurlError cerror = CSiaCurl(siaDriveConfig->GetHostConfig()).Post(SString(L"/renter/upload/") + siaPath, { {L"source", filePath} }, response);
              if (ApiSuccess(cerror) || cerror.GetReason().Contains("already exists"))
              {
                uploading = true;
              }
            }
            else
            {
              HandleFileRemove(siaPath);
            }
          }
          else
          {
            processNext = false;
          }
        }

        if (uploading)
        {
          SET_STATUS(UploadStatus::Uploading, UploadToSiaStarted, ModifyUploadStatusFailed)
          processNext = statusUpdated;
        }
      }
      catch (const SQLite::Exception& e)
      {
        // error condition
        CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred("AutoThreadCallback(processNext)", e)));
        processNext = false;
      }
    }

    _siaApi.GetRenter()->RefreshFileTree();
  }
  while (processNext);
}

UploadStatus CUploadManager::GetUploadStatus(const SString& siaPath)
{
	UploadStatus uploadStatus = UploadStatus::NotFound;

	SQLite::Statement query(_uploadDatabase, QUERY_UPLOADS_BY_SIA_PATH);
	query.bind("@sia_path", SString::ToUtf8(siaPath).c_str());
	if (query.executeStep())
	{
		uploadStatus = static_cast<UploadStatus>(static_cast<unsigned>(query.getColumn(query.getColumnIndex("status"))));
	}

	return uploadStatus;
}

UploadError CUploadManager::AddOrUpdate(const SString& siaPath, SString filePath, const std::uint64_t& lastModified)
{
  UploadError ret;

	// Relative to absolute and grab parent folder of source
	FilePath rootPath = filePath;
  rootPath.MakeAbsolute().RemoveFileName();

	if (FilePath(filePath).IsFile())
	{
		// Lock here - if file is modified again before a prior upload is complete, delete it and 
		//	start again later
		std::lock_guard<std::mutex> l(_uploadMutex);

		try
		{
      bool addToDatabase = true;

			// Check upload status
      {
        SQLite::Statement query(_uploadDatabase, QUERY_UPLOADS_BY_SIA_PATH);
        query.bind("@sia_path", SString::ToUtf8(siaPath).c_str());
        if (query.executeStep())
        {
          std::uint64_t modifiedTime = query.getColumn(query.getColumnIndex("modified_time")).getInt64();
          UploadStatus uploadStatus = static_cast<UploadStatus>(static_cast<unsigned>(query.getColumn(query.getColumnIndex("status"))));
          addToDatabase = (uploadStatus == UploadStatus::Uploading) || ((uploadStatus == UploadStatus::Complete) && (lastModified != modifiedTime));
          if (addToDatabase)
          {
            HandleFileRemove(siaPath);
          }
        }
        else // Not in database, so assume is in Sia
        {
          HandleFileRemove(siaPath);
        }
      }
			
      if (addToDatabase)
			{
				try
				{
				  // Add to db
					SQLite::Statement insert(_uploadDatabase, INSERT_UPLOAD);
					insert.bind("@sia_path", SString::ToUtf8(siaPath).c_str());
					insert.bind("@file_path", SString::ToUtf8(filePath).c_str());
          insert.bind("@modified_time", static_cast<long long>(lastModified));
					insert.bind("@status", static_cast<unsigned>(UploadStatus::Queued));
					if (insert.exec() == 1)
					{
						CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(FileAddedToQueue(siaPath, filePath)));
					}
					else
					{
						CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseInsertFailed(siaPath, filePath, insert.getErrorMsg())));
						ret = UploadErrorCode::DatabaseError;
					}
				}
				catch (SQLite::Exception e)
				{
					CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseInsertFailed(siaPath, filePath, e.getErrorStr())));
          ret = { UploadErrorCode::DatabaseError, e.getErrorStr() };
				}
			}
		}
		catch (SQLite::Exception e)
		{
			CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred("AddOrUpdate(query)", e)));
      ret = { UploadErrorCode::DatabaseError, e.getErrorStr() };
		}
	}
	else
	{
		CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(SourceFileNotFound(siaPath, filePath)));
		ret = UploadErrorCode::SourceFileNotFound;
	}

	return ret;
}

UploadError CUploadManager::RenameFile(const SString& siaPath, const SString& newSiaPath)
{
  UploadError ret;
  std::lock_guard<std::mutex> l(_uploadMutex);
  auto aerror = _siaApi.GetRenter()->RenameFile(siaPath, newSiaPath);
  if (ApiSuccess(aerror))
  {
    UpdateDatabaseAfterRename(siaPath, newSiaPath);
  }
  else
  {
    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(RenameFileFailed(siaPath, newSiaPath, aerror.GetReason())));
    ret = { UploadErrorCode::RenameFileFailed, aerror.GetReason() };
  }

  return ret;
}

UploadError CUploadManager::RenameFolder(const SString& siaPath, const SString& newSiaPath)
{
  UploadError ret;

  std::lock_guard<std::mutex> l(_uploadMutex);

  std::unordered_map<SString, SString> renamedMap;
  auto aerror = _siaApi.GetRenter()->RenameFolder(siaPath, newSiaPath, &renamedMap);
  for (const auto& pair : renamedMap)
  {
    UpdateDatabaseAfterRename(pair.first, pair.second);
  }

  if (!ApiSuccess(aerror))
  {
    CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(RenameFolderFailed(siaPath, newSiaPath, aerror.GetReason())));
    ret = { UploadErrorCode::RenameFolderFailed, aerror.GetReason() };
  }

  return ret;
}

UploadError CUploadManager::Remove(const SString& siaPath)
{
  UploadError ret;
	try
	{
    FilePath filePath(GetSiaDriveConfig().GetCacheFolder(), siaPath);
	  std::lock_guard<std::mutex> l(_uploadMutex);
    HandleFileRemove(siaPath);
	}
  catch (SQLite::Exception e)
	{
		CEventSystem::EventSystem.NotifyEvent(CreateSystemEvent(DatabaseExceptionOccurred("Remove", e)));
    ret = { UploadErrorCode::DatabaseError, e.getErrorStr() };
	}

	return ret;
}

std::shared_ptr<json> CUploadManager::GetUploadFileList() const
{
  return _uploadFileList;
}