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

// own header
#include "association.h"
// qt/kde includes
#include <kdebug.h>
#include <tdelocale.h>
#include <tqregexp.h>
// app includes
#include "classifier.h"
#include "folder.h"
#include "uml.h"
#include "umldoc.h"
#include "umlrole.h"
#include "uniqueid.h"
#include "model_utils.h"

using namespace Uml;

// static members
const Uml::Association_Type UMLAssociation::atypeFirst = Uml::at_Generalization;
const Uml::Association_Type UMLAssociation::atypeLast = Uml::at_Relationship;
const unsigned UMLAssociation::nAssocTypes = (unsigned)atypeLast -
        (unsigned)atypeFirst + 1;

// constructor
UMLAssociation::UMLAssociation( Uml::Association_Type type,
                                UMLObject * roleA, UMLObject * roleB )
        : UMLObject("")
{
    init(type, roleA, roleB);

    m_pRole[Uml::A]->setID( UniqueID::gen() );
    m_pRole[Uml::B]->setID( UniqueID::gen() );
}

UMLAssociation::UMLAssociation( Uml::Association_Type type /* = Uml::at_Unknown */)
        : UMLObject("", Uml::id_Reserved)
{
    init(type, NULL, NULL);
}

// destructor
UMLAssociation::~UMLAssociation( ) {
    if (m_pRole[A] == NULL) {
        kError() << "UMLAssociation destructor: m_pRole[A] is NULL already"
        << endl;
    } else {
        delete m_pRole[A];
        m_pRole[A] = NULL;
    }
    if (m_pRole[B] == NULL) {
        kError() << "UMLAssociation destructor: m_pRole[B] is NULL already"
        << endl;
    } else {
        delete m_pRole[B];
        m_pRole[B] = NULL;
    }
}

bool UMLAssociation::operator==(UMLAssociation &rhs) {
    if (this == &rhs) {
        return true;
    }
    return ( UMLObject::operator== ( rhs ) &&
             m_AssocType == rhs.m_AssocType &&
             m_Name == rhs.m_Name &&
             m_pRole[A] == rhs.m_pRole[A] &&
             m_pRole[B] == rhs.m_pRole[B] );
}

const TQString UMLAssociation::assocTypeStr[UMLAssociation::nAssocTypes] = {
            /* The elements must be listed in the same order as in the
               Uml::Association_Type.  */
            i18n("Generalization"),             // at_Generalization
            i18n("Aggregation"),                // at_Aggregation
            i18n("Dependency"),                 // at_Dependency
            i18n("Association"),                // at_Association
            i18n("Self Association"),           // at_Association_Self
            i18n("Collaboration Message"),      // at_Coll_Message
            i18n("Sequence Message"),           // at_Seq_Message
            i18n("Collaboration Self Message"), // at_Coll_Message_Self
            i18n("Sequence Self Message"),      // at_Seq_Message_Self
            i18n("Containment"),                // at_Containment
            i18n("Composition"),                // at_Composition
            i18n("Realization"),                // at_Realization
            i18n("Uni Association"),            // at_UniAssociation
            i18n("Anchor"),                     // at_Anchor
            i18n("State Transition"),           // at_State
            i18n("Activity"),                   // at_Activity
        };

Uml::Association_Type UMLAssociation::getAssocType() const {
    return m_AssocType;
}

TQString UMLAssociation::toString ( ) const
{
    TQString string;
    if(m_pRole[A])
    {
        string += m_pRole[A]->getObject()->getName();
        string += ':';
        string += m_pRole[A]->getName();
    }
    string += ':' + typeAsString(m_AssocType) + ':';
    if(m_pRole[B])
    {
        string += m_pRole[B]->getObject( )->getName();
        string += ':';
        string += m_pRole[B]->getName();
    }
    return string;
}

TQString UMLAssociation::typeAsString (Uml::Association_Type atype)
{
    if (atype < atypeFirst || atype > atypeLast)
        return "";
    return assocTypeStr[(unsigned)atype - (unsigned)atypeFirst];
}

bool UMLAssociation::assocTypeHasUMLRepresentation(Uml::Association_Type atype)
{
    return (atype == Uml::at_Generalization ||
            atype == Uml::at_Realization ||
            atype == Uml::at_Association ||
            atype == Uml::at_Association_Self ||
            atype == Uml::at_UniAssociation ||
            atype == Uml::at_Aggregation ||
            atype == Uml::at_Relationship ||
            atype == Uml::at_Composition ||
            atype == Uml::at_Dependency);
}

