/***************************************************************************
 *   Copyright (C) 2006 by Bram Biesbrouck                                 *
 *   b@beligum.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 St, Fifth Floor, Boston, MA  02110-1301  USA.             *
 ***************************************************************************/

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <set>
#include <algorithm>

#include <qevent.h>
#include <qpoint.h>
#include <qcursor.h>
#include <qobjectlist.h>

#include <kcursor.h>
#include <kdebug.h>
#include <klocale.h>
#include <kinputdialog.h>
#include <kmessagebox.h>
#include <kconfig.h>

#include <libinstrudeo/isdvideocanvas.h>
#include <libinstrudeo/isdxmlfile.h>
#include <libinstrudeo/isdrecording.h>
#include <libinstrudeo/isdcommentbox.h>

#include <glibmm/ustring.h>

#include "srkprefdialog.h"
#include "srkcommentpropertiesdialog.h"
#include "srkvideopanel.h"
#include "srkcommentbox.h"
#include "srkvideocanvas.h"
#include "srkvideocontainer.h"

//-----CONSTRUCTORS-----
SRKVideoContainer::SRKVideoContainer(QWidget* parent, const char* name)
    : QScrollView(parent, name),
      videoCanvas(new SRKVideoCanvas(viewport())),
      creatingComment(false),
      draggingComment(false),
      activeComment(NULL),
      commentOffsetPoint(),
      recording(NULL),
      currentCommentsLanguage("")
{
    addChild(videoCanvas);

    setResizePolicy(QScrollView::AutoOne);

    //connect the popup and dirty slots
    //Note: the parent is the .ui base class, use the grandparent
    connect(this, SIGNAL(firePopupMenu(QString&, QPoint&)), (SRKVideoPanel*)(parent->parent()), SLOT(popupMenu(QString&, QPoint&)));
    connect(this, SIGNAL(fireMarkDirty()), (SRKVideoPanel*)(parent->parent()), SLOT(markDirty()));
}

//-----DESTRUCTOR-----
SRKVideoContainer::~SRKVideoContainer()
{
    //Note: videoCanvas gets deleted here, as it is a child,
    //Note2: recording is deleted in the SRKMainView class
}

