
/***************************************************************************
 *                                                                         *
 *   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) 2007 Jari-Matti Mäkelä <jmjm@iki.fi>                    *
 *   Umbrello UML Modeller Authors <uml-devel@uml.sf.net>                  *
 ***************************************************************************/

/***************************************************************************
    This is the "old" code generator that does not support code editing
    in the Modeller but uses significantly less file space because the
    source code is not replicated in the XMI file.
 ***************************************************************************/

// own header
#include "dwriter.h"
// qt includes
#include <tqfile.h>
#include <tqtextstream.h>
#include <tqregexp.h>
// kde includes
#include <kdebug.h>
// app includes
#include "../umldoc.h"
#include "../classifier.h"
#include "../operation.h"
#include "../attribute.h"
#include "../association.h"
#include "../template.h"
#include "../umltemplatelist.h"
#include "codegen_utils.h"

DWriter::DWriter() {
    startline = m_endl + m_indentation;
}

DWriter::~DWriter() {}

Uml::Programming_Language DWriter::getLanguage() {
    return Uml::pl_D;
}

// FIXME: doesn't work yet
void DWriter::writeModuleDecl(UMLClassifier *c, TQTextStream &d) {
    if(!c->getPackage().isEmpty())
        d << "module " << c->getPackage() << ";" << m_endl;

    writeBlankLine(d);
}

void DWriter::writeModuleImports(UMLClassifier *c, TQTextStream &d) {
    // another preparation, determine what we have
    UMLAssociationList associations = c->getSpecificAssocs(Uml::at_Association); // BAD! only way to get "general" associations.
    UMLAssociationList uniAssociations = c->getUniAssociationToBeImplemented();

    UMLAssociationList aggregations = c->getAggregations();
    UMLAssociationList compositions = c->getCompositions();

    bool hasAssociations = aggregations.count() + associations.count() +
         compositions.count() + uniAssociations.count() > 0;

    if (hasAssociations) {
        // import tango, if that mode is set
        writeBlankLine(d);
    }

    //only import classes in a different package as this class
    UMLPackageList imports;
    findObjectsRelated(c, imports);
    for (UMLPackage *con = imports.first(); con; con = imports.next()) {
        if (con->getBaseType() == Uml::ot_Datatype)
            continue;
        TQString pkg = con->getPackage();
        if (!pkg.isEmpty() && pkg != c->getPackage())
            d << "import " << pkg << "." << cleanName(con->getName()) << ";"
            << m_endl;
    }

    writeBlankLine(d);
}

