/* -*- 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 <memory>
#include <scitems.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/editeng.hxx>
#include <editeng/editids.hrc>
#include <editeng/fhgtitem.hxx>
#include <editeng/svxrtf.hxx>
#include <svtools/rtftoken.h>
#include <osl/diagnose.h>
#include <svl/itempool.hxx>

#include <rtfparse.hxx>

#define SC_RTFTWIPTOL 10        // 10 Twips tolerance when determining columns

ScRTFParser::ScRTFParser( EditEngine* pEditP ) :
        ScEEParser( pEditP ),
        mnCurPos(0),
        pActDefault( nullptr ),
        pDefMerge( nullptr ),
        nStartAdjust( sal_uLong(~0) ),
        nLastWidth(0),
        bNewDef( false )
{
    // RTF default FontSize 12Pt
    tools::Long nMM = o3tl::convert(12, o3tl::Length::pt, o3tl::Length::mm100);

    pPool->SetUserDefaultItem( SvxFontHeightItem( nMM, 100, EE_CHAR_FONTHEIGHT ) );
    // Free-flying pInsDefault
    pInsDefault.reset( new ScRTFCellDefault( pPool.get() ) );
}

ScRTFParser::~ScRTFParser()
{
    pInsDefault.reset();
    maDefaultList.clear();
}

ErrCode ScRTFParser::Read( SvStream& rStream, const OUString& rBaseURL )
{
    Link<RtfImportInfo&,void> aOldLink = pEdit->GetRtfImportHdl();
    pEdit->SetRtfImportHdl( LINK( this, ScRTFParser, RTFImportHdl ) );
    ErrCode nErr = pEdit->Read( rStream, rBaseURL, EETextFormat::Rtf );
    if ( nRtfLastToken == RTF_PAR )
    {
        if ( !maList.empty() )
        {
            auto& pE = maList.back();
            if (    // Completely empty
                (  !pE->aSel.HasRange()  )
                ||  // Empty paragraph
                (  pE->aSel.start.nPara + 1 == pE->aSel.end.nPara
                   && pE->aSel.start.nIndex      == pEdit->GetTextLen( pE->aSel.start.nPara )
                   && pE->aSel.end.nIndex        == 0
                )
               )
            {   // Don't take over the last paragraph
                maList.pop_back();
            }
        }
    }
    ColAdjust();
    pEdit->SetRtfImportHdl( aOldLink );
    return nErr;
}

void ScRTFParser::EntryEnd( ScEEParseEntry* pE, const ESelection& aSel )
{
    // Paragraph -2 strips the attached empty paragraph
    pE->aSel.end.nPara = aSel.end.nPara - 2;
    // Although it's called nEndPos, the last one is position + 1
    pE->aSel.end.nIndex = pEdit->GetTextLen(aSel.end.nPara - 1);
}

inline void ScRTFParser::NextRow()
{
    if ( nRowMax < ++nRowCnt )
        nRowMax = nRowCnt;
}

bool ScRTFParser::SeekTwips( sal_uInt16 nTwips, SCCOL* pCol )
{
    ScRTFColTwips::const_iterator it = aColTwips.lower_bound( nTwips );
    bool bFound = it != aColTwips.end() && *it == nTwips;
    sal_uInt16 nPos = it - aColTwips.begin();
    *pCol = static_cast<SCCOL>(nPos);
    if ( bFound )
        return true;
    sal_uInt16 nCount = aColTwips.size();
    if ( !nCount )
        return false;
    SCCOL nCol = *pCol;
    // nCol is insertion position; the next one higher up is there (or not)
    if ( nCol < static_cast<SCCOL>(nCount) && ((aColTwips[nCol] - SC_RTFTWIPTOL) <= nTwips) )
        return true;
    // Not smaller than everything else? Then compare with the next lower one
    else if ( nCol != 0 && ((aColTwips[nCol-1] + SC_RTFTWIPTOL) >= nTwips) )
    {
        (*pCol)--;
        return true;
    }
    return false;
}

void ScRTFParser::ColAdjust()
{
    if ( nStartAdjust == sal_uLong(~0) )
        return;

    SCCOL nCol = 0;
    for (size_t i = nStartAdjust, nListSize = maList.size(); i < nListSize; ++i)
    {
        auto& pE = maList[i];
        if ( pE->nCol == 0 )
            nCol = 0;
        pE->nCol = nCol;
        if ( pE->nColOverlap > 1 )
            nCol = nCol + pE->nColOverlap; // Merged cells with \clmrg
        else
        {
            SeekTwips( pE->nTwips, &nCol );
            if ( ++nCol <= pE->nCol )
                nCol = pE->nCol + 1; // Moved cell X
            pE->nColOverlap = nCol - pE->nCol; // Merged cells without \clmrg
        }
        if ( nCol > nColMax )
            nColMax = nCol;
    }
    nStartAdjust = sal_uLong(~0);
    aColTwips.clear();
}

IMPL_LINK( ScRTFParser, RTFImportHdl, RtfImportInfo&, rInfo, void )
{
    switch ( rInfo.eState )
    {
        case RtfImportState::NextToken:
            ProcToken( &rInfo );
            break;
        case RtfImportState::UnknownAttr:
            ProcToken( &rInfo );
            break;
        case RtfImportState::Start:
        {
            SvxRTFParser* pParser = static_cast<SvxRTFParser*>(rInfo.pParser);
            pParser->SetAttrPool( pPool.get() );
            pParser->SetPardMap(SID_ATTR_BRUSH, ATTR_BACKGROUND);
            pParser->SetPardMap(SID_ATTR_BORDER_OUTER, ATTR_BORDER);
            pParser->SetPardMap(SID_ATTR_BORDER_SHADOW, ATTR_SHADOW);
        }
            break;
        case RtfImportState::End:
            if ( rInfo.aSelection.end.nIndex )
            {   // If still text: create last paragraph
                pActDefault = nullptr;
                rInfo.nToken = RTF_PAR;
                // EditEngine did not attach an empty paragraph anymore
                // which EntryEnd could strip
                rInfo.aSelection.end.nPara++;
                ProcToken( &rInfo );
            }
            break;
        case RtfImportState::SetAttr:
            break;
        case RtfImportState::InsertText:
            break;
        case RtfImportState::InsertPara:
            break;
        default:
            OSL_FAIL("unknown ImportInfo.eState");
    }
}

// Bad behavior:
// For RTF_INTBL or respectively at the start of the first RTF_CELL
// after RTF_CELLX if there was no RTF_INTBL
void ScRTFParser::NewCellRow()
{
    if ( bNewDef )
    {
        bNewDef = false;
        // Not flush on the right? => new table
        if ( nLastWidth && !maDefaultList.empty() )
        {
            const ScRTFCellDefault& rD = *maDefaultList.back();
            if (rD.nTwips != nLastWidth)
            {
                SCCOL n1, n2;
                if ( !(  SeekTwips( nLastWidth, &n1 )
                      && SeekTwips( rD.nTwips, &n2 )
                      && n1 == n2
                      )
                )
                {
                    ColAdjust();
                }
            }
        }
        // Build up TwipCols only after nLastWidth comparison!
        for (const std::unique_ptr<ScRTFCellDefault> & pCellDefault : maDefaultList)
        {
            const ScRTFCellDefault& rD = *pCellDefault;
            SCCOL nCol;
            if ( !SeekTwips(rD.nTwips, &nCol) )
                aColTwips.insert( rD.nTwips );
        }
    }
    pDefMerge = nullptr;
    pActDefault = maDefaultList.empty() ? nullptr : maDefaultList[0].get();
    mnCurPos = 0;
    OSL_ENSURE( pActDefault, "NewCellRow: pActDefault==0" );
}

/*
    SW:
    ~~~
    [\par]
    \trowd \cellx \cellx ...
    \intbl \cell \cell ...
    \row
    [\par]
    [\trowd \cellx \cellx ...]
    \intbl \cell \cell ...
    \row
    [\par]

    M$-Word:
    ~~~~~~~~
    [\par]
    \trowd \cellx \cellx ...
    \intbl \cell \cell ...
    \intbl \row
    [\par]
    [\trowd \cellx \cellx ...]
    \intbl \cell \cell ...
    \intbl \row
    [\par]

 */

