//=============================================================================
//
//   File : KvsObject_file.cpp
//   Creation date : Fri Mar 18 21:30:48 CEST 2005
//   by Tonino Imbesi(Grifisx) and Alessandro Carbone(Noldor)
//
//   This file is part of the KVIrc irc client distribution
//   Copyright (C) 2000-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 opinion) 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 "KvsObject_file.h"
#include "kvi_debug.h"
#include "KviLocale.h"
#include "KviError.h"
#include "KviFile.h"
#include "KviFileUtils.h"
#include "KvsObject_memoryBuffer.h"

#include <QStringList>
//#include <QFile>
#include <QTextStream>

#define CHECK_FILE_IS_OPEN if (!m_pFile->isOpen())\
	{c->warning(__tr2qs_ctx("File is not open!","objects"));return true;}

// needed for $open()

const char * const mod_tbl[] = {
	"Raw",
	"ReadOnly",
	"WriteOnly",
	"ReadWrite",
	"Append",
	"Truncate"
};

const QIODevice::OpenMode mod_cod[] = {
	QIODevice::Unbuffered,
	QIODevice::ReadOnly,
	QIODevice::WriteOnly,
	QIODevice::ReadWrite,
	QIODevice::Append,
	QIODevice::Truncate
};

#define mod_num			(sizeof(mod_tbl) / sizeof(mod_tbl[0]))

/*
	@doc:	file
	@keyterms:
		file object class, creating files
	@title:
		file class
	@type:
		class
	@short:
		I/O device that operates on files
	@inherits:
		[class]object[/class]
	@description:
		This object class provides access to files - basic I/O
		operations, copying, removing etc...
	@functions:
		!fn: $setName(<file_name:string>)
		Sets the file name to <file_name>. It does [b]not move[/b] the file,
		it just changes the file the object is "pointing to". You can
		not change names of already open files.[br]
		See also: [classfnc]$open[/classfnc](),
		[classfnc]$name[/classfnc]().
		!fn: <string> $name()
		Returns name set by [classfnc]$setName[/classfnc]().[br]
		See also: [classfnc]$setName[/classfnc]().
		!fn: $open(<mode1:string>, <mode2:string>)
		Attempts to open the file in specified mode or modes "sum".
		Valid modes are:[br]
		[pre]
		Raw	  - raw, non-buffered access[br]
		ReadOnly     - opens the file read-only[br]
		WriteOnly    - opens the file write-only[br]
		ReadWrite    - opens the file in read-write mode[br]
		Append       - opens the file in append mode. The file index is set to the end of the file.[br]
		Truncate     - truncates the file[br]
		[/pre]
		If you call this function without any parameters, the file is
		opened in ReadOnly mode.[br]
		When working with buffered files, data is not written directly
		to the file at once. You must call [classfnc]$flush[/classfnc]() to force it.[br]
		See also: [classfnc]$close[/classfnc](),
		[classfnc]$flush[/classfnc]().
		!fn: <boolean> $isOpen()
		Returns '1' if the file is open, '0' otherwise.
		!fn: $close()
		Closes the file, flushing the buffers first.[br]
		See also: [classfnc]$flush[/classfnc]().
		!fn: $flush()
		Flushes the file buffer to disk. Calling this after opening the
		file in 'Raw' mode doesn't make much sense.[br]
		See also: [classfnc]$open[/classfnc](),
		[classfnc]$close[/classfnc]().
		!fn: <integer> $size()
		Returns current file size.
		!fn: <boolean> $atEnd()
		Returns '1' if you have reached end of the file, '0' otherwise.
		[br]See also: [classfnc]$seek[/classfnc](),
		[classfnc]$where[/classfnc]().
		!fn: <integer> $where()
		Returns current position in the file (file index).[br]
		See also: [classfnc]$seek[/classfnc]().
		!fn: $seek(<index:integer>)
		Sets the file index to <index>.[br]
		See also: [classfnc]$where[/classfnc](),
		[classfnc]$atEnd[/classfnc]().
		!fn: $putch(<char>)
		Writes character <char> to the file and increments file
		index.[br]
		See also: [classfnc]$getch[/classfnc](),
		[classfnc]$ungetch[/classfnc]().
		!fn: <char> $getch()
		Reads a character from the file and increments file index.[br]
		See also: [classfnc]$putch[/classfnc](),
		[classfnc]$ungetch[/classfnc]().
		!fn: $ungetch(<char>)
		Puts the character <char> back to the file and
		decrements the file index. This is usually called to undo a
		$getch() call.[br]
		See also: [classfnc]$getch[/classfnc],
		[classfnc]$putch[/classfnc]().
		!fn: $readLine(<text_line:string>)
		Reads a line of text from the file and increments file index.
		!fn: $writeLine(<text_line:string>)
		Appends a line of text to the end of the file.
		$readBlock()
		$writeBlock()
*/


