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

// own header file
#include "objectwidget.h"
// system includes
#include <qpainter.h>
#include <qvalidator.h>
#include <qevent.h>
#include <klocale.h>
#include <kdebug.h>
// local includes
#include "objectwidgetcontroller.h"
#include "inputdialog.h"
#include "seqlinewidget.h"
#include "umlview.h"
#include "umldoc.h"
#include "uml.h"
#include "umlobject.h"
#include "listpopupmenu.h"
#include "docwindow.h"
#include "dialogs/classpropdlg.h"

/**
 * The number of pixels margin between the lowest message
 * and the bottom of the vertical line
 */
static const int sequenceLineMargin = 20;

ObjectWidget::ObjectWidget(UMLView * view, UMLObject *o, Uml::IDType lid)
        : UMLWidget(view, o) {
    init();
    if( lid != Uml::id_None )
        m_nLocalID = lid;
    //updateComponentSize();
    //                  Doing this during loadFromXMI() gives futile updates.
    //                  Instead, it is done afterwards by UMLWidget::activate()
}

void ObjectWidget::init() {
    UMLWidget::setBaseType(Uml::wt_Object);
    m_nLocalID = Uml::id_None;
    m_InstanceName = "";
    m_bMultipleInstance = false;
    m_bDrawAsActor = false;
    m_bShowDestruction = false;
    messageWidgetList.setAutoDelete(false);
    if( m_pView != NULL && m_pView -> getType() == Uml::dt_Sequence ) {
        m_pLine = new SeqLineWidget( m_pView, this );

        //Sets specific widget controller for sequence diagrams
        delete m_widgetController;
        m_widgetController = new ObjectWidgetController(this);
    } else {
        m_pLine = NULL;
    }
}

ObjectWidget::~ObjectWidget() {}

void ObjectWidget::draw(QPainter & p , int offsetX, int offsetY) {
    if ( m_bDrawAsActor )
        drawActor( p, offsetX, offsetY );
    else
        drawObject( p, offsetX, offsetY );

    UMLWidget::setPen(p);
    if(m_bSelected)
        drawSelected(&p, offsetX, offsetY);
}

void ObjectWidget::slotMenuSelection(int sel) {
    QString name = "";
    switch(sel) {
    case ListPopupMenu::mt_Rename_Object:
        {
            bool ok;
            QRegExpValidator* validator = new QRegExpValidator(QRegExp(".*"), 0);
            name = KInputDialog::getText
                   (i18n("Rename Object"),
                    i18n("Enter object name:"),
                    m_InstanceName,
                    &ok,
                    m_pView,
#if KDE_IS_VERSION(3,1,90)
                    "renameobject",
#endif
                    validator);
            if (ok) {
                m_InstanceName = name;
                updateComponentSize();
                moveEvent( 0 );
                update();
                UMLApp::app()->getDocument()->setModified(true);
            }
            delete validator;
            break;
        }
    case ListPopupMenu::mt_Properties:
        showProperties();
        updateComponentSize();
        moveEvent( 0 );
        update();
        break;

    case ListPopupMenu::mt_Up:
        tabUp();
        break;

    case ListPopupMenu::mt_Down:
        tabDown();
        break;

    default:
        UMLWidget::slotMenuSelection(sel);
        break;
    }
}

QSize ObjectWidget::calculateSize() {
    int width, height;
    const QFontMetrics &fm = getFontMetrics(FT_UNDERLINE);
    const int fontHeight  = fm.lineSpacing();
    const QString t = m_InstanceName + " : " + m_pObject->getName();
    const int textWidth = fm.width(t);
    if ( m_bDrawAsActor ) {
        width = textWidth > A_WIDTH?textWidth:A_WIDTH;
        height = A_HEIGHT + fontHeight + A_MARGIN;
        width += A_MARGIN * 2;
    } else {
        width = textWidth > O_WIDTH?textWidth:O_WIDTH;
        height = fontHeight + O_MARGIN * 2;
        width += O_MARGIN * 2;
        if (m_bMultipleInstance) {
            width += 10;
            height += 10;
        }
    }//end else drawasactor

    return QSize(width, height);
}

void ObjectWidget::setDrawAsActor( bool drawAsActor ) {
    m_bDrawAsActor = drawAsActor;
    updateComponentSize();
}

