//=============================================================================
//
//   File : KviFileUtils.cpp
//   Creation date : Fri Dec 25 1998 18:26:48 by Szymon Stefanek
//
//   This file is part of the KVIrc IRC client distribution
//   Copyright (C) 1998-2010 Szymon Stefanek (pragma at kvirc dot net)
//
//   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.
//
//=============================================================================

#define _KVI_FILEUTLIS_CPP_

#include "KviFileUtils.h"
#include "KviQString.h"
#include "KviFile.h"
#include "KviMemory.h"

#include <QDir>
#include <QFileInfo>
#include <QString>
#include <QStringList>
#include <QtGlobal>
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
#include <QTextCodec>
#else
#include <QStringConverter>
#endif
#include <QTextStream>

namespace KviFileUtils
{
	static char cHexChars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

	bool makeDir(const QString & szPath)
	{
		QDir d;
		QString szDir = szPath.trimmed();
		adjustFilePath(szDir);
		QString szCreatedDir;

#if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW)
		int idx = szDir.indexOf(':');
		if(idx == 1)
		{
			szCreatedDir = szDir.left(2);
			szDir.remove(0, 2);
		}
#endif

		KviQString::stripLeft(szDir, KVI_PATH_SEPARATOR_CHAR);
		while(!szDir.isEmpty())
		{
			szCreatedDir += KVI_PATH_SEPARATOR;
			szCreatedDir += KviQString::getToken(szDir, KVI_PATH_SEPARATOR_CHAR);
			if(!directoryExists(szCreatedDir))
			{
				if(!d.mkdir(szCreatedDir))
				{
					qDebug("Can't create directory %s", szCreatedDir.toUtf8().data());
					return false;
				}
			}
			KviQString::stripLeft(szDir, KVI_PATH_SEPARATOR_CHAR);
		}
		return true;
	}

	bool makeDir(const char * pcPath)
	{
		QString szPath = QString::fromUtf8(pcPath);
		return makeDir(szPath);
	}

	bool renameFile(const QString & szSrc, const QString & szDst)
	{
		QDir d;
		return d.rename(szSrc, szDst);
	}

	bool renameFile(const char * pcSrc, const char * pcDst)
	{
		QString szPath = QString::fromUtf8(pcSrc);
		QString szPath2 = QString::fromUtf8(pcDst);
		return renameFile(szPath, szPath2);
	}

	bool copyFile(const QString & szSrc, const QString & szDst)
	{
		KviFile f1(szSrc);
		if(!f1.open(QFile::ReadOnly))
			return false;
		KviFile f2(szDst);
		if(!f2.open(QFile::WriteOnly | QFile::Truncate))
		{
			f1.close();
			return false;
		}
		char cBuffer[1024];
		while(!f1.atEnd())
		{
			int iLen = f1.read(cBuffer, 1024);
			if(iLen <= 0)
			{
				f1.close();
				f2.close();
				return false; //"serious error"
			}
			f2.write(cBuffer, iLen);
		}
		f1.close();
		f2.close();
		return true;
	}

	bool copyFile(const char * pcSrc, const char * pcDst)
	{
		QString szPath = QString::fromUtf8(pcSrc);
		QString szPath2 = QString::fromUtf8(pcDst);
		return copyFile(szPath, szPath2);
	}

	bool loadFile(const QString & szPath, QString & szBuffer, bool bUtf8)
	{
		KviFile f(szPath);
		if(!f.open(QFile::ReadOnly))
			return false;
		if(bUtf8)
		{
			QByteArray ba = f.readAll();
			szBuffer = QString::fromUtf8(ba.data(), ba.size());
			//qDebug("BUFFERLEN: %d",szBuffer.length());
		}
		else
		{
			szBuffer = QString(f.readAll());
		}
		return true;
	}

	void adjustFilePath(QString & szPath)
	{
#if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW)
		szPath.replace('/', "\\");
		szPath.replace("\\\\", "\\");
		// FIXME: Use the default drive here ?
		if(szPath.startsWith("\\"))
			szPath.prepend("C:");
#else
		szPath.replace('\\', "/");
		szPath.replace("//", "/");
		// deal with windows paths
		if((szPath.length() > 2) && (szPath.at(0) != QChar('/')))
		{
			if((szPath.at(1) == QChar(':')) && (szPath.at(2) == QChar('/')))
			{
				szPath.remove(0, 2);
			}
		}
		szPath = QDir::cleanPath(szPath);
#endif
	}

	bool removeFile(const QString & szPath)
	{
		QDir d;
		return d.remove(szPath);
	}