void DWriter::writeClass(UMLClassifier *c) {
    if (!c) {
        kDebug()<<"Cannot write class of NULL concept!\n";
        return;
    }

    isInterface = c->isInterface();

    TQString fileName = cleanName(c->getName().lower());

    //find an appropriate name for our file
    fileName = findFileName(c, ".d");
    if (fileName.isEmpty()) {
        emit codeGenerated(c, false);
        return;
    }

    // check that we may open that file for writing
    TQFile file;
    if ( !openFile(file, fileName) ) {
        emit codeGenerated(c, false);
        return;
    }

    // open text stream to file
    TQTextStream d(&file);

    //try to find a heading file (license, coments, etc)
    TQString str;
    str = getHeadingFile(".d");
    if(!str.isEmpty()) {
        str.replace(TQRegExp("%filename%"),fileName);
        str.replace(TQRegExp("%filepath%"),file.name());
        d<<str<<m_endl;
    }

    // source file begins with the module declaration
    writeModuleDecl(c, d);

    // imports
    writeModuleImports(c, d);

    // write the opening declaration for the class incl any documentation,
    // interfaces and/or inheritence issues we have
    writeClassDecl(c, d);

    // start body of class
    d << " {" << m_endl;


    // Preparations
    //

    // sort attributes by Scope
    UMLAttributeList  atl;
    UMLAttributeList  atpub, atprot, atpriv, atpkg, atexport;
    UMLAttributeList  final_atpub, final_atprot, final_atpriv, final_atpkg, final_atexport;

    atpub.setAutoDelete(false);
    final_atpub.setAutoDelete(false);
    atprot.setAutoDelete(false);
    final_atprot.setAutoDelete(false);
    atpriv.setAutoDelete(false);
    final_atpriv.setAutoDelete(false);
    atpkg.setAutoDelete(false);
    final_atpkg.setAutoDelete(false);
    atexport.setAutoDelete(false);
    final_atexport.setAutoDelete(false);

    if (!isInterface) {
        UMLAttributeList atl = c->getAttributeList();
        for (UMLAttribute *at = atl.first(); at ; at = atl.next()) {
            switch(at->getVisibility())
            {
                case Uml::Visibility::Public:
                    if(at->getStatic())
                        final_atpub.append(at);
                    else
                        atpub.append(at);
                    break;
                case Uml::Visibility::Protected:
                    if(at->getStatic())
                        final_atprot.append(at);
                    else
                        atprot.append(at);
                    break;
                case Uml::Visibility::Private:
                    if(at->getStatic())
                        final_atpriv.append(at);
                    else
                        atpriv.append(at);
                    break;/* TODO: requires support from the gui & other structures
                case Uml::Visibility::Package:
                    if(at->getStatic())
                    final_atpkg.append(at);
                    else
                    atpkg.append(at);
                    break;
                case Uml::Visibility::Export:
                    if(at->getStatic())
                    final_atexport.append(at);
                    else
                    atexport.append(at);
                    break;*/
                default:
                    break;
            }
        }
    }

    // another preparation, determine what we have
    UMLAssociationList associations = c->getSpecificAssocs(Uml::at_Association); // BAD! only way to get "general" associations.
    UMLAssociationList uniAssociations = c->getUniAssociationToBeImplemented();

    UMLAssociationList aggregations = c->getAggregations();
    UMLAssociationList compositions = c->getCompositions();

    bool hasAssociations = aggregations.count() + associations.count() + compositions.count() + uniAssociations.count() > 0;
    bool hasAttributes = atl.count() > 0;
    bool hasAccessorMethods = hasAttributes || hasAssociations;
    bool hasOperationMethods = c->getOpList().count() > 0;

    // ATTRIBUTES
    //

    // write comment for section IF needed
    if (forceDoc() || hasAccessorMethods)
    {
        writeComment("", m_indentation, d);
        writeComment("Fields", m_indentation, d);
        writeComment("", m_indentation, d);
        writeBlankLine(d);
    }

    writeAttributeDecls(final_atpub, final_atprot, final_atpriv, d);
    writeAttributeDecls(atpub, atprot, atpriv, d);

    writeAssociationDecls(associations, c->getID(), d);
    writeAssociationDecls(uniAssociations, c->getID(), d);
    writeAssociationDecls(aggregations, c->getID(), d);
    writeAssociationDecls(compositions, c->getID(), d);

    //FIXME: find constructors and write them here

    // write constructors
    if(!isInterface) writeConstructor(c, d);


    // METHODS
    //

    // write comment for sub-section IF needed
    if (forceDoc() || hasAccessorMethods ) {
        writeComment("", m_indentation, d);
        writeComment("Accessors", m_indentation, d);
        writeComment("", m_indentation, d);
        writeBlankLine(d);
    }


    // Accessors for attributes
    writeAttributeMethods(final_atpub, Uml::Visibility::Public, d);
    writeAttributeMethods(final_atprot, Uml::Visibility::Protected, d);
    writeAttributeMethods(final_atpriv, Uml::Visibility::Private, d);
    writeAttributeMethods(atpub, Uml::Visibility::Public, d);
    writeAttributeMethods(atprot, Uml::Visibility::Protected, d);
    writeAttributeMethods(atpriv, Uml::Visibility::Private, d);

    // accessor methods for associations

    // first: determine the name of the other class
    writeAssociationMethods(associations, c, d);
    writeAssociationMethods(uniAssociations, c, d);
    writeAssociationMethods(aggregations, c, d);
    writeAssociationMethods(compositions, c, d);

    // Other operation methods
    // all other operations are now written

    // write comment for sub-section IF needed
    if (forceDoc() || hasOperationMethods) {
        writeComment("", m_indentation, d);
        writeComment("Other methods", m_indentation, d);
        writeComment("", m_indentation, d);
        writeBlankLine(d);
    }

    writeOperations(c, d);

    d << "}" << m_endl; // end class

    file.close();
    emit codeGenerated(c, true);
}

