/*************************************************************************
*    UrBackup - Client/Server backup system
*    Copyright (C) 2011-2016 Martin Raiber
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU Affero General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU Affero General Public License for more details.
*
*    You should have received a copy of the GNU Affero General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/

#include "app.h"
#include "../../stringtools.h"
#include "../../Interface/DatabaseCursor.h"
#include <memory>
#include "../FileIndex.h"
#include "../create_files_index.h"
#include "../dao/ServerFilesDao.h"
#include "../server_settings.h"


void open_settings_database();

int check_files_index()
{
	open_server_database(true);
	open_settings_database();

	IDatabase *db=Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER_FILES);
	if(db==NULL)
	{
		Server->Log("Could not open files database", LL_ERROR);
		return 1;
	}

	if(db->getEngineName()=="sqlite")
	{
		ServerSettings server_settings(Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER));
		db->Write("PRAGMA cache_size = -"+convert(server_settings.getSettings()->update_stats_cachesize));
	}


	IQuery* q_iterate;
	
	if(Server->getServerParameter("check_last").empty())
	{
		q_iterate = db->Prepare("SELECT id, shahash, filesize, clientid, fullpath FROM files");
	}
	else
	{
		q_iterate = db->Prepare("SELECT id, shahash, filesize, clientid, fullpath FROM files ORDER BY id DESC LIMIT "+Server->getServerParameter("check_last"));
	}

	IDatabaseCursor* cursor = q_iterate->Cursor();

	std::unique_ptr<FileIndex> fileindex(create_lmdb_files_index());

	if(!fileindex.get())
	{
		Server->Log("Fileindex not present", LL_ERROR);
		return 2;
	}

	ServerFilesDao filesdao(db);

	int64 n_checked = 0;

	bool has_error=false;

	db_single_result res;
	while(cursor->next(res))
	{
		int64 id = watoi64(res["id"]);
		int64 filesize = watoi64(res["filesize"]);
		int clientid = watoi(res["clientid"]);
		bool found_entry=false;

		int64 entryid = fileindex->get_with_cache_exact(FileIndex::SIndexKey(reinterpret_cast<const char*>(res["shahash"].data()),
			filesize, clientid));

		if(entryid==0)
		{
			Server->Log("Cannot find entry for file with id "+convert(id)+" with path \""+res["fullpath"]+"\"", LL_ERROR);
			has_error=true;
			continue;
		}		
		
		int64 found_entryid=entryid;

		bool first=true;

		int64 backward_entryid=0;
		int64 prev_entryid=0;
		while(entryid!=0)
		{
			ServerFilesDao::SFindFileEntry fileentry = filesdao.getFileEntry(entryid);
			
			//Server->Log("Current entry id="+convert(fileentry.id));

			if(fileentry.id == id)
			{
				found_entry=true;
			}

			if(clientid!=fileentry.clientid)
			{
				Server->Log("First entry with id "+convert(entryid)+" has wrong clientid (expected: "+convert(clientid)+" has: "+convert(fileentry.clientid)+")", LL_ERROR);
				has_error=true;
			}

			if(first)
			{
				if(!fileentry.pointed_to)
				{
					Server->Log("First entry with id "+convert(entryid)+" does not have pointed_to set to a value unequal 0 ("+convert(fileentry.pointed_to)+")", LL_ERROR);
					has_error=true;
				}	
				backward_entryid=fileentry.next_entry;
				first=false;
			}

			if(!fileentry.exists)
			{
				Server->Log("File entry for file with id "+convert(entryid)+" in index does not exist in database", LL_ERROR);
				has_error=true;
				break;
			}

			if(prev_entryid!=0 &&
				fileentry.next_entry!=prev_entryid)
			{
				Server->Log("Next entry for file with id "+convert(entryid)+" is wrong. Assumed="+convert(prev_entryid)+" Actual="+convert(fileentry.next_entry)+" Origin="+convert(id), LL_ERROR);
				has_error=true;
				break;
			}

			if(fileentry.shahash!=res["shahash"])
			{
				Server->Log("Shahash of entry with id "+convert(entryid)+" differs from shahash of entry with id "+convert(id)+". It should not differ.", LL_ERROR);
				has_error=true;
				break;
			}

			prev_entryid = entryid;

			entryid = fileentry.prev_entry;

			if(entryid==0 || prev_entryid==id)
			{
				break;
			}
		}

		if(!found_entry)
		{
			entryid = backward_entryid;
			prev_entryid = 0;
			while(entryid!=0)
			{
				ServerFilesDao::SFindFileEntry fileentry = filesdao.getFileEntry(entryid);

				if(fileentry.id == id)
				{
					found_entry=true;
				}

				if(!fileentry.exists)
				{
					Server->Log("File entry for file with id "+convert(entryid)+" in index does not exist in database", LL_ERROR);
					has_error=true;
					break;
				}

				if(prev_entryid!=0 &&
					fileentry.prev_entry!=prev_entryid)
				{
					Server->Log("Previous entry for file with id "+convert(entryid)+" is wrong. Assumed="+convert(prev_entryid)+" Actual="+convert(fileentry.prev_entry)+" Origin="+convert(id), LL_ERROR);
					has_error=true;
					break;
				}

				if(fileentry.shahash!=res["shahash"])
				{
					Server->Log("Shahash of entry with id "+convert(entryid)+" differs from shahash of entry with id "+convert(id)+". It should not differ. -2", LL_ERROR);
					has_error=true;
					break;
				}

				prev_entryid = entryid;

				entryid = fileentry.next_entry;

				if(entryid==0 || prev_entryid==id)
				{
					break;
				}
			}
		}

		if(!found_entry)
		{
			Server->Log("Entry with id "+convert(id)+" is not in the list and therefore not indexed by the file entry index. Initial list id is "+convert(found_entryid), LL_ERROR);
			has_error=true;
		}

		++n_checked;

		if(n_checked%10000==0)
		{
			Server->Log("Checked "+convert(n_checked)+" file entries", LL_INFO);
		}
	}

	Server->Log("Check complete");

	db->destroyAllQueries();

	if(has_error)
	{
		Server->Log("There were errors.", LL_ERROR);
		return 1;
	}
	
	return 0;
}