KVSO_BEGIN_REGISTERCLASS(KvsObject_file,"file","object")
	KVSO_REGISTER_HANDLER(KvsObject_file,"setName",setName)
	KVSO_REGISTER_HANDLER(KvsObject_file,"name",name)

	KVSO_REGISTER_HANDLER(KvsObject_file,"open",open)
	KVSO_REGISTER_HANDLER(KvsObject_file,"isOpen",isOpen)
	KVSO_REGISTER_HANDLER(KvsObject_file,"close",close)
	KVSO_REGISTER_HANDLER(KvsObject_file,"flush",flush)

	KVSO_REGISTER_HANDLER(KvsObject_file,"size",size)
	KVSO_REGISTER_HANDLER(KvsObject_file,"resize",resize)

	KVSO_REGISTER_HANDLER(KvsObject_file,"atEnd",atEnd)
	KVSO_REGISTER_HANDLER(KvsObject_file,"where",where)
	KVSO_REGISTER_HANDLER(KvsObject_file,"seek",seek)

	KVSO_REGISTER_HANDLER(KvsObject_file,"putch",putch)
	KVSO_REGISTER_HANDLER(KvsObject_file,"getch",getch)
	KVSO_REGISTER_HANDLER(KvsObject_file,"ungetch",unGetch)
	KVSO_REGISTER_HANDLER(KvsObject_file,"readByte",readByte)

	KVSO_REGISTER_HANDLER(KvsObject_file,"readBlock",readBlock)
	KVSO_REGISTER_HANDLER(KvsObject_file,"writeBlock", writeBlock)

	KVSO_REGISTER_HANDLER(KvsObject_file,"readHexBlock",readHexBlock)
	KVSO_REGISTER_HANDLER(KvsObject_file,"writeHexBlock",writeHexBlock)

	KVSO_REGISTER_HANDLER(KvsObject_file,"readLine",readLine)
	KVSO_REGISTER_HANDLER(KvsObject_file,"writeLine",writeLine)

	KVSO_REGISTER_HANDLER(KvsObject_file,"write",write)
	KVSO_REGISTER_HANDLER(KvsObject_file,"read",read)


KVSO_END_REGISTERCLASS(KvsObject_file)


KVSO_BEGIN_CONSTRUCTOR(KvsObject_file,KvsObject_widget)
	m_pFile = new KviFile();

KVSO_END_CONSTRUCTOR(KvsObject_file)

KVSO_BEGIN_DESTRUCTOR(KvsObject_file)

	if (m_pFile) delete m_pFile;

KVSO_END_CONSTRUCTOR(KvsObject_file)

KVSO_CLASS_FUNCTION(file,setName)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	QString szName;
	KVSO_PARAMETERS_BEGIN(c)
		KVSO_PARAMETER("file_name",KVS_PT_STRING,0,szName)
	KVSO_PARAMETERS_END(c)
	m_pFile->setFileName(szName);
	return true;
}

KVSO_CLASS_FUNCTION(file,name)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	c->returnValue()->setString(m_pFile->fileName());
	return true;
}

KVSO_CLASS_FUNCTION(file,open)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	QStringList modes;
	KVSO_PARAMETERS_BEGIN(c)
		KVSO_PARAMETER("file_mode",KVS_PT_STRINGLIST,KVS_PF_OPTIONAL,modes)
	KVSO_PARAMETERS_END(c)

	if(m_pFile->fileName().isEmpty())
	{
		c->warning(__tr2qs_ctx("Empty filename string","objects"));
		return true;
	}
	QIODevice::OpenMode mod,sum;
	if (modes.empty()) sum = QIODevice::ReadWrite|QIODevice::Append; // if no parameters given, default to ReadWrite | Append
	else
	{
		for ( int idx=0;idx<modes.count();idx++)
		{
			int found=false;
			for(unsigned int j = 0; j < mod_num; j++)
			{
				if(KviQString::equalCI(modes.at(idx), mod_tbl[j]))
				{
					mod=mod_cod[j];
					found=true;
					break;
				}
			}
			if(found)
				sum = sum | mod;
			else
				c->warning(__tr2qs_ctx("No such open mode '%Q'","objects"),&modes.at(idx));
		}
	}
	c->returnValue()->setBoolean(m_pFile->open(sum));
	return true;
}