void DWriter::writeClassDecl(UMLClassifier *c, TQTextStream &d) {

    // class documentation
    if (!c->getDoc().isEmpty()) {
        writeDocumentation("", c->getDoc(), "", "", d);
    }

    /*
     * Class declaration
     *
     * (private) class foo(T, ..., Z) : class1, ..., classN, interface1, ..., interfaceN
     *     a       b    c      d      e          f                        g
     */

    // (a) visibility modifier
    switch(c->getVisibility()) {
        case Uml::Visibility::Private: d << "private "; break;
        default: break;
    }

    // (b) keyword
    // TODO what about structs?
    if (isInterface) {
        d << "interface ";
    } else {
        if (c->getAbstract()) {
            d << "abstract ";
        }

        d << "class ";
    }

    // (c) class name
    TQString classname = cleanName(c->getName()); // our class name
    d << classname;

    // (d) template parameters
    UMLTemplateList template_params = c->getTemplateList();
    if (template_params.count()) {
        d << "(";

        for (UMLTemplate *t = template_params.first(); t; ) {
            // TODO: hm, leaving the type blank results in "class"
            // so we omit it (also because "class" in this context is illegal)
            if (t->getTypeName() != "class") {
                d << t->getTypeName();
                d << " ";
            }

            d << t->getName();

            if ((t = template_params.next()) != NULL)
                d << ", ";
        }

        d << ")";
    }

    // (e) inheritances
    UMLClassifierList superclasses =
        c->findSuperClassConcepts(UMLClassifier::CLASS);
    UMLClassifierList superinterfaces =
        c->findSuperClassConcepts(UMLClassifier::INTERFACE);

    int count = superclasses.count() + superinterfaces.count();

    if (count > 0) {
        d << " : ";

        // (f) base classes
        for (UMLClassifier * concept= superclasses.first(); concept; concept = superclasses.next()) {
            d << cleanName(concept->getName());

            count--;

            if (count>0) d << ", ";
        }

        // (g) interfaces
        for (UMLClassifier * concept= superinterfaces.first(); concept; concept = superinterfaces.next()) {
            d << cleanName(concept->getName());

            count--;

            if (count>0) d << ", ";
        }
    }
}

void DWriter::writeProtectionMod(Uml::Visibility visibility, TQTextStream &d) {
    d << m_indentation << scopeToDDecl(visibility) << ":" << m_endl << m_endl;
}

void DWriter::writeAttributeDecl(Uml::Visibility visibility, UMLAttributeList &atlist, TQTextStream &d) {
    if (atlist.count()==0) return;

    writeProtectionMod(visibility, d);

    for(UMLAttribute *at=atlist.first(); at; at=atlist.next()) {
        // documentation
        if (!at->getDoc().isEmpty()) {
            writeComment(at->getDoc(), m_indentation, d, true);
        }

        d << m_indentation;

        // static attribute?
        if (at->getStatic()) d << "static ";

        // method return type
        d << fixTypeName(at->getTypeName()) << " ";

        // TODO: find out whether this class has accessors or not
        bool hasAccessorMethods = true;

        // attribute name
        if (hasAccessorMethods) {
            d << "m_";
        }
        d << cleanName(at->getName());

        // initial value
        TQString initVal = fixInitialStringDeclValue(at->getInitialValue(), at->getTypeName());
        if (!initVal.isEmpty()) d << " = " << initVal;
        d << ";" << m_endl << m_endl;
    }
}

