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

/*  This code generated by:
 *      Author : thomas
 *      Date   : Thu Jun 19 2003
 */

#include <kdebug.h>
#include <qregexp.h>

#include "classifiercodedocument.h"
#include "association.h"
#include "attribute.h"
#include "operation.h"
#include "classifierlistitem.h"
#include "classifier.h"
#include "codegenerator.h"
#include "uml.h"
#include "umldoc.h"
#include "umlrole.h"
#include "umlattributelist.h"
#include "umloperationlist.h"
#include "codegenerators/codegenfactory.h"

// Constructors/Destructors
//

ClassifierCodeDocument::ClassifierCodeDocument ( UMLClassifier * parent )
{
    init (parent);
}

ClassifierCodeDocument::~ClassifierCodeDocument ( )
{
    for (CodeClassFieldListIt ccflit(m_classfieldVector); ccflit.current(); ++ccflit)
    {
        CodeClassField * cf = ccflit.current();
        delete cf;
    }
    m_classfieldVector.clear();
}

//
// Methods
//


// Accessor methods
//

/** get a list of codeclassifier objects held by this classifiercodedocument that meet the passed criteria.
 */
CodeClassFieldList ClassifierCodeDocument::getSpecificClassFields (CodeClassField::ClassFieldType cfType)
{
    CodeClassFieldList list;
    for (CodeClassFieldListIt ccflit(m_classfieldVector); ccflit.current(); ++ccflit)
    {
        CodeClassField * cf = ccflit.current();
        if (cf->getClassFieldType() == cfType)
            list.append(cf);
    }
    return list;
}

/** get a list of codeclassifier objects held by this classifiercodedocument that meet the passed criteria.
 */
CodeClassFieldList ClassifierCodeDocument::getSpecificClassFields (CodeClassField::ClassFieldType cfType, bool isStatic)
{
    CodeClassFieldList list;
    list.setAutoDelete(false);
    for (CodeClassFieldListIt ccflit(m_classfieldVector); ccflit.current(); ++ccflit)
    {
        CodeClassField * cf = ccflit.current();
        if (cf->getClassFieldType() == cfType && cf->getStatic() == isStatic)
            list.append(cf);
    }
    return list;
}

/** get a list of codeclassifier objects held by this classifiercodedocument that meet the passed criteria.
 */
CodeClassFieldList ClassifierCodeDocument::getSpecificClassFields (CodeClassField::ClassFieldType cfType, Uml::Visibility visibility)
{
    CodeClassFieldList list;
    list.setAutoDelete(false);
    for (CodeClassFieldListIt ccflit(m_classfieldVector); ccflit.current(); ++ccflit)
    {
        CodeClassField * cf = ccflit.current();
        if (cf->getClassFieldType() == cfType && cf->getVisibility() == visibility)
            list.append(cf);
    }
    return list;
}

/** get a list of codeclassifier objects held by this classifiercodedocument that meet the passed criteria.
 */
CodeClassFieldList ClassifierCodeDocument::getSpecificClassFields (CodeClassField::ClassFieldType cfType, bool isStatic, Uml::Visibility visibility)
{
    CodeClassFieldList list;
    list.setAutoDelete(false);
    for (CodeClassFieldListIt ccflit(m_classfieldVector); ccflit.current(); ++ccflit)
    {
        CodeClassField * cf = ccflit.current();
        if (cf->getClassFieldType() == cfType && cf->getVisibility() == visibility && cf->getStatic() == isStatic )
            list.append(cf);
    }
    return list;
}

// do we have accessor methods for lists of objects?
// (as opposed to lists of primitive types like 'int' or 'float', etc)
bool ClassifierCodeDocument::hasObjectVectorClassFields() {
    for (CodeClassFieldListIt ccflit(m_classfieldVector); ccflit.current(); ++ccflit)
    {
        CodeClassField * cf = ccflit.current();
        if(cf->getClassFieldType() != CodeClassField::Attribute)
        {
            UMLRole * role = dynamic_cast<UMLRole*>(cf->getParentObject());
            QString multi = role->getMultiplicity();
            if (
                multi.contains(QRegExp("[23456789\\*]")) ||
                multi.contains(QRegExp("1\\d"))
            )
                return true;
        }
    }
    return false;
}

