/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
#include <doc.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <node.hxx>
#include <frmfmt.hxx>
#include <swtable.hxx>
#include <ndtxt.hxx>
#include <swtblfmt.hxx>
#include <cellatr.hxx>
#include <docary.hxx>
#include <ddefld.hxx>
#include <swddetbl.hxx>
#include <svtools/fmtfield.hxx>
#include <boost/foreach.hpp>
#include <vector>


#ifdef DBG_UTIL
#define CHECK_TABLE(t) (t).CheckConsistency();
#else
#define CHECK_TABLE(t)
#endif



// Structure for the mapping from old and new frame formats to the
// boxes and lines of a table
struct _MapTblFrmFmt
{
    const SwFrmFmt *pOld, *pNew;
    _MapTblFrmFmt( const SwFrmFmt *pOldFmt, const SwFrmFmt*pNewFmt )
        : pOld( pOldFmt ), pNew( pNewFmt )
    {}
};

typedef std::vector<_MapTblFrmFmt> _MapTblFrmFmts;

SwCntntNode* SwTxtNode::MakeCopy( SwDoc* pDoc, const SwNodeIndex& rIdx ) const
{
    // the Copy-Textnode is the Node with the Text, the Copy-Attrnode is the
    // node with the collection and hard attributes. Normally is the same
    // node, but if insert a glossary without formatting, then the Attrnode
    // is the prev node of the destionation position in dest. document.
    SwTxtNode* pCpyTxtNd = (SwTxtNode*)this;
    SwTxtNode* pCpyAttrNd = pCpyTxtNd;

    // Copy the formats to the other document
    SwTxtFmtColl* pColl = 0;
    if( pDoc->IsInsOnlyTextGlossary() )
    {
        SwNodeIndex aIdx( rIdx, -1 );
        if( aIdx.GetNode().IsTxtNode() )
        {
            pCpyAttrNd = aIdx.GetNode().GetTxtNode();
            pColl = &pCpyAttrNd->GetTxtColl()->GetNextTxtFmtColl();
        }
    }
    if( !pColl )
        pColl = pDoc->CopyTxtColl( *GetTxtColl() );

    SwTxtNode* pTxtNd = pDoc->GetNodes().MakeTxtNode( rIdx, pColl );

    // METADATA: register copy
    pTxtNd->RegisterAsCopyOf(*pCpyTxtNd);

    // Copy Attribute/Text
    if( !pCpyAttrNd->HasSwAttrSet() )
        // An AttrSet was added for numbering, so delete it
        pTxtNd->ResetAllAttr();

    // if Copy-Textnode unequal to Copy-Attrnode, then copy first
    // the attributes into the new Node.
    if( pCpyAttrNd != pCpyTxtNd )
    {
        pCpyAttrNd->CopyAttr( pTxtNd, 0, 0 );
        if( pCpyAttrNd->HasSwAttrSet() )
        {
            SwAttrSet aSet( *pCpyAttrNd->GetpSwAttrSet() );
            aSet.ClearItem( RES_PAGEDESC );
            aSet.ClearItem( RES_BREAK );
            aSet.CopyToModify( *pTxtNd );
        }
    }

    // Is that enough? What about PostIts/Fields/FieldTypes?
    // #i96213# - force copy of all attributes
    pCpyTxtNd->CopyText( pTxtNd, SwIndex( pCpyTxtNd ),
        pCpyTxtNd->GetTxt().getLength(), true );

    if( RES_CONDTXTFMTCOLL == pColl->Which() )
        pTxtNd->ChkCondColl();

    return pTxtNd;
}

static bool lcl_SrchNew( const _MapTblFrmFmt& rMap, const SwFrmFmt** pPara )
{
    if( rMap.pOld != *pPara )
        return true;
    *pPara = rMap.pNew;
    return false;
}

struct _CopyTable
{
    SwDoc* pDoc;
    sal_uLong nOldTblSttIdx;
    _MapTblFrmFmts& rMapArr;
    SwTableLine* pInsLine;
    SwTableBox* pInsBox;
    SwTableNode *pTblNd;
    const SwTable *pOldTable;

    _CopyTable( SwDoc* pDc, _MapTblFrmFmts& rArr, sal_uLong nOldStt,
                SwTableNode& rTblNd, const SwTable* pOldTbl )
        : pDoc(pDc), nOldTblSttIdx(nOldStt), rMapArr(rArr),
        pInsLine(0), pInsBox(0), pTblNd(&rTblNd), pOldTable( pOldTbl )
    {}
};

static void lcl_CopyTblLine( const SwTableLine* pLine, _CopyTable* pCT );

