/*
  a node in a MIME tree.

  Copyright (C) 2002 by Klarälvdalens Datakonsult AB

  This code is licensed under the GPL, blah, blah, blah...
*/

#include <config.h>
#include "partNode.h"
#include <klocale.h>
#include <kdebug.h>
#include "kmmimeparttree.h"
#include <qregexp.h>

/*
  ===========================================================================


  S T A R T    O F     T E M P O R A R Y     M I M E     C O D E


  ===========================================================================
  N O T E :   The partNode structure will most likely be replaced by KMime.
  It's purpose: Speed optimization for KDE 3.   (khz, 28.11.01)
  ===========================================================================
*/

void partNode::buildObjectTree( bool processSiblings )
{
    partNode* curNode = this;
    while( curNode && curNode->dwPart() ) {
        //dive into multipart messages
        while( DwMime::kTypeMultipart == curNode->type() ) {
            partNode* newNode = curNode->setFirstChild(
                new partNode( curNode->dwPart()->Body().FirstBodyPart() ) );
            curNode = newNode;
        }
        // go up in the tree until reaching a node with next
        // (or the last top-level node)
        while(     curNode
               && !(    curNode->dwPart()
                     && curNode->dwPart()->Next() ) ) {
            curNode = curNode->mRoot;
        }
        // we might have to leave when all children have been processed
        if( this == curNode && !processSiblings )
            return;
        // store next node
        if( curNode && curNode->dwPart() && curNode->dwPart()->Next() ) {
            partNode* nextNode = new partNode( curNode->dwPart()->Next() );
            curNode = curNode->setNext( nextNode );
        } else
            curNode = 0;
    }
}


partNode::CryptoType partNode::firstCryptoType() const
{
    CryptoType ret = cryptoType();
    if(    (CryptoTypeUnknown == ret || CryptoTypeNone == ret)
        && mChild )
        ret = mChild->firstCryptoType();
    if(    (CryptoTypeUnknown == ret || CryptoTypeNone == ret)
        && mNext )
        ret = mNext->firstCryptoType();
    return ret;
}


KMMsgEncryptionState partNode::overallEncryptionState() const
{
    KMMsgEncryptionState myState = KMMsgEncryptionStateUnknown;
    if( mEncryptionState == KMMsgNotEncrypted ) {
        // NOTE: children are tested ONLY when parent is not encrypted
        if( mChild )
            myState = mChild->overallEncryptionState();
        else
            myState = KMMsgNotEncrypted;
    }
    else { // part is partially or fully encrypted
        myState = mEncryptionState;
    }
    // siblings are tested allways
    if( mNext ) {
        KMMsgEncryptionState otherState = mNext->overallEncryptionState();
        switch( otherState ) {
        case KMMsgEncryptionStateUnknown:
            break;
        case KMMsgNotEncrypted:
            if( myState == KMMsgFullyEncrypted )
                myState = KMMsgPartiallyEncrypted;
            else if( myState != KMMsgPartiallyEncrypted )
                myState = KMMsgNotEncrypted;
            break;
        case KMMsgPartiallyEncrypted:
            myState = KMMsgPartiallyEncrypted;
            break;
        case KMMsgFullyEncrypted:
            if( myState != KMMsgFullyEncrypted )
                myState = KMMsgPartiallyEncrypted;
            break;
        case KMMsgEncryptionProblematic:
            break;
        }
    }

kdDebug(5006) << "\n\n  KMMsgEncryptionState: " << myState << endl;

    return myState;
}


KMMsgSignatureState  partNode::overallSignatureState() const
{
    KMMsgSignatureState myState = KMMsgSignatureStateUnknown;
    if( mSignatureState == KMMsgNotSigned ) {
        // children are tested ONLY when parent is not signed
        if( mChild )
            myState = mChild->overallSignatureState();
        else
            myState = KMMsgNotSigned;
    }
    else { // part is partially or fully signed
        myState = mSignatureState;
    }
    // siblings are tested allways
    if( mNext ) {
        KMMsgSignatureState otherState = mNext->overallSignatureState();
        switch( otherState ) {
        case KMMsgSignatureStateUnknown:
            break;
        case KMMsgNotSigned:
            if( myState == KMMsgFullySigned )
                myState = KMMsgPartiallySigned;
            else if( myState != KMMsgPartiallySigned )
                myState = KMMsgNotSigned;
            break;
        case KMMsgPartiallySigned:
            myState = KMMsgPartiallySigned;
            break;
        case KMMsgFullySigned:
            if( myState != KMMsgFullySigned )
                myState = KMMsgPartiallySigned;
            break;
        case KMMsgEncryptionProblematic:
            break;
        }
    }

kdDebug(5006) << "\n\n  KMMsgSignatureState: " << myState << endl;

    return myState;
}


