/**************************************************************
 * 
 * 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_sot.hxx"

#include "sot/stg.hxx"
#include "stgelem.hxx"
#include "stgcache.hxx"
#include "stgstrms.hxx"
#include "stgdir.hxx"
#include "stgio.hxx"
#include <rtl/instance.hxx>

///////////////////////////// class StgIo //////////////////////////////

// This class holds the storage header and all internal streams.

StgIo::StgIo() : StgCache()
{
    pTOC      = NULL;
    pDataFAT  = NULL;
    pDataStrm = NULL;
    pFAT      = NULL;
	bCopied   = sal_False;
}

StgIo::~StgIo()
{
    delete pTOC;
    delete pDataFAT;
    delete pDataStrm;
    delete pFAT;
}

// Load the header. Do not set an error code if the header is invalid.

sal_Bool StgIo::Load()
{
    if( pStrm )
    {
        if( aHdr.Load( *this ) )
        {
            if( aHdr.Check() )
                SetupStreams();
            else
				return sal_False;
        }
        else
            return sal_False;
    }
    return Good();
}

// Set up an initial, empty storage

sal_Bool StgIo::Init()
{
    aHdr.Init();
    SetupStreams();
    return CommitAll();
}

void StgIo::SetupStreams()
{
    delete pTOC;
    delete pDataFAT;
    delete pDataStrm;
    delete pFAT;
    pTOC      = NULL;
    pDataFAT  = NULL;
    pDataStrm = NULL;
    pFAT      = NULL;
    ResetError();
    SetPhysPageSize( 1 << aHdr.GetPageSize() );
    pFAT = new StgFATStrm( *this );
    pTOC = new StgDirStrm( *this );
	if( !GetError() )
	{
		StgDirEntry* pRoot = pTOC->GetRoot();
		if( pRoot )
		{
			pDataFAT = new StgDataStrm( *this, aHdr.GetDataFATStart(), -1 );
			pDataStrm = new StgDataStrm( *this, *pRoot );
			pDataFAT->SetIncrement( 1 << aHdr.GetPageSize() );
			pDataStrm->SetIncrement( GetDataPageSize() );
			pDataStrm->SetEntry( *pRoot );
		}
		else
			SetError( SVSTREAM_FILEFORMAT_ERROR );
	}
}

// get the logical data page size

short StgIo::GetDataPageSize()
{
    return 1 << aHdr.GetDataPageSize();
}

// Commit everything

sal_Bool StgIo::CommitAll()
{
	// Store the data (all streams and the TOC)
    if( pTOC && pTOC->Store() && pDataFAT )
    {
        if( Commit( NULL ) )
        {
            aHdr.SetDataFATStart( pDataFAT->GetStart() );
            aHdr.SetDataFATSize( pDataFAT->GetPages() );
            aHdr.SetTOCStart( pTOC->GetStart() );
            if( aHdr.Store( *this ) )
			{
				pStrm->Flush();
				sal_uLong n = pStrm->GetError();
				SetError( n );
#ifdef DBG_UTIL
				if( n==0 ) ValidateFATs();
#endif
				return sal_Bool( n == 0 );
			}
        }
    }
	SetError( SVSTREAM_WRITE_ERROR );
	return sal_False;
}


class EasyFat
{
	sal_Int32 *pFat;
	sal_Bool  *pFree;
	sal_Int32 nPages;
	sal_Int32 nPageSize;

public:
	EasyFat( StgIo & rIo, StgStrm *pFatStream, sal_Int32 nPSize );
	~EasyFat() { delete[] pFat; delete[] pFree; }

	sal_Int32 GetPageSize() { return nPageSize; }
	sal_Int32 Count() { return nPages; }
	sal_Int32 operator[]( sal_Int32 nOffset )
    {
        OSL_ENSURE( nOffset >= 0 && nOffset < nPages, "Unexpected offset!" );
        return nOffset >= 0 && nOffset < nPages ? pFat[ nOffset ] : -2;
    }

	sal_uLong Mark( sal_Int32 nPage, sal_Int32 nCount, sal_Int32 nExpect );
	sal_Bool HasUnrefChains();
};

EasyFat::EasyFat( StgIo& rIo, StgStrm* pFatStream, sal_Int32 nPSize )
{
	nPages = pFatStream->GetSize() >> 2;
	nPageSize = nPSize;
	pFat = new sal_Int32[ nPages ];
	pFree = new sal_Bool[ nPages ];

	StgPage *pPage = NULL;
	sal_Int32 nFatPageSize = (1 << rIo.aHdr.GetPageSize()) - 2;

	for( sal_Int32 nPage = 0; nPage < nPages; nPage++ )
	{
		if( ! (nPage % nFatPageSize) )
		{
			pFatStream->Pos2Page( nPage << 2 );
			sal_Int32 nPhysPage = pFatStream->GetPage();
			pPage = rIo.Get( nPhysPage, sal_True );
		}

		pFat[ nPage ] = pPage->GetPage( short( nPage % nFatPageSize ) );
		pFree[ nPage ] = sal_True;
	}
}

sal_Bool EasyFat::HasUnrefChains()
{
	for( sal_Int32 nPage = 0; nPage < nPages; nPage++ )
	{
		if( pFree[ nPage ] && pFat[ nPage ] != -1 )
			return sal_True;
	}
	return sal_False;
}

sal_uLong EasyFat::Mark( sal_Int32 nPage, sal_Int32 nCount, sal_Int32 nExpect )
{
	if( nCount > 0 )
		--nCount /= GetPageSize(), nCount++;

	sal_Int32 nCurPage = nPage;
	while( nCount != 0 )
	{
		if( nCurPage < 0 || nCurPage >= nPages )
			return FAT_OUTOFBOUNDS;
		pFree[ nCurPage ] = sal_False;
		nCurPage = pFat[ nCurPage ];
		//Stream zu lang
		if( nCurPage != nExpect && nCount == 1 )
			return FAT_WRONGLENGTH;
        //Stream zu kurz
		if( nCurPage == nExpect && nCount != 1 && nCount != -1 )
			return FAT_WRONGLENGTH;
		// letzter Block bei Stream ohne Laenge
		if( nCurPage == nExpect && nCount == -1 )
			nCount = 1;
		if( nCount != -1 )
			nCount--;
	}
	return FAT_OK;
}

class Validator
{
	sal_uLong nError;

	EasyFat aSmallFat;
	EasyFat aFat;

	StgIo &rIo;

	sal_uLong ValidateMasterFATs();
	sal_uLong ValidateDirectoryEntries();
	sal_uLong FindUnrefedChains();
	sal_uLong MarkAll( StgDirEntry *pEntry );

public:

	Validator( StgIo &rIo );
	sal_Bool IsError() { return nError != 0; }
};

Validator::Validator( StgIo &rIoP )
	: aSmallFat( rIoP, rIoP.pDataFAT, 1 << rIoP.aHdr.GetDataPageSize() ),
	  aFat( rIoP, rIoP.pFAT, 1 << rIoP.aHdr.GetPageSize() ),
	  rIo( rIoP )
{
	sal_uLong nErr = nError = FAT_OK;

	if(	( nErr = ValidateMasterFATs() ) != FAT_OK )
		nError = nErr;
	else if(	( nErr = ValidateDirectoryEntries() ) != FAT_OK )
		nError = nErr;
	else if(	( nErr = FindUnrefedChains()) != FAT_OK )
		nError = nErr;
}

sal_uLong Validator::ValidateMasterFATs()
{
    sal_Int32 nCount = rIo.aHdr.GetFATSize();
    sal_uLong nErr;
    if ( !rIo.pFAT )
	    return FAT_INMEMORYERROR;

    for( sal_Int32 i = 0; i < nCount; i++ )
    {
        if( ( nErr = aFat.Mark(rIo.pFAT->GetPage( short(i), sal_False ), aFat.GetPageSize(), -3 )) != FAT_OK )
            return nErr;
    }
    if( rIo.aHdr.GetMasters() )
        if( ( nErr = aFat.Mark(rIo.aHdr.GetFATChain( ), aFat.GetPageSize(), -4 )) != FAT_OK )
            return nErr;

    return FAT_OK;
}

sal_uLong Validator::MarkAll( StgDirEntry *pEntry )
{
    if ( !pEntry )
	    return FAT_INMEMORYERROR;

	StgIterator aIter( *pEntry );
	sal_uLong nErr = FAT_OK;
	for( StgDirEntry* p = aIter.First(); p ; p = aIter.Next() )
	{
		if( p->aEntry.GetType() == STG_STORAGE )
		{
			nErr = MarkAll( p );
			if( nErr != FAT_OK )
				return nErr;
		}
		else
		{
			sal_Int32 nSize = p->aEntry.GetSize();
			if( nSize < rIo.aHdr.GetThreshold()  )
				nErr = aSmallFat.Mark( p->aEntry.GetStartPage(),nSize, -2 );
			else
				nErr = aFat.Mark( p->aEntry.GetStartPage(),nSize, -2 );
			if( nErr != FAT_OK )
				return nErr;
		}
	}
	return FAT_OK;
}

sal_uLong Validator::ValidateDirectoryEntries()
{
    if ( !rIo.pTOC )
	    return FAT_INMEMORYERROR;

	// Normale DirEntries
	sal_uLong nErr = MarkAll( rIo.pTOC->GetRoot() );
	if( nErr != FAT_OK )
		return nErr;
	// Small Data
	nErr = aFat.Mark( rIo.pTOC->GetRoot()->aEntry.GetStartPage(),
				 rIo.pTOC->GetRoot()->aEntry.GetSize(), -2 );
	if( nErr != FAT_OK )
		return nErr;
	// Small Data FAT
	nErr = aFat.Mark(
		rIo.aHdr.GetDataFATStart(),
		rIo.aHdr.GetDataFATSize() * aFat.GetPageSize(), -2 );
	if( nErr != FAT_OK )
		return nErr;
	// TOC
	nErr = aFat.Mark(
		rIo.aHdr.GetTOCStart(),	-1, -2 );
	return nErr;
}

sal_uLong Validator::FindUnrefedChains()
{
	if( aSmallFat.HasUnrefChains() ||
		aFat.HasUnrefChains() )
		return FAT_UNREFCHAIN;
	else
		return FAT_OK;
}

namespace { struct ErrorLink : public rtl::Static<Link, ErrorLink > {}; }

void StgIo::SetErrorLink( const Link& rLink )
{
	ErrorLink::get() = rLink;
}

const Link& StgIo::GetErrorLink()
{
    return ErrorLink::get();
}

sal_uLong StgIo::ValidateFATs()
{
	if( bFile )
	{
		Validator *pV = new Validator( *this );
		sal_Bool bRet1 = !pV->IsError(), bRet2 = sal_True ;
		delete pV;

		SvFileStream *pFileStrm = ( SvFileStream *) GetStrm();
        if ( !pFileStrm )
            return FAT_INMEMORYERROR;

		StgIo aIo;
		if( aIo.Open( pFileStrm->GetFileName(),
					  STREAM_READ  | STREAM_SHARE_DENYNONE) &&
			aIo.Load() )
		{
			pV = new Validator( aIo );
			bRet2 = !pV->IsError();
			delete pV;
		}

		sal_uLong nErr;
		if( bRet1 != bRet2 )
			nErr = bRet1 ? FAT_ONFILEERROR : FAT_INMEMORYERROR;
		else nErr = bRet1 ? FAT_OK : FAT_BOTHERROR;
		if( nErr != FAT_OK && !bCopied )
		{
			StgLinkArg aArg;
			aArg.aFile = pFileStrm->GetFileName();
			aArg.nErr = nErr;
			ErrorLink::get().Call( &aArg );
			bCopied = sal_True;
		}
//		DBG_ASSERT( nErr == FAT_OK ,"Storage kaputt");
		return nErr;
	}
//	DBG_ERROR("Validiere nicht (kein FileStorage)");
	return FAT_OK;
}