void DWriter::writeAttributeDecls(UMLAttributeList &atpub, UMLAttributeList &atprot,
                                     UMLAttributeList &atpriv, TQTextStream &d ) {

    writeAttributeDecl(Uml::Visibility::Public, atpub, d);
    writeAttributeDecl(Uml::Visibility::Protected, atprot, d);
    writeAttributeDecl(Uml::Visibility::Private, atpriv, d);
    //TODO: export and package
}

void DWriter::writeAttributeMethods(UMLAttributeList &atpub, Uml::Visibility visibility, TQTextStream &d) {
    if (atpub.count()==0) return;

    writeProtectionMod(visibility, d);

    for(UMLAttribute *at=atpub.first(); at; at=atpub.next()) {
        TQString fieldName = cleanName(at->getName());
        writeSingleAttributeAccessorMethods(
            at->getTypeName(), "m_" + fieldName, fieldName, at->getDoc(),
            visibility, Uml::chg_Changeable, at->getStatic(), d);
    }
}

void DWriter::writeComment(const TQString &comment, const TQString &myIndent,
                           TQTextStream &d, bool dDocStyle) {
    if(dDocStyle) {
        d << myIndent << "/**" << m_endl;
    }

    TQStringList lines = TQStringList::split("\n", comment);

    if (lines.count() == 0) lines << comment;

    for (uint i = 0; i < lines.count(); ++i) {
        TQString tmp = lines[i];

        while (tmp.length() > 77) {
            uint l = tmp.left(77).findRev(' ');
            if (l < 1) l = tmp.find(' ', 77);
            if (l < 1 || l > tmp.length()) {
                d << myIndent << (dDocStyle ? " * " : "// ") << tmp << m_endl;
                break;
            }
            d << myIndent << (dDocStyle ? " * " : "// ") << tmp.left(l) << m_endl;
            tmp = tmp.right(tmp.length() - l);
        }

        d << myIndent << (dDocStyle ? " * " : "// ") << tmp << m_endl;
    }

    if(dDocStyle) {
        d << myIndent << " */" << m_endl;
    }
}

void DWriter::writeDocumentation(TQString header, TQString body, TQString end, TQString indent, TQTextStream &d) {
    d << indent << "/**" << m_endl;
    if (!header.isEmpty())
        d << formatDoc(header, indent+" * ");
    if (!body.isEmpty())
        d << formatDoc(body, indent+" * ");
    if (!end.isEmpty())
    {
        TQStringList lines = TQStringList::split( "\n", end);
        for (uint i= 0; i < lines.count(); i++)
            d << formatDoc(lines[i], indent + " * ");
    }
    d<<indent<< " */" << m_endl;
}

void DWriter::writeAssociationDecls(UMLAssociationList associations, Uml::IDType id, TQTextStream &d) {

    if( forceSections() || !associations.isEmpty() )
    {
        bool printRoleA = false, printRoleB = false;
        for(UMLAssociation *a = associations.first(); a; a = associations.next())
        {
            // it may seem counter intuitive, but you want to insert the role of the
            // *other* class into *this* class.
            if (a->getObjectId(Uml::A) == id)
                printRoleB = true;

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

            // First: we insert documentaion for association IF it has either role AND some documentation (!)
            if ((printRoleA || printRoleB) && !(a->getDoc().isEmpty()))
                writeComment(a->getDoc(), m_indentation, d);

            // print RoleB decl
            if (printRoleB)
            {
                TQString fieldClassName = cleanName(getUMLObjectName(a->getObject(Uml::B)));
                writeAssociationRoleDecl(fieldClassName, a->getRoleName(Uml::B), a->getMulti(Uml::B), a->getRoleDoc(Uml::B), a->getVisibility(Uml::B), d);
            }

            // print RoleA decl
            if (printRoleA)
            {
                TQString fieldClassName = cleanName(getUMLObjectName(a->getObject(Uml::A)));
                writeAssociationRoleDecl(fieldClassName, a->getRoleName(Uml::A), a->getMulti(Uml::A), a->getRoleDoc(Uml::A), a->getVisibility(Uml::A), d);
            }
        }
    }
}

