/**************************************************************
 * 
 * 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
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sc.hxx"



// INCLUDE ---------------------------------------------------------------

#include <svl/intitem.hxx>
#include <svl/zforlist.hxx>
#include <vcl/sound.hxx>
#include <formula/token.hxx>

#include "document.hxx"
#include "table.hxx"
#include "globstr.hrc"
#include "subtotal.hxx"
#include "docoptio.hxx"
#include "interpre.hxx"
#include "markdata.hxx"
#include "validat.hxx"
#include "scitems.hxx"
#include "stlpool.hxx"
#include "poolhelp.hxx"
#include "detdata.hxx"
#include "patattr.hxx"
#include "chgtrack.hxx"
#include "progress.hxx"
#include "paramisc.hxx"
#include "compiler.hxx"
#include "externalrefmgr.hxx"

using namespace formula;

// -----------------------------------------------------------------------

// Nach der Regula Falsi Methode
sal_Bool ScDocument::Solver(SCCOL nFCol, SCROW nFRow, SCTAB nFTab,
						SCCOL nVCol, SCROW nVRow, SCTAB nVTab,
						const String& sValStr, double& nX)
{
	sal_Bool bRet = sal_False;
	nX = 0.0;
	if (ValidColRow(nFCol, nFRow) && ValidColRow(nVCol, nVRow) &&
		VALIDTAB(nFTab) && VALIDTAB(nVTab) && pTab[nFTab] && pTab[nVTab])
	{
		CellType eFType, eVType;
		GetCellType(nFCol, nFRow, nFTab, eFType);
		GetCellType(nVCol, nVRow, nVTab, eVType);
        // CELLTYPE_NOTE: no value, but referenced by formula
        // #i108005# convert target value to number using default format,
        // as previously done in ScInterpreter::GetDouble
        double nTargetVal = 0.0;
        sal_uInt32 nFIndex = 0;
        if (eFType == CELLTYPE_FORMULA && (eVType == CELLTYPE_VALUE || eVType == CELLTYPE_NOTE) &&
            GetFormatTable()->IsNumberFormat(sValStr, nFIndex, nTargetVal))
		{
			ScSingleRefData aRefData;
			aRefData.InitFlags();
			aRefData.nCol = nVCol;
			aRefData.nRow = nVRow;
			aRefData.nTab = nVTab;

			ScTokenArray aArr;
			aArr.AddOpCode( ocBackSolver );
			aArr.AddOpCode( ocOpen );
			aArr.AddSingleReference( aRefData );
			aArr.AddOpCode( ocSep );

			aRefData.nCol = nFCol;
			aRefData.nRow = nFRow;
			aRefData.nTab = nFTab;

			aArr.AddSingleReference( aRefData );
			aArr.AddOpCode( ocSep );
            aArr.AddDouble( nTargetVal );
			aArr.AddOpCode( ocClose );
			aArr.AddOpCode( ocStop );

			ScFormulaCell* pCell = new ScFormulaCell( this, ScAddress(), &aArr );

			if (pCell)
			{
                // FIXME FIXME FIXME this might need to be reworked now that we have formula::FormulaErrorToken and ScFormulaResult, double check !!!
                DBG_ERRORFILE("ScDocument::Solver: -> ScFormulaCell::GetValueAlways might need reimplementation");
				pCell->Interpret();
				sal_uInt16 nErrCode = pCell->GetErrCode();
				nX = pCell->GetValueAlways();
				if (nErrCode == 0)					// kein fehler beim Rechnen
					bRet = sal_True;
				delete pCell;
			}
		}
	}
	return bRet;
}

void ScDocument::InsertMatrixFormula(SCCOL nCol1, SCROW nRow1,
									 SCCOL nCol2, SCROW nRow2,
									 const ScMarkData& rMark,
									 const String& rFormula,
									 const ScTokenArray* pArr,
                                     const formula::FormulaGrammar::Grammar eGram )
{
	PutInOrder(nCol1, nCol2);
	PutInOrder(nRow1, nRow2);
	SCTAB i, nTab1;
    SCCOL j;
    SCROW k;
	i = 0;
	sal_Bool bStop = sal_False;
	while (i <= MAXTAB && !bStop)				// erste markierte Tabelle finden
	{
		if (pTab[i] && rMark.GetTableSelect(i))
			bStop = sal_True;
		else
			i++;
	}
	nTab1 = i;
	if (i == MAXTAB + 1)
	{
		Sound::Beep();
		DBG_ERROR("ScDocument::InsertMatrixFormula Keine Tabelle markiert");
		return;
	}

	ScFormulaCell* pCell;
	ScAddress aPos( nCol1, nRow1, nTab1 );
	if (pArr)
		pCell = new ScFormulaCell( this, aPos, pArr, eGram, MM_FORMULA );
	else
        pCell = new ScFormulaCell( this, aPos, rFormula, eGram, MM_FORMULA );
	pCell->SetMatColsRows( nCol2 - nCol1 + 1, nRow2 - nRow1 + 1 );
	for (i = 0; i <= MAXTAB; i++)
	{
		if (pTab[i] && rMark.GetTableSelect(i))
		{
			if (i == nTab1)
				pTab[i]->PutCell(nCol1, nRow1, pCell);
			else
                pTab[i]->PutCell(nCol1, nRow1, pCell->CloneWithoutNote(*this, ScAddress( nCol1, nRow1, i), SC_CLONECELL_STARTLISTENING));
		}
	}

	ScSingleRefData aRefData;
	aRefData.InitFlags();
	aRefData.nCol = nCol1;
	aRefData.nRow = nRow1;
	aRefData.nTab = nTab1;
	aRefData.SetColRel( sal_True );
	aRefData.SetRowRel( sal_True );
	aRefData.SetTabRel( sal_True );
	aRefData.CalcRelFromAbs( ScAddress( nCol1, nRow1, nTab1 ) );

	ScTokenArray aArr;
	ScToken* t = static_cast<ScToken*>(aArr.AddMatrixSingleReference( aRefData));

	for (i = 0; i <= MAXTAB; i++)
	{
		if (pTab[i] && rMark.GetTableSelect(i))
		{
			pTab[i]->DoColResize( nCol1, nCol2, static_cast<SCSIZE>(nRow2 - nRow1 + 1) );
			if (i != nTab1)
			{
				aRefData.nTab = i;
				aRefData.nRelTab = i - nTab1;
				t->GetSingleRef() = aRefData;
			}
			for (j = nCol1; j <= nCol2; j++)
			{
				for (k = nRow1; k <= nRow2; k++)
				{
					if (j != nCol1 || k != nRow1)		// nicht in der ersten Zelle
					{
						// Array muss geklont werden, damit jede
						// Zelle ein eigenes Array erhaelt!
						aPos = ScAddress( j, k, i );
						t->CalcRelFromAbs( aPos );
						pCell = new ScFormulaCell( this, aPos, aArr.Clone(), eGram, MM_REFERENCE );
						pTab[i]->PutCell(j, k, (ScBaseCell*) pCell);
					}
				}
			}
		}
	}
}

void ScDocument::InsertTableOp(const ScTabOpParam& rParam,      // Mehrfachoperation
							   SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
							   const ScMarkData& rMark)
{
	PutInOrder(nCol1, nCol2);
	PutInOrder(nRow1, nRow2);
	SCTAB i, nTab1;
    SCCOL j;
    SCROW k;
	i = 0;
	sal_Bool bStop = sal_False;
	while (i <= MAXTAB && !bStop)				// erste markierte Tabelle finden
	{
		if (pTab[i] && rMark.GetTableSelect(i))
			bStop = sal_True;
		else
			i++;
	}
	nTab1 = i;
	if (i == MAXTAB + 1)
	{
		Sound::Beep();
		DBG_ERROR("ScDocument::InsertTableOp: Keine Tabelle markiert");
		return;
	}

    ScRefAddress aRef;
	String aForString = '=';
    aForString += ScCompiler::GetNativeSymbol(ocTableOp);
	aForString += ScCompiler::GetNativeSymbol( ocOpen);

    const String& sSep = ScCompiler::GetNativeSymbol( ocSep);
	if (rParam.nMode == 0)							// nur Spalte
	{
        aRef.Set( rParam.aRefFormulaCell.GetAddress(), sal_True, sal_False, sal_False );
        aForString += aRef.GetRefString(this, nTab1);
        aForString += sSep; 
        aForString += rParam.aRefColCell.GetRefString(this, nTab1);
        aForString += sSep;
        aRef.Set( nCol1, nRow1, nTab1, sal_False, sal_True, sal_True );
        aForString += aRef.GetRefString(this, nTab1);
        nCol1++;
        nCol2 = Min( nCol2, (SCCOL)(rParam.aRefFormulaEnd.Col() -
                    rParam.aRefFormulaCell.Col() + nCol1 + 1));
    }
	else if (rParam.nMode == 1)					// nur zeilenweise
	{
        aRef.Set( rParam.aRefFormulaCell.GetAddress(), sal_False, sal_True, sal_False );
        aForString += aRef.GetRefString(this, nTab1);
        aForString += sSep;
        aForString += rParam.aRefRowCell.GetRefString(this, nTab1);
        aForString += sSep;
        aRef.Set( nCol1, nRow1, nTab1, sal_True, sal_False, sal_True );
        aForString += aRef.GetRefString(this, nTab1);
        nRow1++;
        nRow2 = Min( nRow2, (SCROW)(rParam.aRefFormulaEnd.Row() -
                    rParam.aRefFormulaCell.Row() + nRow1 + 1));
	}
	else					// beides
	{
        aForString += rParam.aRefFormulaCell.GetRefString(this, nTab1);
        aForString += sSep;
        aForString += rParam.aRefColCell.GetRefString(this, nTab1);
        aForString += sSep;
        aRef.Set( nCol1, nRow1 + 1, nTab1, sal_False, sal_True, sal_True );
        aForString += aRef.GetRefString(this, nTab1);
        aForString += sSep;
        aForString += rParam.aRefRowCell.GetRefString(this, nTab1);
        aForString += sSep;
        aRef.Set( nCol1 + 1, nRow1, nTab1, sal_True, sal_False, sal_True );
        aForString += aRef.GetRefString(this, nTab1);
        nCol1++; nRow1++;
	}
	aForString += ScCompiler::GetNativeSymbol( ocClose);

    ScFormulaCell aRefCell( this, ScAddress( nCol1, nRow1, nTab1 ), aForString,
           formula::FormulaGrammar::GRAM_NATIVE, MM_NONE );
    for( j = nCol1; j <= nCol2; j++ )
        for( k = nRow1; k <= nRow2; k++ )
            for (i = 0; i <= MAXTAB; i++)
                if( pTab[i] && rMark.GetTableSelect(i) )
                    pTab[i]->PutCell( j, k, aRefCell.CloneWithoutNote( *this, ScAddress( j, k, i ), SC_CLONECELL_STARTLISTENING ) );
}

bool ScDocument::MarkUsedExternalReferences( ScTokenArray & rArr )
{
    bool bAllMarked = false;
    if (rArr.GetLen())
    {
        ScExternalRefManager* pRefMgr = NULL;
        rArr.Reset();
        ScToken* t;
        while (!bAllMarked && (t = static_cast<ScToken*>(rArr.GetNextReferenceOrName())) != NULL)
        {
            if (t->GetOpCode() == ocExternalRef)
            {
                if (!pRefMgr)
                    pRefMgr = GetExternalRefManager();
                switch (t->GetType())
                {
                    case svExternalSingleRef:
                        bAllMarked = pRefMgr->setCacheTableReferenced( 
                                t->GetIndex(), t->GetString(), 1);
                        break;
                    case svExternalDoubleRef:
                        {
                            const ScComplexRefData& rRef = t->GetDoubleRef();
                            size_t nSheets = rRef.Ref2.nTab - rRef.Ref1.nTab + 1;
                            bAllMarked = pRefMgr->setCacheTableReferenced( 
                                    t->GetIndex(), t->GetString(), nSheets);
                        }
                        break;
                    case svExternalName:
                        /* TODO: external names aren't supported yet, but would 
                         * have to be marked as well, if so. Mechanism would be 
                         * different. */
                        DBG_ERRORFILE("ScDocument::MarkUsedExternalReferences: implement the svExternalName case!");
                        break;
                    default: break;
                }
            }
        }
    }
    return bAllMarked;
}