bool UMLAssociation::resolveRef() {
    bool successA = getUMLRole(A)->resolveRef();
    bool successB = getUMLRole(B)->resolveRef();
    if (successA && successB) {
        UMLObject *objA = getUMLRole(A)->getObject();
        UMLObject *objB = getUMLRole(B)->getObject();
        // Check if need to change the assoc type to Realization
        if (m_AssocType == Uml::at_Generalization &&
                (objA && objA->getBaseType() == Uml::ot_Interface ||
                 objB && objB->getBaseType() == Uml::ot_Interface))
            m_AssocType = Uml::at_Realization;
        m_pUMLPackage->addAssocToConcepts(this);
        return true;
    }
    return false;
}

void UMLAssociation::saveToXMI( TQDomDocument & qDoc, TQDomElement & qElement ) {
    if (m_AssocType == Uml::at_Generalization) {
        TQDomElement assocElement = UMLObject::save("UML:Generalization", qDoc);
        assocElement.setAttribute( "discriminator", "" );
        assocElement.setAttribute( "child", ID2STR(getObjectId(A)) );
        assocElement.setAttribute( "parent", ID2STR(getObjectId(B)) );
        qElement.appendChild( assocElement );
        return;
    }
    if (m_AssocType == Uml::at_Realization) {
        TQDomElement assocElement = UMLObject::save("UML:Abstraction", qDoc);
        assocElement.setAttribute( "client", ID2STR(getObjectId(A)) );
        assocElement.setAttribute( "supplier", ID2STR(getObjectId(B)) );
        qElement.appendChild( assocElement );
        return;
    }
    if (m_AssocType == Uml::at_Dependency) {
        TQDomElement assocElement = UMLObject::save("UML:Dependency", qDoc);
        assocElement.setAttribute( "client", ID2STR(getObjectId(A)) );
        assocElement.setAttribute( "supplier", ID2STR(getObjectId(B)) );
        qElement.appendChild( assocElement );
        return;
    }
    TQDomElement associationElement = UMLObject::save("UML:Association", qDoc);
    TQDomElement connElement = qDoc.createElement("UML:Association.connection");
    getUMLRole(A)->saveToXMI (qDoc, connElement);
    getUMLRole(B)->saveToXMI (qDoc, connElement);
    associationElement.appendChild (connElement);
    qElement.appendChild( associationElement );
}

