/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   copyright (C) 2002-2006                                               *
 *   Umbrello UML Modeller Authors <uml-devel@ uml.sf.net>                 *
 ***************************************************************************/

#include <kdebug.h>
#include <kmessagebox.h>
#include <klocale.h>

#include "umlclipboard.h"
#include "umldrag.h"
#include "idchangelog.h"
#include "../associationwidget.h"
#include "../attribute.h"
#include "../classifier.h"
#include "../floatingtextwidget.h"
#include "../operation.h"
#include "../umldoc.h"
#include "../umllistview.h"
#include "../umllistviewitem.h"
#include "../umlobjectlist.h"
#include "../umlview.h"
#include "../umlwidget.h"
#include "../uml.h"
#include "../model_utils.h"

UMLClipboard::UMLClipboard() {
    m_type = clip1;
}

UMLClipboard::~UMLClipboard() {
}

QMimeSource* UMLClipboard::copy(bool fromView/*=false*/) {
    //Clear previous copied data
    m_AssociationList.clear();
    m_ItemList.clear();
    m_ObjectList.clear();
    m_ViewList.clear();

    UMLDrag *data = 0;
    QPixmap* png = 0;

    UMLListView * listView = UMLApp::app()->getListView();
    UMLListViewItemList selectedItems;
    selectedItems.setAutoDelete(false);

    if(fromView) {
        m_type = clip4;
        UMLView *view = UMLApp::app()->getCurrentView();
        view->checkSelections();
        if(!view->getSelectedWidgets(m_WidgetList)) {
            return 0;
        }
        //if there is no selected widget then there is no copy action
        if(!m_WidgetList.count()) {
            return 0;
        }
        m_AssociationList = view->getSelectedAssocs();
        view->copyAsImage(png);
    } else { //if the copy action is being performed from the ListView
        if(!listView->getSelectedItems(selectedItems)) {
            return 0;
        }
        //Set What type of copy operation are we performing and
        //also fill m_ViewList with all the selected Diagrams
        setCopyType(selectedItems);

        //if we are copying a diagram or part of a diagram, select the items
        //on the ListView that correspond to a UseCase, Actor or Concept
        //in the Diagram
        if(m_type == clip2) {
            //Fill the member lists with all the object and stuff to be copied
            //to the clipboard
            selectedItems.clear();
            //For each selected view select all the Actors, USe Cases and Concepts
            //widgets in the ListView
            for (UMLViewListIt vit(m_ViewList); vit.current(); ++vit) {
                UMLObjectList objects = vit.current()->getUMLObjects();
                for (UMLObjectListIt oit(objects); oit.current(); ++oit) {
                    UMLObject *o = oit.current();
                    UMLListViewItem *item = listView->findUMLObject(o);
                    if(item) {
                        listView->setSelected(item, true);
                    }
                }
            }
            if(!listView->getSelectedItems(selectedItems)) {
                return 0;
            }
        }
        if(!fillSelectionLists(selectedItems)) {
            return 0;
        }
    }
    int i =0;
    switch(m_type) {
    case clip1:
        data = new UMLDrag(m_ObjectList, m_ItemList);
        break;
    case clip2:
        data = new UMLDrag(m_ObjectList, m_ItemList, m_ViewList);
        break;
    case clip3:
        data = new UMLDrag(m_ItemList);
        break;
    case clip4:
        if(png) {
            UMLView *view = UMLApp::app()->getCurrentView();
            data = new UMLDrag(m_ObjectList, m_ItemList, m_WidgetList,
                               m_AssociationList, *png, view->getType());
        } else {
            return 0;
        }
        break;
    case clip5:
        data = new UMLDrag(m_ObjectList, m_ItemList, i);
        // The int i is used to differentiate
        // which UMLDrag constructor gets called.
        break;
    }

    return (QMimeSource*)data;
}

bool UMLClipboard::paste(QMimeSource* data) {
    UMLDoc *doc = UMLApp::app()->getDocument();
    bool result = false;
    doc->beginPaste();
    switch(UMLDrag::getCodingType(data)) {
    case 1:
        result = pasteClip1(data);
        break;
    case 2:
        result = pasteClip2(data);
        break;
    case 3:
        result = pasteClip3(data);
        break;
    case 4:
        result = pasteClip4(data);
        break;
    case 5:
        result = pasteClip5(data);
        break;
    default:
        break;
    }
    doc->endPaste();
    return result;
}