void ObjectWidget::setMultipleInstance(bool multiple) {
    //make sure only calling this in relation to an object on a collab. diagram
    if(m_pView -> getType() != Uml::dt_Collaboration)
        return;
    m_bMultipleInstance = multiple;
    updateComponentSize();
    update();
}

void ObjectWidget::activate(IDChangeLog* ChangeLog /*= 0*/) {
    UMLWidget::activate(ChangeLog);
    if (m_bShowDestruction && m_pLine)
        m_pLine->setupDestructionBox();
    moveEvent(0);
}

void ObjectWidget::setX( int x ) {
    UMLWidget::setX(x);
    moveEvent(0);
}

void ObjectWidget::setY( int y ) {
    UMLWidget::setY(y);
    moveEvent(0);
}

void ObjectWidget::moveEvent(QMoveEvent */*m*/) {
    emit sigWidgetMoved( m_nLocalID );
    if( m_pLine )
        m_pLine -> setStartPoint( getX() + width() / 2, getY() + height() );
}

void ObjectWidget::slotColorChanged(Uml::IDType /*viewID*/) {
    UMLWidget::setFillColour( m_pView->getFillColor() );
    UMLWidget::setLineColor( m_pView->getLineColor() );

    if( m_pLine)
        m_pLine -> setPen( QPen( UMLWidget::getLineColor(), UMLWidget::getLineWidth(), Qt::DashLine ) );
}

void ObjectWidget::cleanup() {

    UMLWidget::cleanup();
    if( m_pLine ) {
        m_pLine -> cleanup();
        delete m_pLine;
    }
}

bool ObjectWidget::showProperties() {
    DocWindow *docwindow = UMLApp::app()->getDocWindow();
    docwindow->updateDocumentation(false);
    ClassPropDlg *dlg = new ClassPropDlg((QWidget*)UMLApp::app(), this);
    bool modified = false;
    if (dlg->exec()) {
        docwindow->showDocumentation(this, true);
        UMLApp::app()->getDocument()->setModified(true);
        modified = true;
    }
    dlg->close(true);//wipe from memory
    return modified;
}

void ObjectWidget::drawObject(QPainter & p, int offsetX, int offsetY) {

    QFont oldFont = p.font();
    QFont font = UMLWidget::getFont();
    font.setUnderline( true );
    p.setFont( font );

    UMLWidget::setPen(p);
    if(UMLWidget::getUseFillColour())
        p.setBrush(UMLWidget::getFillColour());
    else
        p.setBrush(m_pView -> viewport() -> backgroundColor());
    const int w = width();
    const int h = height();

    const QString t = m_InstanceName + " : " + m_pObject -> getName();
    int multiInstOfst = 0;
    if ( m_bMultipleInstance ) {
        p.drawRect(offsetX + 10, offsetY + 10, w - 10, h - 10);
        p.drawRect(offsetX + 5, offsetY + 5, w - 10, h - 10);
        multiInstOfst = 10;
    }
    p.drawRect(offsetX, offsetY, w - multiInstOfst, h - multiInstOfst);
    p.setPen(QPen(Qt::black));
    p.drawText(offsetX + O_MARGIN, offsetY + O_MARGIN,
               w - O_MARGIN * 2 - multiInstOfst, h - O_MARGIN * 2 - multiInstOfst,
               Qt::AlignCenter, t);

    p.setFont( oldFont );
}

void ObjectWidget::drawActor(QPainter & p, int offsetX, int offsetY) {
    const QFontMetrics &fm = getFontMetrics(FT_UNDERLINE);

    UMLWidget::setPen(p);
    if ( UMLWidget::getUseFillColour() )
        p.setBrush( UMLWidget::getFillColour() );
    const int w = width();
    const int textStartY = A_HEIGHT + A_MARGIN;
    const int fontHeight  = fm.lineSpacing();

    const int middleX = offsetX + w / 2;
    const int thirdH = A_HEIGHT / 3;

    //draw actor
    p.drawEllipse(middleX - A_WIDTH / 2, offsetY,  A_WIDTH, thirdH);//head
    p.drawLine(middleX, offsetY + thirdH, middleX, offsetY + thirdH * 2);//body
    p.drawLine(middleX, offsetY + 2 * thirdH,
               middleX - A_WIDTH / 2, offsetY + A_HEIGHT);//left leg
    p.drawLine(middleX, offsetY +  2 * thirdH,
               middleX + A_WIDTH / 2, offsetY + A_HEIGHT);//right leg
    p.drawLine(middleX - A_WIDTH / 2, offsetY + thirdH + thirdH / 2,
               middleX + A_WIDTH / 2, offsetY + thirdH + thirdH / 2);//arms
    //draw text
    p.setPen(QPen(Qt::black));
    QString t = m_InstanceName + " : " + m_pObject -> getName();
    p.drawText(offsetX + A_MARGIN, offsetY + textStartY,
               w - A_MARGIN * 2, fontHeight, Qt::AlignCenter, t);
}