//-----PUBLIC METHODS-----
SRKError::SRKErrorCode SRKVideoContainer::loadRecording(ISDRecording* recording)
{
    SRKError::SRKErrorCode retVal = videoCanvas->loadRecording(recording);

    maximizeVideo();
    recenterVideo();

    //save a reference
    this->recording = recording;

    return retVal;
}
SRKError::SRKErrorCode SRKVideoContainer::closeRecording()
{
    setActiveComment(NULL);

    //delete all commentbox-wrappers
    set<ISDGLWidget*> showingComments;
    QObjectList* children = (QObjectList*)videoCanvas->children();
    QObject* child;
    SRKCommentbox* comment;
    //Note: children returns NULL if this object has no children
    if (children) {
	for (child = children->first();child;child=children->next()) {
	    if ((comment = dynamic_cast<SRKCommentbox*>(child)) != NULL) {
   	        delete comment;
	    }
	}
    }

    SRKError::SRKErrorCode retVal = videoCanvas->closeRecording();
    return retVal;
}
SRKError::SRKErrorCode SRKVideoContainer::addComment(ISDCommentbox::commentboxType type, int position)
{
    ISDXmlFile* xmlFile = recording->getXmlFile();

    ISDCommentbox* newIsdComment = ISDCommentbox::createCommentbox(type, recording->getVideoCanvas(), xmlFile->getUniqueCommentId());

    //adjust duration with ScreenKast's default value instead of libinstrudeo's
    KConfig* config = KGlobal::config();
    config->setGroup("Defaults");
    int passLocation();

    newIsdComment->setDuration(config->readEntry("DefaultCommentDuration", QString::number(DEFAULT_DEFAULT_COMMENT_DURATION)).toInt());
    newIsdComment->setSize(config->readEntry("DefaultCommentWidth", QString::number(DEFAULT_DEFAULT_COMMENT_WIDTH)).toInt(),
			   config->readEntry("DefaultCommentHeight", QString::number(DEFAULT_DEFAULT_COMMENT_HEIGHT)).toInt());
    newIsdComment->setDuration(config->readEntry("DefaultCommentDuration").toInt());
    newIsdComment->setDuration(config->readEntry("DefaultCommentDuration").toInt());
    newIsdComment->setDuration(config->readEntry("DefaultCommentDuration").toInt());
    
    float alpha = config->readEntry("DefaultCommentOpacity", QString::number(DEFAULT_DEFAULT_COMMENT_OPACITY)).toInt()/100.0;
    float red = config->readEntry("DefaultCommentFillColorRed", QString::number(DEFAULT_DEFAULT_COMMENT_FILL_COLOR_RED)).toFloat();
    float green = config->readEntry("DefaultCommentFillColorGreen", QString::number(DEFAULT_DEFAULT_COMMENT_FILL_COLOR_GREEN)).toFloat();
    float blue = config->readEntry("DefaultCommentFillColorBlue", QString::number(DEFAULT_DEFAULT_COMMENT_FILL_COLOR_BLUE)).toFloat();
    newIsdComment->setColor(red, green, blue, alpha);

    red = config->readEntry("DefaultCommentTextColorRed", QString::number(DEFAULT_DEFAULT_COMMENT_TEXT_COLOR_RED)).toFloat();
    green = config->readEntry("DefaultCommentTextColorGreen", QString::number(DEFAULT_DEFAULT_COMMENT_TEXT_COLOR_GREEN)).toFloat();
    blue = config->readEntry("DefaultCommentTextColorBlue", QString::number(DEFAULT_DEFAULT_COMMENT_TEXT_COLOR_BLUE)).toFloat();
    newIsdComment->setTextColor(red, green, blue, alpha);

    //show the commentbox immediately, don't wait for an update
    recording->getVideoCanvas()->addChild(newIsdComment);

    if (newIsdComment==NULL) {
	kdDebug() << i18n("Error occurred while creating Instrudeo commentbox.") << endl;
	RETURN_ERROR(SRKError::SRK_INIT_ERROR);
    }

    Glib::ustring lang(currentCommentsLanguage.utf8().data());
    newIsdComment->setLanguage(lang);

    newIsdComment->setStartTime(position);

    //init the commentbox with a default string
    Glib::ustring text(i18n(DEFAULT_COMMENT_TEXT).utf8().data());
    newIsdComment->setText(text);

    SRKCommentbox* newComment = createCommentbox(newIsdComment);
    if (newComment==NULL) {
	kdDebug() << i18n("Error occurred while creating wrapper-commentbox.") << endl;
	RETURN_ERROR(SRKError::SRK_INIT_ERROR);
    }

    int size[2];
    newIsdComment->getSize(size);
    newComment->resize(size[0], size[1]);
    //position the new commentbox at the center of the recording
    newComment->move(videoCanvas->width()/2-newComment->width()/2,
		     videoCanvas->height()/2-newComment->height()/2);

    newComment->show();
    update();

    //move the mouse to the center of the commentbox
    QPoint commentCenter = videoCanvas->mapToGlobal(newComment->pos());
    QPoint centerOffset(newComment->width()/2, newComment->height()/2);
    commentCenter += centerOffset;
    QCursor::setPos(commentCenter);
    commentOffsetPoint = centerOffset;
    
    setActiveComment(newComment);

    setCursor(KCursor::sizeAllCursor());

    //enable tracking
    videoCanvas->setMouseTracking(true);
    newComment->setMouseTracking(true);
    viewport()->setMouseTracking(true);

    creatingComment = true;

    RETURN_SUCCESS;
}
SRKError::SRKErrorCode SRKVideoContainer::deleteActiveComment()
{
    if (activeComment) {
	//remove the comment from the XML file
	ISDXmlFile* xmlFile = recording->getXmlFile();
	ISDCommentbox* activeIsd = activeComment->getISDCommentbox();
	xmlFile->removeComment(activeIsd);
	
	delete activeComment;
	//avoid crashing
	activeComment = NULL;
	setActiveComment(NULL);
    }

    RETURN_SUCCESS;
}
SRKError::SRKErrorCode SRKVideoContainer::editActiveCommentProperties()
{
    if (activeComment){
	SRKCommentPropertiesDialog dlg(activeComment, this->viewport());
	//if OK was pressed
	if (dlg.exec()){
	    activeComment->scaleChanged();
	    videoCanvas->update();
	}
    }

    RETURN_SUCCESS;
}
SRKError::SRKErrorCode SRKVideoContainer::endActiveCommentAtPosition(int position)
{
    if (activeComment){
	ISDCommentbox* comment = activeComment->getISDCommentbox();
	if (comment->getStartTime() < position && 
	    position <= comment->getStartTime()+comment->getDuration())
	    {
		int newDuration = position - comment->getStartTime();
		activeComment->getISDCommentbox()->setDuration(newDuration);
		videoCanvas->update();
	    }
    }

    RETURN_SUCCESS;
}
SRKError::SRKErrorCode SRKVideoContainer::changeZoomFactor(int factor)
{
    SRKError::SRKErrorCode retVal;

    retVal = videoCanvas->changeZoomFactor(factor);

    //if we go back to auto zoom,
    //reset the width of the video to the current size of the container
    //otherwise, the video has no way of knowing the best size
    if (factor == SRKVideoCanvas::AUTO_ZOOM_FACTOR) {
	maximizeVideo();
    }

    recenterVideo();

    return retVal;
}
SRKError::SRKErrorCode SRKVideoContainer::changeCommentsLanguage(QString& lang)
{
    currentCommentsLanguage = lang;
    
    RETURN_SUCCESS;
}
SRKError::SRKErrorCode SRKVideoContainer::updateCanvas()
{
    return videoCanvas->update();
}
SRKError::SRKErrorCode SRKVideoContainer::updateComments()
{
    /*
     * Update the commentbox wrapper widgets:
     * the children of the Instrudeo videocanvas are all
     * commentboxes that are currently visible for the selected
     * language. We need to synchronize them with the currently showing
     * SRKCommentboxes (wraps around an ISDBox).
     */
    
    set<ISDGLWidget*>* currentIsdBoxes = recording->getVideoCanvas()->getChildren();
    
    /*
     * First, we build a list of all showing ISDCommentboxes that have a wrapper
     */
    set<ISDGLWidget*> showingComments;
    QObjectList* children = (QObjectList*)videoCanvas->children();
    QObject* child;
    SRKCommentbox* comment;
    //Note: children returns NULL if this object has no children
    if (children) {
	for (child = children->first();child;child=children->next()) {
	    if ((comment = dynamic_cast<SRKCommentbox*>(child)) != NULL) {
		showingComments.insert(comment->getISDCommentbox());
	    }
	}
    }
    
    /*
     * Now, we compute the intersection of the two sets:
     * - The comments that must be displayed
     * - The comments that are already showing
     *
     * All values in this intersection can be left alone.
     */
    set<ISDGLWidget*> intersection;
    insert_iterator<set<ISDGLWidget*> > interIter(intersection, intersection.begin());
    set_intersection(currentIsdBoxes->begin(), currentIsdBoxes->end(),
		     showingComments.begin(), showingComments.end(),
		     interIter);
    intersection.insert(intersection.begin(), intersection.end());

    /*
     * Iterate over all showing comments, deleting all that don't occur
     * in the above difference-set.
     * Note: the "advance-part" of this loop is embedded in the loop,
     *       because if we include "child=children->next();" in the definition,
     *       and the child gets deleted, we go one child too far, leaving the
     *       possibility that some commentboxes don't get deleted.
     *       
     */
    if (children) {
	for (child = children->first(); child;) {
	    if ((comment = dynamic_cast<SRKCommentbox*>(child)) != NULL) {
		if (intersection.find(comment->getISDCommentbox()) == intersection.end()) {
		    if (comment->isActive()) {
			//we are deleting the active comment
			setActiveComment(NULL);
		    }
		    //this is the reason the "advance-part" is embedded;
		    //we must advance before deleting the child.
		    child=children->next();
		    delete comment;
		}
		else {
		    child=children->next();
		}
	    }
	    else {
		child=children->next();
	    }
	}
    }
    
    /*
     * Iterate over all comments that must be shown, creating all that don't
     * occur in the above intersection.
     */
    set<ISDGLWidget*>::const_iterator iter;
    for (iter=currentIsdBoxes->begin(); iter!=currentIsdBoxes->end(); iter++) {
	if (intersection.find(*iter) == intersection.end()) {
	    ISDCommentbox* isdComment = dynamic_cast<ISDCommentbox*>(*iter);
	    if (isdComment!=NULL) {
		SRKCommentbox* newComment = createCommentbox(isdComment);
		if (newComment) {
		    newComment->show();
		}
	    }
	    else {
		kdDebug() << i18n("Error while casting ISDVideoCanvas child to commentbox.") << endl;
		RETURN_ERROR(SRKError::SRK_ARGS_ERROR);
	    }
	}
    }
    
    videoCanvas->update();

    RETURN_SUCCESS;
}