void DWriter::writeAssociationRoleDecl(TQString fieldClassName,
        TQString roleName, TQString multi,
        TQString doc, Uml::Visibility /*visib*/, TQTextStream &d) {
    // ONLY write out IF there is a rolename given
    // otherwise its not meant to be declared in the code
    if (roleName.isEmpty()) return;

    if (!doc.isEmpty()) {
        writeComment(doc, m_indentation, d);
    }

    bool hasAccessors = true;

    // declare the association based on whether it is this a single variable
    // or a List (Vector). One day this will be done correctly with special
    // multiplicity object that we don't have to figure out what it means via regex.
    if(multi.isEmpty() || multi.contains(TQRegExp("^[01]$"))) {
        d << m_indentation << fieldClassName << " ";

        if (hasAccessors) d << "m_";

        d << deCapitaliseFirstLetter(roleName) << ";";
    } else {
        d << m_indentation << fieldClassName << "[] ";
        //TODO: templated containers

        if (hasAccessors) d << "m_";

        d << pluralize(deCapitaliseFirstLetter(roleName)) << ";";
        // from here we could initialize default values, or put in an init() section
        // of the constructors
    }

    // always put space between this and following decl, if any
    writeBlankLine(d);
}

void DWriter::writeAssociationMethods (UMLAssociationList associations, UMLClassifier *thisClass, TQTextStream &d) {
    if( forceSections() || !associations.isEmpty() ) {
        for(UMLAssociation *a = associations.first(); a; a = associations.next()) {
            // insert the methods to access the role of the other
            // class in the code of this one
            if (a->getObjectId(Uml::A) == thisClass->getID()) {
                // only write out IF there is a rolename given
                if(!a->getRoleName(Uml::B).isEmpty()) {
                    TQString fieldClassName = getUMLObjectName(a->getObject(Uml::B));
                    writeAssociationRoleMethod(fieldClassName,
                                               a->getRoleName(Uml::B),
                                               a->getMulti(Uml::B), a->getRoleDoc(Uml::B),
                                               a->getVisibility(Uml::B),
                                               a->getChangeability(Uml::B), d);
                }
            }

            if (a->getObjectId(Uml::B) == thisClass->getID()) {
                // only write out IF there is a rolename given
                if(!a->getRoleName(Uml::A).isEmpty()) {
                    TQString fieldClassName = getUMLObjectName(a->getObject(Uml::A));
                    writeAssociationRoleMethod(fieldClassName, a->getRoleName(Uml::A),
                                               a->getMulti(Uml::A),
                                               a->getRoleDoc(Uml::A),
                                               a->getVisibility(Uml::A),
                                               a->getChangeability(Uml::A),
                                               d);
                }
            }
        }
    }
}

void DWriter::writeAssociationRoleMethod (TQString fieldClassName, TQString roleName, TQString multi,
        TQString description, Uml::Visibility visib, Uml::Changeability_Type change,
        TQTextStream &d) {
    if(multi.isEmpty() || multi.contains(TQRegExp("^[01]$"))) {
        TQString fieldVarName = "m_" + deCapitaliseFirstLetter(roleName);

        writeSingleAttributeAccessorMethods(
            fieldClassName, fieldVarName, roleName, description, visib, change, false, d);
    } else {
        TQString fieldVarName = "m_" + pluralize(deCapitaliseFirstLetter(roleName));

        writeVectorAttributeAccessorMethods(
            fieldClassName, fieldVarName, pluralize(roleName), description, visib, change, d);
    }
}