bool ClassifierCodeDocument::hasClassFields() {
    if(m_classfieldVector.count() > 0 )
        return true;
    return false;
}

/**
 * Tell if one or more codeclassfields are derived from associations.
 */
bool ClassifierCodeDocument::hasAssociationClassFields() {
    CodeClassFieldList list = getSpecificClassFields(CodeClassField::Attribute);
    return (m_classfieldVector.count() - list.count()) > 0 ? true : false;
}

/**
 * Tell if one or more codeclassfields are derived from attributes.
 */
bool ClassifierCodeDocument::hasAttributeClassFields() {
    CodeClassFieldList list = getSpecificClassFields(CodeClassField::Attribute);
    return list.count() > 0 ? true : false;
}

/**
 * Add a CodeClassField object to the m_classfieldVector List
 * @return boolean true if successful in adding
 */
// We DON'T add methods of the code classfield here because we need to allow
// the codegenerator writer the liberty to organize their document as they desire.
bool ClassifierCodeDocument::addCodeClassField ( CodeClassField * add_object ) {
    UMLObject * umlobj = add_object->getParentObject();
    if(!(m_classFieldMap.contains(umlobj)))
    {
        m_classfieldVector.append(add_object);
        m_classFieldMap.insert(umlobj,add_object);

        return true;
    }
    return false;
}

// this is a slot..should only be called from a signal
void ClassifierCodeDocument::addAttributeClassField (UMLClassifierListItem *obj, bool syncToParentIfAdded) {
    UMLAttribute *at = (UMLAttribute*)obj;
    CodeClassField * cf = CodeGenFactory::newCodeClassField(this, at);
    if(cf)
        if (addCodeClassField(cf) && syncToParentIfAdded)
            updateContent();
}

/**
 * Remove a CodeClassField object from m_classfieldVector List
 */
bool ClassifierCodeDocument::removeCodeClassField ( CodeClassField * remove_object ) {
    UMLObject * umlobj = remove_object->getParentObject();
    if(m_classFieldMap.contains(umlobj))
    {
        if (m_classfieldVector.removeRef(remove_object))
        {
            // remove from our classfield map
            m_classFieldMap.remove(umlobj);
            delete remove_object;
            return true;
        }
    }
    return false;
}

void ClassifierCodeDocument::removeAttributeClassField(UMLClassifierListItem *obj)
{
    CodeClassField * remove_object = m_classFieldMap[obj];
    if(remove_object)
        removeCodeClassField(remove_object);
}

void ClassifierCodeDocument::removeAssociationClassField (UMLAssociation *assoc )
{

    // the object could be either (or both!) role a or b. We should check
    // both parts of the association.
    CodeClassField * remove_object = m_classFieldMap[assoc->getUMLRole(Uml::A)];
    if(remove_object)
        removeCodeClassField(remove_object);

    // check role b
    remove_object = m_classFieldMap[assoc->getUMLRole(Uml::B)];
    if(remove_object)
        removeCodeClassField(remove_object);

}

/**
 * Get the list of CodeClassField objects held by m_classfieldVector
 * @return CodeClassFieldList list of CodeClassField objects held by
 * m_classfieldVector
 */
CodeClassFieldList * ClassifierCodeDocument::getCodeClassFieldList ( ) {
    return &m_classfieldVector;
}

/**
 * Get the value of m_parentclassifier
 * @return the value of m_parentclassifier
 */
UMLClassifier * ClassifierCodeDocument::getParentClassifier ( ) {
    return m_parentclassifier;
}

/**
 * @return      QPtrList<CodeOperation>
 */
QPtrList<CodeOperation> ClassifierCodeDocument::getCodeOperations ( ) {

    QPtrList<CodeOperation> list;
    list.setAutoDelete(false);

    TextBlockList * tlist = getTextBlockList();
    for (TextBlock *tb = tlist->first(); tb; tb=tlist->next())
    {
        CodeOperation * cop = dynamic_cast<CodeOperation*>(tb);
        if(cop)
            list.append(cop);
    }
    return list;
}