bool UMLClipboard::fillSelectionLists(UMLListViewItemList& SelectedItems) {
    UMLListViewItemListIt it(SelectedItems);
    UMLListViewItem* item = 0;
    Uml::ListView_Type type;
    switch(m_type) {
    case clip4:
        break;
    case clip3:
        for ( ; it.current(); ++it ) {
            item = (UMLListViewItem*)it.current();
            type = item->getType();
            if ( !Model_Utils::typeIsClassifierList(type) ) {
                m_ItemList.append(item);
                insertItemChildren(item, SelectedItems);
                //Because it is being called when m_type is 3
                //it will insert only child empty folders of other folders.
                //If a child folder
                //is not empty that means m_type wouldn't be 3 because if a folder is
                //selected then its complete contents are treated as if
                //they were selected
            }
        }
        break;
    case clip2:
    case clip1:
        for ( ; it.current(); ++it ) {
            item = (UMLListViewItem*)it.current();
            type = item->getType();
            if ( !Model_Utils::typeIsClassifierList(type) ) {

                m_ItemList.append(item);

                if ( Model_Utils::typeIsCanvasWidget(type) ) {
                    m_ObjectList.append(item->getUMLObject());
                }
                insertItemChildren(it.current(), SelectedItems);
            }
        }
        break;
    case clip5:
        for ( ; it.current(); ++it ) {
            item = (UMLListViewItem*)it.current();
            type = item->getType();
            if( Model_Utils::typeIsClassifierList(type) ) {
                m_ItemList.append(item);
                m_ObjectList.append(item->getUMLObject());

            } else {
                return false;
            }
        }
        break;
    }

    return true;
}

void UMLClipboard::setCopyType(UMLListViewItemList& SelectedItems) {
    bool withDiagrams = false; //If the selection includes diagrams
    bool withObjects = false; //If the selection includes objects
    bool onlyAttsOps = false; //If the selection only includes Attributes and/or Operations
    UMLListViewItemListIt it(SelectedItems);
    for ( ; it.current(); ++it ) {
        checkItemForCopyType(it.current(), withDiagrams, withObjects, onlyAttsOps);
    }
    if(onlyAttsOps) {
        m_type = clip5;
    } else if(withDiagrams) {
        m_type = clip2;
    } else if(withObjects) {
        m_type = clip1;
    } else {
        m_type = clip3;
    }
}

void UMLClipboard::checkItemForCopyType(UMLListViewItem* Item, bool & WithDiagrams, bool &WithObjects,
                                        bool &OnlyAttsOps) {
    if(!Item) {
        return;
    }
    UMLDoc *doc = UMLApp::app()->getDocument();
    OnlyAttsOps = true;
    UMLView * view = 0;
    UMLListViewItem * child = 0;
    Uml::ListView_Type type = Item->getType();
    if ( Model_Utils::typeIsCanvasWidget(type) ) {
        WithObjects = true;
        OnlyAttsOps = false;
    } else if ( Model_Utils::typeIsDiagram(type) ) {
        WithDiagrams = true;
        OnlyAttsOps = false;
        view = doc->findView( Item->getID() );
        m_ViewList.append( view );
    } else if ( Model_Utils::typeIsFolder(type) ) {
        OnlyAttsOps = false;
        if(Item->childCount()) {
            child = (UMLListViewItem*)Item->firstChild();
            while(child) {
                checkItemForCopyType(child, WithDiagrams, WithObjects, OnlyAttsOps);
                child = (UMLListViewItem*)child->nextSibling();
            }
        }
    }
}

/** Adds the children of a UMLListViewItem to m_ItemList */
bool UMLClipboard::insertItemChildren(UMLListViewItem * Item, UMLListViewItemList& SelectedItems) {
    if(Item->childCount()) {
        UMLListViewItem * child = (UMLListViewItem*)Item->firstChild();
        int type;
        while(child) {
            m_ItemList.append(child);
            type = child->getType();
            if(type == Uml::lvt_Actor || type == Uml::lvt_UseCase || type == Uml::lvt_Class) {
                m_ObjectList.append(child->getUMLObject());
            }
            // If the child is selected, remove it from the list of selected items
            // otherwise it will be inserted twice in m_ObjectList.
            if(child->isSelected()) {
                SelectedItems.remove(SelectedItems.find(child) );
            }
            insertItemChildren(child, SelectedItems);
            child = (UMLListViewItem*)child->nextSibling();
        }
    }
    return true;
}