void DWriter::writeVectorAttributeAccessorMethods (TQString fieldClassName, TQString fieldVarName,
        TQString fieldName, TQString description,
        Uml::Visibility /*visibility*/, Uml::Changeability_Type changeType,
        TQTextStream &d) {

    fieldClassName = fixTypeName(fieldClassName);
    TQString fieldNameUP = unPluralize(fieldName);
    TQString fieldNameUC = Codegen_Utils::capitalizeFirstLetter(fieldNameUP);

    // ONLY IF changeability is NOT Frozen
    if (changeType != Uml::chg_Frozen) {
        writeDocumentation("Adds a " + fieldNameUP + " to the list of " +
                           fieldName + '.', description, "", m_indentation, d);

        d << m_indentation << "void add" << fieldNameUC << "(";
        d << fieldClassName << " new" << fieldNameUC << ") {";
        d << startline << m_indentation << fieldVarName << " ~= new" << fieldNameUC << ";";
        d << startline << "}" << m_endl << m_endl;
    }

    // ONLY IF changeability is Changeable
    if (changeType == Uml::chg_Changeable) {
        writeDocumentation("Removes a " + fieldNameUP + " from the list of " +
                           fieldName + '.', description, "", m_indentation, d);

        d << m_indentation << "void remove" << fieldNameUC << "(";
        d << fieldClassName << " " << fieldNameUP << ") {" << startline;
        d << m_indentation << "int idx = " << fieldVarName << ".length;" << startline;
        d << m_indentation << "foreach(i, o; " << fieldVarName << ")" << startline;
        d << m_indentation << m_indentation << "if (o && o == " << fieldNameUP << ") {" << startline;
        d << m_indentation << m_indentation << m_indentation << "idx = i;" << startline;
        d << m_indentation << m_indentation << m_indentation << "break;" << startline;
        d << m_indentation << m_indentation << "}" << m_endl << startline;
        d << m_indentation << fieldVarName << " = " << fieldVarName;
        d << "[0..idx] ~ " << fieldVarName << "[idx..$];" << startline;
        d << "}" << m_endl << m_endl;
    }

    // always allow getting the list of stuff
    writeDocumentation("Returns the list of " + fieldName + '.',
                       description, "@return List of " + fieldName + '.',
                       m_indentation, d);

    d << m_indentation << fieldClassName << "[] get" << fieldName << "() {";
    d << startline << m_indentation << "return " << fieldVarName << ";";
    d << startline << "}" << m_endl << m_endl;
}


void DWriter::writeSingleAttributeAccessorMethods(TQString fieldClassName,
     TQString fieldVarName, TQString fieldName, TQString description, Uml::Visibility /*visibility*/,
     Uml::Changeability_Type change, bool isFinal, TQTextStream &d) {

    fieldClassName = fixTypeName(fieldClassName);
    TQString fieldNameUC = Codegen_Utils::capitalizeFirstLetter(fieldName);
    if (fieldName.left(2) == "m_") fieldName = fieldName.right(fieldName.length()-2);

    // set method
    if (change == Uml::chg_Changeable && !isFinal) {
        writeDocumentation("Sets the value of " + fieldName + '.', description,
                           "@param new" + fieldNameUC + " The new value of " + fieldName + '.',
                           m_indentation, d);

        d << m_indentation << fieldClassName << " " << fieldName << "(";
        d << fieldClassName << " new" << fieldNameUC << ") {";
        d << startline << m_indentation << "return " << fieldVarName << " = new" << fieldNameUC << ";";
        d << startline << "}" << m_endl << m_endl;
    }

    // get method
    writeDocumentation("Returns the value of " + fieldName + '.', description,
                       "@return The value of " + fieldName + '.',
                       m_indentation, d);

    d << m_indentation << fieldClassName << " " << fieldName << "() {";
    d << startline << m_indentation << "return " << fieldVarName << ";";
    d << startline << "}" << m_endl << m_endl;
}

void DWriter::writeConstructor(UMLClassifier *c, TQTextStream &d) {

    if (forceDoc())
    {
        d<<startline;
        writeComment("", m_indentation, d);
        writeComment("Constructors", m_indentation, d);
        writeComment("", m_indentation, d);
        writeBlankLine(d);
    }

    // write the first constructor
    TQString className = cleanName(c->getName());
    d << m_indentation << "public this("<<") { }";

}

// IF the type is "string" we need to declare it as
// the D Object "String" (there is no string primative in D).
// Same thing again for "bool" to "boolean"
TQString DWriter::fixTypeName(const TQString& string) {
    if (string.isEmpty())
        return "void";
    if (string == "string")
        return "char[]";
    if (string == "unsigned short")
        return "ushort";
    if (string == "unsigned int")
        return "uint";
    if (string == "unsigned long")
        return "ulong";
    return string;
}

