/* -*- 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/.
 */

#include <memory>
#include <clipcontext.hxx>
#include <document.hxx>
#include <mtvelements.hxx>
#include <column.hxx>
#include <scitems.hxx>
#include <tokenarray.hxx>
#include <editutil.hxx>
#include <clipparam.hxx>

#include <svl/intitem.hxx>
#include <formula/errorcodes.hxx>

namespace sc {

ClipContextBase::ClipContextBase(ScDocument& rDoc) :
    mpSet(new ColumnBlockPositionSet(rDoc)) {}

ClipContextBase::~ClipContextBase() {}

ColumnBlockPosition* ClipContextBase::getBlockPosition(SCTAB nTab, SCCOL nCol)
{
    return mpSet->getBlockPosition(nTab, nCol);
}

CopyFromClipContext::CopyFromClipContext(ScDocument& rDoc,
    ScDocument* pRefUndoDoc, ScDocument* pClipDoc, InsertDeleteFlags nInsertFlag,
    bool bAsLink, bool bSkipAttrForEmptyCells) :
    ClipContextBase(rDoc),
    mnDestCol1(-1), mnDestCol2(-1),
    mnDestRow1(-1), mnDestRow2(-1),
    mnTabStart(-1), mnTabEnd(-1),
    mrDestDoc(rDoc),
    mpRefUndoDoc(pRefUndoDoc), mpClipDoc(pClipDoc),
    mnInsertFlag(nInsertFlag), mnDeleteFlag(InsertDeleteFlags::NONE),
    mpCondFormatList(nullptr),
    mbAsLink(bAsLink), mbSkipAttrForEmptyCells(bSkipAttrForEmptyCells),
    mbCloneNotes (mnInsertFlag & (InsertDeleteFlags::NOTE|InsertDeleteFlags::ADDNOTES)),
    mbTableProtected(false)
{
}

CopyFromClipContext::~CopyFromClipContext()
{
}

void CopyFromClipContext::setTabRange(SCTAB nStart, SCTAB nEnd)
{
    mnTabStart = nStart;
    mnTabEnd = nEnd;
}

SCTAB CopyFromClipContext::getTabStart() const
{
    return mnTabStart;
}

SCTAB CopyFromClipContext::getTabEnd() const
{
    return mnTabEnd;
}

void CopyFromClipContext::setDestRange( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
{
    mnDestCol1 = nCol1;
    mnDestRow1 = nRow1;
    mnDestCol2 = nCol2;
    mnDestRow2 = nRow2;
}

CopyFromClipContext::Range CopyFromClipContext::getDestRange() const
{
    Range aRet;
    aRet.mnCol1 = mnDestCol1;
    aRet.mnCol2 = mnDestCol2;
    aRet.mnRow1 = mnDestRow1;
    aRet.mnRow2 = mnDestRow2;
    return aRet;
}

ScDocument* CopyFromClipContext::getUndoDoc()
{
    return mpRefUndoDoc;
}

ScDocument* CopyFromClipContext::getClipDoc()
{
    return mpClipDoc;
}

InsertDeleteFlags CopyFromClipContext::getInsertFlag() const
{
    return mnInsertFlag;
}

void CopyFromClipContext::setDeleteFlag( InsertDeleteFlags nFlag )
{
    mnDeleteFlag = nFlag;
}

InsertDeleteFlags CopyFromClipContext::getDeleteFlag() const
{
    return mnDeleteFlag;
}

void CopyFromClipContext::setSingleCellColumnSize( size_t nSize )
{
    maSingleCells.resize(nSize);
    maSingleCellAttrs.resize(nSize);
    maSinglePatterns.resize(nSize, nullptr);
    maSingleNotes.resize(nSize, nullptr);
}

ScCellValue& CopyFromClipContext::getSingleCell( size_t nColOffset )
{
    assert(nColOffset < maSingleCells.size());
    return maSingleCells[nColOffset];
}

sc::CellTextAttr& CopyFromClipContext::getSingleCellAttr( size_t nColOffset )
{
    assert(nColOffset < maSingleCellAttrs.size());
    return maSingleCellAttrs[nColOffset];
}

void CopyFromClipContext::setSingleCell( const ScAddress& rSrcPos, const ScColumn& rSrcCol )
{
    SCCOL nColOffset = rSrcPos.Col() - mpClipDoc->GetClipParam().getWholeRange().aStart.Col();
    ScCellValue& rSrcCell = getSingleCell(nColOffset);

    const sc::CellTextAttr* pAttr = rSrcCol.GetCellTextAttr(rSrcPos.Row());

    if (pAttr)
    {
        sc::CellTextAttr& rAttr = getSingleCellAttr(nColOffset);
        rAttr = *pAttr;
    }

    if (mbAsLink)
    {
        ScSingleRefData aRef;
        aRef.InitAddress(rSrcPos);
        aRef.SetFlag3D(true);

        ScTokenArray aArr;
        aArr.AddSingleReference(aRef);
        rSrcCell.set(new ScFormulaCell(mpClipDoc, rSrcPos, aArr));
        return;
    }

    rSrcCell.assign(*mpClipDoc, rSrcPos);

    // Check the paste flag to see whether we want to paste this cell.  If the
    // flag says we don't want to paste this cell, we'll return with true.
    InsertDeleteFlags nFlags = getInsertFlag();
    bool bNumeric  = (nFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE;
    bool bDateTime = (nFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE;
    bool bString   = (nFlags & InsertDeleteFlags::STRING) != InsertDeleteFlags::NONE;
    bool bBoolean  = (nFlags & InsertDeleteFlags::SPECIAL_BOOLEAN) != InsertDeleteFlags::NONE;
    bool bFormula  = (nFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE;

    switch (rSrcCell.meType)
    {
        case CELLTYPE_VALUE:
        {
            bool bPaste = isDateCell(rSrcCol, rSrcPos.Row()) ? bDateTime : bNumeric;
            if (!bPaste)
                // Don't paste this.
                rSrcCell.clear();
        }
        break;
        case CELLTYPE_STRING:
        case CELLTYPE_EDIT:
        {
            if (!bString)
                // Skip pasting.
                rSrcCell.clear();
        }
        break;
        case CELLTYPE_FORMULA:
        {
            if (bBoolean)
            {
                // Check if this formula cell is a boolean cell, and if so, go ahead and paste it.
                ScTokenArray* pCode = rSrcCell.mpFormula->GetCode();
                if (pCode && pCode->GetLen() == 1)
                {
                    const formula::FormulaToken* p = pCode->FirstToken();
                    if (p->GetOpCode() == ocTrue || p->GetOpCode() == ocFalse)
                        // This is a boolean formula. Good.
                        break;
                }
            }

            if (bFormula)
                // Good.
                break;

            FormulaError nErr = rSrcCell.mpFormula->GetErrCode();
            if (nErr != FormulaError::NONE)
            {
                // error codes are cloned with values
                if (!bNumeric)
                    // Error code is treated as numeric value. Don't paste it.
                    rSrcCell.clear();
                else
                {
                    // Turn this into a formula cell with just the error code.
                    ScFormulaCell* pErrCell = new ScFormulaCell(mpClipDoc, rSrcPos);
                    pErrCell->SetErrCode(nErr);
                    rSrcCell.set(pErrCell);
                }
            }
            else if (rSrcCell.mpFormula->IsEmptyDisplayedAsString())
            {
                // Empty stays empty and doesn't become 0.
                rSrcCell.clear();
            }
            else if (rSrcCell.mpFormula->IsValue())
            {
                bool bPaste = isDateCell(rSrcCol, rSrcPos.Row()) ? bDateTime : bNumeric;
                if (!bPaste)
                {
                    // Don't paste this.
                    rSrcCell.clear();
                    break;
                }

                // Turn this into a numeric cell.
                rSrcCell.set(rSrcCell.mpFormula->GetValue());
            }
            else if (bString)
            {
                svl::SharedString aStr = rSrcCell.mpFormula->GetString();
                if (aStr.isEmpty())
                {
                    // do not clone empty string
                    rSrcCell.clear();
                    break;
                }

                // Turn this into a string or edit cell.
                if (rSrcCell.mpFormula->IsMultilineResult())
                {
                    // TODO : Add shared string support to the edit engine to
                    // make this process simpler.
                    ScFieldEditEngine& rEngine = mrDestDoc.GetEditEngine();
                    rEngine.SetText(rSrcCell.mpFormula->GetString().getString());
                    std::unique_ptr<EditTextObject> pObj(rEngine.CreateTextObject());
                    pObj->NormalizeString(mrDestDoc.GetSharedStringPool());
                    rSrcCell.set(*pObj);
                }
                else
                    rSrcCell.set(rSrcCell.mpFormula->GetString());
            }
            else
                // We don't want to paste this.
                rSrcCell.clear();
        }
        break;
        case CELLTYPE_NONE:
        default:
            // There is nothing to paste.
            rSrcCell.clear();
    }
}

const ScPatternAttr* CopyFromClipContext::getSingleCellPattern( size_t nColOffset ) const
{
    assert(nColOffset < maSinglePatterns.size());
    return maSinglePatterns[nColOffset];
}

void CopyFromClipContext::setSingleCellPattern( size_t nColOffset, const ScPatternAttr* pAttr )
{
    assert(nColOffset < maSinglePatterns.size());
    maSinglePatterns[nColOffset] = pAttr;
}

const ScPostIt* CopyFromClipContext::getSingleCellNote( size_t nColOffset ) const
{
    assert(nColOffset < maSingleNotes.size());
    return maSingleNotes[nColOffset];
}

void CopyFromClipContext::setSingleCellNote( size_t nColOffset, const ScPostIt* pNote )
{
    assert(nColOffset < maSingleNotes.size());
    maSingleNotes[nColOffset] = pNote;
}

void CopyFromClipContext::setCondFormatList( ScConditionalFormatList* pCondFormatList )
{
    mpCondFormatList = pCondFormatList;
}

ScConditionalFormatList* CopyFromClipContext::getCondFormatList()
{
    return mpCondFormatList;
}

void CopyFromClipContext::setTableProtected( bool b )
{
    mbTableProtected = b;
}

bool CopyFromClipContext::isTableProtected() const
{
    return mbTableProtected;
}

bool CopyFromClipContext::isAsLink() const
{
    return mbAsLink;
}

bool CopyFromClipContext::isSkipAttrForEmptyCells() const
{
    return mbSkipAttrForEmptyCells;
}

bool CopyFromClipContext::isCloneNotes() const
{
    return mbCloneNotes;
}

bool CopyFromClipContext::isDateCell( const ScColumn& rCol, SCROW nRow ) const
{
    sal_uLong nNumIndex = static_cast<const SfxUInt32Item&>(rCol.GetAttr(nRow, ATTR_VALUE_FORMAT)).GetValue();
    short nType = mpClipDoc->GetFormatTable()->GetType(nNumIndex);
    return (nType == css::util::NumberFormat::DATE) || (nType == css::util::NumberFormat::TIME) || (nType == css::util::NumberFormat::DATETIME);
}

CopyToClipContext::CopyToClipContext(
    ScDocument& rDoc, bool bKeepScenarioFlags) :
    ClipContextBase(rDoc), mbKeepScenarioFlags(bKeepScenarioFlags) {}

CopyToClipContext::~CopyToClipContext() {}

bool CopyToClipContext::isKeepScenarioFlags() const
{
    return mbKeepScenarioFlags;
}

CopyToDocContext::CopyToDocContext(ScDocument& rDoc) :
    ClipContextBase(rDoc), mbStartListening(true) {}

CopyToDocContext::~CopyToDocContext() {}

void CopyToDocContext::setStartListening( bool b )
{
    mbStartListening = b;
}

bool CopyToDocContext::isStartListening() const
{
    return mbStartListening;
}

MixDocContext::MixDocContext(ScDocument& rDoc) : ClipContextBase(rDoc) {}
MixDocContext::~MixDocContext() {}

}

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