bool UMLAssociation::load( TQDomElement & element ) {
    if (getID() == Uml::id_None)
        return false; // old style XMI file. No real info in this association.

    UMLDoc * doc = UMLApp::app()->getDocument();
    UMLObject * obj[2] = { NULL, NULL };
    if (m_AssocType == Uml::at_Generalization ||
        m_AssocType == Uml::at_Realization ||
        m_AssocType == Uml::at_Dependency) {
        for (unsigned r = Uml::A; r <= Uml::B; r++) {
            const TQString fetch = (m_AssocType == Uml::at_Generalization ?
                                   r == Uml::A ? "child" : "parent"
                       : r == Uml::A ? "client" : "supplier");
            TQString roleIdStr = element.attribute(fetch, "");
            if (roleIdStr.isEmpty()) {
                // Might be given as a child node instead - see below.
                continue;
            }

            // set umlobject of role if possible (else defer resolution)
            obj[r] = doc->findObjectById(STR2ID(roleIdStr));
            Uml::Role_Type role = (Uml::Role_Type)r;
            if (obj[r] == NULL) {
                m_pRole[role]->setSecondaryId(roleIdStr);  // defer to resolveRef()
            } else {
                m_pRole[role]->setObject(obj[r]);
                if (m_pUMLPackage == NULL) {
                    Uml::Model_Type mt = Model_Utils::convert_OT_MT(obj[r]->getBaseType());
                    m_pUMLPackage = doc->getRootFolder(mt);
                    kDebug() << "UMLAssociation::load(assoctype " << m_AssocType
                        << "): setting model type " << mt << endl;
                }
            }
        }
        if (obj[A] == NULL || obj[B] == NULL) {
            for (TQDomNode node = element.firstChild(); !node.isNull();
                    node = node.nextSibling()) {
                if (node.isComment())
                    continue;
                TQDomElement tempElement = node.toElement();
                TQString tag = tempElement.tagName();
                if (Model_Utils::isCommonXMIAttribute(tag))
                    continue;
                // Permitted tag names:
                //  roleA: "child" "subtype" "client"
                //  roleB: "parent" "supertype" "supplier"
                TQString idStr = tempElement.attribute( "xmi.id", "" );
                if (idStr.isEmpty())
                    idStr = tempElement.attribute( "xmi.idref", "" );
                if (idStr.isEmpty()) {
                    TQDomNode inner = node.firstChild();
                    TQDomElement tmpElem = inner.toElement();
                    idStr = tmpElem.attribute( "xmi.id", "" );
                    if (idStr.isEmpty())
                        idStr = tmpElem.attribute( "xmi.idref", "" );
                }
                if (idStr.isEmpty()) {
                    kError() << "UMLAssociation::load (type " << m_AssocType
                        << ", id " << ID2STR(getID()) << "): "
                        << "xmi id not given for " << tag << endl;
                    continue;
                }
                // Since we know for sure that we're dealing with a non
                // umbrello file, use deferred resolution unconditionally.
                if (tagEq(tag, "child") || tagEq(tag, "subtype") || tagEq(tag, "client")) {
                    getUMLRole(A)->setSecondaryId(idStr);
                } else {
                    getUMLRole(B)->setSecondaryId(idStr);
                }
            }
        }

        // its a realization if either endpoint is an interface
        if (m_AssocType == Uml::at_Generalization &&
                (obj[A] && obj[A]->getBaseType() == Uml::ot_Interface ||
                 obj[B] && obj[B]->getBaseType() == Uml::ot_Interface))
            m_AssocType = Uml::at_Realization;

        return true;
    }

    for (TQDomNode node = element.firstChild(); !node.isNull();
            node = node.nextSibling()) {
        // uml13.dtd compliant format (new style)
        if (node.isComment())
            continue;
        TQDomElement tempElement = node.toElement();
        TQString tag = tempElement.tagName();
        if (Model_Utils::isCommonXMIAttribute(tag))
            continue;
        if (!tagEq(tag, "Association.connection") &&
                !tagEq(tag, "Namespace.ownedElement") &&
                !tagEq(tag, "Namespace.contents")) {
            kWarning() << "UMLAssociation::load: "
            << "unknown child node " << tag << endl;
            continue;
        }
        // Load role A.
        node = tempElement.firstChild();
        while (node.isComment())
            node = node.nextSibling();
        tempElement = node.toElement();
        if (tempElement.isNull()) {
            kWarning() << "UML:Association : element (A) is Null" << endl;
            return false;
        }
        tag = tempElement.tagName();
        if (!tagEq(tag, "AssociationEndRole") &&
                !tagEq(tag, "AssociationEnd")) {
            kWarning() << "UMLAssociation::load: "
            << "unknown child (A) tag " << tag << endl;
            return false;
        }
        if (! getUMLRole(A)->loadFromXMI(tempElement))
            return false;
        // Load role B.
        node = node.nextSibling();
        while (node.isComment())
            node = node.nextSibling();
        tempElement = node.toElement();
        if (tempElement.isNull()) {
            kWarning() << "UML:Association : element (B) is Null" << endl;
            return false;
        }
        tag = tempElement.tagName();
        if (!tagEq(tag, "AssociationEndRole") &&
                !tagEq(tag, "AssociationEnd")) {
            kWarning() << "UMLAssociation::load: "
            << "unknown child (B) tag " << tag << endl;
            return false;
        }
        if (! getUMLRole(B)->loadFromXMI(tempElement))
            return false;

        if (m_pUMLPackage == NULL) {
            Uml::Model_Type mt = Model_Utils::convert_OT_MT(getObject(B)->getBaseType());
            m_pUMLPackage = doc->getRootFolder(mt);
            kDebug() << "UMLAssociation::load: setting model type " << mt << endl;
        }

        // setting the association type:
        //
        // In the old days, we could just record this on the association,
        // and be done with it. But thats not how the UML13.dtd does things.
        // As a result, we are checking roleA for information about the
        // parent association (!) which by this point in the parse, should
        // be set. However, the information that the roles are allowed to have
        // is not complete, so we need to finish the analysis here.

        // find self-associations
        if (m_AssocType == Uml::at_Association && getObjectId(A) == getObjectId(B))
            m_AssocType = Uml::at_Association_Self;

        // fall-back default type
        if (m_AssocType == Uml::at_Unknown) {
            m_AssocType = Uml::at_Association;
        }

        return true;
    }

    // From here on it's old-style stuff.
    TQString assocTypeStr = element.attribute( "assoctype", "-1" );
    Uml::Association_Type assocType = Uml::at_Unknown;
    if (assocTypeStr[0] >= 'a' && assocTypeStr[0] <= 'z') {
        // In an earlier version, the natural assoctype names were saved.
        const TQString assocTypeString[nAssocTypes] = {
                    "generalization",   // at_Generalization
                    "aggregation",      // at_Aggregation
                    "dependency",       // at_Dependency
                    "association",      // at_Association
                    "associationself",  // at_Association_Self
                    "collmessage",      // at_Coll_Message
                    "seqmessage",       // at_Seq_Message
                    "collmessageself",  // at_Coll_Message_Self
                    "seqmessageself",   // at_Seq_Message_Self
                    "implementation",   // at_Implementation
                    "composition",      // at_Composition
                    "realization",      // at_Realization
                    "uniassociation",   // at_UniAssociation
                    "anchor",           // at_Anchor
                    "state",            // at_State
                    "activity",         // at_Activity
                    "relationship"      // at_Relationship
                };

        unsigned index;
        for (index = 0; index < nAssocTypes; index++)
            if (assocTypeStr == assocTypeString[index])
                break;
        if (index < nAssocTypes)
            assocType = (Uml::Association_Type)index;
    } else {
        int assocTypeNum = assocTypeStr.toInt();
        if (assocTypeNum < (int)atypeFirst || assocTypeNum > (int)atypeLast) {
            kWarning() << "bad assoctype of UML:Association "
            << ID2STR(getID()) << endl;
            return false;
        }
        assocType = (Uml::Association_Type)assocTypeNum;
    }
    setAssocType( assocType );

    Uml::IDType roleAObjID = STR2ID(element.attribute( "rolea", "-1" ));
    Uml::IDType roleBObjID = STR2ID(element.attribute( "roleb", "-1" ));
    if (assocType == at_Aggregation || assocType == at_Composition) {
        // Flip roles to compensate for changed diamond logic in LinePath.
        // For further explanations see AssociationWidget::loadFromXMI.
        Uml::IDType tmp = roleAObjID;
        roleAObjID = roleBObjID;
        roleBObjID = tmp;
    }

    UMLObject * objA = doc->findObjectById(roleAObjID);
    UMLObject * objB = doc->findObjectById(roleBObjID);

    if(objA)
        getUMLRole(A)->setObject(objA);
    else
        return false;

    if(objB)
        getUMLRole(B)->setObject(objB);
    else
        return false;

    setMulti(element.attribute( "multia", "" ), A);
    setMulti(element.attribute( "multib", "" ), B);

    setRoleName(element.attribute( "namea", "" ), A);
    setRoleName(element.attribute( "nameb", "" ), B);

    setRoleDoc(element.attribute( "doca", "" ), A);
    setRoleDoc(element.attribute( "docb", "" ), B);

    // Visibility defaults to Public if it cant set it here..
    TQString visibilityA = element.attribute( "visibilitya", "0");
    TQString visibilityB = element.attribute( "visibilityb", "0");
    if (visibilityA.toInt() > 0)
        setVisibility((Uml::Visibility::Value)visibilityA.toInt(), A);
    if (visibilityB.toInt() > 0)
        setVisibility((Uml::Visibility::Value)visibilityB.toInt(), B);

    // Changeability defaults to Changeable if it cant set it here..
    TQString changeabilityA = element.attribute( "changeabilitya", "0");
    TQString changeabilityB = element.attribute( "changeabilityb", "0");
    if (changeabilityA.toInt() > 0)
        setChangeability ( (Uml::Changeability_Type) changeabilityA.toInt(), A);
    if (changeabilityB.toInt() > 0)
        setChangeability ( (Uml::Changeability_Type) changeabilityB.toInt(), B);

    return true;
}

