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

#define _DIR_CXX

#include <stdlib.h>
#include <cstdarg>
#include <limits.h>
#include <tools/debug.hxx>
#include <tools/list.hxx>

#ifndef _COMPED_HXX
#include "comdep.hxx"
#endif
#include <tools/fsys.hxx>


DBG_NAME( Dir )

DECLARE_LIST( DirEntryList, DirEntry* )
DECLARE_LIST( FSysSortList, FSysSort* )
DECLARE_LIST( FileStatList, FileStat* )

#define APPEND (sal_uInt16) 65535

/*************************************************************************
|*
|*    Dir::InsertPointReached()
|*
|*    Beschreibung      stellt fest, ob eingefuegt werden musz
|*    Ersterstellung    MA 05.11.91
|*    Letzte Aenderung  MI 05.02.92
|*
*************************************************************************/

sal_Bool Dir::ImpInsertPointReached( const DirEntry& rNewEntry,
                                 const FileStat& rNewStat,
                                 sal_uIntPtr nCurPos, sal_uIntPtr nSortIndex ) const
{
#define VALUE( nKindFlags ) \
    ( ( FSYS_KIND_FILE | FSYS_KIND_DIR | FSYS_KIND_DEV | \
        FSYS_KIND_CHAR | FSYS_KIND_BLOCK ) & nKindFlags )

    // einfache Dinge erfordern einfache Loesungen
    if ( !pLst->Count() )
        return sal_True;

    FSysSort  nSort      = *( pSortLst->GetObject( nSortIndex ) );
    FileStat *pOldStat   = NULL;
    DirEntry *pCurLstObj = pLst->GetObject( nCurPos );
    if ( pStatLst )
        pOldStat = pStatLst->GetObject( nCurPos );

    switch( nSort )
    {
        case  FSYS_SORT_NAME:
        case (FSYS_SORT_NAME | FSYS_SORT_ASCENDING):
            if ( pCurLstObj->aName > rNewEntry.aName )
                return sal_True;
            if ( !(pCurLstObj->aName == rNewEntry.aName) )
                return sal_False;
            break;
        case (FSYS_SORT_NAME | FSYS_SORT_DESCENDING):
            if ( pCurLstObj->aName < rNewEntry.aName )
                return sal_True;
            if ( !(pCurLstObj->aName == rNewEntry.aName) )
                return sal_False;
            break;

        case  FSYS_SORT_EXT:
        case (FSYS_SORT_EXT | FSYS_SORT_ASCENDING):
        {
            if ( pCurLstObj->GetExtension() > rNewEntry.GetExtension() )
                return sal_True;
            if ( !(pCurLstObj->GetExtension() == rNewEntry.GetExtension()) )
                return sal_False;
            break;
        }
        case (FSYS_SORT_EXT | FSYS_SORT_DESCENDING):
        {
            if ( pCurLstObj->GetExtension() < rNewEntry.GetExtension() )
                return sal_True;
            if ( !(pCurLstObj->GetExtension() == rNewEntry.GetExtension()) )
                return sal_False;
            break;
        }

        case  FSYS_SORT_KIND:
        case (FSYS_SORT_KIND | FSYS_SORT_ASCENDING ):
            if ( VALUE(pOldStat->nKindFlags) > VALUE(rNewStat.nKindFlags) )
                return sal_True;
            if ( !(VALUE(pOldStat->nKindFlags) == VALUE(rNewStat.nKindFlags)) )
                return sal_False;
            break;
        case (FSYS_SORT_KIND | FSYS_SORT_DESCENDING):
            if ( VALUE(pOldStat->nKindFlags) < VALUE(rNewStat.nKindFlags) )
                return sal_True;
            if ( !(VALUE(pOldStat->nKindFlags) == VALUE(rNewStat.nKindFlags)) )
                return sal_False;
            break;

        case  FSYS_SORT_SIZE:
        case (FSYS_SORT_SIZE | FSYS_SORT_ASCENDING):
            if ( pOldStat->nSize > rNewStat.nSize )
                return sal_True;
            if ( !(pOldStat->nSize == rNewStat.nSize) )
                return sal_False;
            break;
        case (FSYS_SORT_SIZE | FSYS_SORT_DESCENDING):
            if ( pOldStat->nSize < rNewStat.nSize )
                return sal_True;
            if ( !(pOldStat->nSize == rNewStat.nSize) )
                return sal_False;
            break;

        case  FSYS_SORT_MODIFYED:
        case (FSYS_SORT_MODIFYED | FSYS_SORT_ASCENDING):
            if ( (pOldStat->aDateModified >= rNewStat.aDateModified) &&
                 (pOldStat->aTimeModified >  rNewStat.aTimeModified) )
                 return sal_True;
            if ( !((pOldStat->aDateModified == rNewStat.aDateModified) &&
                   (pOldStat->aTimeModified == rNewStat.aTimeModified)) )
                return sal_False;
            break;
        case (FSYS_SORT_MODIFYED | FSYS_SORT_DESCENDING):
            if ( (pOldStat->aDateModified <= rNewStat.aDateModified) &&
                 (pOldStat->aTimeModified <  rNewStat.aTimeModified) )
                 return sal_True;
            if ( !((pOldStat->aDateModified == rNewStat.aDateModified) &&
                   (pOldStat->aTimeModified == rNewStat.aTimeModified)) )
                return sal_False;
            break;

        case  FSYS_SORT_CREATED:
        case (FSYS_SORT_CREATED | FSYS_SORT_ASCENDING):
            if ( (pOldStat->aDateCreated >= rNewStat.aDateCreated) &&
                 (pOldStat->aTimeCreated >  rNewStat.aTimeCreated) )
                 return sal_True;
            if ( !((pOldStat->aDateCreated == rNewStat.aDateCreated) &&
                   (pOldStat->aTimeCreated == rNewStat.aTimeCreated)) )
                return sal_False;
            break;
        case (FSYS_SORT_CREATED | FSYS_SORT_DESCENDING):
            if ( (pOldStat->aDateCreated <= rNewStat.aDateCreated) &&
                 (pOldStat->aTimeCreated <  rNewStat.aTimeCreated) )
                 return sal_True;
            if ( !((pOldStat->aDateCreated == rNewStat.aDateCreated) &&
                   (pOldStat->aTimeCreated == rNewStat.aTimeCreated)) )
                return sal_False;
            break;

        case  FSYS_SORT_ACCESSED:
        case (FSYS_SORT_ACCESSED | FSYS_SORT_ASCENDING):
            if ( (pOldStat->aDateAccessed >= rNewStat.aDateAccessed) &&
                 (pOldStat->aTimeAccessed >  rNewStat.aTimeAccessed) )
                 return sal_True;
            if ( !((pOldStat->aDateAccessed == rNewStat.aDateAccessed) &&
                   (pOldStat->aTimeAccessed == rNewStat.aTimeAccessed)) )
                return sal_False;
            break;
        case (FSYS_SORT_ACCESSED | FSYS_SORT_DESCENDING):
            if ( (pOldStat->aDateAccessed <= rNewStat.aDateAccessed) &&
                 (pOldStat->aTimeAccessed <  rNewStat.aTimeAccessed) )
                 return sal_True;
            if ( !((pOldStat->aDateAccessed == rNewStat.aDateAccessed) &&
                   (pOldStat->aTimeAccessed == rNewStat.aTimeAccessed)) )
                return sal_False;
            break;
        default: /* Kann nicht sein */;
    }

    if ( nSortIndex == ( pSortLst->Count() - 1 ) )
        return sal_True;
    else
        //Rekursion
        return ImpInsertPointReached( rNewEntry, rNewStat,
                                      nCurPos, nSortIndex + 1 );
#undef VALUE
}

