/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

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

#include "XclImpChangeTrack.hxx"
#include <tools/debug.hxx>
#include <sot/storage.hxx>
#include <svl/zforlist.hxx>
#include "chgviset.hxx"
#include "cell.hxx"
#include "chgtrack.hxx"
#include "xihelper.hxx"
#include "xilink.hxx"
#include "externalrefmgr.hxx"

//___________________________________________________________________
// class XclImpChangeTrack

XclImpChangeTrack::XclImpChangeTrack( const XclImpRoot& rRoot, const XclImpStream& rBookStrm ) :
    XclImpRoot( rRoot ),
	aRecHeader(),
	sOldUsername(),
	pChangeTrack( NULL ),
	pStrm( NULL ),
	nTabIdCount( 0 ),
	bGlobExit( sal_False ),
	eNestedMode( nmBase )
{
	// Verify that the User Names stream exists before going any further. Excel adds both
	// "Revision Log" and "User Names" streams when Change Tracking is active but the Revision log
	// remains if Change Tracking is turned off.
    SotStorageStreamRef xUserStrm = OpenStream( EXC_STREAM_USERNAMES );
    if( !xUserStrm.Is() )
		return;

    xInStrm = OpenStream( EXC_STREAM_REVLOG );
    if( xInStrm.Is() )
	{
        xInStrm->Seek( STREAM_SEEK_TO_END );
        sal_uLong nStreamLen = xInStrm->Tell();
        if( (xInStrm->GetErrorCode() == ERRCODE_NONE) && (nStreamLen != STREAM_SEEK_TO_END) )
		{
            xInStrm->Seek( STREAM_SEEK_TO_BEGIN );
            pStrm = new XclImpStream( *xInStrm, GetRoot() );
            pStrm->CopyDecrypterFrom( rBookStrm );
            pChangeTrack = new ScChangeTrack( GetDocPtr() );

            sOldUsername = pChangeTrack->GetUser();
            pChangeTrack->SetUseFixDateTime( sal_True );

            ReadRecords();
		}
	}
}

XclImpChangeTrack::~XclImpChangeTrack()
{
    delete pChangeTrack;
    delete pStrm;
}

void XclImpChangeTrack::DoAcceptRejectAction( ScChangeAction* pAction )
{
	if( !pAction ) return;
	switch( aRecHeader.nAccept )
	{
		case EXC_CHTR_ACCEPT:
			pChangeTrack->Accept( pAction );
		break;
		case EXC_CHTR_REJECT:
		break;
	}
}

void XclImpChangeTrack::DoAcceptRejectAction( sal_uInt32 nFirst, sal_uInt32 nLast )
{
	for( sal_uInt32 nIndex = nFirst; nIndex <= nLast; nIndex++ )
		DoAcceptRejectAction( pChangeTrack->GetAction( nIndex ) );
}

void XclImpChangeTrack::DoInsertRange( const ScRange& rRange )
{
	sal_uInt32 nFirst = pChangeTrack->GetActionMax() + 1;
	pChangeTrack->AppendInsert( rRange );
	sal_uInt32 nLast = pChangeTrack->GetActionMax();
	DoAcceptRejectAction( nFirst, nLast );
}

void XclImpChangeTrack::DoDeleteRange( const ScRange& rRange )
{
	sal_uLong nFirst, nLast;
	pChangeTrack->AppendDeleteRange( rRange, NULL, nFirst, nLast );
	DoAcceptRejectAction( nFirst, nLast );
}

SCTAB XclImpChangeTrack::ReadTabNum()
{
    return static_cast<SCTAB>(GetTabInfo().GetCurrentIndex(
                pStrm->ReaduInt16(), nTabIdCount ));
}

void XclImpChangeTrack::ReadDateTime( DateTime& rDateTime )
{
	sal_uInt16 nYear;
	sal_uInt8 nMonth, nDay, nHour, nMin, nSec;

	*pStrm >> nYear >> nMonth >> nDay >> nHour >> nMin >> nSec;

	rDateTime.SetYear( nYear );
	rDateTime.SetMonth( nMonth );
	rDateTime.SetDay( nDay );
	rDateTime.SetHour( nHour );
	rDateTime.SetMin( nMin );
	rDateTime.SetSec( nSec );
	rDateTime.Set100Sec( 0 );
}

sal_Bool XclImpChangeTrack::CheckRecord( sal_uInt16 nOpCode )
{
	if( (nOpCode != EXC_CHTR_OP_UNKNOWN) && (aRecHeader.nOpCode != nOpCode) )
	{
		DBG_ERROR( "XclImpChangeTrack::CheckRecord - unknown action" );
		return sal_False;
	}
	return aRecHeader.nIndex != 0;
}