sal_Bool ScDocument::GetNextSpellingCell(SCCOL& nCol, SCROW& nRow, SCTAB nTab,
						sal_Bool bInSel, const ScMarkData& rMark) const
{
	if (ValidTab(nTab) && pTab[nTab])
		return pTab[nTab]->GetNextSpellingCell( nCol, nRow, bInSel, rMark );
	else
		return sal_False;
}

sal_Bool ScDocument::GetNextMarkedCell( SCCOL& rCol, SCROW& rRow, SCTAB nTab,
										const ScMarkData& rMark )
{
	if (ValidTab(nTab) && pTab[nTab])
		return pTab[nTab]->GetNextMarkedCell( rCol, rRow, rMark );
	else
		return sal_False;
}

sal_Bool ScDocument::ReplaceStyle(const SvxSearchItem& rSearchItem,
							  SCCOL nCol, SCROW nRow, SCTAB nTab,
							  ScMarkData& rMark,
                              sal_Bool bIsUndoP)
{
	if (pTab[nTab])
        return pTab[nTab]->ReplaceStyle(rSearchItem, nCol, nRow, rMark, bIsUndoP);
	else
		return sal_False;
}

void ScDocument::CompileDBFormula()
{
	for (SCTAB i=0; i<=MAXTAB; i++)
	{
		if (pTab[i]) pTab[i]->CompileDBFormula();
	}
}