	bool removeFile(const char * pcPath)
	{
		QString szPath = QString::fromUtf8(pcPath);
		return removeFile(szPath);
	}

	bool removeDir(const QString & szPath)
	{
		QDir d;
		return d.rmdir(szPath);
	}

	bool removeDir(const char * pcPath)
	{
		QString szPath = QString::fromUtf8(pcPath);
		return removeDir(szPath);
	}

	bool deleteDir(const QString & szPath)
	{
		QDir d(szPath);

		QFileInfoList lFileInfo = d.entryInfoList(QDir::Files | QDir::Dirs | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot);

		for(const auto & inf : lFileInfo)
		{
			// just to be sure check that we're not deleting ..
			if(KviQString::equalCS(inf.fileName(), "..") || KviQString::equalCS(inf.fileName(), "."))
				continue;

			if(inf.isDir())
			{
				if(!deleteDir(inf.absoluteFilePath()))
					return false;
				continue;
			}

			if(!KviFileUtils::removeFile(inf.absoluteFilePath()))
			{
				qDebug("Failed to remove file %s", inf.absoluteFilePath().toUtf8().data());
				return false;
			}
		}

		if(!KviFileUtils::removeDir(szPath))
		{
			qDebug("Failed to remove directory %s", szPath.toUtf8().data());
			return false;
		}

		return true;
	}

	bool writeFile(const QString & szPath, const QByteArray & oData, bool bAppend)
	{
		KviFile f(szPath);
		if(!f.open(QFile::WriteOnly | (bAppend ? QFile::Append : QFile::Truncate)))
			return false;
		if(oData.length() < 1)
			return true;
		if(f.write(oData.data(), oData.length()) != ((unsigned int)(oData.length())))
			return false;
		return true;
	}

	bool writeFile(const QString & szPath, const QString & szData, bool bAppend)
	{
		KviFile f(szPath);
		if(!f.open(QFile::WriteOnly | (bAppend ? QFile::Append : QFile::Truncate)))
			return false;
		QByteArray szTmp = szData.toUtf8();
		if(f.write(szTmp.data(), szTmp.length()) != ((unsigned int)(szTmp.length())))
			return false;
		return true;
	}

	bool writeFile(const char * pcPath, const QString & szData, bool bAppend)
	{
		QString szPath = QString::fromUtf8(pcPath);
		return writeFile(szPath, szData, bAppend);
	}

	bool writeFileLocal8Bit(const QString & szPath, const QString & szData, bool bAppend)
	{
		KviFile f(szPath);
		if(!f.open(QFile::WriteOnly | (bAppend ? QFile::Append : QFile::Truncate)))
			return false;
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
		QByteArray szTmp = QTextCodec::codecForLocale()->fromUnicode(szData);
#else
		QStringEncoder enc = QStringEncoder(QStringConverter::Latin1);
		QByteArray szTmp = enc.encode(szData);
#endif

		if(f.write(szTmp.data(), szTmp.length()) != ((unsigned int)(szTmp.length())))
			return false;
		return true;
	}

	bool writeFileLocal8Bit(const char * pcPath, const QString & szData, bool bAppend)
	{
		QString szPath = QString::fromUtf8(pcPath);
		return writeFileLocal8Bit(szPath, szData, bAppend);
	}

	bool readFile(const QString & szPath, QString & szBuffer, unsigned int uMaxSize)
	{
		KviFile f(szPath);
		if(!f.open(QFile::ReadOnly))
			return false;
		if(f.size() < 1)
		{
			szBuffer = "";
			f.close();
			return true;
		}
		if(f.size() > uMaxSize)
			return false;
		char * pcBuf = new char[f.size() + 1];
		if(f.read(pcBuf, f.size()) != ((long int)f.size()))
		{
			delete[] pcBuf;
			pcBuf = nullptr;
			return false;
		}
		pcBuf[f.size()] = '\0';
		szBuffer = QString::fromUtf8(pcBuf);
		delete[] pcBuf;
		pcBuf = nullptr;
		return true;
	}

	bool readFile(const char * pcPath, QString & szBuffer, unsigned int uMaxSize)
	{
		QString szPath = QString::fromUtf8(pcPath);
		return readFile(szPath, szBuffer, uMaxSize);
	}

	QString extractFileName(const QString & szFileNameWithPath, bool bAllowEmpty)
	{
		if(!bAllowEmpty)
		{
			if(szFileNameWithPath.endsWith(KVI_PATH_SEPARATOR))
				return QFileInfo(szFileNameWithPath.left(szFileNameWithPath.length() - 1)).fileName();
		}
		return QFileInfo(szFileNameWithPath).fileName();
	}