sal_Bool XclImpChangeTrack::Read3DTabRefInfo( SCTAB& rFirstTab, SCTAB& rLastTab, ExcelToSc8::ExternalTabInfo& rExtInfo )
{
	if( LookAtuInt8() == 0x01 )
	{
        rExtInfo.mbExternal = false;
		// internal ref - read tab num and return sc tab num (position in TABID list)
        pStrm->Ignore( 3 );
        rFirstTab = static_cast< SCTAB >( GetTabInfo().GetCurrentIndex( pStrm->ReaduInt16(), nTabIdCount ) );
		sal_uInt8 nFillByte = pStrm->ReaduInt8();
		rLastTab = (nFillByte == 0x00) ?
            static_cast< SCTAB >( GetTabInfo().GetCurrentIndex( pStrm->ReaduInt16(), nTabIdCount ) ) : rFirstTab;
	}
	else
	{
		// external ref - read doc and tab name and find sc tab num
        // - URL
        String aEncUrl( pStrm->ReadUniString() );
        String aUrl;
        bool bSelf;
        XclImpUrlHelper::DecodeUrl( aUrl, bSelf, GetRoot(), aEncUrl );
        pStrm->Ignore( 1 );
        // - sheet name, always separated from URL
        String aTabName( pStrm->ReadUniString() );
        pStrm->Ignore( 1 );

        rExtInfo.mbExternal = true;
        ScExternalRefManager* pRefMgr = GetDoc().GetExternalRefManager();
        pRefMgr->convertToAbsName(aUrl);
        rExtInfo.mnFileId = pRefMgr->getExternalFileId(aUrl);
        rExtInfo.maTabName = aTabName;
        rFirstTab = rLastTab = 0;
	}
	return sal_True;
}

void XclImpChangeTrack::ReadFormula( ScTokenArray*& rpTokenArray, const ScAddress& rPosition )
{
	sal_uInt16 nFmlSize;
	*pStrm >> nFmlSize;

	// create a memory stream and copy the formula to be able to read simultaneously
	// the formula and the additional 3D tab ref data following the formula
	// here we have to simulate an Excel record to be able to use an XclImpStream...
	// 2do: remove the stream member from formula converter and add it as a parameter
	// to the Convert() routine (to prevent the construction/destruction of the
	// converter in each formula)
	SvMemoryStream aMemStrm;
	aMemStrm << (sal_uInt16) 0x0001 << nFmlSize;
    pStrm->CopyToStream( aMemStrm, nFmlSize );
    XclImpStream aFmlaStrm( aMemStrm, GetRoot() );
	aFmlaStrm.StartNextRecord();
    XclImpChTrFmlConverter aFmlConv( GetRoot(), *this );

	// read the formula, 3D tab refs from extended data
	const ScTokenArray* pArray = NULL;
	aFmlConv.Reset( rPosition );
    sal_Bool bOK = (aFmlConv.Convert( pArray, aFmlaStrm, nFmlSize, false, FT_CellFormula) == ConvOK);	// JEG : Check This
	rpTokenArray = (bOK && pArray) ? new ScTokenArray( *pArray ) : NULL;
    pStrm->Ignore( 1 );
}

void XclImpChangeTrack::ReadCell(
		ScBaseCell*& rpCell,
		sal_uInt32& rFormat,
		sal_uInt16 nFlags,
		const ScAddress& rPosition )
{
	rpCell = NULL;
	rFormat = 0;
	switch( nFlags & EXC_CHTR_TYPE_MASK )
	{
		case EXC_CHTR_TYPE_EMPTY:
		break;
		case EXC_CHTR_TYPE_RK:
		{
			double fValue = ReadRK();
			if( pStrm->IsValid() )
				rpCell = new ScValueCell( fValue );
		}
		break;
		case EXC_CHTR_TYPE_DOUBLE:
		{
			double fValue;
			*pStrm >> fValue;
			if( pStrm->IsValid() )
				rpCell = new ScValueCell( fValue );
		}
		break;
		case EXC_CHTR_TYPE_STRING:
		{
            String sString( pStrm->ReadUniString() );
			if( pStrm->IsValid() )
				rpCell = new ScStringCell( sString );
		}
		break;
		case EXC_CHTR_TYPE_BOOL:
		{
			double fValue = (double) ReadBool();
			if( pStrm->IsValid() )
			{
				rpCell = new ScValueCell( fValue );
                rFormat = GetFormatter().GetStandardFormat( NUMBERFORMAT_LOGICAL, ScGlobal::eLnge );
			}
		}
		break;
		case EXC_CHTR_TYPE_FORMULA:
		{
			ScTokenArray* pTokenArray = NULL;
			ReadFormula( pTokenArray, rPosition );
			if( pStrm->IsValid() && pTokenArray )
                rpCell = new ScFormulaCell( GetDocPtr(), rPosition, pTokenArray );
		}
		break;
		default:
			DBG_ERROR( "XclImpChangeTrack::ReadCell - unknown data type" );
	}
}