void ScDocument::CompileDBFormula( sal_Bool bCreateFormulaString )
{
	for (SCTAB i=0; i<=MAXTAB; i++)
	{
		if (pTab[i]) pTab[i]->CompileDBFormula( bCreateFormulaString );
	}
}

void ScDocument::CompileNameFormula( sal_Bool bCreateFormulaString )
{
	if ( pCondFormList )
		pCondFormList->CompileAll();	// nach ScNameDlg noetig

	for (SCTAB i=0; i<=MAXTAB; i++)
	{
		if (pTab[i]) pTab[i]->CompileNameFormula( bCreateFormulaString );
	}
}

void ScDocument::CompileColRowNameFormula()
{
	for (SCTAB i=0; i<=MAXTAB; i++)
	{
		if (pTab[i]) pTab[i]->CompileColRowNameFormula();
	}
}

void ScDocument::DoColResize( SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCSIZE nAdd )
{
	if (ValidTab(nTab) && pTab[nTab])
		pTab[nTab]->DoColResize( nCol1, nCol2, nAdd );
	else
	{
		DBG_ERROR("DoColResize: falsche Tabelle");
	}
}

void ScDocument::InvalidateTableArea()
{
	for (SCTAB nTab=0; nTab<=MAXTAB && pTab[nTab]; nTab++)
	{
		pTab[nTab]->InvalidateTableArea();
		if ( pTab[nTab]->IsScenario() )
			pTab[nTab]->InvalidateScenarioRanges();
	}
}

void ScDocument::GetLastAttrCell( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow ) const
{
	if ( ValidTab( nTab ) && pTab[nTab] )
	{
		pTab[nTab]->GetLastAttrCell( rEndCol, rEndRow );
	}
}

sal_Int32 ScDocument::GetMaxStringLen( SCTAB nTab, SCCOL nCol,
        SCROW nRowStart, SCROW nRowEnd, CharSet eCharSet ) const
{
	if (ValidTab(nTab) && pTab[nTab])
		return pTab[nTab]->GetMaxStringLen( nCol, nRowStart, nRowEnd, eCharSet );
	else
        return 0;
}

xub_StrLen ScDocument::GetMaxNumberStringLen( sal_uInt16& nPrecision, SCTAB nTab,
                                    SCCOL nCol,
                                    SCROW nRowStart, SCROW nRowEnd ) const
{
    if (ValidTab(nTab) && pTab[nTab])
        return pTab[nTab]->GetMaxNumberStringLen( nPrecision, nCol,
            nRowStart, nRowEnd );
    else
        return 0;
}

sal_Bool ScDocument::GetSelectionFunction( ScSubTotalFunc eFunc,
										const ScAddress& rCursor, const ScMarkData& rMark,
										double& rResult )
{
	ScFunctionData aData(eFunc);

	ScRange aSingle( rCursor );
	if ( rMark.IsMarked() )
		rMark.GetMarkArea(aSingle);

	SCCOL nStartCol = aSingle.aStart.Col();
	SCROW nStartRow = aSingle.aStart.Row();
	SCCOL nEndCol = aSingle.aEnd.Col();
	SCROW nEndRow = aSingle.aEnd.Row();

	for (SCTAB nTab=0; nTab<=MAXTAB && !aData.bError; nTab++)
		if (pTab[nTab] && rMark.GetTableSelect(nTab))
			pTab[nTab]->UpdateSelectionFunction( aData,
							nStartCol, nStartRow, nEndCol, nEndRow, rMark );

			//!	rMark an UpdateSelectionFunction uebergeben !!!!!

	if (!aData.bError)
		switch (eFunc)
		{
			case SUBTOTAL_FUNC_SUM:
				rResult = aData.nVal;
				break;
			case SUBTOTAL_FUNC_CNT:
			case SUBTOTAL_FUNC_CNT2:
				rResult = aData.nCount;
				break;
			case SUBTOTAL_FUNC_AVE:
				if (aData.nCount)
					rResult = aData.nVal / (double) aData.nCount;
				else
					aData.bError = sal_True;
				break;
			case SUBTOTAL_FUNC_MAX:
			case SUBTOTAL_FUNC_MIN:
				if (aData.nCount)
					rResult = aData.nVal;
				else
					aData.bError = sal_True;
				break;
            default:
            {
                // added to avoid warnings
            }
		}

	if (aData.bError)
		rResult = 0.0;

	return !aData.bError;
}