KVSO_CLASS_FUNCTION(file,isOpen)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	c->returnValue()->setBoolean(m_pFile->isOpen());
	return true;
}

KVSO_CLASS_FUNCTION(file,close)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	CHECK_FILE_IS_OPEN
	m_pFile->close();
	return true;
}

KVSO_CLASS_FUNCTION(file,flush)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	CHECK_FILE_IS_OPEN
	m_pFile->flush();
	return true;
}

KVSO_CLASS_FUNCTION(file,size)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	c->returnValue()->setInteger((kvs_int_t)(m_pFile->size()));
	return true;
}

KVSO_CLASS_FUNCTION(file,atEnd)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	c->returnValue()->setInteger((kvs_int_t)(m_pFile->atEnd()));
	return true;
}

KVSO_CLASS_FUNCTION(file,where)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	CHECK_FILE_IS_OPEN
	c->returnValue()->setInteger((kvs_int_t)(m_pFile->pos()));
	return true;
}

KVSO_CLASS_FUNCTION(file,seek)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	kvs_uint_t uIndex;
	KVSO_PARAMETERS_BEGIN(c)
		KVSO_PARAMETER("index",KVS_PT_UNSIGNEDINTEGER,0,uIndex)
	KVSO_PARAMETERS_END(c)
	CHECK_FILE_IS_OPEN
	m_pFile->seek(uIndex);
	return true;
}


KVSO_CLASS_FUNCTION(file,resize)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	kvs_int_t iSize;
	KVSO_PARAMETERS_BEGIN(c)
		KVSO_PARAMETER("size",KVS_PT_INTEGER,0,iSize)
	KVSO_PARAMETERS_END(c)
	CHECK_FILE_IS_OPEN
	m_pFile->resize(iSize);
	return true;
}

KVSO_CLASS_FUNCTION(file,putch)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	QString szChar;
	KVSO_PARAMETERS_BEGIN(c)
		KVSO_PARAMETER("char",KVS_PT_STRING,0,szChar)
	KVSO_PARAMETERS_END(c)
	if (szChar.length()>1)c->warning(__tr2qs_ctx("Argument too long, using only first char","objects"));
	const char *ch=szChar.toUtf8().data();
	if (!m_pFile->putChar(ch[0])) c->warning(__tr2qs_ctx("Write error occured!","objects"));
	return true;
}

KVSO_CLASS_FUNCTION(file,getch)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	CHECK_FILE_IS_OPEN
	char ch;
	if (!m_pFile->getChar(&ch)) c->warning(__tr2qs_ctx("Read error occured!","objects"));	// c->error ?
	QString szChar = QChar(ch);
	c->returnValue()->setString(szChar);
	return true;
}

KVSO_CLASS_FUNCTION(file,readByte)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	CHECK_FILE_IS_OPEN
	char ch;
	if (!m_pFile->getChar(&ch)) c->warning(__tr2qs_ctx("Read error occured!","objects"));	// c->error ?
	//QString szChar = QChar(ch);
	c->returnValue()->setInteger((kvs_int_t) ch);
	return true;
}

KVSO_CLASS_FUNCTION(file,unGetch)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	CHECK_FILE_IS_OPEN
	QString szChar;
	KVSO_PARAMETERS_BEGIN(c)
		KVSO_PARAMETER("char",KVS_PT_STRING,0,szChar)
	KVSO_PARAMETERS_END(c)
	if (szChar.length()>1) c->warning(__tr2qs_ctx("Argument too long, using only the first char","objects"));
	const char *ch=szChar.toUtf8().data();
	m_pFile->ungetChar(ch[0]);
	return true;
}

