/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2003 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "commonheaders.h"

int CDb3x::FindSectionForOffset(const DWORD ofs)
{
	for (int i = 0; i < CACHESECTIONCOUNT; i++)
		if (ofs >= cacheSectionInfo[i].ofsBase && ofs<cacheSectionInfo[i].ofsBase + CACHESECTIONSIZE)
			return i;
	return -1;
}

int CDb3x::FindLRUSection(void)
{
	int lru = 0;
	DWORD lowestLastUse = cacheSectionInfo[0].lastUsed;
	for (int i = 1; i < CACHESECTIONCOUNT; i++)
		if (cacheSectionInfo[i].lastUsed < lowestLastUse) {
			lru = i; 
			lowestLastUse = cacheSectionInfo[i].lastUsed;
		}
	return lru;
}

void CDb3x::LoadSection(const int i,DWORD ofs)
{
	cacheSectionInfo[i].ofsBase = ofs - ofs%CACHESECTIONSIZE;
	log1("readsect %08x",ofs);
	SetFilePointer(m_hDbFile,cacheSectionInfo[i].ofsBase,NULL,FILE_BEGIN);
	ReadFile(m_hDbFile,m_pDbCache+i*CACHESECTIONSIZE,CACHESECTIONSIZE,&ofs,NULL);
}

void CDb3x::MoveSection(int *sectId,int dest)
{
	CopyMemory(m_pDbCache+dest*CACHESECTIONSIZE,m_pDbCache+(*sectId)*CACHESECTIONSIZE,CACHESECTIONSIZE);
	cacheSectionInfo[dest].ofsBase = cacheSectionInfo[*sectId].ofsBase;
	*sectId = dest;
}

//we are assumed to be in a mutex here
PBYTE CDb3x::DBRead(DWORD ofs,int bytesRequired,int *bytesAvail)
{
	int part1sect = FindSectionForOffset(ofs);
	if (ofs%CACHESECTIONSIZE+bytesRequired<CACHESECTIONSIZE) {
		//only one section required
		if (part1sect == -1) {
			part1sect = FindLRUSection();
			LoadSection(part1sect,ofs);
		}
		cacheSectionInfo[part1sect].lastUsed = ++m_lastUseCounter;
		if (bytesAvail!= NULL) *bytesAvail = cacheSectionInfo[part1sect].ofsBase+CACHESECTIONSIZE-ofs;
		return m_pDbCache+part1sect*CACHESECTIONSIZE+(ofs-cacheSectionInfo[part1sect].ofsBase);
	}
	//two sections are required
	int part2sect = FindSectionForOffset(ofs+CACHESECTIONSIZE);
	if (part1sect != -1) {
		if (part2sect == -1) {  //first part in cache, but not second part
			if (part1sect == CACHESECTIONCOUNT-1) MoveSection(&part1sect,0);
			LoadSection(part1sect+1,ofs+CACHESECTIONSIZE);
		}
		else if (part2sect!= part1sect+1) {   //both parts are in cache, but not already consecutive
			if (part1sect == CACHESECTIONCOUNT-1) {
				//first part is at end, move to before second part
				if (part2sect == 0) //second part is at start: need to move both
					MoveSection(&part2sect,1);
				MoveSection(&part1sect,part2sect-1);
			}
			else  //move second part to after first part
				MoveSection(&part2sect,part1sect+1);
		}
	}
	else {
		if (part2sect == -1) {  //neither section is in cache
			part1sect = 0; part2sect = 1;
			LoadSection(part1sect,ofs); LoadSection(part2sect,ofs+CACHESECTIONSIZE);
		}
		else {    //part 2 is in cache, but not part 1
			if (part2sect == 0) MoveSection(&part2sect,1);
			part1sect = part2sect-1;
			LoadSection(part1sect,ofs);
		}
	}

	//both sections are now consecutive, starting at part1sect
	cacheSectionInfo[part1sect].lastUsed = ++m_lastUseCounter;
	cacheSectionInfo[part1sect+1].lastUsed = ++m_lastUseCounter;
	if (bytesAvail!= NULL)
		*bytesAvail = cacheSectionInfo[part1sect+1].ofsBase+CACHESECTIONSIZE-ofs;
	return m_pDbCache+part1sect*CACHESECTIONSIZE+(ofs-cacheSectionInfo[part1sect].ofsBase);
}