/*************************************************************************
|*
|*    Dir::ImpSortedInsert()
|*
|*    Beschreibung      fuegt sortiert ein
|*    Ersterstellung    MA 05.11.91
|*    Letzte Aenderung  MA 03.12.91
|*
*************************************************************************/

void Dir::ImpSortedInsert( const DirEntry *pNewEntry, const FileStat *pNewStat )
{
    //Sonderfall, keine Sortierung gewuenscht.
    if ( !pSortLst ) {
        pLst->Insert( (DirEntry*)pNewEntry, APPEND );
        return;
    }

    pLst->First();
    do {
        if ( ImpInsertPointReached( *pNewEntry, *pNewStat, pLst->GetCurPos(),
                                    (sal_uIntPtr)0  ) )
        {
            if ( pStatLst )
                pStatLst->Insert( (FileStat*)pNewStat, pLst->GetCurPos() );
            pLst->Insert( (DirEntry*)pNewEntry );
            return;
        }
    } while( pLst->Next() );

    if ( pStatLst )
        pStatLst->Insert( (FileStat*)pNewStat, APPEND );
    pLst->Insert( (DirEntry*)pNewEntry, APPEND );
}

/*************************************************************************
|*
|*    Dir::Construct()
|*
|*    Beschreibung      gemeinsame Implementation der Ctoren
|*    Ersterstellung    MI 02.06.93
|*    Letzte Aenderung  MI 02.06.93
|*
*************************************************************************/