UMLObject* UMLAssociation::getObject(Uml::Role_Type role) {
    return m_pRole[role]->getObject();
}

Uml::IDType UMLAssociation::getObjectId(Uml::Role_Type role) {
    UMLRole *roleObj = m_pRole[role];
    UMLObject *o = roleObj->getObject();
    if (o == NULL) {
        TQString auxID = roleObj->getSecondaryId();
        if (auxID.isEmpty()) {
            kError() << "UMLAssociation::getObjectId(" << role
            << "): getObject returns NULL" << endl;
            return Uml::id_None;
        } else {
            kDebug() << "UMLAssociation::getObjectId(" << role
            << "): using secondary ID " << auxID << endl;
            return STR2ID(auxID);
        }
    }
    return o->getID();
}

/* CURRENTLY UNUSED
Uml::IDType UMLAssociation::getRoleId(Role_Type role) const {
    return m_pRole[role]->getID();
}
 */

Uml::Changeability_Type UMLAssociation::getChangeability(Uml::Role_Type role) const {
    return m_pRole[role]->getChangeability();
}

Uml::Visibility UMLAssociation::getVisibility(Uml::Role_Type role) const {
    return m_pRole[role]->getVisibility();
}

TQString UMLAssociation::getMulti(Uml::Role_Type role) const {
    return m_pRole[role]->getMultiplicity();
}