static void lcl_CopyTblBox( SwTableBox* pBox, _CopyTable* pCT )
{
    SwTableBoxFmt* pBoxFmt = static_cast<SwTableBoxFmt*>(pBox->GetFrmFmt());
    for( _MapTblFrmFmts::const_iterator it = pCT->rMapArr.begin(); it != pCT->rMapArr.end(); ++it )
        if ( !lcl_SrchNew( *it, (const SwFrmFmt**)&pBoxFmt ) )
            break;
    if( pBoxFmt == pBox->GetFrmFmt() ) // Create a new one?
    {
        const SfxPoolItem* pItem;
        if( SfxItemState::SET == pBoxFmt->GetItemState( RES_BOXATR_FORMULA, false,
            &pItem ) && static_cast<const SwTblBoxFormula*>(pItem)->IsIntrnlName() )
        {
            const_cast<SwTblBoxFormula*>(static_cast<const SwTblBoxFormula*>(pItem))->PtrToBoxNm( pCT->pOldTable );
        }

        pBoxFmt = pCT->pDoc->MakeTableBoxFmt();
        pBoxFmt->CopyAttrs( *pBox->GetFrmFmt() );

        if( pBox->GetSttIdx() )
        {
            SvNumberFormatter* pN = pCT->pDoc->GetNumberFormatter( false );
            if( pN && pN->HasMergeFmtTbl() && SfxItemState::SET == pBoxFmt->
                GetItemState( RES_BOXATR_FORMAT, false, &pItem ) )
            {
                sal_uLong nOldIdx = static_cast<const SwTblBoxNumFormat*>(pItem)->GetValue();
                sal_uLong nNewIdx = pN->GetMergeFmtIndex( nOldIdx );
                if( nNewIdx != nOldIdx )
                    pBoxFmt->SetFmtAttr( SwTblBoxNumFormat( nNewIdx ));

            }
        }

        pCT->rMapArr.push_back( _MapTblFrmFmt( pBox->GetFrmFmt(), pBoxFmt ) );
    }

    sal_uInt16 nLines = pBox->GetTabLines().size();
    SwTableBox* pNewBox;
    if( nLines )
        pNewBox = new SwTableBox( pBoxFmt, nLines, pCT->pInsLine );
    else
    {
        SwNodeIndex aNewIdx( *pCT->pTblNd,
                            pBox->GetSttIdx() - pCT->nOldTblSttIdx );
        OSL_ENSURE( aNewIdx.GetNode().IsStartNode(), "Index is not on the start node" );
        pNewBox = new SwTableBox( pBoxFmt, aNewIdx, pCT->pInsLine );
        pNewBox->setRowSpan( pBox->getRowSpan() );
    }

    pCT->pInsLine->GetTabBoxes().push_back( pNewBox );

    if( nLines )
    {
        _CopyTable aPara( *pCT );
        aPara.pInsBox = pNewBox;
        BOOST_FOREACH( const SwTableLine* pLine, pBox->GetTabLines() )
            lcl_CopyTblLine( pLine, &aPara );
    }
    else if( pNewBox->IsInHeadline( &pCT->pTblNd->GetTable() ))
        // In the headline, the paragraphs must match conditional styles
        pNewBox->GetSttNd()->CheckSectionCondColl();
}

static void lcl_CopyTblLine( const SwTableLine* pLine, _CopyTable* pCT )
{
    SwTableLineFmt* pLineFmt = static_cast<SwTableLineFmt*>(pLine->GetFrmFmt());
    for( _MapTblFrmFmts::const_iterator it = pCT->rMapArr.begin(); it != pCT->rMapArr.end(); ++it )
        if ( !lcl_SrchNew( *it, (const SwFrmFmt**)&pLineFmt ) )
            break;
    if( pLineFmt == pLine->GetFrmFmt() ) // Create a new one?
    {
        pLineFmt = pCT->pDoc->MakeTableLineFmt();
        pLineFmt->CopyAttrs( *pLine->GetFrmFmt() );
        pCT->rMapArr.push_back( _MapTblFrmFmt( pLine->GetFrmFmt(), pLineFmt ) );
    }
    SwTableLine* pNewLine = new SwTableLine( pLineFmt,
                            pLine->GetTabBoxes().size(), pCT->pInsBox );
    // Insert the new row into the table
    if( pCT->pInsBox )
    {
        pCT->pInsBox->GetTabLines().push_back( pNewLine );
    }
    else
    {
        pCT->pTblNd->GetTable().GetTabLines().push_back( pNewLine );
    }
    pCT->pInsLine = pNewLine;
    for( SwTableBoxes::iterator it = ((SwTableLine*)pLine)->GetTabBoxes().begin();
             it != ((SwTableLine*)pLine)->GetTabBoxes().end(); ++it)
        lcl_CopyTblBox(*it, pCT );
}