void ObjectWidget::tabUp() {
    int newY = getY() - height();
    if (newY < topMargin())
        newY = topMargin();
    setY( newY );
    moveEvent( 0 );
    adjustAssocs( getX(), newY);
}

void ObjectWidget::tabDown() {
    int newY = getY() + height();
    setY( newY );
    moveEvent( 0 );
    adjustAssocs( getX(), newY);
}

int ObjectWidget::topMargin() {
    return 80 - height();
}

bool ObjectWidget::canTabUp() {
    int y = getY();
    //kdDebug() << "ObjectWidget::canTabUp: y is " << y << endl;
    return (y > topMargin());
}

void ObjectWidget::setShowDestruction( bool bShow ) {
    m_bShowDestruction = bShow;
    if( m_pLine )
        m_pLine -> setupDestructionBox();
}

int ObjectWidget::getEndLineY() {
    int y = this -> getY() + getHeight();
    if( m_pLine)
        y += m_pLine -> getLineLength();
    if ( m_bShowDestruction )
        y += 10;
    return y;
}

void ObjectWidget::messageAdded(MessageWidget* message) {
    if (messageWidgetList.containsRef(message) ) {
        kdError() << "ObjectWidget::messageAdded("
                  << message->getName() << ") : duplicate entry !"
                  << endl;
        return ;
    }
    messageWidgetList.append(message);
}

void ObjectWidget::messageRemoved(MessageWidget* message) {
    if ( messageWidgetList.remove(message) == false ) {
        kdError() << "ObjectWidget::messageRemoved("
                  << message->getName() << ") : missing entry !"
                  << endl;
        return ;
    }
}

void ObjectWidget::slotMessageMoved() {
    MessageWidgetListIt iterator(messageWidgetList);
    MessageWidget* message;
    int lowestMessage = 0;
    while ( (message = iterator.current()) != 0 ) {
        ++iterator;
        int messageHeight = message->getY() + message->getHeight();
        if (lowestMessage < messageHeight) {
            lowestMessage = messageHeight;
        }
    }
    m_pLine->setEndOfLine(lowestMessage + sequenceLineMargin);
}

bool ObjectWidget::messageOverlap(int y, MessageWidget* messageWidget) {
    MessageWidgetListIt iterator(messageWidgetList);
    MessageWidget* message;
    while ( (message = iterator.current()) != 0 ) {
        ++iterator;
        const int msgY = message->getY();
        const int msgHeight = msgY + message->getHeight();
        if (y >= msgY && y <= msgHeight && message != messageWidget) {
            return true;
        }
    }
    return false;
}

void ObjectWidget::saveToXMI( QDomDocument & qDoc, QDomElement & qElement ) {
    QDomElement objectElement = qDoc.createElement( "objectwidget" );
    UMLWidget::saveToXMI( qDoc, objectElement );
    objectElement.setAttribute( "instancename", m_InstanceName );
    objectElement.setAttribute( "drawasactor", m_bDrawAsActor );
    objectElement.setAttribute( "multipleinstance", m_bMultipleInstance );
    objectElement.setAttribute( "localid", ID2STR(m_nLocalID) );
    objectElement.setAttribute( "decon", m_bShowDestruction );
    qElement.appendChild( objectElement );
}

bool ObjectWidget::loadFromXMI( QDomElement & qElement ) {
    if( !UMLWidget::loadFromXMI( qElement ) )
        return false;
    m_InstanceName = qElement.attribute( "instancename", "" );
    QString draw = qElement.attribute( "drawasactor", "0" );
    QString multi = qElement.attribute( "multipleinstance", "0" );
    QString localid = qElement.attribute( "localid", "0" );
    QString decon = qElement.attribute( "decon", "0" );

    m_bDrawAsActor = (bool)draw.toInt();
    m_bMultipleInstance = (bool)multi.toInt();
    m_nLocalID = STR2ID(localid);
    m_bShowDestruction = (bool)decon.toInt();
    return true;

}

#include "objectwidget.moc"
