/***************************************************************************
 *   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.             *
 ***************************************************************************/
#include "fileops.h"
#include <config-btcore.h>

#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <klocale.h>
#include <kio/job.h> 
#include <kio/netaccess.h>
#include <kio/copyjob.h> 
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <qdir.h>
#include <qfile.h>
#include <qstringlist.h>
#include "error.h"
#include "log.h"
#include "file.h"
#include "array.h"

#ifdef HAVE_XFS_XFS_H

#if !defined(HAVE___S64) || !defined(HAVE___U64)
#include <stdint.h>
#endif

#ifndef HAVE___U64
typedef	uint64_t	__u64;
#endif

#ifndef HAVE___S64
typedef	int64_t		__s64;
#endif

#include <xfs/xfs.h>
#endif

#ifndef O_LARGEFILE
#define O_LARGEFILE 0
#endif

#ifndef Q_WS_WIN
#include <sys/statvfs.h>
#endif
#ifdef CopyFile
#undef CopyFile
#endif

namespace bt
{
	void MakeDir(const QString & dir,bool nothrow)
	{
		KIO::Job* j = KIO::mkdir(KUrl(dir));
		if (!j->exec())
		{
			QString error = i18n("Cannot create directory %1: %2",dir,j->errorString());
			Out(SYS_DIO|LOG_NOTICE) << error <<endl;
			j->deleteLater();
			if (!nothrow)
				throw Error(error);
		}
		else
		{
			j->deleteLater();
		}
	}
	
	void SymLink(const QString & link_to,const QString & link_url,bool nothrow)
	{
		if (symlink(QFile::encodeName(link_to),QFile::encodeName(link_url)) != 0)
		{
			if (!nothrow)
				throw Error(i18n("Cannot symlink %1 to %2: %3"
					,link_url,link_to
					,strerror(errno)));
			else
				Out(SYS_DIO|LOG_NOTICE) << QString("Error : Cannot symlink %1 to %2: %3")
						.arg(link_url).arg(link_to)
						.arg(strerror(errno)) << endl;
		}
	}

	void Move(const QString & src,const QString & dst,bool nothrow)
	{
	//	Out() << "Moving " << src << " -> " << dst << endl;
		KIO::CopyJob *mv = KIO::move(KUrl(src),KUrl(dst)); 
		if (!KIO::NetAccess::synchronousRun(mv , 0)) 
		{
			if (!nothrow)
				throw Error(i18n("Cannot move %1 to %2: %3",
					src,dst,
						KIO::NetAccess::lastErrorString()));
			else
				Out(SYS_DIO|LOG_NOTICE) << QString("Error : Cannot move %1 to %2: %3")
						.arg(src).arg(dst)
						.arg(KIO::NetAccess::lastErrorString()) << endl;
		
		}
	}

	void CopyFile(const QString & src,const QString & dst,bool nothrow)
	{
		if (!KIO::NetAccess::file_copy(KUrl(src),KUrl(dst)))
		{
			if (!nothrow)
				throw Error(i18n("Cannot copy %1 to %2: %3",
						src,dst,
						KIO::NetAccess::lastErrorString()));
			else
				Out(SYS_DIO|LOG_NOTICE) << QString("Error : Cannot copy %1 to %2: %3")
						.arg(src).arg(dst)
						.arg(KIO::NetAccess::lastErrorString()) << endl;
	
		}
	}
	
	void CopyDir(const QString & src,const QString & dst,bool nothrow)
	{
		if (!KIO::NetAccess::dircopy(KUrl(src),KUrl(dst),0))
		{
			if (!nothrow)
				throw Error(i18n("Cannot copy %1 to %2: %3",
						src,dst,
						KIO::NetAccess::lastErrorString()));
			else
				Out(SYS_DIO|LOG_NOTICE) << QString("Error : Cannot copy %1 to %2: %3")
						.arg(src).arg(dst)
						.arg(KIO::NetAccess::lastErrorString()) << endl;
	
		}
	}