void XclImpChangeTrack::ReadChTrInsert()
{
	*pStrm >> aRecHeader;
	if( CheckRecord( EXC_CHTR_OP_UNKNOWN ) )
	{
        if( (aRecHeader.nOpCode != EXC_CHTR_OP_INSROW) &&
            (aRecHeader.nOpCode != EXC_CHTR_OP_INSCOL) &&
            (aRecHeader.nOpCode != EXC_CHTR_OP_DELROW) &&
            (aRecHeader.nOpCode != EXC_CHTR_OP_DELCOL) )
		{
			DBG_ERROR( "XclImpChangeTrack::ReadChTrInsert - unknown action" );
			return;
		}

		ScRange aRange;
		aRange.aStart.SetTab( ReadTabNum() );
		aRange.aEnd.SetTab( aRange.aStart.Tab() );
        pStrm->Ignore( 2 );
		Read2DRange( aRange );

		if( aRecHeader.nOpCode & EXC_CHTR_OP_COLFLAG )
			aRange.aEnd.SetRow( MAXROW );
		else
			aRange.aEnd.SetCol( MAXCOL );

		sal_Bool bValid = pStrm->IsValid();
		if( FoundNestedMode() )
			ReadNestedRecords();

		if( bValid )
		{
			if( aRecHeader.nOpCode & EXC_CHTR_OP_DELFLAG )
				DoDeleteRange( aRange );
			else
				DoInsertRange( aRange );
		}
	}
}

void XclImpChangeTrack::ReadChTrInfo()
{
    pStrm->DisableDecryption();
    pStrm->Ignore( 32 );
    String sUsername( pStrm->ReadUniString() );
	if( !pStrm->IsValid() ) return;

	if( sUsername.Len() )
		pChangeTrack->SetUser( sUsername );
    pStrm->Seek( 148 );
	if( !pStrm->IsValid() ) return;

	DateTime aDateTime;
	ReadDateTime( aDateTime );
	if( pStrm->IsValid() )
		pChangeTrack->SetFixDateTimeLocal( aDateTime );
}

void XclImpChangeTrack::ReadChTrCellContent()
{
	*pStrm >> aRecHeader;
	if( CheckRecord( EXC_CHTR_OP_CELL ) )
	{
		ScAddress aPosition;
		SCTAB nTab = ReadTabNum();
		aPosition.SetTab( nTab );
		sal_uInt16 nValueType;
		*pStrm >> nValueType;
		sal_uInt16 nOldValueType = (nValueType >> 3) & EXC_CHTR_TYPE_MASK;
		sal_uInt16 nNewValueType = nValueType & EXC_CHTR_TYPE_MASK;
        pStrm->Ignore( 2 );
		Read2DAddress( aPosition );
		sal_uInt16 nOldSize;
		*pStrm >> nOldSize;
		DBG_ASSERT( (nOldSize == 0) == (nOldValueType == EXC_CHTR_TYPE_EMPTY),
			"XclImpChangeTrack::ReadChTrCellContent - old value mismatch" );
        pStrm->Ignore( 4 );
		switch( nValueType & EXC_CHTR_TYPE_FORMATMASK )
		{
			case 0x0000:							break;
            case 0x1100:    pStrm->Ignore( 16 );    break;
            case 0x1300:    pStrm->Ignore( 8 );     break;
			default:		DBG_ERROR( "XclImpChangeTrack::ReadChTrCellContent - unknown format info" );
		}

		ScBaseCell* pOldCell;
		ScBaseCell* pNewCell;
		sal_uInt32 nOldFormat;
		sal_uInt32 nNewFormat;
		ReadCell( pOldCell, nOldFormat, nOldValueType, aPosition );
		ReadCell( pNewCell, nNewFormat, nNewValueType, aPosition );
        if( !pStrm->IsValid() || (pStrm->GetRecLeft() > 0) )
		{
			DBG_ERROR( "XclImpChangeTrack::ReadChTrCellContent - bytes left, action ignored" );
			if( pOldCell )
				pOldCell->Delete();
			if( pNewCell )
				pNewCell->Delete();
		}
		else
		{
			ScChangeActionContent* pNewAction =
				pChangeTrack->AppendContentOnTheFly( aPosition, pOldCell, pNewCell, nOldFormat, nNewFormat );
			DoAcceptRejectAction( pNewAction );
		}
	}
}

