/***************************************************************************
 *   Copyright (C) 2003 by S�astien Laot                                 *
 *   slaout@linux62.org                                                    *
 *                                                                         *
 *   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 <tqdragobject.h>
#include <tqdir.h>
#include <tqpainter.h>
#include <tqtextcodec.h>
#include <tqbuffer.h>
#include <kurldrag.h>
#include <tdeapplication.h>
#include <tqdesktopwidget.h>

#include "basket.h"
#include "notedrag.h"
#include "notefactory.h"
#include "tools.h"
#include "global.h"

#include <iostream>

/** NoteDrag */

const char * NoteDrag::NOTE_MIME_STRING = "application/x-basket-note";

void NoteDrag::createAndEmptyCuttingTmpFolder()
{
	Tools::deleteRecursively(Global::tempCutFolder());
	TQDir dir;
	dir.mkdir(Global::tempCutFolder());
}

TQDragObject* NoteDrag::dragObject(NoteSelection *noteList, bool cutting, TQWidget *source)
{
	if (noteList->count() <= 0)
		return 0;

	// The MimeSource:
	KMultipleDrag *multipleDrag = new KMultipleDrag(source);

	// Make sure the temporary folder exists and is empty (we delete previously moved file(s) (if exists)
	// since we override the content of the clipboard and previous file willn't be accessable anymore):
	createAndEmptyCuttingTmpFolder();

	// The "Native Format" Serialization:
	TQBuffer buffer;
	if (buffer.open(IO_WriteOnly)) {
		TQDataStream stream(&buffer);
		// First append a pointer to the basket:
		stream << (TQ_UINT64)(noteList->firstStacked()->note->basket());
		// Then a list of pointers to all notes, and parent groups:
		for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked())
			stream << (TQ_UINT64)(node->note);
		TQValueList<Note*> groups = noteList->parentGroups();
		for (TQValueList<Note*>::iterator it = groups.begin(); it != groups.end(); ++it)
			stream << (TQ_UINT64)(*it);
		stream << (TQ_UINT64)0;
		// And finally the notes themselves:
		serializeNotes(noteList, stream, cutting);
		// Append the object:
		buffer.close();
		TQStoredDrag *dragObject = new TQStoredDrag(NOTE_MIME_STRING, source);
		dragObject->setEncodedData(buffer.buffer());
		multipleDrag->addDragObject(dragObject);
	}

	// The "Other Flavours" Serialization:
	serializeText(  noteList, multipleDrag          );
	serializeHtml(  noteList, multipleDrag          );
	serializeImage( noteList, multipleDrag          );
	serializeLinks( noteList, multipleDrag, cutting );

	// The Alternate Flavours:
	if (noteList->count() == 1)
		noteList->firstStacked()->note->content()->addAlternateDragObjects(multipleDrag);

	// If it is a drag, and not a copy/cut, add the feedback pixmap:
	if (source)
		setFeedbackPixmap(noteList, multipleDrag);

	return multipleDrag;
}

void NoteDrag::serializeNotes(NoteSelection *noteList, TQDataStream &stream, bool cutting)
{
	for (NoteSelection *node = noteList; node; node = node->next) {
		stream << (TQ_UINT64)(node->note);
		if (node->firstChild) {
			stream << (TQ_UINT64)(NoteType::Group) << (TQ_UINT64)(node->note->groupWidth()) << (TQ_UINT64)(node->note->isFolded());
			serializeNotes(node->firstChild, stream, cutting);
		} else {
			NoteContent *content = node->note->content();
			stream << (TQ_UINT64)(content->type()) << (TQ_UINT64)(node->note->groupWidth());
			// Serialize file name, and move the file to a temporary place if the note is to be cuttted.
			// If note does not have file name, we append empty string to be able to easily decode the notes later:
			stream << content->fileName();
			if (content->shouldSerializeFile()) {
				if (cutting) {
					// Move file in a temporary place:
					TQString fullPath = Global::tempCutFolder() + Tools::fileNameForNewFile(content->fileName(), Global::tempCutFolder());
					TDEIO::move(KURL(content->fullPath()), KURL(fullPath), /*showProgressInfo=*/false);
					node->fullPath = fullPath;
					stream << fullPath;
				} else
					stream << content->fullPath();
			} else
				stream << TQString("");
			stream << content->note()->addedDate() << content->note()->lastModificationDate();
			content->serialize(stream);
			State::List states = node->note->states();
			for (State::List::Iterator it = states.begin(); it != states.end(); ++it)
				stream << (TQ_UINT64)(*it);
			stream << (TQ_UINT64)0;
		}
	}
	stream << (TQ_UINT64)0; // Mark the end of the notes in this group/hierarchy.
}