TQString UMLAssociation::getRoleName(Uml::Role_Type role) const {
    return m_pRole[role]->getName();
}

TQString UMLAssociation::getRoleDoc(Uml::Role_Type role) const {
    return m_pRole[role]->getDoc();
}

UMLRole * UMLAssociation::getUMLRole(Uml::Role_Type role) {
    return m_pRole[role];
}

void UMLAssociation::setOldLoadMode(bool value /* = true */) {
    m_bOldLoadMode = value;
}

bool UMLAssociation::getOldLoadMode() const {
    return m_bOldLoadMode;
}

void UMLAssociation::setAssocType(Uml::Association_Type assocType) {
    m_AssocType = assocType;
    if(m_AssocType == at_UniAssociation)
    {
        // In this case we need to auto-set the multiplicity/rolenames
        // of the roles
#ifdef VERBOSE_DEBUGGING
        kDebug() << " A new uni-association has been created." << endl;
#endif
    }
    UMLDoc *umldoc = UMLApp::app()->getDocument();
    if (! umldoc->loading())
        emit modified();
}

void UMLAssociation::setObject(UMLObject *obj, Uml::Role_Type role) {
    m_pRole[role]->setObject(obj);
}

void UMLAssociation::setVisibility(Uml::Visibility value, Uml::Role_Type role) {
    m_pRole[role]->setVisibility(value);
}

void UMLAssociation::setChangeability(Uml::Changeability_Type value, Uml::Role_Type role) {
    m_pRole[role]->setChangeability(value);
}

void UMLAssociation::setMulti(const TQString &value, Uml::Role_Type role) {
    m_pRole[role]->setMultiplicity(value);
}

void UMLAssociation::setRoleName(const TQString &value, Uml::Role_Type role) {
    m_pRole[role]->setName(value);
}

void UMLAssociation::setRoleDoc(const TQString &doc, Uml::Role_Type role) {
    m_pRole[role]->setDoc(doc);
}

TQString UMLAssociation::ChangeabilityToString(Uml::Changeability_Type type) {
    switch (type) {
    case Uml::chg_Frozen:
        return "frozen";
        break;
    case Uml::chg_AddOnly:
        return "addOnly";
        break;
    case Uml::chg_Changeable:
    default:
        return "changeable";
        break;
    }
}

void UMLAssociation::init(Uml::Association_Type type, UMLObject *roleAObj, UMLObject *roleBObj) {
    m_AssocType = type;
    m_BaseType = ot_Association;
    m_Name = "";
    m_bOldLoadMode = false;
    nrof_parent_widgets = -1;
    if (!UMLApp::app()->getDocument()->loading())
        m_pUMLPackage = UMLApp::app()->getDocument()->currentRoot();
    m_pRole[Uml::A] = new UMLRole (this, roleAObj, Uml::A);
    m_pRole[Uml::B] = new UMLRole (this, roleBObj, Uml::B);
}


#include "association.moc"
