/***************************************************************************
                          dcfiletool.cpp  -  description
                             -------------------
    begin                : Die Mär 26 2002
    copyright            : (C) 2002 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "dcfiletool.h"

#include <qfile.h>
#include <qfileinfo.h>
#include <qmessagebox.h>
#include <qinputdialog.h>
#include <QMenu>
#include <QList>
#include <qcursor.h>
#include <qdialog.h>
#include <qcheckbox.h>
#include <qlabel.h>
#include <qapplication.h>
#include <qstyle.h>

#include <dclib/core/cdir.h>

#include "dcconfig.h"
#include "dctransferview.h"
#include "dcconnectionmanager.h"
#include "dcguiutils.h"

#include "DCDialogAskDownloadMode.h"

#include <dclib/dcos.h>

/** */
DCFileTool::DCFileTool()
{
}

/** */
DCFileTool::~DCFileTool()
{
}

/** */
bool DCFileTool::SelectFileSource( QWidget * parent, ulonglong size, QString tth, QString & file, QString & localrootpath, QString & localpath )
{
	bool res = false;
	QStringList LocalFilesList;

	if ( tth.isEmpty() )
	{
		g_pTransferView->GetLocalFilesList( LocalFilesList, size );
	}
	else
	{
		g_pTransferView->GetLocalFilesList( LocalFilesList, tth, size );
	}

	if ( LocalFilesList.empty() )
	{
		return res;
	}

	// Add as an extra source
	QMenu * m = new QMenu(parent);
	QList<QAction*> actlist;

	QAction * title = m->addAction( QObject::tr("(choose an existing download to add to)") );
	title->setEnabled(false);

	for ( int iter=0; iter<(int)LocalFilesList.size(); iter++ )
	{
		if ( LocalFilesList[iter].contains("\\") )
		{
			actlist.append(m->addAction( LocalFilesList[iter].section("\\",-1,-1) ));
		}
		else
		{
			actlist.append(m->addAction( LocalFilesList[iter].section("/",-1,-1) ));
		}
	}

	QAction * chosen = m->exec(QCursor::pos());

	delete m;

	if ( chosen != 0 )
	{
		QFileInfo fi(LocalFilesList[actlist.indexOf(chosen)]);

		file          = fi.fileName();
		localpath     = "";
		localrootpath = fi.path();

		// now we must check if a localpath exist
		if ( localrootpath.indexOf( g_pConfig->GetDownloadFolder().Data(), 0 ) == 0 )
		{
			localpath     = localrootpath.mid( g_pConfig->GetDownloadFolder().Length(), localrootpath.length()-g_pConfig->GetDownloadFolder().Length() );
			localrootpath = g_pConfig->GetDownloadFolder().Data();
		}

		res = true;
	}

	return res;
}

/** */
bool DCFileTool::AddFileSource( QWidget * parent, CString nick, CString hubname, CString hubhost,
				CString remotename, CString localname,
				CString localpath, CString localrootpath,
				eltMedium medium, ulonglong size, CString tth )
{
	bool res = false;
	QString sourcefile,slocalrootpath,slocalpath;

	if ( SelectFileSource( parent, size, tth.Data(), sourcefile, slocalrootpath, slocalpath ) == false )
	{
		return CheckFile( parent, nick, hubname, hubhost,
				remotename, localname,
				localpath, localrootpath,
				medium, size, tth );
	}

	if ( !sourcefile.isEmpty() )
	{
		// add transfer to the waitlist
		res = CheckFile( parent, nick, hubname, hubhost,
				remotename, sourcefile.toAscii().constData(),
				slocalpath.toAscii().constData(), slocalrootpath.toAscii().constData(),
				medium, size, tth, true );

	}

	return res;
}