void NoteDrag::serializeText(NoteSelection *noteList, KMultipleDrag *multipleDrag)
{
	TQString textEquivalent;
	TQString text;
	for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked()) {
		text = node->note->toText(node->fullPath); // note->toText() and not note->content()->toText() because the first one will also export the tags as text.
		if (!text.isEmpty())
			textEquivalent += (!textEquivalent.isEmpty() ? "\n" : "") + text;
	}
	if (!textEquivalent.isEmpty())
		multipleDrag->addDragObject( new TQTextDrag(textEquivalent) );
}

void NoteDrag::serializeHtml(NoteSelection *noteList, KMultipleDrag *multipleDrag)
{
	TQString htmlEquivalent;
	TQString html;
	for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked()) {
		html = node->note->content()->toHtml("", node->fullPath);
		if (!html.isEmpty())
			htmlEquivalent += (!htmlEquivalent.isEmpty() ? "<br>\n" : "") + html;
	}
	if (!htmlEquivalent.isEmpty()) {
		// Add HTML flavour:
		TQTextDrag *htmlDrag = new TQTextDrag(htmlEquivalent);
		htmlDrag->setSubtype("html");
		multipleDrag->addDragObject(htmlDrag);
		// But also TQTextEdit flavour, to be able to paste several notes to a text edit:
		TQByteArray byteArray = ("<!--StartFragment--><p>" + htmlEquivalent).local8Bit();
		TQStoredDrag *richTextDrag = new TQStoredDrag("application/x-qrichtext");
		richTextDrag->setEncodedData(byteArray);
		multipleDrag->addDragObject(richTextDrag);
	}
}

void NoteDrag::serializeImage(NoteSelection *noteList, KMultipleDrag *multipleDrag)
{
	TQValueList<TQPixmap> pixmaps;
	TQPixmap pixmap;
	for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked()) {
		pixmap = node->note->content()->toPixmap();
		if (!pixmap.isNull())
			pixmaps.append(pixmap);
	}
	if (!pixmaps.isEmpty()) {
		TQPixmap pixmapEquivalent;
		if (pixmaps.count() == 1)
			pixmapEquivalent = pixmaps[0];
		else {
			// Search the total size:
			int height = 0;
			int width  = 0;
			for (TQValueList<TQPixmap>::iterator it = pixmaps.begin(); it != pixmaps.end(); ++it) {
				height += (*it).height();
				if ((*it).width() > width)
					width = (*it).width();
			}
			// Create the image by painting all image into one big image:
			pixmapEquivalent.resize(width, height);
			pixmapEquivalent.fill(TQt::white);
			TQPainter painter(&pixmapEquivalent);
			height = 0;
			for (TQValueList<TQPixmap>::iterator it = pixmaps.begin(); it != pixmaps.end(); ++it) {
				painter.drawPixmap(0, height, *it);
				height += (*it).height();
			}
		}
		TQImageDrag *imageDrag = new TQImageDrag(pixmapEquivalent.convertToImage());
		multipleDrag->addDragObject(imageDrag);
	}
}