TQStringList DWriter::defaultDatatypes() {
    TQStringList l;
    l << "void"
    << "bool"
    << "byte"
    << "ubyte"
    << "short"
    << "ushort"
    << "int"
    << "uint"
    << "long"
    << "ulong"
    << "cent"
    << "ucent"
    << "float"
    << "double"
    << "real"
    << "ifloat"
    << "idouble"
    << "ireal"
    << "cfloat"
    << "cdouble"
    << "creal"
    << "char"
    << "wchar"
    << "dchar";
    return l;
}


bool DWriter::compareDMethod(UMLOperation *op1, UMLOperation *op2) {
    if (op1 == NULL || op2 == NULL)
        return false;
    if (op1 == op2)
        return true;
    if (op1->getName() != op2->getName())
        return false;
    UMLAttributeList atl1 = op1->getParmList();
    UMLAttributeList atl2 = op2->getParmList();
    if (atl1.count() != atl2.count())
        return false;
    UMLAttribute *at1;
    UMLAttribute *at2;
    for (at1  = atl1.first(), at2 = atl2.first(); at1 && at2 ; at1 = atl1.next(),at2 = atl2.next())
    {
        if (at1->getTypeName() != at2->getTypeName())
            return false;
    }
    return true;

}

bool DWriter::dMethodInList(UMLOperation *umlOp, UMLOperationList &opl) {
    for (UMLOperation *op = opl.first(); op; op = opl.next()) {
        if (DWriter::compareDMethod(op, umlOp)) {
            return true;
        }
    }
    return false;
}

void DWriter::getSuperImplementedOperations(UMLClassifier *c, UMLOperationList &yetImplementedOpList ,UMLOperationList &toBeImplementedOpList, bool noClassInPath) {
    UMLClassifierList superClasses = c->findSuperClassConcepts();

    for (UMLClassifier *concept= superClasses.first(); concept; concept = superClasses.next())
    {
        getSuperImplementedOperations(concept, yetImplementedOpList, toBeImplementedOpList, (concept->isInterface() && noClassInPath));
        UMLOperationList opl = concept->getOpList();
        for (UMLOperation *op = opl.first(); op; op = opl.next()) {
            if (concept->isInterface() && noClassInPath) {
                if (!DWriter::dMethodInList(op,toBeImplementedOpList))
                    toBeImplementedOpList.append(op);
            }
            else
            {
                if (!DWriter::dMethodInList(op, yetImplementedOpList))
                    yetImplementedOpList.append(op);
            }
        }
    }

}

void DWriter::getInterfacesOperationsToBeImplemented(UMLClassifier *c, UMLOperationList &opList ) {
    UMLOperationList yetImplementedOpList;
    UMLOperationList toBeImplementedOpList;

    getSuperImplementedOperations(c,yetImplementedOpList, toBeImplementedOpList);
    for (UMLOperation *op = toBeImplementedOpList.first(); op; op = toBeImplementedOpList.next())
    {
        if ( ! DWriter::dMethodInList(op, yetImplementedOpList) && ! DWriter::dMethodInList(op, opList) )
            opList.append(op);
    }
}