/**
 * @param       op
 */
void ClassifierCodeDocument::addOperation (UMLClassifierListItem * op ) {

    QString tag = CodeOperation::findTag((UMLOperation*)op);
    CodeOperation * codeOp = dynamic_cast<CodeOperation*>(findTextBlockByTag(tag, true));
    bool createdNew = false;

    // create the block, if it doesn't already exist
    if(!codeOp)
    {
        codeOp = CodeGenFactory::newCodeOperation(this, (UMLOperation*)op);
        createdNew = true;
    }

    // now try to add it. This may fail because it (or a block with
    // the same tag) is already in the document somewhere. IF we
    // created this new, then we need to delete our object.
    if(!addCodeOperation(codeOp)) // wont add if already present
        if(createdNew)
            delete codeOp;

}

/**
 * @param       op
 */
void ClassifierCodeDocument::removeOperation (UMLClassifierListItem * op ) {

    QString tag = CodeOperation::findTag((UMLOperation*)op);
    TextBlock *tb = findTextBlockByTag(tag, true);
    if(tb)
    {
        if(removeTextBlock(tb)) // wont add if already present
            delete tb; // delete unused operations
        else
            kdError()<<"Cant remove CodeOperation from ClassCodeDocument!"<<endl;

    }
    else
        kdError()<<"Cant Find codeOperation for deleted operation!"<<endl;
}

// Other methods
//

void ClassifierCodeDocument::addCodeClassFieldMethods(CodeClassFieldList &list )
{

    for (CodeClassFieldListIt ccflit(list); ccflit.current(); ++ccflit)
    {
        CodeClassField * field = ccflit.current();
        CodeAccessorMethodList * list = field->getMethodList();
        for (CodeAccessorMethod * method = list->first(); method; method = list->next())
        {
            /*
                                QString tag = method->getTag();
                                if(tag.isEmpty())
                                {
                                        tag = getUniqueTag();
                                        method->setTag(tag);
                                }
            */
            addTextBlock(method); // wont add if already exists in document, will add a tag if missing;

        }

    }

}

// add declaration blocks for the passed classfields
void ClassifierCodeDocument::declareClassFields (CodeClassFieldList & list ,
        CodeGenObjectWithTextBlocks * parent )
{

    for (CodeClassFieldListIt ccflit(list); ccflit.current(); ++ccflit)
    {
        CodeClassField * field = ccflit.current();
        CodeClassFieldDeclarationBlock * declBlock = field->getDeclarationCodeBlock();

        /*
                        // if it has a tag, check
                        if(!declBlock->getTag().isEmpty())
                        {
                                // In C++, because we may shift the declaration to a different parent
                                // block for a change in scope, we need to track down any pre-existing
                                // location, and remove FIRST before adding to new parent
                                CodeGenObjectWithTextBlocks * oldParent = findParentObjectForTaggedTextBlock (declBlock->getTag());
                                if(oldParent) {
                                        if(oldParent != parent)
                                                oldParent->removeTextBlock(declBlock);
                                }
                        }
        */

        parent->addTextBlock(declBlock); // wont add it IF its already present. Will give it a tag if missing

    }
}

bool ClassifierCodeDocument::parentIsClass() {
    return (m_parentclassifier->getBaseType() == Uml::ot_Class);
}

bool ClassifierCodeDocument::parentIsInterface() {
    return (m_parentclassifier->getBaseType() == Uml::ot_Interface);
}

/**
 * Init from a UMLClassifier object.
 * @param       classifier
 * @param       package
 */