void NoteDrag::serializeLinks(NoteSelection *noteList, KMultipleDrag *multipleDrag, bool cutting)
{
	KURL::List  urls;
	TQStringList titles;
	KURL    url;
	TQString title;
	for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked()) {
		node->note->content()->toLink(&url, &title, node->fullPath);
		if (!url.isEmpty()) {
			urls.append(url);
			titles.append(title);
		}
	}
	if (!urls.isEmpty()) {
		// First, the standard text/uri-list MIME format:
		KURLDrag *urlsDrag = new KURLDrag(urls);
		// ONLY export as text/uri-list, and not as text/plain* as we wil do that better ourself
		urlsDrag->setExportAsText(false);
		multipleDrag->addDragObject(urlsDrag);
		// Then, also provide it in the Mozilla proprietary format (that also allow to add titles to URLs):
		// A version for Mozilla applications (convert to "theUrl\ntheTitle", into UTF-16):
		// FIXME: Does Mozilla support the drag of several URLs at once?
		// FIXME: If no, only provide that if theire is only ONE URL.
		TQString xMozUrl;
		for (uint i = 0; i < urls.count(); ++i)
			xMozUrl += (xMozUrl.isEmpty() ? "" : "\n") + urls[i].prettyURL() + "\n" + titles[i];
/*		Code for only one: ===============
		xMozUrl = note->title() + "\n" + note->url().prettyURL();*/
		TQByteArray baMozUrl;
		TQTextStream stream(baMozUrl, IO_WriteOnly);
		stream.setEncoding(TQTextStream::RawUnicode); // It's UTF16 (aka UCS2), but with the first two order bytes
		stream << xMozUrl;
		TQStoredDrag *xMozUrlDrag = new TQStoredDrag("text/x-moz-url");
		xMozUrlDrag->setEncodedData(baMozUrl);
		multipleDrag->addDragObject(xMozUrlDrag);

		if (cutting) {
			TQByteArray  arrayCut(2);
			TQStoredDrag *storedDragCut = new TQStoredDrag("application/x-tde-cutselection");
			arrayCut[0] = '1';
			arrayCut[1] = 0;
			storedDragCut->setEncodedData(arrayCut);
			multipleDrag->addDragObject(storedDragCut);
		}
	}
}

void NoteDrag::setFeedbackPixmap(NoteSelection *noteList, KMultipleDrag *multipleDrag)
{
	TQPixmap pixmap = feedbackPixmap(noteList);
	if (!pixmap.isNull())
		multipleDrag->setPixmap(pixmap, TQPoint(-8, -8));
}