double ScDocument::RoundValueAsShown( double fVal, sal_uLong nFormat )
{
	short nType;
	if ( (nType = GetFormatTable()->GetType( nFormat )) != NUMBERFORMAT_DATE
	  && nType != NUMBERFORMAT_TIME && nType != NUMBERFORMAT_DATETIME )
	{
		short nPrecision;
        if ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0)
		{
			nPrecision = (short)GetFormatTable()->GetFormatPrecision( nFormat );
			switch ( nType )
			{
				case NUMBERFORMAT_PERCENT:		// 0,41% == 0,0041
					nPrecision += 2;
					break;
				case NUMBERFORMAT_SCIENTIFIC:	// 1,23e-3 == 0,00123
				{
					if ( fVal > 0.0 )
                        nPrecision = sal::static_int_cast<short>( nPrecision - (short)floor( log10( fVal ) ) );
					else if ( fVal < 0.0 )
                        nPrecision = sal::static_int_cast<short>( nPrecision - (short)floor( log10( -fVal ) ) );
					break;
				}
			}
		}
		else
        {
			nPrecision = (short)GetDocOptions().GetStdPrecision();
            // #i115512# no rounding for automatic decimals
            if (nPrecision == static_cast<short>(SvNumberFormatter::UNLIMITED_PRECISION))
                return fVal;
        }
		double fRound = ::rtl::math::round( fVal, nPrecision );
		if ( ::rtl::math::approxEqual( fVal, fRound ) )
			return fVal;		// durch Rundung hoechstens Fehler
		else
			return fRound;
	}
	else
		return fVal;
}

//
//			bedingte Formate und Gueltigkeitsbereiche
//

sal_uLong ScDocument::AddCondFormat( const ScConditionalFormat& rNew )
{
	if (rNew.IsEmpty())
		return 0;					// leer ist immer 0

	if (!pCondFormList)
		pCondFormList = new ScConditionalFormatList;

	sal_uLong nMax = 0;
	sal_uInt16 nCount = pCondFormList->Count();
	for (sal_uInt16 i=0; i<nCount; i++)
	{
		const ScConditionalFormat* pForm = (*pCondFormList)[i];
		sal_uLong nKey = pForm->GetKey();
		if ( pForm->EqualEntries( rNew ) )
			return nKey;
		if ( nKey > nMax )
			nMax = nKey;
	}

	// Der Aufruf kann aus ScPatternAttr::PutInPool kommen, darum Clone (echte Kopie)

	sal_uLong nNewKey = nMax + 1;
	ScConditionalFormat* pInsert = rNew.Clone(this);
	pInsert->SetKey( nNewKey );
	pCondFormList->InsertNew( pInsert );
	return nNewKey;
}

sal_uLong ScDocument::AddValidationEntry( const ScValidationData& rNew )
{
	if (rNew.IsEmpty())
		return 0;					// leer ist immer 0

	if (!pValidationList)
		pValidationList = new ScValidationDataList;

	sal_uLong nMax = 0;
	sal_uInt16 nCount = pValidationList->Count();
	for (sal_uInt16 i=0; i<nCount; i++)
	{
		const ScValidationData* pData = (*pValidationList)[i];
		sal_uLong nKey = pData->GetKey();
		if ( pData->EqualEntries( rNew ) )
			return nKey;
		if ( nKey > nMax )
			nMax = nKey;
	}

	// Der Aufruf kann aus ScPatternAttr::PutInPool kommen, darum Clone (echte Kopie)

	sal_uLong nNewKey = nMax + 1;
	ScValidationData* pInsert = rNew.Clone(this);
	pInsert->SetKey( nNewKey );
	pValidationList->InsertNew( pInsert );
	return nNewKey;
}

const SfxPoolItem* ScDocument::GetEffItem(
						SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich ) const
{
	const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
	if ( pPattern )
	{
		const SfxItemSet& rSet = pPattern->GetItemSet();
		const SfxPoolItem* pItem;
		if ( rSet.GetItemState( ATTR_CONDITIONAL, sal_True, &pItem ) == SFX_ITEM_SET )
		{
			sal_uLong nIndex = ((const SfxUInt32Item*)pItem)->GetValue();
			if (nIndex && pCondFormList)
			{
				const ScConditionalFormat* pForm = pCondFormList->GetFormat( nIndex );
				if ( pForm )
				{
					ScBaseCell* pCell = ((ScDocument*)this)->GetCell(ScAddress(nCol,nRow,nTab));
					String aStyle = pForm->GetCellStyle( pCell, ScAddress(nCol, nRow, nTab) );
					if (aStyle.Len())
					{
						SfxStyleSheetBase* pStyleSheet = xPoolHelper->GetStylePool()->Find(
																aStyle, SFX_STYLE_FAMILY_PARA );
						if ( pStyleSheet && pStyleSheet->GetItemSet().GetItemState(
												nWhich, sal_True, &pItem ) == SFX_ITEM_SET )
							return pItem;
					}
				}
			}
		}
		return &rSet.Get( nWhich );
	}
	DBG_ERROR("kein Pattern");
	return NULL;
}

