/*
* Copyright (c) 2006 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:  Implementation of CHttpCacheLookupTable
*
*/

// INCLUDE FILES
#include "HttpCacheLookupTable.h"
#include "HttpCacheEntry.h"
#include "HttpCacheUtil.h"
#include "HttpCacheEvictionHandler.h"
#include "HttpCacheStreamHandler.h"
#include <e32cmn.h>

// EXTERNAL DATA STRUCTURES

// EXTERNAL FUNCTION PROTOTYPES

// CONSTANTS
// Golden ratio - arbitrary start value to avoid mapping all 0's to all 0's
// or anything like that.
const TUint KHashPHI = 0x9e3779b9U;
//
const TUint KHashStringLength = 16;
// kbyte
//
const TUint KHttpCacheLookupTableSize = 217;

// MACROS

// LOCAL CONSTANTS AND MACROS

// MODULE DATA STRUCTURES

// LOCAL FUNCTION PROTOTYPES

// FORWARD DECLARATIONS

// ============================= LOCAL FUNCTIONS ===============================

// ============================ MEMBER FUNCTIONS ===============================

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::CHttpCacheLookupTable
// C++ default constructor can NOT contain any code, that
// might leave.
// -----------------------------------------------------------------------------
//
CHttpCacheLookupTable::CHttpCacheLookupTable(
    CHttpCacheEvictionHandler& aEvictionHandler,
    CHttpCacheStreamHandler& aStreamHandler )
    : iEvictionHandler( &aEvictionHandler ),
      iStreamHandler( &aStreamHandler )
    {
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::ConstructL
// Symbian 2nd phase constructor can leave.
// -----------------------------------------------------------------------------
//
void CHttpCacheLookupTable::ConstructL()
    {
  iEntries = new( ELeave )CArrayPtrFlat<CHttpCacheEntry>( KHttpCacheLookupTableSize );
    for( TInt i = 0; i < KHttpCacheLookupTableSize; i++ )
        {
        iEntries->AppendL( NULL );
        }
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::NewL
// Two-phased constructor.
// -----------------------------------------------------------------------------
//
CHttpCacheLookupTable* CHttpCacheLookupTable::NewL(
    CHttpCacheEvictionHandler& aEvictionHandler,
    CHttpCacheStreamHandler& aStreamHandler )
    {
    CHttpCacheLookupTable* self = new( ELeave ) CHttpCacheLookupTable( aEvictionHandler, aStreamHandler );

    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop();

    return self;
    }

// -----------------------------------------------------------------------------
// Destructor
// -----------------------------------------------------------------------------
CHttpCacheLookupTable::~CHttpCacheLookupTable()
    {
    // do not call ResetAndDestroy on iEntries
    // as delete item are not set to NULL
    if (iEntries)
    {
      for( TInt i = 0; i < iEntries->Count(); i++ )
          {
          if( Valid( i ) )
              {
              CHttpCacheEntry* entry = iEntries->At( i );
              delete entry;
              }
          }
      iEntries->Reset();
    }

    delete iEntries;

    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::InsertL
//
// -----------------------------------------------------------------------------
//
CHttpCacheEntry* CHttpCacheLookupTable::InsertL(
    const TDesC8& aUrl )
    {
    // create and insert the item
    CHttpCacheEntry* entry = CHttpCacheEntry::NewLC( aUrl, *iEvictionHandler );

    if( InsertL( entry ) == KErrCorrupt )
        {
        // cleanup
        CleanupStack::PopAndDestroy(); // entry
        entry = NULL;
        }
    else
        {
        entry->Accessed();
        // lookuptable takes ownership
        CleanupStack::Pop(); // entry
        }
    return entry;
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::Find
//
// -----------------------------------------------------------------------------
//
CHttpCacheEntry* CHttpCacheLookupTable::Find( const TDesC8& aUrl )
    {
    CHttpCacheEntry* entry = NULL;
    TInt pos( Probe( aUrl, EFalse ) );

    if ( Valid( pos ) )
        {
        entry = iEntries->At( pos );
        
        if ( entry )
            {
            if ( entry->BodySize() == 0 &&
                 entry->State() == CHttpCacheEntry::ECacheComplete )
                {

#ifdef __CACHELOG__
                HttpCacheUtil::WriteLogFilenameAndUrl( 0,
                                           _L("CHttpCacheLookupTable::Find - Found ZERO size"),
                                           entry->Filename(),
                                           entry->Url(),
                                           pos,
                                           ELogLookupTablePos );
#endif
                return NULL;
                }
            }
        }

    return entry;
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::Remove
//
// -----------------------------------------------------------------------------
//
TInt CHttpCacheLookupTable::Remove( const TDesC8& aUrl )
    {
    TInt status( KErrNotFound );
    TInt pos( Probe( aUrl, EFalse ) );

    if( Valid( pos ) )
        {
        // remove only nonactive entry
        CHttpCacheEntry* entry = iEntries->At( pos );
        if ( entry->State() == CHttpCacheEntry::ECacheComplete )
            {
#ifdef __CACHELOG__
            HttpCacheUtil::WriteLogFilenameAndUrl( 0,
                                           _L("CHttpCacheLookupTable::Remove - ERASING item"),
                                           entry->Filename(),
                                           entry->Url(),
                                           pos,
                                           ELogLookupTablePos );
#endif
            Erase( pos );
            status = KErrNone;
            }
#ifndef __CACHELOG__
        }
#else //__CACHELOG__
        else
            {
            HttpCacheUtil::WriteLogFilenameAndUrl( 0,
                                           _L("CHttpCacheLookupTable::Remove - can NOT remove item, is active state"),
                                           entry->Filename(),
                                           entry->Url(),
                                           pos,
                                           ELogLookupTablePos );
            }
        }
    else
        {
        HttpCacheUtil::WriteLog( 0, _L( "CHttpCacheLookupTable::Remove - item is not valid. cannot be removed" ), pos );
        }
#endif // __CACHELOG__

    return status;
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::EraseCacheEntry
//
// -----------------------------------------------------------------------------
//
void CHttpCacheLookupTable::EraseCacheEntry( const TDesC8& aUrl )
    {
    TInt pos( Probe( aUrl, EFalse ) );
    
    if ( Valid( pos ) )
        {
        Erase( pos );
        }
#ifdef __CACHELOG__
    else
        {
        // there must be a valid position for this entry
        __ASSERT_DEBUG( EFalse, User::Panic( _L("cacheHandler Panic"), KErrCorrupt ) );
        }
#endif // __CACHELOG__
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::RemoveAll
//
// -----------------------------------------------------------------------------
//
TInt CHttpCacheLookupTable::RemoveAll()
    {
    TInt numberOfBytes( 0 );

#ifdef __CACHELOG__
    HttpCacheUtil::WriteLog( 0, _L( "CHttpCacheLookupTable::RemoveAll - remove 'em all" ) );
#endif

    for ( TInt i = 0; i < iEntries->Count(); i++ )
        {
        CHttpCacheEntry* entry = iEntries->At( i );
        // Remove all nonactive entries
        if ( Valid( i ) && entry->State() == CHttpCacheEntry::ECacheComplete )
            {
            numberOfBytes += ( entry->HeaderSize() + entry->BodySize() );
            Erase( i );
            }
        }

#ifdef __CACHELOG__
    HttpCacheUtil::WriteLog( 0, _L( "number of items left:" ), iCount );
    // check if there are pending items -should not be though
    __ASSERT_DEBUG( iCount == 0, User::Panic( _L("cacheHandler Panic"), KErrCorrupt ) );
#endif // __CACHELOG__

    return numberOfBytes;
    }


// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::ListFiles
// Adds all filenames known to this lookup table to aFilenameList
// No ownership transfer occurs, only pointers are added
// -----------------------------------------------------------------------------
//
TInt CHttpCacheLookupTable::ListFiles(RPointerArray<TDesC>& aFilenameList)
    {
    TInt count( 0 );
    TInt error( KErrNone );

    //1. Tally up 
    for (TInt i = 0; i < iEntries->Count(); i++)
        {
        if (Valid(i)) count++;
        }

    //2. Preallocation. 
    TInt existing( aFilenameList.Count() );
    error = aFilenameList.Reserve( existing + count );

    if (error == KErrNone)
        {
        //3. Iterate once more and add pointers to filename strings
        for (TInt i = 0; i < iEntries->Count(); i++)
            {
            if (Valid(i))
                {
                //add filename pointer to the array. 
                const TDesC* ptr = &(iEntries->At(i)->Filename());
                aFilenameList.Append( ptr ); // no ownership transfer happens here
                }
            }
        }
     
    return error;

    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::InternalizeL
//
// -----------------------------------------------------------------------------
//
void CHttpCacheLookupTable::InternalizeL(
    RFileReadStream& aReadStream,
    const TDesC& /*aDirectory*/ )
    {
    // get number of entries
    TInt version = 0;
    version = aReadStream.ReadInt32L();
    if( version == KCacheVersionNumber )
        {
        TInt count( aReadStream.ReadInt32L() );
        TInt contentSize( 0 );
        TInt err;
        for( TInt i = 0; i < count; i++ )
            {
            // create empty object
            CHttpCacheEntry* entry = CHttpCacheEntry::NewLC( KNullDesC8, *iEvictionHandler );
            // read it
            err = entry->Internalize( aReadStream );
            // leave only on no memory
            if( err == KErrNone )
                {
                // insert to the table
                InsertL( entry );
                contentSize += entry->HeaderSize();
                contentSize += entry->BodySize();
                }
            else if ( err == KErrNoMemory )
                {
                User::Leave( KErrNoMemory );
                }
            else
                {
                // suggestions
                }

            // takes ownership
            CleanupStack::Pop(); // entry
            }

        // set startup cache size
#ifdef __CACHELOG__
        HttpCacheUtil::WriteLog( 0, _L( "startup content size" ), contentSize );
#endif
        iStreamHandler->SetSavedContentSize( contentSize );
        }
    else
        {
        // cleanup index.dat
        }
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::Externalize
//
// -----------------------------------------------------------------------------
//
void CHttpCacheLookupTable::ExternalizeL(
    RFileWriteStream& aWriteStream )
    {
    // write version number and the number of entries
    TRAP_IGNORE( aWriteStream.WriteInt32L( KCacheVersionNumber );
    aWriteStream.WriteInt32L( iCount ) );
    for( TInt i = 0; i < iEntries->Count(); i++ )
        {
        CHttpCacheEntry* entry = iEntries->At( i );
        // save complete entries only
        if( Valid( i ) )
            {
            // save entry
            TInt err;
            err = entry->Externalize( aWriteStream );
            // leave only on no memory
            if( err == KErrNoMemory )
                {
                User::Leave( KErrNoMemory );
                }
            }
        }
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::InsertL
//
// -----------------------------------------------------------------------------
//
TInt CHttpCacheLookupTable::InsertL(
    CHttpCacheEntry* aCacheEntry )
    {
    __ASSERT_DEBUG( aCacheEntry != NULL,
        User::Panic( _L("cacheHandler Panic"), KErrCorrupt ) );

    TInt pos( -1 );

    if ( aCacheEntry )
        {
        pos = Probe( aCacheEntry->Url(), ETrue );
        // double check
        if( Valid( pos ) )
            {
            // try to rehash the table if probe failed
            ReHashL();
            pos = Probe( aCacheEntry->Url(), ETrue );

            if( pos == -1 || Valid( pos ) )
                {
                // completly failed
                pos = -1;
                }
            }

        // insert
        if( pos != -1 )
            {
            iEntries->At( pos ) = aCacheEntry;
            iCount++;

#ifdef __CACHELOG__
            HttpCacheUtil::WriteLog( 0, _L( "insert new item to the lookuptable" ), pos );
#endif
            // check if the hashtable is full
            if ( iCount > ( iEntries->Count() >> 1 ) )
                {
                ReHashL();
                }
            }
        else
            {
            // lose entry???
            __ASSERT_DEBUG( EFalse, User::Panic( _L("cacheHandler Panic"), KErrCorrupt ) );
            }
        }
    return pos == -1 ? KErrCorrupt : KErrNone;
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::Probe
//
// -----------------------------------------------------------------------------
//
TInt CHttpCacheLookupTable::Probe(
    const TDesC8& aKey,
    TBool aInsert )
    {
    //
  TInt collision( 0 );
  TInt pos( HashUrl( aKey ) );

  if( aInsert )
        {
        // insert
        while( Valid( pos ) && ( collision < ( iEntries->Count() / 2 ) ) )
            {
            pos += ++collision * 2 - 1;
            pos = pos % iEntries->Count();
            }
        }
    else
        {
        // find
      while( !Empty( pos ) )
            {
            CHttpCacheEntry* entry = iEntries->At( pos );
            if( Valid( pos ) && entry && entry->Url().CompareF( aKey ) == 0 )
                {
                break;
                }
            if( collision > iEntries->Count() )
                {
                return -1;
                }
            pos += ++collision * 2 - 1;
            pos = pos % iEntries->Count();
            }
        }
#ifdef __CACHELOG__
    if( collision > 0 )
        {
        HttpCacheUtil::WriteLog( 1, _L( "collision" ), collision );
        }
#endif // __CACHELOG__
  return pos;
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::HashUrl
//
// -----------------------------------------------------------------------------
//
TInt CHttpCacheLookupTable::HashUrl(
    const TDesC8& aUrl )
    {
    // This hash algorithm comes from:
    // http://burtleburtle.net/bob/hash/hashfaq.html
    // http://burtleburtle.net/bob/hash/doobs.html
    TUint len( aUrl.Length() );
    TUint h( KHashPHI );

    h += len;
    h += (h << 10);
    h ^= (h << 6);
    //
    if( len )
        {
        // hash backward to avoid collisions
        // as the first part of the url is
        // always the same http://www.
        // take 16 characters by default
        TUint hashStringLength( len < KHashStringLength ? len : KHashStringLength );

        for( TUint i = ( len - 1 ); i > len - hashStringLength; i-- )
            {
        h += aUrl[ i ];
        h += (h << 10);
        h ^= (h << 6);
          }
        }
    h += (h << 3);
    h ^= (h >> 11);
    h += (h << 15);

    //
    h = h % iEntries->Count();
#ifdef __CACHELOG__
    HttpCacheUtil::WriteUrlToLog( 1, aUrl, h );
#endif // __CACHELOG__
    return h;
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::ReHashL
//
// -----------------------------------------------------------------------------
//
void CHttpCacheLookupTable::ReHashL()
    {
#ifdef __CACHELOG__
    HttpCacheUtil::WriteLog( 1, _L( "Resize lookuptable!" ) );
    HttpCacheUtil::WriteLog( 1, _L( "count=" ), iCount );
    HttpCacheUtil::WriteLog( 1, _L( "Table size=" ), iEntries->Count() );
#endif

    TUint newSize( NextPrime( iEntries->Count() * 2 ) );
    CArrayPtrFlat<CHttpCacheEntry>* newEntries =
        new( ELeave )CArrayPtrFlat<CHttpCacheEntry>( newSize );
    // hash must operate on the new table
    CArrayPtrFlat<CHttpCacheEntry>* oldEntries = iEntries;
    iEntries = newEntries;
    CleanupStack::PushL( oldEntries );
    // fill list with 0
    for( TUint i = 0; i < newSize; i++ )
        {
        iEntries->AppendL( NULL );
        }
    //
  for( TUint i = 0; i < oldEntries->Count(); i++ )
      {
    CHttpCacheEntry* entry = oldEntries->At( i );
        // transfer valid entries only
    if( entry && entry != (CHttpCacheEntry*)0xffffffff )
            {
            TInt pos( Probe( entry->Url(), ETrue ) );
            //
      if( pos != -1 && !Valid( pos ) )
                {
                iEntries->At( pos ) = entry;
                // remove ownership
                oldEntries->At( i ) = NULL;
                }
            // else lose the entry??
            else
                {
                // assert
                __ASSERT_DEBUG( EFalse, User::Panic( _L("cacheHandler Panic"), KErrCorrupt ) );
                }
        }
      }
#ifdef __CACHELOG__
  for( TUint i = 0; i < iEntries->Count(); i++ )
        {
    CHttpCacheEntry* entry = iEntries->At( i );
        if( entry && Valid( i ) )
            {
            HttpCacheUtil::WriteUrlToLog( 1, entry->Url(), i );
            }
        }
#endif // __CACHELOG__
    CleanupStack::PopAndDestroy(); // oldEntries
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::NextPrime
//
// -----------------------------------------------------------------------------
//
TUint CHttpCacheLookupTable::NextPrime(
    TUint aNum )
    {
  if( aNum % 2 == 0 )
        {
        aNum++;
        }

  for( ;; )
        {
    for( TUint i = 3; i * i <= aNum; i += 2 )
      if( aNum % i == 0 )
                {
        aNum += 2;
        i = 1;
        continue;
      }
    return aNum;
      }
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::Erase
//
// -----------------------------------------------------------------------------
//
void CHttpCacheLookupTable::Erase( TInt aPos )
    {
    // must be a valid pos
    __ASSERT_DEBUG( Valid( aPos ),  User::Panic( _L("cacheHandler Panic"), KErrCorrupt ) );

    CHttpCacheEntry* entry = iEntries->At( aPos );

    if( entry )
        {
        // delete file associated with this entry
        TBool attached( EFalse );
        
#ifdef __CACHELOG__
        HttpCacheUtil::WriteLogFilenameAndUrl( 0,
                                           _L("CHttpCacheLookupTable::Erase"),
                                           entry->Filename(),
                                           entry->Url(),
                                           aPos,
                                           ELogLookupTablePos );
#endif        
        TRAPD( err, attached = iStreamHandler->AttachL( *entry ) );
        if ( err == KErrNone && attached )
            {
            iStreamHandler->EraseCacheFile( *entry );
            iStreamHandler->Detach( *entry );
            }

        SetDeleted( aPos );
        delete entry;
        iCount--;
        }
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::Valid
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheLookupTable::Valid(
    TInt aPos )
    {
    return( BoundaryCheck( aPos ) && !Empty( aPos ) && !Deleted( aPos ) );
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::Empty
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheLookupTable::Empty(
    TInt aPos )
    {
    return( BoundaryCheck( aPos ) && iEntries->At( aPos ) == NULL );
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::Deleted
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheLookupTable::Deleted(
    TInt aPos )
    {
    return( BoundaryCheck( aPos ) && iEntries->At( aPos ) == (CHttpCacheEntry*)0xffffffff );
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::SetDeleted
//
// -----------------------------------------------------------------------------
//
void CHttpCacheLookupTable::SetDeleted(
    TInt aPos )
    {
    if( BoundaryCheck( aPos ) )
        {
        iEntries->At( aPos ) = (CHttpCacheEntry*)0xffffffff;
        }
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::BoundaryCheck
//
// -----------------------------------------------------------------------------
//
TBool CHttpCacheLookupTable::BoundaryCheck(
    TInt aPos )
    {
    return ( aPos >= 0 && aPos < iEntries->Count() );
    }

// -----------------------------------------------------------------------------
// CHttpCacheLookupTable::MergeL
//
// -----------------------------------------------------------------------------
//
void CHttpCacheLookupTable::MergeL( CHttpCacheLookupTable* aHttpCacheLookupTable, RFs aRfs )
    {
    TInt myCount = iEntries->Count();
    TInt i = 0;
    for (i = myCount - 1; i >= 0; i--)
        {
        if ( Valid( i ) )
            {
            CHttpCacheEntry* entry = iEntries->At(i);
            CHttpCacheEntry* newEntry = aHttpCacheLookupTable->Find(entry->Url());
            if (newEntry)
                {
                // Entry is in the new index file
                if (newEntry->LastAccessed() > entry->LastAccessed())
                    {
                    entry->Accessed(newEntry->LastAccessed(), newEntry->Ref());
                    }
                TInt pos = aHttpCacheLookupTable->Probe(newEntry->Url(), EFalse);
                if (aHttpCacheLookupTable->Valid(pos))
                    {
                    aHttpCacheLookupTable->SetDeleted(pos);
                    delete newEntry;
                    aHttpCacheLookupTable->iCount--;
                    }
                }
            else // (newEntry)
                {
                // Entry is not in the new index file
                TUint att;
                if (aRfs.Att(entry->Filename(), att) != KErrNone)
                    {
                    TInt thePos = Probe(entry->Url(), EFalse);
                    if (Valid(thePos))
                        {
                        Erase(thePos);
                        }
                    }
                }
            }
        }

    TInt newCount = aHttpCacheLookupTable->iEntries->Count();
    for (i = newCount - 1; i >= 0; i--)
        {
        if ( aHttpCacheLookupTable->Valid( i ) )
            {
            CHttpCacheEntry* newEntry = aHttpCacheLookupTable->iEntries->At(i);
            TInt pos = aHttpCacheLookupTable->Probe(newEntry->Url(), EFalse);
            TUint att;
            if (aRfs.Att(newEntry->Filename(), att) == KErrNone)
                {
                CHttpCacheEntry* myEntry = InsertL(newEntry->Url());
                myEntry->SetState( CHttpCacheEntry::ECacheComplete );
                myEntry->Accessed(newEntry->LastAccessed(), newEntry->Ref());
                }          
            aHttpCacheLookupTable->SetDeleted(pos);
            delete newEntry;
            aHttpCacheLookupTable->iCount--;
            }
        }
    }
//  End of File