TQPixmap NoteDrag::feedbackPixmap(NoteSelection *noteList)
{
	if (noteList == 0)
		return TQPixmap();

	static const int MARGIN  = 2;
	static const int SPACING = 1;

	TQColor textColor       = noteList->firstStacked()->note->basket()->textColor();
	TQColor backgroundColor = noteList->firstStacked()->note->basket()->backgroundColor().dark(NoteContent::FEEDBACK_DARKING);

	TQValueList<TQPixmap> pixmaps;
	TQValueList<TQColor>  backgrounds;
	TQValueList<bool>    spaces;
	TQPixmap pixmap;
	int height = 0;
	int width  = 0;
	int i      = 0;
	bool elipsisImage = false;
	TQColor bgColor;
	bool needSpace;
	for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked(), ++i) {
		if (elipsisImage) {
			pixmap = TQPixmap(7, 2);
			pixmap.fill(backgroundColor);
			TQPainter painter(&pixmap);
			painter.setPen(textColor);
			painter.drawPoint(1, 1);
			painter.drawPoint(3, 1);
			painter.drawPoint(5, 1);
			painter.end();
			bgColor   = node->note->basket()->backgroundColor();
			needSpace = false;
		} else {
			pixmap    = node->note->content()->feedbackPixmap(/*maxWidth=*/tdeApp->desktop()->width() / 2, /*maxHeight=*/96);
			bgColor   = node->note->backgroundColor();
			needSpace = node->note->content()->needSpaceForFeedbackPixmap();
		}
		if (!pixmap.isNull()) {
			if (pixmap.width() > width)
				width = pixmap.width();
			pixmaps.append(pixmap);
			backgrounds.append(bgColor);
			spaces.append(needSpace);
			height += (i > 0 && needSpace ? 1 : 0) + pixmap.height() + SPACING + (needSpace ? 1 : 0);
			if (elipsisImage)
				break;
			if (height > tdeApp->desktop()->height() / 2)
				elipsisImage = true;
		}
	}
	if (!pixmaps.isEmpty()) {
		TQPixmap result(MARGIN + width + MARGIN, MARGIN + height - SPACING + MARGIN - (spaces.last() ? 1 : 0));
		TQPainter painter(&result);
		// Draw all the images:
		height = MARGIN;
		TQValueList<TQPixmap>::iterator it;
		TQValueList<TQColor>::iterator  it2;
		TQValueList<bool>::iterator    it3;
		int i = 0;
		for (it = pixmaps.begin(), it2 = backgrounds.begin(), it3 = spaces.begin(); it != pixmaps.end(); ++it, ++it2, ++it3, ++i) {
			if (i != 0 && (*it3)) {
				painter.fillRect(MARGIN, height, result.width() - 2 * MARGIN, SPACING, (*it2).dark(NoteContent::FEEDBACK_DARKING));
				++height;
			}
			painter.drawPixmap(MARGIN, height, *it);
			if ((*it).width() < width)
				painter.fillRect(MARGIN + (*it).width(), height, width - (*it).width(), (*it).height(), (*it2).dark(NoteContent::FEEDBACK_DARKING));
			if (*it3) {
				painter.fillRect(MARGIN, height + (*it).height(), result.width() - 2 * MARGIN, SPACING, (*it2).dark(NoteContent::FEEDBACK_DARKING));
				++height;
			}
			painter.fillRect(MARGIN, height + (*it).height(), result.width() - 2 * MARGIN, SPACING, Tools::mixColor(textColor, backgroundColor));
			height += (*it).height() + SPACING;
		}
		// Draw the border:
		painter.setPen(textColor);
		painter.drawLine(0,                  0,                   result.width() - 1, 0);
		painter.drawLine(0,                  0,                   0,                  result.height() - 1);
		painter.drawLine(0,                  result.height() - 1, result.width() - 1, result.height() - 1);
		painter.drawLine(result.width() - 1, 0,                   result.width() - 1, result.height() - 1);
		// Draw the "lightly rounded" border:
		painter.setPen(Tools::mixColor(textColor, backgroundColor));
		painter.drawPoint(0,                  0);
		painter.drawPoint(0,                  result.height() - 1);
		painter.drawPoint(result.width() - 1, result.height() - 1);
		painter.drawPoint(result.width() - 1, 0);
		// Draw the background in the margin (the inside will be painted above, anyway):
		painter.setPen(backgroundColor);
		painter.drawLine(1,                  1,                   result.width() - 2, 1);
		painter.drawLine(1,                  1,                   1,                  result.height() - 2);
		painter.drawLine(1,                  result.height() - 2, result.width() - 2, result.height() - 2);
		painter.drawLine(result.width() - 2, 1,                   result.width() - 2, result.height() - 2);
		// And assign the feedback pixmap to the drag object:
		//multipleDrag->setPixmap(result, TQPoint(-8, -8));
		return result;
	}
	return TQPixmap();
}

bool NoteDrag::canDecode(TQMimeSource *source)
{
	return source->provides(NOTE_MIME_STRING);
}

Basket* NoteDrag::basketOf(TQMimeSource *source)
{
	TQBuffer buffer(source->encodedData(NOTE_MIME_STRING));
	if (buffer.open(IO_ReadOnly)) {
		TQDataStream stream(&buffer);
		// Get the parent basket:
		TQ_UINT64 basketPointer;
		stream >> (TQ_UINT64&)basketPointer;
		return (Basket*)basketPointer;
	} else
		return 0;
}

TQValueList<Note*> NoteDrag::notesOf(TQMimeSource *source)
{
	TQBuffer buffer(source->encodedData(NOTE_MIME_STRING));
	if (buffer.open(IO_ReadOnly)) {
		TQDataStream stream(&buffer);
		// Get the parent basket:
		TQ_UINT64 basketPointer;
		stream >> (TQ_UINT64&)basketPointer;
		// Get the note list:
		TQ_UINT64          notePointer;
		TQValueList<Note*> notes;
		do {
			stream >> notePointer;
			if (notePointer != 0)
				notes.append((Note*)notePointer);
		} while (notePointer);
		// Done:
		return notes;
	} else
		return TQValueList<Note*>();
}