void ClassifierCodeDocument::init (UMLClassifier * c )
{

    m_parentclassifier = c;
    m_classfieldVector.setAutoDelete(false);

    updateHeader();
    syncNamesToParent();
    // initCodeClassFields(); // cant call here?..newCodeClassField is pure virtual

    // slots
    if (parentIsClass())  {
        connect(c,SIGNAL(attributeAdded(UMLClassifierListItem*)),this,SLOT(addAttributeClassField(UMLClassifierListItem*)));
        connect(c,SIGNAL(attributeRemoved(UMLClassifierListItem*)),this,SLOT(removeAttributeClassField(UMLClassifierListItem*)));
    }

    connect(c,SIGNAL(sigAssociationAdded(UMLAssociation*)),this,SLOT(addAssociationClassField(UMLAssociation*)));
    connect(c,SIGNAL(sigAssociationRemoved(UMLAssociation*)),this,SLOT(removeAssociationClassField(UMLAssociation*)));
    connect(c,SIGNAL(operationAdded(UMLClassifierListItem*)),this,SLOT(addOperation(UMLClassifierListItem*)));
    connect(c,SIGNAL(operationRemoved(UMLClassifierListItem*)),this,SLOT(removeOperation(UMLClassifierListItem*)));
    connect(c,SIGNAL(modified()),this,SLOT(syncToParent()));

}

// IF the classifier object is modified, this will get called.
// @todo we cannot make this virtual as long as the
//       ClassifierCodeDocument constructor calls it because that gives
//       a call-before-construction error.
// Example of the problem: CPPSourceCodeDocument reimplementing syncNamesToParent()
//  CPPCodeGenerator::initFromParentDocument()
//    CodeDocument * codeDoc = new CPPSourceCodeDocument(c);
//      CPPSourceCodeDocument::CPPSourceCodeDocument(UMLClassifier * concept)
//       : ClassifierCodeDocument(concept)
//        ClassifierCodeDocument::ClassifierCodeDocument(concept)
//         init(concept);
//          syncNamesToParent();
//            dispatches to CPPSourceCodeDocument::syncNamesToParent()
//            but that object is not yet constructed.
//
void ClassifierCodeDocument::syncNamesToParent( ) {
    QString fileName = CodeGenerator::cleanName(getParentClassifier()->getName());
    if (!UMLApp::app()->activeLanguageIsCaseSensitive()) {
        // @todo let the user decide about mixed case file names (codegen setup menu)
        fileName = fileName.lower();
    }
    setFileName(fileName);
    setPackage(m_parentclassifier->getUMLPackage());
}

void ClassifierCodeDocument::synchronize( ) {

    updateHeader(); // doing this insures time/date stamp is at the time of this call
    syncNamesToParent();
    updateContent();
    syncClassFields();
    updateOperations();

}

void ClassifierCodeDocument::syncClassFields( )
{
    for (CodeClassFieldListIt ccflit(m_classfieldVector); ccflit.current(); ++ccflit)
    {
        CodeClassField * cf = ccflit.current();
        cf->synchronize();
    }
}

void ClassifierCodeDocument::updateOperations( ) {

    UMLOperationList opList(getParentClassifier()->getOpList());
    for (UMLOperation *op = opList.first(); op; op = opList.next())
    {
        QString tag = CodeOperation::findTag(op);
        CodeOperation * codeOp = dynamic_cast<CodeOperation*>(findTextBlockByTag(tag, true));
        bool createdNew = false;

        if(!codeOp)
        {
            codeOp = CodeGenFactory::newCodeOperation(this, op);
            createdNew = true;
        }

        // now try to add it. This may fail because it (or a block with
        // the same tag) is already in the document somewhere. IF we
        // created this new, then we need to delete our object.
        if(!addCodeOperation(codeOp)) // wont add if already present
            if(createdNew)
                delete codeOp;

        // synchronize all non-new operations
        if(!createdNew)
            codeOp->syncToParent();
    }

}

void ClassifierCodeDocument::syncToParent( ) {
    synchronize();
}

/**
 * add codeclassfields to this classifiercodedocument. IF a codeclassfield
 * already exists, it is not added.
 */