//-----PUBLIC SLOTS-----
void SRKVideoContainer::popupMenu(QString& name, QPoint& pos)
{
    emit firePopupMenu(name, pos);
}
void SRKVideoContainer::markDirty()
{
    emit fireMarkDirty();
}

//-----PROTECTED OVERLOADED METHODS-----
void SRKVideoContainer::resizeEvent(QResizeEvent*)
{
    if (videoCanvas!=NULL) {
	maximizeVideo();
	recenterVideo();
    }
}
void SRKVideoContainer::contentsMousePressEvent(QMouseEvent* e)
{
    if (!creatingComment){
	QWidget* comment = childAt(e->pos());
	
	//if we clicked on a commentbox
	if (dynamic_cast<SRKCommentbox*>(comment)) {
	    setActiveComment(dynamic_cast<SRKCommentbox*>(comment));

	    //with the left mousebutton
	    if (e->button()==Qt::LeftButton) {
		//we save the relative position of the widget
		commentOffsetPoint = activeComment->mapFromParent(e->pos());
		draggingComment = true;
		setCursor(KCursor::sizeAllCursor());
	    }
	    //with the right mousebutton
	    else if (e->button()==Qt::RightButton){
		QString popupName = COMMENT_MENU_POPUP_NAME;
		popupMenu(popupName, (QPoint&)e->globalPos());
	    }
	}
	//if we didn't click on a commentbox and the button is rightbutton
	else if (e->button()==Qt::RightButton){
	    QString popupName = VIDEO_MENU_POPUP_NAME;
	    popupMenu(popupName, (QPoint&)e->globalPos());
	}
	//if we didn't click on a commentbox, but one is active, deactivate it
	else if (activeComment) {
	    setActiveComment(NULL);
	}
    }
}
void SRKVideoContainer::contentsMouseReleaseEvent(QMouseEvent*)
{
    if (activeComment) {
	if (creatingComment) {
	    videoCanvas->setMouseTracking(false);
	    activeComment->setMouseTracking(false);
	    viewport()->setMouseTracking(false);
	    
	    commentCreationFinished();
	}
	else {
	    commentDraggingFinished();
	}
    }
}
void SRKVideoContainer::contentsMouseMoveEvent(QMouseEvent* e)
{    
    if (activeComment) {
	if (creatingComment) {
	    activeComment->move(videoCanvas->mapFromGlobal(e->globalPos())-commentOffsetPoint);
	    update();
	}
	else if (draggingComment) {
	    activeComment->move(e->globalPos()-mapToGlobal(commentOffsetPoint));
	    update();
	}
    }
}
void SRKVideoContainer::contentsMouseDoubleClickEvent(QMouseEvent* e)
{
    QWidget* comment = childAt(e->pos());
    //check if we doubleclicked on a commentbox
    if (dynamic_cast<SRKCommentbox*>(comment)) {
	setActiveComment(dynamic_cast<SRKCommentbox*>(comment));
	if (e->button() == Qt::LeftButton){
	    //request the user for the comment on doubleclick
	    QString text = KInputDialog::getMultiLineText(i18n("Add comment"), 
							  i18n("Please enter the comment text below:"),
							  QString(activeComment->getISDCommentbox()
								  ->getText().c_str()), 0, this);
	    if (text){
		Glib::ustring newText(i18n(text).utf8().data());
		activeComment->getISDCommentbox()->setText(newText);
	    }
	}
    }
}