void Dir::Construct( DirEntryKind nKindFlags )
{
    pLst     = NULL;
    pSortLst = NULL;
    pStatLst = NULL;
    eAttrMask = nKindFlags;
    ByteString aTempName( GetName(), osl_getThreadTextEncoding() );
    if ( aTempName.Search( "*" ) != STRING_NOTFOUND ||
         aTempName.Search( "?" ) != STRING_NOTFOUND )
#if defined( WNT ) && !defined( WTC )
    {
        ByteString aTStr(CutName(), osl_getThreadTextEncoding());
        char* pBuffer = new char[aTStr.Len()+1];
        strcpy( pBuffer, aTStr.GetBuffer() );
        CharLowerBuff( pBuffer, aTStr.Len() );
        aNameMask = WildCard( String(pBuffer, osl_getThreadTextEncoding()), ';' );
        delete [] pBuffer;
    }
#else
    aNameMask = WildCard( CutName(), ';' );
#endif
    else
        aNameMask = String("*", osl_getThreadTextEncoding());
}

/*************************************************************************
|*
|*    Dir::Update()
|*
|*    Beschreibung      FSYS.SDW
|*    Ersterstellung    MI 16.05.91
|*    Letzte Aenderung  MI 19.09.96
|*
*************************************************************************/

sal_Bool Dir::Update()
{
	Reset();
	return Scan( USHRT_MAX ) > 0;
}

/*************************************************************************
|*
|*    Dir::Reset()
|*
|*    Beschreibung
|*    Ersterstellung    MI 22.10.96
|*    Letzte Aenderung  MI 22.10.96
|*
*************************************************************************/

void Dir::Reset()
{
	// ggf. alten Reader l"oschen
	if ( pReader && pReader->bInUse )
		DELETEZ(pReader);

	// alle DirEntries aus der Liste entfernen und deren Speicher freigeben
	if ( pLst )
	{
		DirEntry* pEntry = pLst->First();
		while (pEntry)
		{
			DirEntry* pNext = pLst->Next();
			delete pEntry;
			pEntry = pNext;
		}
		pLst->Clear();
	}
	else
		pLst = new DirEntryList();

	//	Alte File-Stat's Loeschen
	if ( pStatLst )
	{
		//Erstmal die alten Loeschen
		FileStat* pEntry = pStatLst->First();
		while (pEntry)
		{
			FileStat*  pNext = pStatLst->Next();
			delete pEntry;
			pEntry = pNext;
		}
		pStatLst->Clear();
		delete pStatLst;
	}

	// Verlangen die Sortierkriterien FileStat's?
	if ( pSortLst )
	{
		pSortLst->First();
		do
		{
			if ( *( pSortLst->GetCurObject() ) &
					( FSYS_SORT_KIND | FSYS_SORT_SIZE |
					FSYS_SORT_CREATED | FSYS_SORT_MODIFYED | FSYS_SORT_ACCESSED ) )
				pStatLst = new FileStatList();
		} while ( !pStatLst && pSortLst->Next() );
	}

#ifndef BOOTSTRAP
	// ggf. einen neuen Reader aufsetzen
	if ( !pReader )
		pReader = new DirReader_Impl( *this );
#endif

	// gibt es das zu oeffnende Verzeichnis ueberhaupt?
#if !defined(UNX) && !defined(OS2) //explanation: see DirReader_Impl::Read() in unx.cxx
	if( !pReader->pDosDir )
	{
		nError = FSYS_ERR_NOTADIRECTORY;
		DELETEZ( pReader );
		return;
	}
#endif
}