int partNode::nodeId()
{
    int curId = 0;
    partNode* rootNode = this;
    while( rootNode->mRoot )
        rootNode = rootNode->mRoot;
    return rootNode->calcNodeIdOrFindNode( curId, this, 0, 0 );
}


partNode* partNode::findId( int id )
{
    int curId = 0;
    partNode* rootNode = this;
    while( rootNode->mRoot )
        rootNode = rootNode->mRoot;
    partNode* foundNode;
    rootNode->calcNodeIdOrFindNode( curId, 0, id, &foundNode );
    return foundNode;
}


int partNode::calcNodeIdOrFindNode( int &curId, const partNode* findNode, int findId, partNode** foundNode )
{
    // We use the same algorithm to determine the id of a node and
    //                           to find the node when id is known.
    curId++;
    // check for node ?
    if( findNode && this == findNode )
        return curId;
    // check for id ?
    if(  foundNode && curId == findId ) {
        *foundNode = this;
        return curId;
    }
    if( mChild )
    {
        int res = mChild->calcNodeIdOrFindNode( curId, findNode, findId, foundNode );
        if (res != -1) return res;
    }
    if( mNext )
        return mNext->calcNodeIdOrFindNode( curId, findNode, findId, foundNode );

    if(  foundNode )
        *foundNode = 0;
    return -1;
}


partNode* partNode::findType( int type, int subType, bool deep, bool wide )
{
    if(    (mType != DwMime::kTypeUnknown)
           && (    (type == DwMime::kTypeUnknown)
                   || (type == mType) )
           && (    (subType == DwMime::kSubtypeUnknown)
                   || (subType == mSubType) ) )
        return this;
    else if( mChild && deep )
        return mChild->findType( type, subType, deep, wide );
    else if( mNext && wide )
        return mNext->findType(  type, subType, deep, wide );
    else
        return 0;
}

partNode* partNode::findTypeNot( int type, int subType, bool deep, bool wide )
{
    if(    (mType != DwMime::kTypeUnknown)
           && (    (type == DwMime::kTypeUnknown)
                   || (type != mType) )
           && (    (subType == DwMime::kSubtypeUnknown)
                   || (subType != mSubType) ) )
        return this;
    else if( mChild && deep )
        return mChild->findTypeNot( type, subType, deep, wide );
    else if( mNext && wide )
        return mNext->findTypeNot(  type, subType, deep, wide );
    else
        return 0;
}