Note* NoteDrag::decode(TQMimeSource *source, Basket *parent, bool moveFiles, bool moveNotes)
{
	TQBuffer buffer(source->encodedData(NOTE_MIME_STRING));
	if (buffer.open(IO_ReadOnly)) {
		TQDataStream stream(&buffer);
		// Get the parent basket:
		TQ_UINT64 basketPointer;
		stream >> (TQ_UINT64&)basketPointer;
		Basket *basket = (Basket*)basketPointer;
		// Get the note list:
		TQ_UINT64          notePointer;
		TQValueList<Note*> notes;
		do {
			stream >> notePointer;
			if (notePointer != 0)
				notes.append((Note*)notePointer);
		} while (notePointer);
		// Decode the note hierarchy:
		Note *hierarchy = decodeHierarchy(stream, parent, moveFiles, moveNotes, basket);
		// In case we moved notes from one basket to another, save the source basket where notes were removed:
		basket->filterAgainDelayed(); // Delayed, because if a note is moved to the same basket, the note is not at its
		basket->save();               //  new position yet, and the call to ensureNoteVisible would make the interface flicker!!
		return hierarchy;
	} else
		return 0;
}

Note* NoteDrag::decodeHierarchy(TQDataStream &stream, Basket *parent, bool moveFiles, bool moveNotes, Basket *originalBasket)
{
	TQ_UINT64  notePointer;
	TQ_UINT64  type;
	TQString   fileName;
	TQString   fullPath;
	TQDateTime addedDate;
	TQDateTime lastModificationDate;

	Note *firstNote    = 0; // TODO: class NoteTreeChunk
	Note *lastInserted = 0;

	do {
		stream >> notePointer;
		if (notePointer == 0)
			return firstNote;
		Note *oldNote = (Note*)notePointer;

		Note *note = 0;
		TQ_UINT64 groupWidth;
		stream >> type >> groupWidth;
		if (type == NoteType::Group) {
			note = new Note(parent);
			note->setGroupWidth(groupWidth);
			TQ_UINT64 isFolded;
			stream >> isFolded;
			if (isFolded)
				note->toggleFolded(/*animate=*/false);
			if (moveNotes) {
				note->setX(oldNote->x()); // We don't move groups but re-create them (every childs can to not be selected)
				note->setY(oldNote->y()); // We just set the position of the copied group so the animation seems as if the group is the same as (or a copy of) the old.
				note->setHeight(oldNote->height()); // Idem: the only use of Note::setHeight()
			}
			Note* childs = decodeHierarchy(stream, parent, moveFiles, moveNotes, originalBasket);
			if (childs) {
				for (Note *n = childs; n; n = n->next())
					n->setParentNote(note);
				note->setFirstChild(childs);
			}
		} else {
			stream >> fileName >> fullPath >> addedDate >> lastModificationDate;
			if (moveNotes) {
				originalBasket->unplugNote(oldNote);
				note = oldNote;
				if (note->basket() != parent) {
					TQString newFileName = NoteFactory::createFileForNewNote(parent, "", fileName);
					note->content()->setFileName(newFileName);
					TDEIO::FileCopyJob *copyJob = TDEIO::file_move(KURL(fullPath), KURL(parent->fullPath() + newFileName),
					                                           /*perms=*/-1, /*override=*/true, /*resume=*/false, /*showProgressInfo=*/false);
					parent->connect( copyJob, TQ_SIGNAL(result(TDEIO::Job *)),
					                 parent,  TQ_SLOT(slotCopyingDone2(TDEIO::Job *)) );
				}
				note->setGroupWidth(groupWidth);
				note->setParentNote(0);
				note->setPrev(0);
				note->setNext(0);
				note->setParentBasket(parent);
				NoteFactory::consumeContent(stream, (NoteType::Id)type);
			} else if ( (note = NoteFactory::decodeContent(stream, (NoteType::Id)type, parent)) ) {
				note->setGroupWidth(groupWidth);
				note->setAddedDate(addedDate);
				note->setLastModificationDate(lastModificationDate);
			} else if (!fileName.isEmpty()) {
				// Here we are CREATING a new EMPTY file, so the name is RESERVED
				// (while dropping several files at once a filename cannot be used by two of them).
				// Later on, file_copy/file_move will copy/move the file to the new location.
				TQString newFileName = NoteFactory::createFileForNewNote(parent, "", fileName);
				note = NoteFactory::loadFile(newFileName, (NoteType::Id)type, parent);
				TDEIO::FileCopyJob *copyJob;
				if (moveFiles)
					copyJob = TDEIO::file_move(KURL(fullPath), KURL(parent->fullPath() + newFileName),
					                         /*perms=*/-1, /*override=*/true, /*resume=*/false, /*showProgressInfo=*/false);
				else
					copyJob = TDEIO::file_copy(KURL(fullPath), KURL(parent->fullPath() + newFileName),
					                         /*perms=*/-1, /*override=*/true, /*resume=*/false, /*showProgressInfo=*/false);
				parent->connect( copyJob, TQ_SIGNAL(result(TDEIO::Job *)),
				                 parent,  TQ_SLOT(slotCopyingDone2(TDEIO::Job *)) );
				note->setGroupWidth(groupWidth);
				note->setAddedDate(addedDate);
				note->setLastModificationDate(lastModificationDate);
			}
		}
		// Retreive the states (tags) and assign them to the note:
		if (note && note->content()) {
			TQ_UINT64 statePointer;
			do {
				stream >> statePointer;
				if (statePointer)
					note->addState((State*)statePointer);
			} while (statePointer);
		}
		// Now that we have created the note, insert it:
		if (note) {
			if (!firstNote)
				firstNote = note;
			else {
				lastInserted->setNext(note);
				note->setPrev(lastInserted);
			}
			lastInserted = note;
		}
	} while (true);

	// We've done: return!
	return firstNote;
}