/*************************************************************************
|*
|*    Dir::Scan()
|*
|*    Beschreibung      FSYS.SDW
|*    Ersterstellung    MI 18.09.96
|*    Letzte Aenderung  MI 19.09.96
|*
*************************************************************************/

sal_uInt16 Dir::Scan( sal_uInt16 nCount )
{

	sal_uInt16 nRead = 0; // Anzahl in dieser Runde gelesener Eintr"age
	FSysFailOnErrorImpl();

	// noch nicht fertig gewesen
	if ( pReader )
	{
		// frischer Reader?
		if ( !pLst->Count() )
		{
			// dann ggf. Laufwerke scannen
			pReader->bInUse = sal_True;
			nRead = pReader->Init();
		}

		// weiterlesen...
		while ( nRead <= nCount && !pReader->bReady )
			nRead = nRead + pReader->Read();

		// fertig?
		if ( pReader && pReader->bReady )
			DELETEZ( pReader );
	}

	// Anzahl der gelesenen zur"uckgeben
	return nRead;
}

/*************************************************************************
|*
|*    Dir::Dir()
|*
|*    Beschreibung      FSYS.SDW
|*    Ersterstellung    MI 16.05.91
|*    Letzte Aenderung  MI 04.03.92
|*
*************************************************************************/

Dir::Dir( const DirEntry& rDirEntry, DirEntryKind nKindFlags, FSysSort nSort, ... ):
    DirEntry( rDirEntry ),
	pReader( 0 )
{
    DBG_CTOR( Dir, NULL );

    Construct( nKindFlags );

    std::va_list pArgs;
    va_start( pArgs, nSort );
    ImpSetSort( pArgs, nSort );

	Reset();
}

/*************************************************************************
|*
|*    Dir::Dir()
|*
|*    Beschreibung      FSYS.SDW
|*    Ersterstellung    MI 02.06.93
|*    Letzte Aenderung  MI 02.06.93
|*
*************************************************************************/

Dir::Dir( const DirEntry& rDirEntry, DirEntryKind nKindFlags ):
    DirEntry( rDirEntry ),
	pReader( 0 )
{
    DBG_CTOR( Dir, NULL );

    Construct( nKindFlags );
    Reset();
}

/*************************************************************************
|*
|*    Dir::Dir()
|*
|*    Beschreibung      FSYS.SDW
|*    Ersterstellung    MI 16.05.91
|*    Letzte Aenderung  MA 04.11.91
|*
*************************************************************************/

Dir::Dir():
	pReader( 0 )
{
    DBG_CTOR( Dir, NULL );

    pLst     = NULL;
    pSortLst = NULL;
    pStatLst = NULL;
    eAttrMask = FSYS_KIND_ALL;
    aNameMask = String("*", osl_getThreadTextEncoding());
}

/*************************************************************************
|*
|*    Dir::~Dir()
|*
|*    Beschreibung      FSYS.SDW
|*    Ersterstellung    MI 16.05.91
|*    Letzte Aenderung  MA 04.11.91
|*
*************************************************************************/

Dir::~Dir()
{
    DBG_DTOR( Dir, NULL );

    // alle DirEntries aus der Liste entfernen und deren Speicher freigeben
    if ( pLst )
    {
		DirEntry* pEntry = pLst->First();
		while (pEntry)
		{
			DirEntry* pNext = pLst->Next();
			delete pEntry;
			pEntry = pNext;
		}
		pLst->Clear();

        delete pLst;
    }

    // alle Sorts aus der Liste entfernen und deren Speicher freigeben
    if ( pSortLst )
    {
		FSysSort* pEntry = pSortLst->First();
		while (pEntry)
		{
			FSysSort*  pNext = pSortLst->Next();
			delete pEntry;
			pEntry = pNext;
		}
		pSortLst->Clear();

        delete pSortLst;
    }

    // alle FileStat's aus der Liste entfernen und deren Speicher freigeben
    if ( pStatLst )
    {
		FileStat* pEntry = pStatLst->First();
		while (pEntry)
		{
			FileStat*  pNext = pStatLst->Next();
			delete pEntry;
			pEntry = pNext;
		}
		pStatLst->Clear();
        delete pStatLst;
    }

	// ggf. laufenden Reader freigeben
	delete pReader;
}