	bool Exists(const QString & url)
	{
		return QFile::exists(url);
	}
	
	static bool DelDir(const QString & fn)
	{
		QDir d(fn);
		QStringList subdirs = d.entryList(QDir::Dirs);
		
		for (QStringList::iterator i = subdirs.begin(); i != subdirs.end();i++)
		{
			QString entry = *i;

			if (entry == ".." || entry == ".")
				continue;
			
			if (!DelDir(d.absoluteFilePath(entry)))
				return false;	
		}
		
		QStringList files = d.entryList(QDir::Files | QDir::System | QDir::Hidden);
		for (QStringList::iterator i = files.begin(); i != files.end();i++)
		{
			QString file = d.absoluteFilePath(*i);
			if (!QFile::remove(file))
				return false;	
		}
		
		if (!d.rmdir(d.absolutePath()))
			return false;
		
		return true;
	}

	void Delete(const QString & url,bool nothrow)
	{
		bool ok = true;
		// first see if it is a directory
		if (QDir(url).exists())
		{
			ok = DelDir(url);
		}
		else
		{
			ok = QFile::remove(url);
		}
		
		if (!ok)
		{
			QString err = i18n("Cannot delete %1: %2",url,strerror(errno));
			if (!nothrow)
				throw Error(err);
			else
				Out(SYS_DIO|LOG_NOTICE) << "Error : " << err << endl;
		}
	}

	void Touch(const QString & url,bool nothrow)
	{
		if (Exists(url))
			return;
		
		File fptr;
		if (!fptr.open(url,"wb"))
		{
			if (!nothrow)
				throw Error(i18n("Cannot create %1: %2",url,fptr.errorString()));
			else
				Out(SYS_DIO|LOG_NOTICE) << "Error : Cannot create " << url << " : "
						<< fptr.errorString() << endl;
		
		}
	}
	
	Uint64 FileSize(const QString & url)
	{
		int ret = 0;
#ifdef HAVE_STAT64
		struct stat64 sb;
		ret = stat64(QFile::encodeName(url),&sb);
#else
		struct stat sb;
		ret = stat(QFile::encodeName(url),&sb);
#endif
		if (ret < 0)
			throw Error(i18n("Cannot calculate the filesize of %1: %2",url,strerror(errno)));
		
		return (Uint64)sb.st_size;
	}
	
	Uint64 FileSize(int fd)
	{
		int ret = 0;
#ifdef HAVE_STAT64
		struct stat64 sb;
		ret = fstat64(fd,&sb);
#else
		struct stat sb;
		ret = fstat(fd,&sb);
#endif
		if (ret < 0)
			throw Error(i18n("Cannot calculate the filesize : %1",strerror(errno)));
		
		return (Uint64)sb.st_size;
	}

	bool FatPreallocate(int fd,Uint64 size)
	{
		try
		{
			SeekFile(fd, size - 1, SEEK_SET);
			char zero = 0;		
			if (write(fd, &zero, 1) == -1)
				return false;
				
			TruncateFile(fd,size,true);
		}
		catch (bt::Error & e)
		{
			Out(SYS_DIO|LOG_NOTICE) << e.toString() << endl;
			return false;
		}
		return true;
	}
	
	bool FatPreallocate(const QString & path,Uint64 size)
	{
		int fd = ::open(QFile::encodeName(path),O_RDWR | O_LARGEFILE);
		if (fd < 0)
			throw Error(i18n("Cannot open %1 : %2",path,strerror(errno)));
		
		bool ret = FatPreallocate(fd,size);
		close(fd);
		return ret;
	}

#ifdef HAVE_XFS_XFS_H
	
	bool XfsPreallocate(int fd, Uint64 size)
	{
		if( ! platform_test_xfs_fd(fd) )
		{
			return false;
		}
		
		xfs_flock64_t allocopt;
		allocopt.l_whence = 0;
		allocopt.l_start = 0;
		allocopt.l_len  = size;
		
		return (! static_cast<bool>(xfsctl(0, fd, XFS_IOC_RESVSP64, &allocopt)) );
		
	}
	