KVSO_CLASS_FUNCTION(file,readBlock)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	CHECK_FILE_IS_OPEN
	kvs_uint_t uLen;
	KviKvsObject * pObject;
	kvs_hobject_t hObject;
	KVSO_PARAMETERS_BEGIN(c)
		KVSO_PARAMETER("length",KVS_PT_UNSIGNEDINTEGER,0,uLen)
		KVSO_PARAMETER("hobject",KVS_PT_HOBJECT,KVS_PF_OPTIONAL,hObject)
	KVSO_PARAMETERS_END(c)
	if (uLen>(kvs_uint_t)m_pFile->size())
		uLen=m_pFile->size();
	if (hObject)
	{
		pObject=KviKvsKernel::instance()->objectController()->lookupObject(hObject);
		if (!pObject)
		{
			c->warning(__tr2qs_ctx("Buffer parameter is not an object","objects"));
			return true;
		}
		if (!pObject->inheritsClass("memorybuffer"))
		{
			c->warning(__tr2qs_ctx("Buffer parameter is not a memorybuffer object","objects"));
			return true;
		}
		//m_pFile->flush();
		((KvsObject_memoryBuffer *)pObject)->pBuffer()->append(m_pFile->read(uLen));
		return true;
	}
	else
	{
		char * buff = new char[uLen + 1];
		//m_pFile->flush(); // advice from QFile man page (to avoid trash)
		int rlen = m_pFile->read(buff, uLen);
		buff[rlen] = '\0';
		QString szBlock(buff);
		delete[] buff;
		c->returnValue()->setString(szBlock);
	}
	return true;
}

KVSO_CLASS_FUNCTION(file,read)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	CHECK_FILE_IS_OPEN
	QString szType;
	KVSO_PARAMETERS_BEGIN(c)
			KVSO_PARAMETER("type",KVS_PT_STRING,0,szType)
	KVSO_PARAMETERS_END(c)
	if(KviQString::equalCI(szType, "String"))
	{
		QString szStr;
		m_pFile->load(szStr);
		c->returnValue()->setString(szStr);
	}
	else if(KviQString::equalCI(szType, "Integer"))
	{
		int iValue;
		m_pFile->load(iValue);
		c->returnValue()->setInteger(iValue);
	}
	else if(KviQString::equalCI(szType, "Array"))
	{
		QString szData;
		m_pFile->load(szData);
		KviKvsVariant *pVar = KviKvsVariant::unserialize(szData);
		if (pVar->isArray())
			c->returnValue()->setArray(pVar->array());
		else
			c->warning(__tr2qs_ctx("The incoming data isn't an array","objects"));
	}
	else if(KviQString::equalCI(szType, "Dict"))
	{
		QString szData;
		m_pFile->load(szData);
		KviKvsVariant *pVar = KviKvsVariant::unserialize(szData);
		if (pVar->isHash())
			c->returnValue()->setHash(pVar->hash());
		else
			c->warning(__tr2qs_ctx("The incoming data isn't a Dictionary","objects"));
	}
	else if(KviQString::equalCI(szType, "String"))
	{
		QString szStr;
		m_pFile->load(szStr);
		c->returnValue()->setString(szStr);
	}
	else
	{
		c->warning(__tr2qs_ctx("Unsupported datatype '%Q'","objects"),&szType);
	}
	return true;
}

KVSO_CLASS_FUNCTION(file,write)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	CHECK_FILE_IS_OPEN
	KviKvsVariant * pVariantData;
	KVSO_PARAMETERS_BEGIN(c)
			KVSO_PARAMETER("variant_data",KVS_PT_VARIANT,0,pVariantData)
	KVSO_PARAMETERS_END(c)
	if (pVariantData->isInteger())
	{
		kvs_int_t num;
		pVariantData->asInteger(num);
		m_pFile->save(num);
	}
	if (pVariantData->isHash() || pVariantData->isArray() || pVariantData->isString())
	{
		QString szTmp;
		pVariantData->serialize(szTmp);
		m_pFile->save(szTmp);
	}
	return true;
}

KVSO_CLASS_FUNCTION(file,writeBlock)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	kvs_uint_t uLen;
	KviKvsObject * pObject;
	KviKvsVariant * pVariantData;
	kvs_hobject_t hObject;
	KVSO_PARAMETERS_BEGIN(c)
			KVSO_PARAMETER("string_or_hobject",KVS_PT_VARIANT,0,pVariantData)
			KVSO_PARAMETER("length",KVS_PT_UNSIGNEDINTEGER,KVS_PF_OPTIONAL,uLen)
	KVSO_PARAMETERS_END(c)
	if(!m_pFile->isOpen())c->warning(__tr2qs_ctx("File is not open!","objects"));
	else
	{
		if (pVariantData->isHObject())
		{
			pVariantData->asHObject(hObject);
			pObject=KviKvsKernel::instance()->objectController()->lookupObject(hObject);
			if (!pObject)
			{
				c->warning(__tr2qs_ctx("Buffer parameter is not an object","objects"));
				return true;
			}
			if (pObject->inheritsClass("memorybuffer"))
			{
				if (!uLen)
				{
					if (((KvsObject_memoryBuffer *)pObject)->pBuffer()->size())
						uLen=((KvsObject_memoryBuffer *)pObject)->pBuffer()->size();
					else
					{
						c->warning(__tr2qs_ctx("The memoryBuffer object is empty: nothing will be saved","objects"));
					}
				}
				const char *pData=((KvsObject_memoryBuffer *)pObject)->pBuffer()->data();
				int rlen=m_pFile->write(pData,uLen);
				c->returnValue()->setInteger(rlen);
				//m_pFile->flush();
			} else {
				c->warning(__tr2qs_ctx("Buffer parameter is not a memorybuffer object","objects"));
				return true;
			}

		}
		else
		{
			if (!pVariantData->isString()){
				c->warning(__tr2qs_ctx("Block parameter must be a string or a memorybuffer object","objects"));
				return true;
			}
			QString szBlock;
			pVariantData->asString(szBlock);
			const char *block=szBlock.toUtf8().data();
			int rlen = m_pFile->write(block, uLen);
			c->returnValue()->setInteger(rlen);
			//m_pFile->flush();
		}
	}
	return true;
}