const SfxItemSet* ScDocument::GetCondResult( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
	const ScConditionalFormat* pForm = GetCondFormat( nCol, nRow, nTab );
	if ( pForm )
	{
		ScBaseCell* pCell = ((ScDocument*)this)->GetCell(ScAddress(nCol,nRow,nTab));
		String aStyle = pForm->GetCellStyle( pCell, ScAddress(nCol, nRow, nTab) );
		if (aStyle.Len())
		{
			SfxStyleSheetBase* pStyleSheet = xPoolHelper->GetStylePool()->Find( aStyle, SFX_STYLE_FAMILY_PARA );
			if ( pStyleSheet )
				return &pStyleSheet->GetItemSet();
			// if style is not there, treat like no condition
		}
	}
	return NULL;
}

const ScConditionalFormat* ScDocument::GetCondFormat(
							SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
	sal_uLong nIndex = ((const SfxUInt32Item*)GetAttr(nCol,nRow,nTab,ATTR_CONDITIONAL))->GetValue();
	if (nIndex)
	{
		if (pCondFormList)
			return pCondFormList->GetFormat( nIndex );
		else
		{
			DBG_ERROR("pCondFormList ist 0");
		}
	}

	return NULL;
}

const ScValidationData*	ScDocument::GetValidationEntry( sal_uLong nIndex ) const
{
	if ( pValidationList )
		return pValidationList->GetData( nIndex );
	else
		return NULL;
}

void ScDocument::FindConditionalFormat( sal_uLong nKey, ScRangeList& rRanges )
{
	for (SCTAB i=0; i<=MAXTAB && pTab[i]; i++)
		pTab[i]->FindConditionalFormat( nKey, rRanges );
}

void ScDocument::FindConditionalFormat( sal_uLong nKey, ScRangeList& rRanges, SCTAB nTab )
{
    if(VALIDTAB(nTab) && pTab[nTab])
        pTab[nTab]->FindConditionalFormat( nKey, rRanges );
}

void ScDocument::ConditionalChanged( sal_uLong nKey )
{
	if ( nKey && pCondFormList && !bIsClip && !bIsUndo )		// nKey==0 -> noop
	{
		ScConditionalFormat* pForm = pCondFormList->GetFormat( nKey );
		if (pForm)
			pForm->InvalidateArea();
	}
}

void ScDocument::SetCondFormList(ScConditionalFormatList* pNew)
{
	if (pCondFormList)
	{
		pCondFormList->DeleteAndDestroy( 0, pCondFormList->Count() );
		delete pCondFormList;
	}

	pCondFormList = pNew;
}

//------------------------------------------------------------------------

sal_Bool ScDocument::HasDetectiveOperations() const
{
	return pDetOpList && pDetOpList->Count();
}

void ScDocument::AddDetectiveOperation( const ScDetOpData& rData )
{
	if (!pDetOpList)
		pDetOpList = new ScDetOpList;

	pDetOpList->Append( new ScDetOpData( rData ) );
}

void ScDocument::ClearDetectiveOperations()
{
	delete pDetOpList;		// loescht auch die Eintraege
	pDetOpList = NULL;
}

void ScDocument::SetDetOpList(ScDetOpList* pNew)
{
	delete pDetOpList;		// loescht auch die Eintraege
	pDetOpList = pNew;
}

//------------------------------------------------------------------------
//
//		Vergleich von Dokumenten
//
//------------------------------------------------------------------------

//	Pfriemel-Faktoren
#define SC_DOCCOMP_MAXDIFF	256
#define SC_DOCCOMP_MINGOOD	128
#define SC_DOCCOMP_COLUMNS	10
#define SC_DOCCOMP_ROWS		100


sal_uInt16 ScDocument::RowDifferences( SCROW nThisRow, SCTAB nThisTab,
									ScDocument& rOtherDoc, SCROW nOtherRow, SCTAB nOtherTab,
									SCCOL nMaxCol, SCCOLROW* pOtherCols )
{
	sal_uLong nDif = 0;
	sal_uLong nUsed = 0;
	for (SCCOL nThisCol=0; nThisCol<=nMaxCol; nThisCol++)
	{
		SCCOL nOtherCol;
		if ( pOtherCols )
			nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
		else
			nOtherCol = nThisCol;

		if (ValidCol(nOtherCol))	// nur Spalten vergleichen, die in beiden Dateien sind
		{
			const ScBaseCell* pThisCell = GetCell( ScAddress( nThisCol, nThisRow, nThisTab ) );
			const ScBaseCell* pOtherCell = rOtherDoc.GetCell( ScAddress( nOtherCol, nOtherRow, nOtherTab ) );
			if (!ScBaseCell::CellEqual( pThisCell, pOtherCell ))
			{
				if ( pThisCell && pOtherCell )
					nDif += 3;
				else
					nDif += 4;		// Inhalt <-> leer zaehlt mehr
			}

			if ( ( pThisCell  && pThisCell->GetCellType()!=CELLTYPE_NOTE ) ||
				 ( pOtherCell && pOtherCell->GetCellType()!=CELLTYPE_NOTE ) )
				++nUsed;
		}
	}

	if (nUsed > 0)
		return static_cast<sal_uInt16>((nDif*64)/nUsed);			// max.256 (SC_DOCCOMP_MAXDIFF)

	DBG_ASSERT(!nDif,"Diff ohne Used");
	return 0;
}