	bool XfsPreallocate(const QString & path, Uint64 size)
	{
		int fd = ::open(QFile::encodeName(path), O_RDWR | O_LARGEFILE);
		if (fd < 0)
			throw Error(i18n("Cannot open %1 : %2",path,strerror(errno)));
		
		bool ret = XfsPreallocate(fd,size);
		close(fd);
		return ret;
	}

#endif

	void TruncateFile(int fd,Uint64 size,bool quick)
	{
		if (FileSize(fd) == size)
			return;
		
		if (quick)
		{
#ifdef HAVE_FTRUNCATE64
			if (ftruncate64(fd,size) == -1)
#else
			if (ftruncate(fd,size) == -1)
#endif
				throw Error(i18n("Cannot expand file : %1",strerror(errno)));
		}
		else
		{
#ifdef HAVE_POSIX_FALLOCATE64
			if (posix_fallocate64(fd,0,size) != 0)
				throw Error(i18n("Cannot expand file : %1",strerror(errno)));
#elif HAVE_POSIX_FALLOCATE
			if (posix_fallocate(fd,0,size) != 0)
				throw Error(i18n("Cannot expand file : %1",strerror(errno)));
#else
			SeekFile(fd,0,SEEK_SET);
			bt::Array<Uint8> buf(4096);
			buf.fill(0);
	
			Uint64 written = 0;
			while (written < size)
			{
				int to_write = size - written;
				if (to_write > 4096)
					to_write = 4096;
				
				int ret = write(fd,buf,to_write);
				if (ret < 0)
					throw Error(i18n("Cannot expand file: %1",strerror(errno)));
				else if (ret == 0 || ret != (int)to_write)
					throw Error(i18n("Cannot expand file: %1",strerror(errno)));
				else
					written += to_write;
			}
#endif
		}
	}
	
	void TruncateFile(const QString & path,Uint64 size)
	{
		int fd = ::open(QFile::encodeName(path),O_RDWR | O_LARGEFILE);
		if (fd < 0)
			throw Error(i18n("Cannot open %1 : %2",path,strerror(errno)));
		
		try
		{
			TruncateFile(fd,size,true);
			close(fd);
		}
		catch (...)
		{
			close(fd);
			throw;
		}
	}
	
	void SeekFile(int fd,Int64 off,int whence)
	{
#ifdef HAVE_LSEEK64
		if (lseek64(fd,off,whence) == -1)
#else
		if (lseek(fd,off,whence) == -1)
#endif
			throw Error(i18n("Cannot seek in file : %1",strerror(errno)));
	}
	
	bool FreeDiskSpace(const QString & path,Uint64 & bytes_free)
	{
#ifdef HAVE_STATVFS
#ifdef HAVE_STATVFS64
		struct statvfs64 stfs;
		if (statvfs64(QFile::encodeName(path), &stfs) == 0)
#else
		struct statvfs stfs;
		if (statvfs(QFile::encodeName(path), &stfs) == 0)
#endif
		{
			bytes_free = ((Uint64)stfs.f_bavail) * ((Uint64)stfs.f_frsize);
			return true;
		}
		else
		{
			Out(SYS_GEN|LOG_DEBUG) << "Error : statvfs for " << path << " failed :  "
						<< QString(strerror(errno)) << endl;

			return false;
		}
#elif defined(Q_WS_WIN)
#ifdef UNICODE
		LPCWSTR tpath = (LPCWSTR)path.utf16();
#else
		const char *tpath = path.toLocal8Bit();
#endif
		if(GetDiskFreeSpaceEx(tpath, (PULARGE_INTEGER)&bytes_free, NULL, NULL)) {
			return true;
		} else {
			return false;
		}
#else
		return false;
#endif
	}
}