void partNode::fillMimePartTree( KMMimePartTreeItem* parentItem,
                                 KMMimePartTree*     mimePartTree,
                                 QString labelDescr,
                                 QString labelCntType,
                                 QString labelEncoding,
                                 KIO::filesize_t size,
                                 bool revertOrder )
{
  if( parentItem || mimePartTree ) {

    if( mNext )
        mNext->fillMimePartTree( parentItem, mimePartTree,
                                 QString::null, QString::null, QString::null, 0,
                                 revertOrder );

    QString cntDesc, cntType, cntEnc;
    KIO::filesize_t cntSize = 0;

    if( labelDescr.isEmpty() ) {
        DwHeaders* headers = 0;
        if( mDwPart && mDwPart->hasHeaders() )
          headers = &mDwPart->Headers();
        if( headers && headers->HasSubject() )
            cntDesc = headers->Subject().AsString().c_str();
        if( headers && headers->HasContentType()) {
            cntType = headers->ContentType().TypeStr().c_str();
            cntType += '/';
            cntType += headers->ContentType().SubtypeStr().c_str();
            if( 0 < headers->ContentType().Name().length() ) {
                if( !cntDesc.isEmpty() )
                    cntDesc += ", ";
                cntDesc = headers->ContentType().Name().c_str();
            }
        }
        else
            cntType = "text/plain";
        if( headers && headers->HasContentDescription()) {
            if( !cntDesc.isEmpty() )
                cntDesc += ", ";
            cntDesc = headers->ContentDescription().AsString().c_str();
        }
        if( cntDesc.isEmpty() ) {
            bool bOk = false;
            if( headers && headers->HasContentDisposition() ) {
                QCString dispo = headers->ContentDisposition().AsString().c_str();
                int i = dispo.find("filename=", 0, false);
                if( -1 < i ) {  //  123456789
                    QCString s = dispo.mid( i + 9 ).stripWhiteSpace();
                    if( !s.isEmpty() ) {
                        if( '\"' == s[0])
                            s.remove( 0, 1 );
                        if( '\"' == s[s.length()-1])
                            s.truncate( s.length()-1 );
                        if( !s.isEmpty() ) {
                            cntDesc  = "file: ";
                            cntDesc += s;
                            bOk = true;
                        }
                    }
                }
            }
            if( !bOk ) {
                if( mRoot && mRoot->mRoot )
                    cntDesc = i18n("internal part");
                else
                    cntDesc = i18n("body part");
            }
        }
        if(    mDwPart
            && mDwPart->hasHeaders()
            && mDwPart->Headers().HasContentTransferEncoding() )
            cntEnc = mDwPart->Headers().ContentTransferEncoding().AsString().c_str();
        else
            cntEnc = "7bit";
        if( mDwPart )
            cntSize = mDwPart->Body().AsString().length();
    } else {
        cntDesc = labelDescr;
        cntType = labelCntType;
        cntEnc  = labelEncoding;
        cntSize = size;
    }
    // remove linebreak+whitespace from folded Content-Description
    cntDesc.replace( QRegExp("\\n\\s*"), " " );

kdDebug(5006) << "      Inserting one item into MimePartTree" << endl;
kdDebug(5006) << "                Content-Type: " << cntType << endl;
    if( parentItem )
      mMimePartTreeItem = new KMMimePartTreeItem( *parentItem,
                                                  this,
                                                  cntDesc,
                                                  cntType,
                                                  cntEnc,
                                                  cntSize,
                                                  revertOrder );
    else if( mimePartTree )
      mMimePartTreeItem = new KMMimePartTreeItem( *mimePartTree,
                                                  this,
                                                  cntDesc,
                                                  cntType,
                                                  cntEnc,
                                                  cntSize );
    mMimePartTreeItem->setOpen( true );
    if( mChild )
        mChild->fillMimePartTree( mMimePartTreeItem, 0,
                                  QString::null, QString::null, QString::null, 0,
                                  revertOrder );

  }
}

void partNode::adjustDefaultType( partNode* node )
{
    // Only bodies of  'Multipart/Digest'  objects have
    // default type 'Message/RfC822'.  All other bodies
    // have default type 'Text/Plain'  (khz, 5.12.2001)
    if( node && DwMime::kTypeUnknown == node->type() ) {
        if(    node->mRoot
               && DwMime::kTypeMultipart == node->mRoot->type()
               && DwMime::kSubtypeDigest == node->mRoot->subType() ) {
            node->setType(    DwMime::kTypeMessage   );
            node->setSubType( DwMime::kSubtypeRfc822 );
        }
        else
            {
                node->setType(    DwMime::kTypeText     );
                node->setSubType( DwMime::kSubtypePlain );
            }
    }
}

bool partNode::isAttachment() const
{
  if( !dwPart() )
    return false;
  DwHeaders& headers = dwPart()->Headers();
  if( headers.HasContentDisposition() )
    return ( headers.ContentDisposition().DispositionType()
             == DwMime::kDispTypeAttachment );
  else
    return false;
}

bool partNode::hasContentDispositionInline() const
{
  if( !dwPart() )
    return false;
  DwHeaders& headers = dwPart()->Headers();
  if( headers.HasContentDisposition() )
    return ( headers.ContentDisposition().DispositionType()
             == DwMime::kDispTypeInline );
  else
    return false;
}

const QString& partNode::trueFromAddress() const
{
  const partNode* node = this;
  while( node->mFromAddress.isEmpty() && node->mRoot )
    node = node->mRoot;
  return node->mFromAddress;
}