bool UMLClipboard::pasteChildren(UMLListViewItem *parent, IDChangeLog *chgLog) {
    if (!parent) {
        kdWarning() << "Paste Children Error, parent missing" << endl;
        return false;
    }
    UMLDoc *doc = UMLApp::app()->getDocument();
    UMLListView *listView = UMLApp::app()->getListView();
    UMLListViewItem *childItem = static_cast<UMLListViewItem*>(parent->firstChild());
    while (childItem) {
        Uml::IDType oldID = childItem->getID();
        Uml::IDType newID = chgLog->findNewID(oldID);
        UMLListViewItem *shouldNotExist = listView->findItem(newID);
        if (shouldNotExist) {
            kdError() << "UMLClipboard::pasteChildren: new list view item " << ID2STR(newID)
            << " already exists (internal error)" << endl;
            childItem = static_cast<UMLListViewItem*>(childItem->nextSibling());
            continue;
        }
        UMLObject *newObj = doc->findObjectById(newID);
        if (newObj) {
            kdDebug() << "UMLClipboard::pasteChildren: adjusting lvitem(" << ID2STR(oldID)
            << ") to new UMLObject(" << ID2STR(newID) << ")" << endl;
            childItem->setUMLObject(newObj);
            childItem->setText(newObj->getName());
        } else {
            kdDebug() << "UMLClipboard::pasteChildren: no UMLObject found for lvitem "
            << ID2STR(newID) << endl;
        }
        childItem = static_cast<UMLListViewItem*>(childItem->nextSibling());
    }
    return true;
}

/** Cleans the list of associations taking out the ones that point to an object
    not in m_ObjectList. */
void UMLClipboard::CleanAssociations(AssociationWidgetList& associations) {
    AssociationWidgetListIt it(associations);
    AssociationWidget* assoc = it.current();

    while (assoc) {
        ++it;
        assoc = it.current();
    }
}

/** If clipboard has mime type application/x-uml-clip1,
Pastes the data from the clipboard into the current Doc */
bool UMLClipboard::pasteClip1(QMimeSource* data) {
    UMLDoc *doc = UMLApp::app()->getDocument();
    UMLListViewItemList itemdatalist;
    UMLObjectList objects;
    IDChangeLog* idchanges = doc->getChangeLog();
    if (! UMLDrag::decodeClip1(data, objects, itemdatalist)) {
        return false;
    }
    UMLListView *lv = UMLApp::app()->getListView();
    if ( !lv->startedCopy() )
        return true;
    lv->setStartedCopy(false);
    /* If we get here we are pasting after a Copy and need to
    // paste possible children.
    UMLListViewItem* itemdata = 0;
    UMLListViewItemListIt it(itemdatalist);
    while ( (itemdata=it.current()) != 0 ) {
        if(itemdata -> childCount()) {
                if(!pasteChildren(itemdata, idchanges)) {
                        return false;
                }
        }
        ++it;
    }
     */
    return true;
}

/** If clipboard has mime type application/x-uml-clip2,
Pastes the data from the clipboard into the current Doc */
bool UMLClipboard::pasteClip2(QMimeSource* data) {
    UMLDoc *doc = UMLApp::app()->getDocument();
    UMLListViewItemList itemdatalist;
    UMLObjectList objects;
    objects.setAutoDelete(false);
    UMLViewList         views;
    IDChangeLog* idchanges = 0;

    bool result = UMLDrag::decodeClip2(data, objects, itemdatalist, views);
    if(!result) {
        return false;
    }
    UMLObject *obj = 0;
    UMLObjectListIt object_it(objects);
    idchanges = doc->getChangeLog();
    if(!idchanges) {
        return false;
    }
    while ( (obj=object_it.current()) != 0 ) {
        ++object_it;
        if(!doc->assignNewIDs(obj)) {
            kdDebug()<<"UMLClipboard: error adding umlobject"<<endl;
            return false;
        }
    }

    UMLView * pView = 0;
    UMLViewListIt view_it( views );
    while ( ( pView =view_it.current()) != 0 ) {
        ++view_it;
        if( !doc->addUMLView( pView ) ) {
            return false;
        }
    }

    UMLListView *listView = UMLApp::app()->getListView();
    UMLListViewItem* item = 0;
    UMLListViewItem* itemdata = 0;
    UMLListViewItemListIt it(itemdatalist);
    while ( (itemdata=it.current()) != 0 ) {
        item = listView->createItem(*itemdata, *idchanges);
        if(!item) {
            return false;
        }
        if(itemdata -> childCount()) {
            if(!pasteChildren(item, idchanges)) {
                return false;
            }
        }
        ++it;
    }

    return result;
}