sal_uInt16 ScDocument::ColDifferences( SCCOL nThisCol, SCTAB nThisTab,
									ScDocument& rOtherDoc, SCCOL nOtherCol, SCTAB nOtherTab,
									SCROW nMaxRow, SCCOLROW* pOtherRows )
{
	//!	optimieren mit Iterator oder so

	sal_uLong nDif = 0;
	sal_uLong nUsed = 0;
	for (SCROW nThisRow=0; nThisRow<=nMaxRow; nThisRow++)
	{
		SCROW nOtherRow;
		if ( pOtherRows )
			nOtherRow = pOtherRows[nThisRow];
		else
			nOtherRow = nThisRow;

		if (ValidRow(nOtherRow))	// nur Zeilen vergleichen, die in beiden Dateien sind
		{
			const ScBaseCell* pThisCell = GetCell( ScAddress( nThisCol, nThisRow, nThisTab ) );
			const ScBaseCell* pOtherCell = rOtherDoc.GetCell( ScAddress( nOtherCol, nOtherRow, nOtherTab ) );
			if (!ScBaseCell::CellEqual( pThisCell, pOtherCell ))
			{
				if ( pThisCell && pOtherCell )
					nDif += 3;
				else
					nDif += 4;		// Inhalt <-> leer zaehlt mehr
			}

			if ( ( pThisCell  && pThisCell->GetCellType()!=CELLTYPE_NOTE ) ||
				 ( pOtherCell && pOtherCell->GetCellType()!=CELLTYPE_NOTE ) )
				++nUsed;
		}
	}

	if (nUsed > 0)
		return static_cast<sal_uInt16>((nDif*64)/nUsed);	// max.256

	DBG_ASSERT(!nDif,"Diff ohne Used");
	return 0;
}

void ScDocument::FindOrder( SCCOLROW* pOtherRows, SCCOLROW nThisEndRow, SCCOLROW nOtherEndRow,
							sal_Bool bColumns, ScDocument& rOtherDoc, SCTAB nThisTab, SCTAB nOtherTab,
							SCCOLROW nEndCol, SCCOLROW* pTranslate, ScProgress* pProgress, sal_uLong nProAdd )
{
	//	bColumns=sal_True: Zeilen sind Spalten und umgekehrt

	SCCOLROW nMaxCont;						// wieviel weiter
	SCCOLROW nMinGood;						// was ist ein Treffer (incl.)
	if ( bColumns )
	{
		nMaxCont = SC_DOCCOMP_COLUMNS;		// 10 Spalten
		nMinGood = SC_DOCCOMP_MINGOOD;
		//!	Extra Durchgang mit nMinGood = 0 ????
	}
	else
	{
		nMaxCont = SC_DOCCOMP_ROWS;			// 100 Zeilen
		nMinGood = SC_DOCCOMP_MINGOOD;
	}
	sal_Bool bUseTotal = bColumns && !pTranslate;		// nur beim ersten Durchgang


	SCCOLROW nOtherRow = 0;
	sal_uInt16 nComp;
	SCCOLROW nThisRow;
	sal_Bool bTotal = sal_False;		// ueber verschiedene nThisRow beibehalten
	SCCOLROW nUnknown = 0;
	for (nThisRow = 0; nThisRow <= nThisEndRow; nThisRow++)
	{
		SCCOLROW nTempOther = nOtherRow;
		sal_Bool bFound = sal_False;
		sal_uInt16 nBest = SC_DOCCOMP_MAXDIFF;
		SCCOLROW nMax = Min( nOtherEndRow, static_cast<SCCOLROW>(( nTempOther + nMaxCont + nUnknown )) );
		for (SCCOLROW i=nTempOther; i<=nMax && nBest>0; i++)	// bei 0 abbrechen
		{
			if (bColumns)
				nComp = ColDifferences( static_cast<SCCOL>(nThisRow), nThisTab, rOtherDoc, static_cast<SCCOL>(i), nOtherTab, nEndCol, pTranslate );
			else
				nComp = RowDifferences( nThisRow, nThisTab, rOtherDoc, i, nOtherTab, static_cast<SCCOL>(nEndCol), pTranslate );
			if ( nComp < nBest && ( nComp <= nMinGood || bTotal ) )
			{
				nTempOther = i;
				nBest = nComp;
				bFound = sal_True;
			}
			if ( nComp < SC_DOCCOMP_MAXDIFF || bFound )
				bTotal = sal_False;
			else if ( i == nTempOther && bUseTotal )
				bTotal = sal_True;							// nur ganz oben
		}
		if ( bFound )
		{
			pOtherRows[nThisRow] = nTempOther;
			nOtherRow = nTempOther + 1;
			nUnknown = 0;
		}
		else
		{
			pOtherRows[nThisRow] = SCROW_MAX;
			++nUnknown;
		}

		if (pProgress)
			pProgress->SetStateOnPercent(nProAdd+static_cast<sal_uLong>(nThisRow));
	}

	//	Bloecke ohne Uebereinstimmung ausfuellen

	SCROW nFillStart = 0;
	SCROW nFillPos = 0;
	sal_Bool bInFill = sal_False;
	for (nThisRow = 0; nThisRow <= nThisEndRow+1; nThisRow++)
	{
		SCROW nThisOther = ( nThisRow <= nThisEndRow ) ? pOtherRows[nThisRow] : (nOtherEndRow+1);
		if ( ValidRow(nThisOther) )
		{
			if ( bInFill )
			{
				if ( nThisOther > nFillStart )		// ist was zu verteilen da?
				{
					SCROW nDiff1 = nThisOther - nFillStart;
					SCROW nDiff2 = nThisRow   - nFillPos;
					SCROW nMinDiff = Min(nDiff1, nDiff2);
					for (SCROW i=0; i<nMinDiff; i++)
						pOtherRows[nFillPos+i] = nFillStart+i;
				}

				bInFill = sal_False;
			}
			nFillStart = nThisOther + 1;
			nFillPos = nThisRow + 1;
		}
		else
			bInFill = sal_True;
	}
}