//-----PROTECTED FUNCTIONS-----
void SRKVideoContainer::recenterVideo()
{
    //position 0 0 is default
    this->moveChild(videoCanvas, 0, 0);
    
    //if there is spare horizontal room, center horizontally
    if (videoCanvas->width()<width())
	this->moveChild(videoCanvas, (int)(((float)width()-(float)videoCanvas->width())/2.0), 0);
    //same vertically, but preserve horizontal centering
    if (videoCanvas->height()<height())
	this->moveChild(videoCanvas, videoCanvas->x(), (int)(((float)height()-(float)videoCanvas->height())/2.0));
}
void SRKVideoContainer::maximizeVideo()
{
    //we give the video as much space as possible;
    //the 5px border causes the scrollview not to show the scrollbars if no image is set
    videoCanvas->resize(width()-5, height()-5);
}
void SRKVideoContainer::commentDraggingFinished()
{
    draggingComment = false;
    setCursor(KCursor::arrowCursor());
    fireMarkDirty();
}
void SRKVideoContainer::commentCreationFinished()
{
    creatingComment = false;
    setCursor(KCursor::arrowCursor());

    //store the comment in the XML file
    ISDXmlFile* xmlFile = recording->getXmlFile();
    
    if (xmlFile->addComment(activeComment->getISDCommentbox()) != ISDObject::ISD_SUCCESS) {
	/*
	 * This does the same as the first part of the deleteActiveComment() method,
	 * but we can't use that method, because it tried to delete the comment from
	 * the XML file. Try to keep both actions synchronised...
	 */
	//manually remove the ISDBox from the parent's child-list.
	delete activeComment->getISDCommentbox();
	delete activeComment;
	//avoid crashing
	activeComment = NULL;
	setActiveComment(NULL);
	
	KMessageBox::error(this, i18n("Error while saving the new comment."), i18n("Error"));
    }
}
void SRKVideoContainer::setActiveComment(SRKCommentbox* comment)
{
    //if another comment is active, deactivate it
    if (activeComment) {
	activeComment->setActive(false);
	activeComment = NULL;
    }
    if (comment != NULL) {
	activeComment = comment;
	activeComment->setActive(true);
    }
}
SRKCommentbox* SRKVideoContainer::createCommentbox(ISDCommentbox* isdBox)
{
    if (isdBox==NULL) {
	kdDebug() << i18n("Trying to create a commentbox-wrapper from a NULL isdBox") << endl;
	return NULL;
    }
    
    //create it
    SRKCommentbox* newComment = new SRKCommentbox(isdBox, videoCanvas, "");
    
    //resize the wrapper
    int size[2];
    isdBox->getScaledSize(size);
    newComment->resize(size[0], size[1]);
    
    //position the wrapper
    int pos[2];
    isdBox->getScaledPosition(pos);
    newComment->move(pos[0], pos[1]);

    return newComment;
}