	QString extractFilePath(const QString & szFileNameWithPath)
	{
		return QFileInfo(szFileNameWithPath).absolutePath();
	}

	bool readLine(QFile * pFile, QString & szBuffer, bool bUtf8)
	{
		QTextStream stream(pFile);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
		/*
		* Name: UTF-8
		* MIBenum: 106
		* from: http://www.iana.org/assignments/character-sets
		*/
    	stream.setCodec(bUtf8 ? QTextCodec::codecForMib(106) : QTextCodec::codecForLocale());
#else
		stream.setEncoding(bUtf8 ? QStringConverter::Utf8 : QStringConverter::Latin1);
#endif
		szBuffer = stream.readLine();
		return !szBuffer.isNull();
	}

	bool readLines(QFile * pFile, QStringList & buffer, int iStartLine, int iCount, bool bUtf8)
	{
		QTextStream stream(pFile);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
		//please read comments in the readLine(...) method
    	stream.setCodec(bUtf8 ? QTextCodec::codecForMib(106) : QTextCodec::codecForLocale());
#else
		stream.setEncoding(bUtf8 ? QStringConverter::Utf8 : QStringConverter::Latin1);
#endif

		for(int i = 0; i < iStartLine; i++)
			stream.readLine();

		if(iCount > 0)
		{
			for(; (iCount > 0 && !stream.atEnd()); iCount--)
				buffer.append(stream.readLine());
		}
		else
		{
			while(!stream.atEnd())
			{
				buffer.append(stream.readLine());
			}
		}
		return buffer.count() != 0;
	}

	bool directoryExists(const QString & szPath)
	{
		QFileInfo f(szPath);
		return (f.exists() && f.isDir());
	}

	bool isReadable(const QString & szFname)
	{
		QFileInfo f(szFname);
		return (f.exists() && f.isFile() && f.isReadable());
	}

	bool isReadable(const char * pcPath)
	{
		QString szPath = QString::fromUtf8(pcPath);
		return isReadable(szPath);
	}

	bool isAbsolutePath(const QString & szPath)
	{
		QFileInfo f(szPath);
		return !f.isRelative();
	}

	void encodeFileName(QString & szPath)
	{
		QString szSrc(szPath);
		szPath = "";
		for(const auto & i : szSrc)
		{
			QChar cur = i;
			if(!(cur.isLetter() || cur.isDigit() || cur == ' ' || cur == '_' || cur == '.' || cur == '#' || cur == '%'))
			{
				if(cur.row() != 0)
				{
					szPath += '%';
					szPath += cHexChars[cur.row() >> 4];
					szPath += cHexChars[cur.row() & 15];
				}
				szPath += '%';
				szPath += cHexChars[cur.cell() >> 4];
				szPath += cHexChars[cur.cell() & 15];
			}
			else if(cur == '%')
			{
				szPath += "%%";
			}
			else
			{
				szPath += cur;
			}
		}
	}

	void cleanFileName(QString & szPath)
	{
		QString szSrc(szPath);
		szPath = "";
		for(const auto & i : szSrc)
		{
			QChar cur = i;
#if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW)
			if(cur.unicode() < 32 || cur == '<' || cur == '>' || cur == ':' || cur == '"' || cur == '/' || cur == '\\' || cur == '|' || cur == '?' || cur == '*')
#else
			if(cur.unicode() == 0 || cur == '/')
#endif
			{
				szPath += '-';
			}
			else
			{
				szPath += cur;
			}
		}
	}

	static QStringList getFileListingInternal(const QString & szPath, const QString & szPrefix)
	{
		//qDebug("Scan %s",szPath.toUtf8().data());
		QStringList sl;

		QDir d(szPath);

		QFileInfoList fl = d.entryInfoList(QStringList(), QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDir::Name);

		for(const auto & inf : fl)
		{
			QString szName = szPrefix.isEmpty() ? inf.fileName() : QString("%1" KVI_PATH_SEPARATOR "%2").arg(szPrefix).arg(inf.fileName());
			if(inf.isDir())
			{
				QStringList sub = getFileListingInternal(inf.canonicalFilePath(), szName);
				sl.append(sub);
			}
			else
			{
				sl.append(szName);
			}
		}

		return sl;
	}

	QStringList getFileListing(const QString & szPath)
	{
		return getFileListingInternal(szPath, QString());
	}
}