/** If clipboard has mime type application/x-uml-clip3,
Pastes the data from the clipboard into the current Doc */
bool UMLClipboard::pasteClip3(QMimeSource* data) {
    UMLDoc *doc = UMLApp::app()->getDocument();
    UMLListViewItemList itemdatalist;
    UMLListViewItem* item = 0;
    UMLListViewItem* itemdata = 0;
    IDChangeLog* idchanges = doc->getChangeLog();

    if(!idchanges) {
        return false;
    }

    UMLListView *listView = UMLApp::app()->getListView();
    bool result = UMLDrag::decodeClip3(data, itemdatalist, listView);
    if(!result) {
        return false;
    }
    UMLListViewItemListIt it(itemdatalist);
    while ( (itemdata=it.current()) != 0 ) {
        item = listView->createItem(*itemdata, *idchanges);
        if(itemdata -> childCount()) {
            if(!pasteChildren(item, idchanges)) {
                return false;
            }
        }
        ++it;
    }

    return result;
}

/** If clipboard has mime type application/x-uml-clip4,
Pastes the data from the clipboard into the current Doc */
bool UMLClipboard::pasteClip4(QMimeSource* data) {
    UMLDoc *doc = UMLApp::app()->getDocument();
    UMLListViewItemList itemdatalist;
    itemdatalist.setAutoDelete(false);

    UMLObjectList objects;
    objects.setAutoDelete(false);


    UMLWidgetList               widgets;
    widgets.setAutoDelete(false);

    AssociationWidgetList       assocs;
    assocs.setAutoDelete(false);

    IDChangeLog* idchanges = 0;

    Uml::Diagram_Type diagramType;

    if( !UMLDrag::decodeClip4(data, objects, itemdatalist, widgets, assocs, diagramType) ) {
        return false;
    }

    if( diagramType != UMLApp::app()->getCurrentView()->getType() ) {
        if( !checkPasteWidgets(widgets) ) {
            assocs.setAutoDelete(true);
            assocs.clear();
            return false;
        }
    }
    UMLObjectListIt object_it(objects);
    idchanges = doc->getChangeLog();
    if(!idchanges) {
        return false;
    }
    //make sure the file we are pasting into has the objects
    //we need if there are widgets to be pasted
    UMLObject* obj = 0;
    while ( (obj=object_it.current()) != 0 ) {
        ++object_it;
        if(!doc->assignNewIDs(obj)) {
            return false;
        }
    }

    //now add any widget we are want to paste
    bool objectAlreadyExists = false;
    UMLView *currentView = UMLApp::app()->getCurrentView();
    currentView->beginPartialWidgetPaste();
    UMLWidget* widget =0;
    UMLWidgetListIt widget_it(widgets);
    while ( (widget=widget_it.current()) != 0 ) {
        ++widget_it;

        if (currentView->findWidget(idchanges->findNewID(widget->getID()))) {
            objectAlreadyExists = true;
        }

        if ( !currentView->addWidget(widget, true) ) {
            currentView->endPartialWidgetPaste();
            return false;
        }
    }

    //now paste the associations
    AssociationWidget* assoc;
    AssociationWidgetListIt assoc_it(assocs);
    while ( (assoc=assoc_it.current()) != 0 ) {
        ++assoc_it;
        if (!currentView->addAssociation(assoc, true)) {
            currentView->endPartialWidgetPaste();
            return false;
        }
    }

    //Activate all the pasted associations and widgets
    currentView->activate();
    currentView->endPartialWidgetPaste();

    UMLListView *listView = UMLApp::app()->getListView();
    UMLListViewItem* item = 0;
    UMLListViewItem* itemdata = 0;
    UMLListViewItemListIt it(itemdatalist);
    while ( (itemdata=it.current()) != 0 ) {
        item = listView->createItem(*itemdata, *idchanges);
        if(!item) {
            return false;
        }
        if(itemdata -> childCount()) {
            if(!pasteChildren(item, idchanges)) {
                return false;
            }
        }
        ++it;
    }

    if (objectAlreadyExists) {
        pasteItemAlreadyExists();
    }
    return true;
}