//we are assumed to be in a mutex here
void CDb3x::DBWrite(DWORD ofs,PVOID pData,int bytes)
{
	//write direct, and rely on Windows' write caching
	log2("write %d@%08x", bytes, ofs);
	SetFilePointer(m_hDbFile, ofs, NULL, FILE_BEGIN);

	DWORD bytesWritten;
	if ( WriteFile(m_hDbFile, pData, bytes, &bytesWritten, NULL) == 0)
		DatabaseCorruption( _T("%s (Write error)"));

	logg();

	//check if any of the cache sections contain this bit
	for(int i = 0; i < CACHESECTIONCOUNT; i++) {
		if (ofs+bytes >= cacheSectionInfo[i].ofsBase && ofs<cacheSectionInfo[i].ofsBase+CACHESECTIONSIZE) {
			if (ofs<cacheSectionInfo[i].ofsBase) {  //don't start at beginning
				if (ofs+bytes >= cacheSectionInfo[i].ofsBase+CACHESECTIONSIZE)   //don't finish at end
					CopyMemory(m_pDbCache+i*CACHESECTIONSIZE,(PBYTE)pData+cacheSectionInfo[i].ofsBase-ofs,CACHESECTIONSIZE);
				else CopyMemory(m_pDbCache+i*CACHESECTIONSIZE,(PBYTE)pData+cacheSectionInfo[i].ofsBase-ofs,bytes-(cacheSectionInfo[i].ofsBase-ofs));
			}
			else {	  //start at beginning
				if (ofs+bytes >= cacheSectionInfo[i].ofsBase+CACHESECTIONSIZE)   //don't finish at end
					CopyMemory(m_pDbCache+i*CACHESECTIONSIZE+ofs-cacheSectionInfo[i].ofsBase,pData,cacheSectionInfo[i].ofsBase+CACHESECTIONSIZE-ofs);
				else CopyMemory(m_pDbCache+i*CACHESECTIONSIZE+ofs-cacheSectionInfo[i].ofsBase,pData,bytes);
			}
		}
	}
}

void CDb3x::DBMoveChunk(DWORD ofsDest,DWORD ofsSource,int bytes)
{
	DWORD bytesRead;
	PBYTE buf;

	log3("move %d %08x->%08x",bytes,ofsSource,ofsDest);
	buf = (PBYTE)mir_alloc(bytes);
	SetFilePointer(m_hDbFile,ofsSource,NULL,FILE_BEGIN);
	ReadFile(m_hDbFile,buf,bytes,&bytesRead,NULL);
	DBWrite(ofsDest,buf,bytes);
	mir_free(buf);
	logg();
}

static VOID CALLBACK DoBufferFlushTimerProc(HWND hwnd,UINT message,UINT_PTR idEvent,DWORD dwTime)
{
	for (int i=0; i < g_Dbs.getCount(); i++) {
		CDb3x* db = g_Dbs[i];

		KillTimer(NULL, db->m_flushBuffersTimerId);
		log0("tflush1");
		FlushFileBuffers(db->getFile());
		log0("tflush2");
	}
}

void CDb3x::DBFlush(int setting)
{
	if (!setting) {
		log0("nflush1");
		if (m_safetyMode) FlushFileBuffers(m_hDbFile);
		log0("nflush2");
		return;
	}
	KillTimer(NULL,m_flushBuffersTimerId);
	m_flushBuffersTimerId = SetTimer(NULL,m_flushBuffersTimerId,50,DoBufferFlushTimerProc);
}

void CDb3x::DBFill(DWORD ofs,int bytes)
{
}

int CDb3x::InitCache(void)
{
	m_pDbCache = (PBYTE)mir_alloc(CACHESECTIONSIZE*CACHESECTIONCOUNT);
	m_lastUseCounter = CACHESECTIONCOUNT;
	for(int i = 0; i < CACHESECTIONCOUNT; i++) {
		cacheSectionInfo[i].ofsBase = 0;
		cacheSectionInfo[i].lastUsed = i;
		SetFilePointer(m_hDbFile,cacheSectionInfo[i].ofsBase,NULL,FILE_BEGIN);

		DWORD bytesRead;
		ReadFile(m_hDbFile,m_pDbCache+i*CACHESECTIONSIZE,CACHESECTIONSIZE,&bytesRead,NULL);
	}
	return 0;
}