KVSO_CLASS_FUNCTION(file,readLine)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	CHECK_FILE_IS_OPEN
	QString buffer;
	//bool ok=KviFileUtils::readLine(m_pFile,buffer);
	buffer=m_pFile->readLine();
	c->returnValue()->setString(buffer);
	return true;
}

KVSO_CLASS_FUNCTION(file,writeLine)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	CHECK_FILE_IS_OPEN
	QString szLine;
	KVSO_PARAMETERS_BEGIN(c)
		KVSO_PARAMETER("text_line",KVS_PT_STRING,0,szLine)
	KVSO_PARAMETERS_END(c)
	QTextStream ts(m_pFile);
	ts << szLine;
	return true;
}

KVSO_CLASS_FUNCTION(file,readHexBlock)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	CHECK_FILE_IS_OPEN
	kvs_uint_t uLen;
	KVSO_PARAMETERS_BEGIN(c)
		KVSO_PARAMETER("lenght",KVS_PT_UNSIGNEDINTEGER,0,uLen)
	KVSO_PARAMETERS_END(c)
	if (uLen>(kvs_uint_t)m_pFile->size()) uLen=m_pFile->size();
	char * buff = new char[uLen];
	char * str = new char [(uLen*2)+1];
	m_pFile->flush(); // advice from QFile man page (to avoid trash)
	int rlen = m_pFile->read(buff, uLen);
	int index=0;
	unsigned char byte,msb,lsb=0;
	for (int i=0;i<rlen;i++)
	{
		byte=(unsigned char)buff[i];
		lsb=byte & 0x0f;
		msb=byte>>4;
		msb>9?msb+='7':msb+='0';
		lsb>9?lsb+='7':lsb+='0';
		str[index++]=msb;
		str[index++]=lsb;
	}
	str[index]='\0';
	c->returnValue()->setString(str);
	delete[] str;
	delete[] buff;
	return true;
}

KVSO_CLASS_FUNCTION(file,writeHexBlock)
{
	CHECK_INTERNAL_POINTER(m_pFile)
	CHECK_FILE_IS_OPEN
	QString szBlock;
	kvs_uint_t uLen;
	KVSO_PARAMETERS_BEGIN(c)
		KVSO_PARAMETER("text_block",KVS_PT_STRING,0,szBlock)
		KVSO_PARAMETER("lenght",KVS_PT_UNSIGNEDINTEGER,KVS_PF_OPTIONAL,uLen)
	KVSO_PARAMETERS_END(c)
	if (szBlock.length()%2)
	{
		c->warning(__tr2qs_ctx("Length of hex string is not multiple of 2","objects"));
		return true;
	}
	if (uLen>((uint)szBlock.length()/2)|| !uLen) uLen=szBlock.length();
	else uLen*=2;
	unsigned char byte,lsb,msb;
	for(unsigned int i=0;i<uLen;i+=2)
	{
		msb=szBlock.at(i).toAscii();
		lsb=szBlock.at(i+1).toAscii();

		if (((msb>='A' && msb<='F')||(msb>='0' && msb<='9')) && ((lsb>='A' && lsb<='F')|| (lsb>='0' && lsb<='9')))
		{
			msb>='A'?msb-='7':msb-='0';
			lsb>='A'?lsb-='7':lsb-='0';
		} else{
			c->warning(__tr2qs_ctx("The hex string is not correct!","objects"));
			return true;
		}
		byte=(msb*16)+lsb;
		m_pFile->putChar(byte);
	}
	c->returnValue()->setInteger(uLen/2);
	return true;
}