void ClassifierCodeDocument::initCodeClassFields ( ) {

    UMLClassifier * c = getParentClassifier();
    // first, do the code classifields that arise from attributes
    if (parentIsClass()) {
        UMLAttributeList alist = c->getAttributeList();
        for(UMLAttribute * at = alist.first(); at; at = alist.next())
        {
            CodeClassField * field = CodeGenFactory::newCodeClassField(this, at);
            addCodeClassField(field);
        }

    }

    // now, do the code classifields that arise from associations
    UMLAssociationList ap = c->getSpecificAssocs(Uml::at_Association);
    UMLAssociationList ag = c->getAggregations();
    UMLAssociationList ac = c->getCompositions();
    UMLAssociationList selfAssoc = c->getSpecificAssocs(Uml::at_Association_Self);

    updateAssociationClassFields(ap);
    updateAssociationClassFields(ag);
    updateAssociationClassFields(ac);
    updateAssociationClassFields(selfAssoc);

}

void ClassifierCodeDocument::updateAssociationClassFields ( UMLAssociationList &assocList )
{
    CodeClassFieldList list;
    for(UMLAssociation * a=assocList.first(); a; a=assocList.next())
        addAssociationClassField(a, false); // syncToParent later
}

void ClassifierCodeDocument::addAssociationClassField (UMLAssociation * a, bool syncToParentIfAdded)
{

    Uml::IDType cid = getParentClassifier()->getID(); // so we know who 'we' are
    bool printRoleA = false, printRoleB = false, shouldSync = false;
    // it may seem counter intuitive, but you want to insert the role of the
    // *other* class into *this* class.
    if (a->getObjectId(Uml::A) == cid)
        printRoleB = true;

    if (a->getObjectId(Uml::B) == cid)
        printRoleA = true;

    // grab RoleB decl
    if (printRoleB)
    {

        UMLRole * role = a->getUMLRole(Uml::B);
        if(!m_classFieldMap.contains((UMLObject*)role))
        {
            CodeClassField * classfield = CodeGenFactory::newCodeClassField(this, role);
            if( addCodeClassField(classfield))
                shouldSync = true;
        }
    }

    // print RoleA decl
    if (printRoleA)
    {
        UMLRole * role = a->getUMLRole(Uml::A);
        if(!m_classFieldMap.contains((UMLObject*)role))
        {
            CodeClassField * classfield = CodeGenFactory::newCodeClassField(this, role);
            if( addCodeClassField(classfield))
                shouldSync = true;
        }
    }

    if (shouldSync && syncToParentIfAdded)
        syncToParent(); // needed for a slot add

}

/** set the class attributes of this object from
 * the passed element node.
 */
void ClassifierCodeDocument::setAttributesFromNode ( QDomElement & elem )
{

    // NOTE: we DON'T set the parent here as we ONLY get to this point
    // IF the parent codegenerator could find a matching parent classifier
    // that already has a code document.

    // We FIRST set code class field stuff..check re-linnking with
    // accessor methods by looking for our particular child element
    QDomNode node = elem.firstChild();
    QDomElement childElem = node.toElement();
    while( !childElem.isNull() ) {
        QString tag = childElem.tagName();
        if( tag == "classfields" ) {
            // load classfields
            loadClassFieldsFromXMI(childElem);
            break;
        }
        node = childElem.nextSibling();
        childElem= node.toElement();
    }

    // call super-class after. THis will populate the text blocks (incl
    // the code accessor methods above) as is appropriate
    CodeDocument::setAttributesFromNode(elem);

}

// look at all classfields currently in document.. match up
// by parent object ID and Role ID (needed for self-association CF's)
CodeClassField *
ClassifierCodeDocument::findCodeClassFieldFromParentID (Uml::IDType id,
        int role_id)
{
    for (CodeClassFieldListIt ccflit(m_classfieldVector); ccflit.current(); ++ccflit)
    {
        CodeClassField * cf = ccflit.current();
        if(role_id == -1) { // attribute-based
            if (STR2ID(cf->getID()) == id)
                return cf;
        } else { // association(role)-based
            const Uml::Role_Type r = (Uml::Role_Type)role_id;
            UMLRole * role = dynamic_cast<UMLRole *>(cf->getParentObject());
            if(role && STR2ID(cf->getID()) == id && role->getRole() == r)
                return cf;
        }
    }

    // shouldn't happen..
    kdError() << "Failed to find codeclassfield for parent uml id:"
    << ID2STR(id) << " (role id:" << role_id
    << ") Do you have a corrupt classifier code document?"
    << endl;

    return (CodeClassField*) NULL; // not found
}