/** If clipboard has mime type application/x-uml-clip5,
Pastes the data from the clipboard into the current Doc */
bool UMLClipboard::pasteClip5(QMimeSource* data) {
    UMLDoc *doc = UMLApp::app()->getDocument();
    UMLListView *listView = UMLApp::app()->getListView();
    UMLListViewItem* lvitem = dynamic_cast<UMLListViewItem *>( listView->currentItem() );
    if (!lvitem ||
            (lvitem->getType() != Uml::lvt_Class && lvitem->getType() != Uml::lvt_Interface)) {
        return false;
    }
    UMLClassifier *parent = dynamic_cast<UMLClassifier *>(lvitem->getUMLObject());
    if (parent == NULL) {
        kdError() << "UMLClipboard::pasteClip5: parent is not a UMLClassifier"
        << endl;
        return false;
    }
    UMLListViewItemList itemdatalist;
    UMLObjectList objects;
    objects.setAutoDelete(false);
    IDChangeLog* idchanges = 0;
    bool result = UMLDrag::decodeClip5(data, objects, itemdatalist, parent);

    if(!result) {
        return false;
    }

    bool objectAlreadyExists = false;
    UMLObject   *obj = 0;
    UMLObjectListIt object_it(objects);
    doc->setModified(true);
    idchanges = doc->getChangeLog();

    while ( (obj=object_it.current()) != 0 ) {
        obj->setID(doc->assignNewID(obj->getID()));
        switch(obj->getBaseType()) {
        case Uml::ot_Attribute :
            {
                if (parent->addAttribute(dynamic_cast<UMLAttribute*>(obj), idchanges)) {
                    //FIXME                                     doc -> signalChildUMLObjectCreated(obj);
                } else {
                    objectAlreadyExists = true;
                }
                break;
            }
        case Uml::ot_Operation :
            {
                UMLClassifier * parent = dynamic_cast<UMLClassifier *>(lvitem -> getUMLObject());
                if (parent -> addOperation(dynamic_cast<UMLOperation*>(obj), idchanges)) {
                    //FIXME                             doc -> signalChildUMLObjectCreated(obj);
                } else {
                    objectAlreadyExists = true;
                }
                break;
            }
        default :
            kdWarning() << "pasteing unknown children type in clip type 5" << endl;
            return false;
        }
        ++object_it;
    }

    if (objectAlreadyExists) {
        pasteItemAlreadyExists();
    }

    return result;
}

bool UMLClipboard::insertItemChildren( UMLListViewItem * item ) {
    if( item -> childCount() ) {
        UMLListViewItem * child =dynamic_cast<UMLListViewItem *>( item -> firstChild() );
        while( child ) {
            m_ItemList.append( child );
            insertItemChildren( child );
            child = dynamic_cast<UMLListViewItem *>( child->nextSibling() );
        }
    }
    return true;
}

bool UMLClipboard::checkPasteWidgets( UMLWidgetList & widgetList ) {
    bool retval = true;
    UMLWidget * p = 0;
    UMLWidgetListIt it( widgetList );
    while ( ( p = it.current()) != 0 ) {
        ++it;
        switch( p -> getBaseType() ) {
        case Uml::wt_Note:
            break;

        case Uml::wt_Text:
            {
                FloatingTextWidget *ft = static_cast<FloatingTextWidget*>(p);
                if (ft->getRole() != Uml::tr_Floating) {
                    widgetList.remove(p);
                    delete ft;
                    retval = false;
                }
            }
            break;

        default:
            widgetList.remove(p);
            delete p;
            retval = false;
            break;
        }
    }
    return retval;
}

void UMLClipboard::pasteItemAlreadyExists() {
    UMLView *currentView = UMLApp::app()->getCurrentView();
    KMessageBox::sorry( currentView,
                        i18n("At least one of the items in the clipboard "
                             "could not be pasted because an item of the "
                             "same name already exists.  Any other items "
                             "have been pasted."),
                        i18n("Paste Error") );
}