void XclImpChangeTrack::ReadChTrTabId()
{
    if( nTabIdCount == 0 )  // read only 1st time, otherwise calculated by <ReadChTrInsertTab()>
        nTabIdCount = static_cast< sal_uInt16 >( pStrm->GetRecLeft() >> 1 );
}

void XclImpChangeTrack::ReadChTrMoveRange()
{
	*pStrm >> aRecHeader;
	if( CheckRecord( EXC_CHTR_OP_MOVE ) )
	{
		ScRange aSourceRange;
		ScRange aDestRange;
		aDestRange.aStart.SetTab( ReadTabNum() );
		aDestRange.aEnd.SetTab( aDestRange.aStart.Tab() );
		Read2DRange( aSourceRange );
		Read2DRange( aDestRange );
		aSourceRange.aStart.SetTab( ReadTabNum() );
		aSourceRange.aEnd.SetTab( aSourceRange.aStart.Tab() );

		sal_Bool bValid = pStrm->IsValid();
		if( FoundNestedMode() )
			ReadNestedRecords();

		if( bValid )
		{
			pChangeTrack->AppendMove( aSourceRange, aDestRange, NULL );
			DoAcceptRejectAction( pChangeTrack->GetLast() );
		}
	}
}

void XclImpChangeTrack::ReadChTrInsertTab()
{
	*pStrm >> aRecHeader;
	if( CheckRecord( EXC_CHTR_OP_INSTAB ) )
	{
		SCTAB nTab = ReadTabNum();
		if( pStrm->IsValid() )
		{
			nTabIdCount++;
			DoInsertRange( ScRange( 0, 0, nTab, MAXCOL, MAXROW, nTab ) );
		}
	}
}

void XclImpChangeTrack::InitNestedMode()
{
	DBG_ASSERT( eNestedMode == nmBase, "XclImpChangeTrack::InitNestedMode - unexpected nested mode" );
	if( eNestedMode == nmBase )
		eNestedMode = nmFound;
}

void XclImpChangeTrack::ReadNestedRecords()
{
	DBG_ASSERT( eNestedMode == nmFound, "XclImpChangeTrack::StartNestedMode - missing nested mode" );
	if( eNestedMode == nmFound )
	{
		eNestedMode = nmNested;
		ReadRecords();
	}
}

sal_Bool XclImpChangeTrack::EndNestedMode()
{
	DBG_ASSERT( eNestedMode != nmBase, "XclImpChangeTrack::EndNestedMode - missing nested mode" );
	sal_Bool bReturn = (eNestedMode == nmNested);
	eNestedMode = nmBase;
	return bReturn;
}

void XclImpChangeTrack::ReadRecords()
{
	sal_Bool bExitLoop = sal_False;

	while( !bExitLoop && !bGlobExit && pStrm->StartNextRecord() )
	{
        switch( pStrm->GetRecId() )
		{
			case 0x000A:	bGlobExit = sal_True;			break;
			case 0x0137:	ReadChTrInsert();				break;
			case 0x0138:	ReadChTrInfo();					break;
			case 0x013B:	ReadChTrCellContent();			break;
			case 0x013D:	ReadChTrTabId();				break;
			case 0x0140:	ReadChTrMoveRange();			break;
			case 0x014D:	ReadChTrInsertTab();			break;
			case 0x014E:
			case 0x0150:	InitNestedMode();				break;
			case 0x014F:
			case 0x0151:	bExitLoop = EndNestedMode();	break;
		}
	}
}

void XclImpChangeTrack::Apply()
{
    if( pChangeTrack )
	{
		pChangeTrack->SetUser( sOldUsername );
		pChangeTrack->SetUseFixDateTime( sal_False );

        GetDoc().SetChangeTrack( pChangeTrack );
		pChangeTrack = NULL;

		ScChangeViewSettings aSettings;
		aSettings.SetShowChanges( sal_True );
        GetDoc().SetChangeViewSettings( aSettings );
	}
}

//___________________________________________________________________
// class XclImpChTrFmlConverter

XclImpChTrFmlConverter::~XclImpChTrFmlConverter()
{
}

// virtual, called from ExcToSc8::Convert()
bool XclImpChTrFmlConverter::Read3DTabReference( sal_uInt16 /*nIxti*/, SCTAB& rFirstTab, SCTAB& rLastTab, 
												 ExternalTabInfo& rExtInfo )
{
	return rChangeTrack.Read3DTabRefInfo( rFirstTab, rLastTab, rExtInfo );
}