void ClassifierCodeDocument::loadClassFieldsFromXMI( QDomElement & elem) {

    QDomNode node = elem.firstChild();
    QDomElement childElem = node.toElement();
    while( !childElem.isNull() ) {
        QString nodeName = childElem.tagName();
        if( nodeName == "codeclassfield")
        {
            QString id = childElem.attribute("parent_id","-1");
            int role_id = childElem.attribute("role_id","-1").toInt();
            CodeClassField * cf = findCodeClassFieldFromParentID(STR2ID(id), role_id);
            if(cf)
            {
                // Because we just may change the parent object here,
                // we need to yank it from the map of umlobjects
                m_classFieldMap.remove(cf->getParentObject());

                // configure from XMI
                cf->loadFromXMI(childElem);

                // now add back in
                m_classFieldMap.insert(cf->getParentObject(),cf);

            } else
                kdError()<<" LoadFromXMI: can't load classfield parent_id:"<<id<<" do you have a corrupt savefile?"<<endl;
        }
        node = childElem.nextSibling();
        childElem= node.toElement();
    }
}

/**
 * Save the XMI representation of this object
 */
void ClassifierCodeDocument::saveToXMI ( QDomDocument & doc, QDomElement & root ) {
#if 0
    // avoid the creation of primitive data type
    QString strType;
    if (getParentClassifier()->getBaseType() == Uml::ot_Datatype) {
        strType = getParentClassifier()->getName();
        // lets get the default code generator to check if it is a primitive data type
        // there's a reason to create files for int/boolean and so ?
        if (getParentGenerator()->isReservedKeyword(strType))
           return;
    }
#endif
    QDomElement docElement = doc.createElement( "classifiercodedocument" );

    setAttributesOnNode(doc, docElement);

    root.appendChild( docElement );
}

/**
 * load params from the appropriate XMI element node.
 */
void ClassifierCodeDocument::loadFromXMI ( QDomElement & root ) {

    // set attributes/fields
    setAttributesFromNode(root);

    // now sync our doc, needed?
    // synchronize();
}

/** set attributes of the node that represents this class
 * in the XMI document.
 */
void ClassifierCodeDocument::setAttributesOnNode ( QDomDocument & doc, QDomElement & docElement)
{

    // do super-class first
    CodeDocument::setAttributesOnNode(doc, docElement);

    // cache local attributes/fields
    docElement.setAttribute("parent_class", ID2STR(getParentClassifier()->getID()));

    // (code) class fields
    // which we will store in its own separate child node block
    QDomElement fieldsElement = doc.createElement( "classfields" );
    for (CodeClassFieldListIt ccflit(m_classfieldVector); ccflit.current(); ++ccflit)
    {
        CodeClassField * field = ccflit.current();
        field->saveToXMI(doc, fieldsElement);
    }
    docElement.appendChild( fieldsElement);

}

TextBlock * ClassifierCodeDocument::findCodeClassFieldTextBlockByTag (const QString &tag)
{

    for (CodeClassFieldListIt ccflit(m_classfieldVector); ccflit.current(); ++ccflit)
    {
        CodeClassField * cf = ccflit.current();
        CodeClassFieldDeclarationBlock * decl = cf->getDeclarationCodeBlock();
        if(decl && decl->getTag() == tag)
            return decl;
        // well, if not in the decl block, then in the methods perhaps?
        CodeAccessorMethodList * mlist = cf->getMethodList();
        CodeAccessorMethod *m;
        for(CodeAccessorMethodListIt it(*mlist); (m = it.current()) != NULL; ++it)
            if(m->getTag() == tag)
                return m;
    }

    // if we get here, we failed.
    return (TextBlock*) NULL;

}


#include "classifiercodedocument.moc"