/*************************************************************************
|*
|*    Dir::ImpSetSort()
|*
|*    Beschreibung      FSYS.SDW
|*    Ersterstellung    MA 04.11.91
|*    Letzte Aenderung  MI 05.02.92
|*
*************************************************************************/

FSysError Dir::ImpSetSort( std::va_list pArgs, int nFirstSort )
{
    sal_Bool             bLast;
    FSysSort        *pSort;
    FSysSortList    *pNewSortLst = new FSysSortList;

	*( pSort = new FSysSort ) = nFirstSort;
    do
    {
		// letztes Kriterium?
        bLast = FSYS_SORT_END == (*pSort & FSYS_SORT_END);
        *pSort &= ~FSYS_SORT_END;

		FSysSort nSort = *pSort & ~(sal_uInt16)FSYS_SORT_ASCENDING
							  &  ~(sal_uInt16)FSYS_SORT_DESCENDING;

		// g"utliges Sortierkriterium?
        if ( ( nSort ==  FSYS_SORT_NAME ) ||
             ( nSort ==  FSYS_SORT_SIZE ) ||
             ( nSort ==  FSYS_SORT_EXT )  ||
             ( nSort ==  FSYS_SORT_CREATED ) ||
             ( nSort ==  FSYS_SORT_MODIFYED ) ||
             ( nSort ==  FSYS_SORT_ACCESSED ) ||
			 ( nSort ==  FSYS_SORT_KIND ) )
        {
            pNewSortLst->Insert( pSort, APPEND );
            *(pSort = new FSysSort) = va_arg( pArgs, FSysSort );
        }
        else
        {   // ungueltiger Sort oder FSYS_SORT_NONE
			FSysSort* pEntry = pNewSortLst->First();
			while (pEntry)
			{
				FSysSort* pNext = pNewSortLst->Next();
				delete pEntry;
				pEntry = pNext;
			}
			pNewSortLst->Clear();
            delete pNewSortLst;
            if ( *pSort ==  FSYS_SORT_NONE )
			{
                delete pSort;
                if ( pSortLst )
                    delete pSortLst;
                return FSYS_ERR_OK;
            }
            else
			{
                delete pSort;
                return FSYS_ERR_NOTSUPPORTED;
            }
        }
    } while ( !bLast );

    va_end( pArgs );
	delete pSort;			// JP:6.3.00 - delete the initial pointer

    //Enfernen der alten Sort-Elemente
    if ( pSortLst )
	{
		FSysSort* pEntry = pSortLst->First();
		while (pEntry)
		{
			FSysSort* pNext = pSortLst->Next();
			delete pEntry;
			pEntry = pNext;
		}
		pSortLst->Clear();
		delete pSortLst;
	}
    pSortLst = pNewSortLst;

    //Jetzt noch neu Sortieren...

    //Wenn keine FileStats da sind, aber nun welche gebraucht werden,
    //ist der Aufruf von Update() die einfachste Moeglichkeit
    if ( !pStatLst && pSortLst )
    {
        pSortLst->First();
        do
        {
            if ( *(pSortLst->GetCurObject()) &
                  ( FSYS_SORT_CREATED | FSYS_SORT_MODIFYED | FSYS_SORT_SIZE |
                    FSYS_SORT_ACCESSED | FSYS_SORT_KIND ) )
            {
                Update();
                return FSYS_ERR_OK;
            }
        } while ( !pStatLst && pSortLst->Next() );
    }

    if ( pLst ) { //Keine DirEntry's, kein Sort.
        DirEntryList    *pOldLst = pLst; //alte Liste merken
        pLst = new DirEntryList();       //neue Liste (zu Sortieren)

        FileStatList *pOldStatLst = NULL; //alte StatListe merken
        if ( pStatLst ) {
            pOldStatLst = pStatLst;
            pStatLst = new FileStatList(); //neue StatListe (zu Sortieren)
        }
        pOldLst->First();
        do
        {
            //Sortiertes Einfuegen der Elemente aus den gemerkten Listen
            //in die 'richtigen' Listen
            if ( pOldStatLst )
                ImpSortedInsert( pOldLst->GetCurObject(),
                                 pOldStatLst->GetObject( pOldLst->GetCurPos() ) );
            else
                ImpSortedInsert( pOldLst->GetCurObject(), NULL );
        } while( pOldLst->Next() );

        delete pOldLst;
        if ( pOldStatLst )
            delete pOldStatLst;
    }
    return FSYS_ERR_OK;
}