SwTableNode* SwTableNode::MakeCopy( SwDoc* pDoc, const SwNodeIndex& rIdx ) const
{
    // In which array are we? Nodes? UndoNodes?
    SwNodes& rNds = (SwNodes&)GetNodes();

    {
        if( rIdx < pDoc->GetNodes().GetEndOfInserts().GetIndex() &&
            rIdx >= pDoc->GetNodes().GetEndOfInserts().StartOfSectionIndex() )
            return 0;
    }

    // Copy the TableFrmFmt
    OUString sTblName( GetTable().GetFrmFmt()->GetName() );
    if( !pDoc->IsCopyIsMove() )
    {
        const SwFrmFmts& rTblFmts = *pDoc->GetTblFrmFmts();
        for( sal_uInt16 n = rTblFmts.size(); n; )
            if( rTblFmts[ --n ]->GetName() == sTblName )
            {
                sTblName = pDoc->GetUniqueTblName();
                break;
            }
    }

    SwFrmFmt* pTblFmt = pDoc->MakeTblFrmFmt( sTblName, pDoc->GetDfltFrmFmt() );
    pTblFmt->CopyAttrs( *GetTable().GetFrmFmt() );
    SwTableNode* pTblNd = new SwTableNode( rIdx );
    SwEndNode* pEndNd = new SwEndNode( rIdx, *pTblNd );
    SwNodeIndex aInsPos( *pEndNd );

    SwTable& rTbl = (SwTable&)pTblNd->GetTable();
    rTbl.RegisterToFormat( *pTblFmt );

    rTbl.SetRowsToRepeat( GetTable().GetRowsToRepeat() );
    rTbl.SetTblChgMode( GetTable().GetTblChgMode() );
    rTbl.SetTableModel( GetTable().IsNewModel() );

    SwDDEFieldType* pDDEType = 0;
    if( IS_TYPE( SwDDETable, &GetTable() ))
    {
        // We're copying a DDE table
        // Is the field type available in the new document?
        pDDEType = const_cast<SwDDETable&>(static_cast<const SwDDETable&>(GetTable())).GetDDEFldType();
        if( pDDEType->IsDeleted() )
            pDoc->getIDocumentFieldsAccess().InsDeletedFldType( *pDDEType );
        else
            pDDEType = static_cast<SwDDEFieldType*>(pDoc->getIDocumentFieldsAccess().InsertFldType( *pDDEType ));
        OSL_ENSURE( pDDEType, "unknown FieldType" );

        // Swap the table pointers in the node
        SwDDETable* pNewTable = new SwDDETable( pTblNd->GetTable(), pDDEType );
        pTblNd->SetNewTable( pNewTable, false );
    }
    // First copy the content of the tables, we will later assign the
    // boxes/lines and create the frames
    SwNodeRange aRg( *this, +1, *EndOfSectionNode() );

    // If there is a table in this table, the table format for the outer table
    // does not seem to be used, because the table does not have any contents yet
    // (see IsUsed). Therefore the inner table gets the same name as the outer table.
    // We have to make sure that the table node of the SwTable is accessible, even
    // without any content in m_TabSortContentBoxes. #i26629#
    pTblNd->GetTable().SetTableNode( pTblNd );
    rNds._Copy( aRg, aInsPos, false );
    pTblNd->GetTable().SetTableNode( 0 );

    // Special case for a single box
    if( 1 == GetTable().GetTabSortBoxes().size() )
    {
        aRg.aStart.Assign( *pTblNd, 1 );
        aRg.aEnd.Assign( *pTblNd->EndOfSectionNode() );
        pDoc->GetNodes().SectionDown( &aRg, SwTableBoxStartNode );
    }

    // Delete all frames from the copied area, they will be created
    // during the generation of the table frame
    pTblNd->DelFrms();

    _MapTblFrmFmts aMapArr;
    _CopyTable aPara( pDoc, aMapArr, GetIndex(), *pTblNd, &GetTable() );

    BOOST_FOREACH(const SwTableLine* pLine, GetTable().GetTabLines() )
        lcl_CopyTblLine( pLine, &aPara );

    if( pDDEType )
        pDDEType->IncRefCnt();

    CHECK_TABLE( GetTable() );
    return pTblNd;
}

void SwTxtNode::CopyCollFmt( SwTxtNode& rDestNd )
{
    // Copy the formats into the other document:
    // Special case for PageBreak/PageDesc/ColBrk
    SwDoc* pDestDoc = rDestNd.GetDoc();
    SwAttrSet aPgBrkSet( pDestDoc->GetAttrPool(), aBreakSetRange );
    const SwAttrSet* pSet;

    if( 0 != ( pSet = rDestNd.GetpSwAttrSet() ) )
    {
        // Special cases for Break-Attributes
        const SfxPoolItem* pAttr;
        if( SfxItemState::SET == pSet->GetItemState( RES_BREAK, false, &pAttr ) )
            aPgBrkSet.Put( *pAttr );

        if( SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC, false, &pAttr ) )
            aPgBrkSet.Put( *pAttr );
    }

    rDestNd.ChgFmtColl( pDestDoc->CopyTxtColl( *GetTxtColl() ));
    if( 0 != ( pSet = GetpSwAttrSet() ) )
        pSet->CopyToModify( rDestNd );

    if( aPgBrkSet.Count() )
        rDestNd.SetAttr( aPgBrkSet );
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