void ScDocument::CompareDocument( ScDocument& rOtherDoc )
{
	if (!pChangeTrack)
		return;

	SCTAB nThisCount = GetTableCount();
	SCTAB nOtherCount = rOtherDoc.GetTableCount();
	SCTAB* pOtherTabs = new SCTAB[nThisCount];
	SCTAB nThisTab;

	//	Tabellen mit gleichen Namen vergleichen
	String aThisName;
	String aOtherName;
	for (nThisTab=0; nThisTab<nThisCount; nThisTab++)
	{
		SCTAB nOtherTab = SCTAB_MAX;
		if (!IsScenario(nThisTab))	// Szenarien weglassen
		{
			GetName( nThisTab, aThisName );
			for (SCTAB nTemp=0; nTemp<nOtherCount && nOtherTab>MAXTAB; nTemp++)
				if (!rOtherDoc.IsScenario(nTemp))
				{
					rOtherDoc.GetName( nTemp, aOtherName );
					if ( aThisName == aOtherName )
						nOtherTab = nTemp;
				}
		}
		pOtherTabs[nThisTab] = nOtherTab;
	}
	//	auffuellen, damit einzeln umbenannte Tabellen nicht wegfallen
	SCTAB nFillStart = 0;
	SCTAB nFillPos = 0;
	sal_Bool bInFill = sal_False;
	for (nThisTab = 0; nThisTab <= nThisCount; nThisTab++)
	{
		SCTAB nThisOther = ( nThisTab < nThisCount ) ? pOtherTabs[nThisTab] : nOtherCount;
		if ( ValidTab(nThisOther) )
		{
			if ( bInFill )
			{
				if ( nThisOther > nFillStart )		// ist was zu verteilen da?
				{
					SCTAB nDiff1 = nThisOther - nFillStart;
					SCTAB nDiff2 = nThisTab   - nFillPos;
					SCTAB nMinDiff = Min(nDiff1, nDiff2);
					for (SCTAB i=0; i<nMinDiff; i++)
						if ( !IsScenario(nFillPos+i) && !rOtherDoc.IsScenario(nFillStart+i) )
							pOtherTabs[nFillPos+i] = nFillStart+i;
				}

				bInFill = sal_False;
			}
			nFillStart = nThisOther + 1;
			nFillPos = nThisTab + 1;
		}
		else
			bInFill = sal_True;
	}

	//
	//	Tabellen in der gefundenen Reihenfolge vergleichen
	//

	for (nThisTab=0; nThisTab<nThisCount; nThisTab++)
	{
		SCTAB nOtherTab = pOtherTabs[nThisTab];
		if ( ValidTab(nOtherTab) )
		{
			SCCOL nThisEndCol = 0;
			SCROW nThisEndRow = 0;
			SCCOL nOtherEndCol = 0;
			SCROW nOtherEndRow = 0;
			GetCellArea( nThisTab, nThisEndCol, nThisEndRow );
			rOtherDoc.GetCellArea( nOtherTab, nOtherEndCol, nOtherEndRow );
			SCCOL nEndCol = Max(nThisEndCol, nOtherEndCol);
			SCROW nEndRow = Max(nThisEndRow, nOtherEndRow);
			SCCOL nThisCol;
			SCROW nThisRow;
			sal_uLong n1,n2;	// fuer AppendDeleteRange

			//!	ein Progress ueber alle Tabellen ???
			String aTabName;
			GetName( nThisTab, aTabName );
			String aTemplate = ScGlobal::GetRscString(STR_PROGRESS_COMPARING);
			String aProText = aTemplate.GetToken( 0, '#' );
			aProText += aTabName;
			aProText += aTemplate.GetToken( 1, '#' );
			ScProgress aProgress( GetDocumentShell(),
										aProText, 3*nThisEndRow );	// 2x FindOrder, 1x hier
			long nProgressStart = 2*nThisEndRow;					// start fuer hier

			SCCOLROW* pTempRows = new SCCOLROW[nThisEndRow+1];
			SCCOLROW* pOtherRows = new SCCOLROW[nThisEndRow+1];
			SCCOLROW* pOtherCols = new SCCOLROW[nThisEndCol+1];

			//	eingefuegte/geloeschte Spalten/Zeilen finden:
			//	Zwei Versuche:
			//	1) Original Zeilen vergleichen							(pTempRows)
			//	2) Original Spalten vergleichen							(pOtherCols)
			//	   mit dieser Spaltenreihenfolge Zeilen vergleichen		(pOtherRows)

			//!	Spalten vergleichen zweimal mit unterschiedlichem nMinGood ???

			// 1
			FindOrder( pTempRows, nThisEndRow, nOtherEndRow, sal_False,
						rOtherDoc, nThisTab, nOtherTab, nEndCol, NULL, &aProgress, 0 );
			// 2
			FindOrder( pOtherCols, nThisEndCol, nOtherEndCol, sal_True,
						rOtherDoc, nThisTab, nOtherTab, nEndRow, NULL, NULL, 0 );
			FindOrder( pOtherRows, nThisEndRow, nOtherEndRow, sal_False,
						rOtherDoc, nThisTab, nOtherTab, nThisEndCol,
						pOtherCols, &aProgress, nThisEndRow );

			sal_uLong nMatch1 = 0;	// pTempRows, keine Spalten
			for (nThisRow = 0; nThisRow<=nThisEndRow; nThisRow++)
				if (ValidRow(pTempRows[nThisRow]))
					nMatch1 += SC_DOCCOMP_MAXDIFF -
							   RowDifferences( nThisRow, nThisTab, rOtherDoc, pTempRows[nThisRow],
												nOtherTab, nEndCol, NULL );

			sal_uLong nMatch2 = 0;	// pOtherRows, pOtherCols
			for (nThisRow = 0; nThisRow<=nThisEndRow; nThisRow++)
				if (ValidRow(pOtherRows[nThisRow]))
					nMatch2 += SC_DOCCOMP_MAXDIFF -
							   RowDifferences( nThisRow, nThisTab, rOtherDoc, pOtherRows[nThisRow],
												nOtherTab, nThisEndCol, pOtherCols );

			if ( nMatch1 >= nMatch2 )			// ohne Spalten ?
			{
				//	Spalten zuruecksetzen
				for (nThisCol = 0; nThisCol<=nThisEndCol; nThisCol++)
					pOtherCols[nThisCol] = nThisCol;

				//	Zeilenarrays vertauschen (geloescht werden sowieso beide)
				SCCOLROW* pSwap = pTempRows;
				pTempRows = pOtherRows;
				pOtherRows = pSwap;
			}
			else
			{
				//	bleibt bei pOtherCols, pOtherRows
			}


			//	Change-Actions erzeugen
			//	1) Spalten von rechts
			//	2) Zeilen von unten
			//	3) einzelne Zellen in normaler Reihenfolge

			//	Actions fuer eingefuegte/geloeschte Spalten

			SCCOL nLastOtherCol = static_cast<SCCOL>(nOtherEndCol + 1);
			//	nThisEndCol ... 0
			for ( nThisCol = nThisEndCol+1; nThisCol > 0; )
			{
				--nThisCol;
				SCCOL nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
				if ( ValidCol(nOtherCol) && nOtherCol+1 < nLastOtherCol )
				{
					// Luecke -> geloescht
					ScRange aDelRange( nOtherCol+1, 0, nOtherTab,
										nLastOtherCol-1, MAXROW, nOtherTab );
					pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
				}
				if ( nOtherCol > MAXCOL )						// eingefuegt
				{
					//	zusammenfassen
					if ( nThisCol == nThisEndCol || ValidCol(static_cast<SCCOL>(pOtherCols[nThisCol+1])) )
					{
						SCCOL nFirstNew = static_cast<SCCOL>(nThisCol);
						while ( nFirstNew > 0 && pOtherCols[nFirstNew-1] > MAXCOL )
							--nFirstNew;
						SCCOL nDiff = nThisCol - nFirstNew;
						ScRange aRange( nLastOtherCol, 0, nOtherTab,
										nLastOtherCol+nDiff, MAXROW, nOtherTab );
						pChangeTrack->AppendInsert( aRange );
					}
				}
				else
					nLastOtherCol = nOtherCol;
			}
			if ( nLastOtherCol > 0 )							// ganz oben geloescht
			{
				ScRange aDelRange( 0, 0, nOtherTab,
									nLastOtherCol-1, MAXROW, nOtherTab );
				pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
			}

			//	Actions fuer eingefuegte/geloeschte Zeilen

			SCROW nLastOtherRow = nOtherEndRow + 1;
			//	nThisEndRow ... 0
			for ( nThisRow = nThisEndRow+1; nThisRow > 0; )
			{
				--nThisRow;
				SCROW nOtherRow = pOtherRows[nThisRow];
				if ( ValidRow(nOtherRow) && nOtherRow+1 < nLastOtherRow )
				{
					// Luecke -> geloescht
					ScRange aDelRange( 0, nOtherRow+1, nOtherTab,
										MAXCOL, nLastOtherRow-1, nOtherTab );
					pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
				}
				if ( nOtherRow > MAXROW )						// eingefuegt
				{
					//	zusammenfassen
					if ( nThisRow == nThisEndRow || ValidRow(pOtherRows[nThisRow+1]) )
					{
						SCROW nFirstNew = nThisRow;
						while ( nFirstNew > 0 && pOtherRows[nFirstNew-1] > MAXROW )
							--nFirstNew;
						SCROW nDiff = nThisRow - nFirstNew;
						ScRange aRange( 0, nLastOtherRow, nOtherTab,
										MAXCOL, nLastOtherRow+nDiff, nOtherTab );
						pChangeTrack->AppendInsert( aRange );
					}
				}
				else
					nLastOtherRow = nOtherRow;
			}
			if ( nLastOtherRow > 0 )							// ganz oben geloescht
			{
				ScRange aDelRange( 0, 0, nOtherTab,
									MAXCOL, nLastOtherRow-1, nOtherTab );
				pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
			}

			//	Zeilen durchgehen um einzelne Zellen zu finden

			for (nThisRow = 0; nThisRow <= nThisEndRow; nThisRow++)
			{
				SCROW nOtherRow = pOtherRows[nThisRow];
                for (nThisCol = 0; nThisCol <= nThisEndCol; nThisCol++)
				{
					SCCOL nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
					ScAddress aThisPos( nThisCol, nThisRow, nThisTab );
					const ScBaseCell* pThisCell = GetCell( aThisPos );
					const ScBaseCell* pOtherCell = NULL;
					if ( ValidCol(nOtherCol) && ValidRow(nOtherRow) )
					{
						ScAddress aOtherPos( nOtherCol, nOtherRow, nOtherTab );
						pOtherCell = rOtherDoc.GetCell( aOtherPos );
					}
					if ( !ScBaseCell::CellEqual( pThisCell, pOtherCell ) )
					{
						ScRange aRange( aThisPos );
						ScChangeActionContent* pAction = new ScChangeActionContent( aRange );
						pAction->SetOldValue( pOtherCell, &rOtherDoc, this );
						pAction->SetNewValue( pThisCell, this );
						pChangeTrack->Append( pAction );
					}
				}
				aProgress.SetStateOnPercent(nProgressStart+nThisRow);
			}

			delete[] pOtherCols;
			delete[] pOtherRows;
			delete[] pTempRows;
		}
	}

	//!	Inhalt von eingefuegten / geloeschten Tabellen ???
	//!	Aktionen fuer eingefuegte / geloeschte Tabellen ???

	delete[] pOtherTabs;
}





