/***************************************************************************
 *   Copyright (C) 2005 by Joris Guisson                                   *
 *   joris.guisson@gmail.com                                               *
 *                                                                         *
 *   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.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.             *
 ***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include <qfile.h>
#include <kfileitem.h>
#include <kio/netaccess.h>
#include <klocale.h>
#include <util/error.h>
#include <util/log.h>
#include <torrent/globals.h>
#include "mmapfile.h"

namespace bt
{

	MMapFile::MMapFile() : fd(-1),data(0),size(0),file_size(0),ptr(0),mode(READ)
	{}


	MMapFile::~MMapFile()
	{
		if (fd > 0)
			close();
	}

	bool MMapFile::open(const QString & file,Mode mode)
	{
#if HAVE_STAT64
		struct stat64 sb;
		stat64(QFile::encodeName(file),&sb);
#else
		struct stat sb;
		stat(QFile::encodeName(file),&sb);
#endif
		
		return open(file,mode,(Uint64)sb.st_size);
	}
	
	bool MMapFile::open(const QString & file,Mode mode,Uint64 size)
	{
		// close already open file
		if (fd > 0)
			close();
		
		// setup flags
		int flag = 0,mmap_flag = 0;
		switch (mode)
		{
			case READ:
				flag = O_RDONLY;
				mmap_flag = PROT_READ;
				break;
			case WRITE:
				flag = O_WRONLY | O_CREAT;
				mmap_flag = PROT_WRITE;
				break;
			case RW:
				flag = O_RDWR | O_CREAT;
				mmap_flag = PROT_READ|PROT_WRITE;
				break;
		}

		// Not all systems have O_LARGEFILE as an explicit flag
		// (for instance, FreeBSD. Solaris does, but only if
		// _LARGEFILE_SOURCE is defined in the compile).
		// So OR it in if it is defined.
#ifdef O_LARGEFILE
		flag |= O_LARGEFILE;
#endif

		// open the file
		fd = ::open(QFile::encodeName(file) , flag);//(int)flag);
		if (fd == -1)
			return false;
		
		// read the file size
		this->size = size;
		this->mode = mode;
		
#if HAVE_STAT64
		struct stat64 sb;
		stat64(QFile::encodeName(file),&sb);
#else
		struct stat sb;
		stat(QFile::encodeName(file),&sb);
#endif
		file_size = (Uint64)sb.st_size;
		filename = file;
		
		// mmap the file
		data = (Uint8*)mmap(0, size, mmap_flag, MAP_SHARED, fd, 0);
		if (data == MAP_FAILED) 
		{
			::close(fd);
			data = 0;
			fd = -1;
			ptr = 0;
			return false;
		}
		ptr = 0;
		return true;
	}
		
	void MMapFile::close()
	{
		if (fd > 0)
		{
			munmap(data,size);
			::close(fd);
			ptr = size = 0;
			data = 0;
			fd = -1;
			filename = QString::null;
		}
	}
		
	void MMapFile::flush()
	{
		if (fd > 0)
			msync(data,size,MS_SYNC);
	}
		
	Uint32 MMapFile::write(const void* buf,Uint32 buf_size)
	{
		if (fd == -1 || mode == READ)
			return 0;
		
		// check if data fits in memory mapping
		if (ptr + buf_size > size)
			throw Error(i18n("Cannot write beyond end of the mmap buffer!"));
		
		Out() << "MMapFile::write : " << (ptr + buf_size) << " " << file_size << endl;
		// enlarge the file if necessary
		if (ptr + buf_size > file_size)
		{
			growFile(ptr + buf_size);
		}
		
		// memcpy data
		memcpy(&data[ptr],buf,buf_size);
		// update ptr
		ptr += buf_size;
		// update file size if necessary
		if (ptr >= size)
			size = ptr;
		
		return buf_size;
	}
	
	void MMapFile::growFile(Uint64 new_size)
	{
		Out() << "Growing file to " << new_size << " bytes " << endl;
		Uint64 to_write = new_size - file_size;
		ssize_t written;
		// jump to the end of the file
		lseek(fd,0,SEEK_END);
		
		Uint8 buf[1024];
		memset(buf,0,1024);
		// write data until to_write is 0
		while (to_write > 0)
		{
			ssize_t w = ::write(fd,buf, to_write > 1024 ? 1024 : to_write);
			if (w > 0)
			    to_write -= w;
			else if (w < 0)
			    break;
		}
		file_size = new_size;
	}
		
	Uint32 MMapFile::read(void* buf,Uint32 buf_size)
	{
		if (fd == -1 || mode == WRITE)
			return 0;
		
		// check if we aren't going to read past the end of the file
		Uint32 to_read = ptr + buf_size >= size ? size - ptr : buf_size;
		// read data
		memcpy(buf,data+ptr,to_read);
		ptr += to_read;
		return to_read;
	}

	Uint64 MMapFile::seek(SeekPos from,Int64 num)
	{
		switch (from)
		{
			case BEGIN:
				if (num > 0)
					ptr = num; 
				if (ptr >= size)
					ptr = size - 1;
				break;
			case END:
				{
					Int64 np = (size - 1) + num;
					if (np < 0)
					{
						ptr = 0;
						break;
					}
					if (np >= (Int64) size)
					{
						ptr = size - 1;
						break;
					}
					ptr = np;
				}
				break;
			case CURRENT:
				{
					Int64 np = ptr + num;
					if (np < 0)
					{
						ptr = 0;
						break;
					}
					if (np >= (Int64) size)
					{
						ptr = size - 1;
						break;
					}
					ptr = np;
				}	
				break;
		}
		return ptr;
	}

	bool MMapFile::eof() const
	{
		return ptr >= size;
	}

	Uint64 MMapFile::tell() const
	{
		return ptr;
	}
	
	QString MMapFile::errorString() const
	{
		return strerror(errno);
	}
	
	Uint64 MMapFile::getSize() const
	{
		return size;
	}
	
	Uint8* MMapFile::getData(Uint64 off)
	{
		if (off >= size)
			return 0;
		return &data[off];
	}
}