void DWriter::writeOperations(UMLClassifier *c, TQTextStream &d) {
    UMLOperationList opl;
    UMLOperationList oppub,opprot,oppriv;
    oppub.setAutoDelete(false);
    opprot.setAutoDelete(false);
    oppriv.setAutoDelete(false);

    //sort operations by scope first and see if there are abstract methods
    opl = c->getOpList();
    if (! c->isInterface()) {
        getInterfacesOperationsToBeImplemented(c, opl);
    }
    for (UMLOperation *op = opl.first(); op; op = opl.next()) {
        switch(op->getVisibility()) {
          case Uml::Visibility::Public:
            oppub.append(op);
            break;
          case Uml::Visibility::Protected:
            opprot.append(op);
            break;
          case Uml::Visibility::Private:
            oppriv.append(op);
            break;
          default: //TODO: package, export
            break;
        }
    }

    // do people REALLY want these comments? Hmm.
    /*
      if(forceSections() || oppub.count())
      {
      writeComment("public operations",m_indentation,d);
        writeBlankLine(d);
      }
    */

    if (oppub.count() > 0) {
        writeProtectionMod(Uml::Visibility::Public, d);

        writeOperations(oppub,d);
    }

    if (opprot.count() > 0) {
        writeProtectionMod(Uml::Visibility::Protected, d);

        writeOperations(opprot, d);
    }

    if (oppriv.count() > 0) {
        writeProtectionMod(Uml::Visibility::Private, d);

        writeOperations(oppriv, d);
    }

}

void DWriter::writeOperations(UMLOperationList &oplist, TQTextStream &d) {
    UMLAttributeList atl;
    TQString str;

    // generate method decl for each operation given
    for(UMLOperation *op=oplist.first(); op; op=oplist.next()) {
        TQString returnStr = "";
        // write documentation

        TQString methodReturnType = fixTypeName(op->getTypeName());

        //TODO: return type comment
        if(methodReturnType != "void") {
            returnStr += "@return " + methodReturnType + m_endl;
        }

        str = ""; // reset for next method
        if (op->getAbstract() && !isInterface) str += "abstract ";
        if (op->getStatic()) str += "static ";

        str += methodReturnType + ' ' +cleanName(op->getName()) + '(';

        atl = op->getParmList();
        int i = atl.count();
        int j = 0;
        for (UMLAttribute *at = atl.first(); at; at = atl.next(), j++) {
            TQString typeName = fixTypeName(at->getTypeName());
            TQString atName = cleanName(at->getName());
            str += typeName + ' ' + atName +
                   (!(at->getInitialValue().isEmpty()) ?
                    (TQString(" = ")+at->getInitialValue()) :
                    TQString(""))
                   + ((j < i-1)?", ":"");
            returnStr += "@param " + atName+' '+at->getDoc() + m_endl;
        }

        str+= ')';

        // method only gets a body IF its not abstract
        if (op->getAbstract() || isInterface)
            str += ';'; // terminate now
        else
            str += startline + '{' + startline + '}'; // empty method body

        // write it out
        writeDocumentation("", op->getDoc(), returnStr, m_indentation, d);
        d << m_indentation << str << m_endl << m_endl;
    }
}

TQString DWriter::fixInitialStringDeclValue(TQString value, TQString type) {
    // check for strings only
    if (!value.isEmpty() && type == "String") {
        if (!value.startsWith("\""))
            value.prepend("\"");
        if (!value.endsWith("\""))
            value.append("\"");
    }
    return value;
}

TQString DWriter::scopeToDDecl(Uml::Visibility scope) {
    TQString scopeString;

    switch(scope) {
        case Uml::Visibility::Public: scopeString = "public"; break;
        case Uml::Visibility::Protected: scopeString = "protected"; break;
        case Uml::Visibility::Private: scopeString = "private"; break;
        default: break; //TODO: package and export
    }

    return scopeString;
}

// methods like this _shouldn't_ be needed IF we properly did things thruought the code.
TQString DWriter::getUMLObjectName(UMLObject *obj) {
    return(obj!=0)?obj->getName():TQString("NULL");
}

TQString DWriter::deCapitaliseFirstLetter(TQString string) {
    string.replace( 0, 1, string[0].lower());
    return string;
}

TQString DWriter::pluralize(TQString string) {
    return string + (string.right(1) == "s" ? "es" : "s");
}

TQString DWriter::unPluralize(TQString string) {
    // does not handle special cases liek datum -> data, etc.

    if (string.length() > 2 && string.right(3) == "ses") {
        return string.left(string.length() - 2);
    }

    if (string.right(1) == "s") {
        return string.left(string.length() - 1);
    }

    return string;
}

void DWriter::writeBlankLine(TQTextStream &d) {
    d << m_endl;
}