/*************************************************************************
|*
|*    Dir::SetSort()
|*
|*    Beschreibung      FSYS.SDW
|*    Ersterstellung    MA 04.11.91
|*    Letzte Aenderung  MI 05.02.92
|*
*************************************************************************/

FSysError Dir::SetSort( FSysSort nSort, ... )
{
    std::va_list pArgs;
    va_start( pArgs, nSort );
    return ImpSetSort( pArgs, nSort );
}

/*************************************************************************
|*
|*    Dir::operator[]()
|*
|*    Beschreibung      FSYS.SDW
|*    Ersterstellung    MI 16.05.91
|*    Letzte Aenderung  MI 16.05.91
|*
*************************************************************************/

DirEntry& Dir::operator[] ( sal_uInt16 nIndex ) const
{
    DBG_ASSERT( nIndex < Count(), "Dir::operator[] : nIndex > Count()" );

    DirEntry *pEntry = pLst->GetObject( nIndex );
	return *pEntry;
}

/*************************************************************************
|*
|*    Dir::operator+= ()
|*
|*    Beschreibung      FSYS.SDW
|*    Ersterstellung    MI 16.05.91
|*    Letzte Aenderung  MI 16.05.91
|*
*************************************************************************/

Dir& Dir::operator+=( const Dir& rDir )
{
	// ggf. erst den Rest lesen
	if ( pReader )
		Scan( USHRT_MAX );
	DBG_ASSERT( !rDir.pReader, "Dir::+= with incomplete Dir" );

	// ggf. initiale Liste erzeugen
    if ( !pLst )
        pLst = new DirEntryList();

    //Verlangen die Sortierkriterien FileStat's?
    sal_Bool bStat = sal_False;
    if ( pSortLst ) {
        pSortLst->First();
        do {
            if ( *(pSortLst->GetCurObject()) &
                  ( FSYS_SORT_CREATED | FSYS_SORT_MODIFYED | FSYS_SORT_SIZE |
                    FSYS_SORT_ACCESSED | FSYS_SORT_KIND ) )
                bStat = sal_True;
        } while ( !bStat && pSortLst->Next() );
    }
    FileStat * stat = NULL;
    for ( sal_uInt16 nNr = 0; nNr < rDir.Count(); nNr++ )
    {
        if ( bStat )
        {
            if ( rDir.pStatLst )
                stat = new FileStat( *rDir.pStatLst->GetObject(nNr) );
            else
                stat = new FileStat( rDir[nNr] );
        }
        ImpSortedInsert( new DirEntry( rDir[nNr] ), stat );
    }
    return *this;
}

/*************************************************************************
|*
|*    Dir::Count()
|*
|*    Beschreibung      FSYS.SDW
|*    Ersterstellung    MI 16.05.91
|*    Letzte Aenderung  MI 18.09.96
|*
*************************************************************************/


sal_uInt16 Dir::Count( sal_Bool bUpdated ) const
{
	// ggf. erst den Rest lesen
	if ( bUpdated && pReader )
		((Dir*)this)->Scan( USHRT_MAX );

	return pLst == NULL ? 0 : (sal_uInt16) pLst->Count();
}