void ScRTFParser::ProcToken( RtfImportInfo* pInfo )
{
    switch ( pInfo->nToken )
    {
        case RTF_TROWD:         // denotes table row default, before RTF_CELLX
        {
            if (!maDefaultList.empty())
                nLastWidth = maDefaultList.back()->nTwips;

            nColCnt = 0;
            if (pActDefault != pInsDefault.get())
                pActDefault = nullptr;
            maDefaultList.clear();
            pDefMerge = nullptr;
            nRtfLastToken = pInfo->nToken;
            mnCurPos = 0;
        }
        break;
        case RTF_CLMGF:         // The first cell of cells to be merged
        {
            pDefMerge = pInsDefault.get();
            nRtfLastToken = pInfo->nToken;
        }
        break;
        case RTF_CLMRG:         // A cell to be merged with the preceding cell
        {
            if (!pDefMerge && !maDefaultList.empty())
            {
                pDefMerge = maDefaultList.back().get();
                mnCurPos = maDefaultList.size() - 1;
            }
            OSL_ENSURE( pDefMerge, "RTF_CLMRG: pDefMerge==0" );
            if ( pDefMerge ) // Else broken RTF
                pDefMerge->nColOverlap++;   // multiple successive ones possible
            pInsDefault->nColOverlap = 0;   // Flag: ignore these
            nRtfLastToken = pInfo->nToken;
        }
        break;
        case RTF_CELLX:         // closes cell default
        {
            bNewDef = true;
            pInsDefault->nCol = nColCnt;
            pInsDefault->nTwips = pInfo->nTokenValue; // Right cell border
            maDefaultList.push_back( std::move(pInsDefault) );
            // New free-flying pInsDefault
            pInsDefault.reset( new ScRTFCellDefault( pPool.get() ) );
            if ( ++nColCnt > nColMax )
                nColMax = nColCnt;
            nRtfLastToken = pInfo->nToken;
        }
        break;
        case RTF_INTBL:         // before the first RTF_CELL
        {
            // Once over NextToken and once over UnknownAttrToken
            // or e.g. \intbl ... \cell \pard \intbl ... \cell
            if ( nRtfLastToken != RTF_INTBL && nRtfLastToken != RTF_CELL && nRtfLastToken != RTF_PAR )
            {
                NewCellRow();
                nRtfLastToken = pInfo->nToken;
            }
        }
        break;
        case RTF_CELL:          // denotes the end of a cell.
        {
            OSL_ENSURE( pActDefault, "RTF_CELL: pActDefault==0" );
            if ( bNewDef || !pActDefault )
                NewCellRow();    // before was no \intbl, bad behavior
            // Broken RTF? Let's save what we can
            if ( !pActDefault )
                pActDefault = pInsDefault.get();
            if ( pActDefault->nColOverlap > 0 )
            {   // Not merged with preceding
                mxActEntry->nCol = pActDefault->nCol;
                mxActEntry->nColOverlap = pActDefault->nColOverlap;
                mxActEntry->nTwips = pActDefault->nTwips;
                mxActEntry->nRow = nRowCnt;
                mxActEntry->aItemSet.Set(pActDefault->aItemSet);
                EntryEnd(mxActEntry.get(), pInfo->aSelection);

                if ( nStartAdjust == sal_uLong(~0) )
                    nStartAdjust = maList.size();
                maList.push_back(mxActEntry);
                NewActEntry(mxActEntry.get()); // New free-flying mxActEntry
            }
            else
            {   // Assign current Twips to MergeCell
                if ( !maList.empty() )
                {
                    auto& pE = maList.back();
                    pE->nTwips = pActDefault->nTwips;
                }
                // Adjust selection of free-flying mxActEntry
                // Paragraph -1 due to separated text in EditEngine during parsing
                mxActEntry->aSel.start.nPara = pInfo->aSelection.end.nPara - 1;
            }

            pActDefault = nullptr;
            if (!maDefaultList.empty() && (mnCurPos+1) < maDefaultList.size())
                pActDefault = maDefaultList[++mnCurPos].get();

            nRtfLastToken = pInfo->nToken;
        }
        break;
        case RTF_ROW:           // denotes the end of a row
        {
            NextRow();
            nRtfLastToken = pInfo->nToken;
        }
        break;
        case RTF_PAR:           // Paragraph
        {
            if ( !pActDefault )
            {   // text not in table
                ColAdjust();    // close the processing table
                mxActEntry->nCol = 0;
                mxActEntry->nRow = nRowCnt;
                EntryEnd(mxActEntry.get(), pInfo->aSelection);
                maList.push_back(mxActEntry);
                NewActEntry(mxActEntry.get());   // new mxActEntry
                NextRow();
            }
            nRtfLastToken = pInfo->nToken;
        }
        break;
        default:
        {   // do not set nRtfLastToken
            switch ( pInfo->nToken & ~(0xff | RTF_TABLEDEF) )
            {
                case RTF_SHADINGDEF:
                    static_cast<SvxRTFParser*>(pInfo->pParser)->ReadBackgroundAttr(
                        pInfo->nToken, pInsDefault->aItemSet, true );
                break;
                case RTF_BRDRDEF:
                    static_cast<SvxRTFParser*>(pInfo->pParser)->ReadBorderAttr(
                        pInfo->nToken, pInsDefault->aItemSet, true );
                break;
            }
        }
    }
}

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