/** ExtendedTextDrag */

bool ExtendedTextDrag::decode(const TQMimeSource *e, TQString &str)
{
	TQCString subtype("plain");
	return decode(e, str, subtype);
}

bool ExtendedTextDrag::decode(const TQMimeSource *e, TQString &str, TQCString &subtype)
{
	// Get the string:
	bool ok = TQTextDrag::decode(e, str, subtype);

	// Test if it was a UTF-16 string (from eg. Mozilla):
	if (str.length() >= 2) {
		if ((str[0] == 0xFF && str[1] == 0xFE) || (str[0] == 0xFE && str[1] == 0xFF)) {
			TQByteArray utf16 = e->encodedData(TQString("text/" + subtype).local8Bit());
			str = TQTextCodec::codecForName("utf16")->toUnicode(utf16);
			return true;
		}
	}

	// Test if it was empty (sometimes, from GNOME or Mozilla)
	if (str.length() == 0 && subtype == "plain") {
		if (e->provides("UTF8_STRING")) {
			TQByteArray utf8 = e->encodedData("UTF8_STRING");
			str = TQTextCodec::codecForName("utf8")->toUnicode(utf8);
			return true;
		}
		if (e->provides("text/unicode")) { // FIXME: It's UTF-16 without order bytes!!!
			TQByteArray utf16 = e->encodedData("text/unicode");
			str = TQTextCodec::codecForName("utf16")->toUnicode(utf16);
			return true;
		}
		if (e->provides("TEXT")) { // local encoding
			TQByteArray text = e->encodedData("TEXT");
			str = TQString(text);
			return true;
		}
		if (e->provides("COMPOUND_TEXT")) { // local encoding
			TQByteArray text = e->encodedData("COMPOUND_TEXT");
			str = TQString(text);
			return true;
		}
	}
	return ok;
}

#include "notedrag.moc"