/** */
bool DCFileTool::CheckFile( QWidget * parent, CString nick, CString hubname, CString hubhost,
				CString remotename, CString localname,
				CString localpath, CString localrootpath,
				eltMedium medium, ulonglong size, CString hash,
				bool usermulti, eRepairType repair )
{
	CDir dir;
	QString name;
	ulonglong fsize = 0, pos_s = 0, pos_e = 0;
	int filestate;
	bool multi = usermulti;

	// first we get the filestate from the downloadmanager
	filestate = g_pTransferView->DLM_QueueCheck( nick, hubname, hubhost,
			remotename, localname,
			localpath, localrootpath,
			medium, size, hash );

	if ( filestate == 1 )
	{
//		QMessageBox::information( parent, QObject::tr("File download"),
//			QObject::tr("The same user/file is already in the queue !\n"));
		return false;
	}
	else if ( filestate == 2 )
	{
		/*
		 * This was from where uninitialized pos_s and pos_e were
		 * passed to CDownloadManager, they are not used when adding
		 * a source to an existing download.
		 */
		if ( multi == false )
		{
			switch( QMessageBox::warning( parent, QObject::tr("File download"),
				QObject::tr("A same file is already in the queue!"),
		        	QObject::tr("Start a multi-download"),
				QObject::tr("Cancel"), 0, 1 ) )
			{
				case 0:
					multi = true;
		        		break;
				default:
					return false;
					break;
			}
		}
	}
	else if ( filestate == 3 )
	{
		QMessageBox::information( parent, QObject::tr("File download"),
			QObject::tr("File is already in the queue but not mark as a multi-download!\n"));
		return false;
	}
	else if ( filestate == 4 )
	{
		QMessageBox::information( parent, QObject::tr("File download"),
			QObject::tr("File is already in the queue with a different size!\n"));
		return false;
	}
	else if ( filestate == 0 )
	{
		if ( localrootpath.NotEmpty() )
		{
			name = localrootpath.Data();
		}
		else
		{
			name = g_pConfig->GetDownloadFolder().Data();
		}

		if ( name.isEmpty() )
		{
			return false;
		}

		localpath = localpath.Replace(':',"");
		localname = localname.Replace(':',"");

		name += QString("/");
		name += QString(localpath.Data());
		name += QString("/");
		name += QString(localname.Data());
		name = CDir::SimplePath(name.toAscii().constData()).Data();

		fsize = dir.getFileSize(name.toAscii().constData(),false);

		// check already download
		if ( (size == fsize) && (fsize > 0) && !repair )
		{
			QMessageBox::information( parent, QObject::tr("File download"),
				QObject::tr("File already downloaded!\n"));
			return false;
		}

		// check for partial download
		if ( fsize > 0 )
		{
			if ( repair )
			{
				// get desired byte range
				QString range;
				bool ok = false;

				// There is probably a better way to get this info.  We assume 2352 sector size (ss)
				// which is most common for BIN/CUE, but we might want to make two menu entries, one
				// for 2352 and one for 2048 (ie. ISO).  The sector offset (so) is the number of bytes
				// into the BIN file where sector zero is reported.  Fireburner for Linux reports
				// from the beginning of the file, but other programs (like cdmage) report from the
				// first data sector (skipping all header and directory information).  We assume zero.
				int ss = 2352;
				int so = 0;

				if ( repair == ertBYTES )
				{
					range = QInputDialog::getText(
						parent,
						QObject::tr("Repair File"),
						QObject::tr("Enter byte range (m-n)"),
						QLineEdit::Normal,
						QString::null,
						&ok
					);
				}
				else
				{
					range = QInputDialog::getText(
						parent,
						QObject::tr("Repair BIN Sectors"),
						QObject::tr("Enter sector range (m[-n])"),
						QLineEdit::Normal,
						QString::null,
						&ok
					);
				}

				if ( !ok )
				{
					return false;
				}

				int p = range.indexOf( '-' );

				if ( p > 0 )
				{
					pos_s = range.left( p ).toULongLong( &ok );
					
					if ( !ok )
					{
						QMessageBox::critical(
							parent,
							QObject::tr("Repair File"),
							QObject::tr("Invalid start position.")
						);
						return false;
					}
					
					pos_e = range.mid( p + 1 ).toULongLong( &ok );
					
					if ( !ok )
					{
						QMessageBox::critical(
							parent,
							QObject::tr("Repair File"),
							QObject::tr("Invalid end position.")
						);
						return false;
					}
				}
				else if ( repair == ertSECTORS )
				{
					pos_s = range.toULongLong( &ok );
					
					if ( !ok )
					{
						QMessageBox::critical(
							parent,
							QObject::tr("Repair File"),
							QObject::tr("Invalid number of sectors.")
						);
						return false;
					}
					
					pos_e = 0;
				}
				else
				{
					QMessageBox::critical(
						parent,
						QObject::tr("Repair File"),
						QObject::tr("Invalid range entered.")
					);
					return false;
				}

				if ( repair == ertSECTORS )
				{
					if ( pos_e == 0 )
					{
						pos_e = pos_s;
					}
					
					pos_s = so + pos_s * ss;
					pos_e = so + (pos_e+1) * ss - 1;
				}

				if ( pos_e > fsize )
				{
					pos_e = fsize;
				}
				
				if ( pos_s > pos_e )
				{
					pos_s = pos_e;
				}

				multi = true;
				printf( "Starting download repair, %s, %llu-%llu\n", name.toAscii().constData(), pos_s, pos_e );
			}
			else
			{
				pos_e = 0;
				switch( QMessageBox::warning( parent, QObject::tr("File download"),
					QObject::tr("File already exists!") + "\n"
					+ name + "\n"
					+ DCGuiUtils::GetSizeString( fsize ),
					QObject::tr("Resume"),
					QObject::tr("Overwrite"),
					QObject::tr("Cancel"), 0, 2) )
				{
					case 0:
						pos_s = fsize;
						break;
					case 1:
						QFile::remove(name);
						fsize = 0;
						pos_s = 0;
						break;
					case 2:
						return false;
						break;
					default:
						return false;
						break;
				}
			}
		}
		else // fsize == 0, file does not already exist
		{
			pos_s = 0;
			pos_e = 0;
		}

		/* Check move finished downloads to directory */
		CString finished = g_pConfig->GetDownloadFinishedFolder();
		if ( finished.NotEmpty() && localrootpath.IsEmpty() )
		{
			CString destfile = g_pConfig->GetDownloadFolder();
			destfile += DIRSEPARATOR;
			destfile += localpath;
			destfile += DIRSEPARATOR;
			destfile += localname;
			
			destfile = CDir::SimplePath(destfile);
			destfile = finished + destfile.Mid(g_pConfig->GetDownloadFolder().Length());
			if ( destfile.Left(1) != DIRSEPARATOR )
			{
				destfile = DIRSEPARATOR + destfile;
			}
			else if ( destfile.Left(2) == CString(DIRSEPARATOR+DIRSEPARATOR) )
			{
				destfile = destfile.Mid(1);
			}
			
			//printf("Checking for '%s'\n", destfile.Data());
			
			if ( dir.IsFile( destfile, false ) )
			{
				int ret = QMessageBox::warning(
						parent,
						QObject::tr("File download"),
						QObject::tr("File already exists in your finished downloads folder") + "\n"
						+ QString(destfile.Data()) + "\n"
						+ DCGuiUtils::GetSizeString( dir.getFileSize(destfile,false) ) + "\n"
						+ QObject::tr("It will be overwritten when the download finishes"),
						QObject::tr("Overwrite"),
						QObject::tr("Cancel"),
						QString::null,
						1, 1 // set default button and escape button to Cancel
						);
				if ( ret == 1 )
				{
					return false;
				}
			}
		}

		/*
		 * FIXME dclib is perfectly capable of using multi download mode
		 * on files smaller than 1 segment, because the old hack
		 * to disable segmented downloading was to set the segment size to 2GiB or so.
		 */
		if ( size > (1024*1024) )
		{
			if ( (g_pConfig->GetDefaultDownloadMode() == 0) && (multi == false) )
			{
				Ui::DCDialogAskDownloadMode admui;
				QDialog * askdialog = new QDialog(parent);
				admui.setupUi(askdialog);
				admui.Label_QUESTION_PIXMAP->setPixmap( QApplication::style()->standardPixmap(QStyle::SP_MessageBoxQuestion) );
				
				if ( askdialog->exec() == QDialog::Accepted )
				{
					multi = true;
				}
				else
				{
					multi = false;
				}
				
				if ( admui.CheckBox_SET_QUICK_OPTION->isChecked() )
				{
					if ( multi )
					{
						g_pConfig->SetDefaultDownloadMode(2,true);
					}
					else
					{
						g_pConfig->SetDefaultDownloadMode(1,true);
					}
				}
				
				delete askdialog;
			}
			else if ( (g_pConfig->GetDefaultDownloadMode() == 1) && (multi == false) )
			{
				multi = false;
			}
			else
			{
				multi = true;
			}
		}
		else
		{
			multi = false;
		}
	}
	else
	{
		QMessageBox::critical(
			parent,
			"DCFileTool::CheckFile",
			QObject::tr("Download manager returned unknown value: ") + QString().setNum(filestate)
		);
		return false;
	}

	if ( g_pConnectionManager->IsHubOnline(hubname,hubhost) == ehsNONE )
	{
		/* Check if the user is connected to another hub that we are connected to.
		 * I'm uncertain how it will react when multi==false (thus that is not handled),
		 * but as this concerns small files it is a bit annoying.
		 */
		bool foundUser = false;

		if ( multi )
		{
			CList<DCHubObject> HubList;
			CString emptyString;
			DCHubObject * CurrentHub = 0;
			
			/* Get hublist for the user, ie hubs that both us and them are
			 * connected to.
			 */
			if( g_pConnectionManager->IsUserOnline(nick,emptyString,emptyString,&HubList) )
			{
				/* Add an item in the queue for each hub, mark the user as found */
				while((CurrentHub =  HubList.Next(CurrentHub) ) != 0 )
				{
					foundUser = true;
					g_pTransferView->DLM_QueueAdd( nick, CurrentHub->m_sHubName, CurrentHub->m_sHubHost,
								remotename, localname,
								localpath, localrootpath,
								medium, size, pos_s, pos_e, hash, multi );
				}
			}
		}
		/* Usual behavior:
		 * (no user found or option not enabled)
		 */
		if ( foundUser == false )
		{
			switch( QMessageBox::warning( parent, QObject::tr("File download"),
						QObject::tr("Not connected to required hub!"),
						QObject::tr("Connect"),
						QObject::tr("Cancel"), 0, 0, 1 ) )
			{
				case 0:
					g_pConnectionManager->Connect(hubname,hubhost);
					break;
				case 1:
					break;
			}
		}
	}

	g_pTransferView->DLM_QueueAdd( nick, hubname, hubhost,
				remotename, localname,
				localpath, localrootpath,
				medium, size, pos_s, pos_e, hash, multi );

	return true;
}
