// Copyright (C) 1997 Microsoft Corporation. All rights reserved.

#include "header.h"

#include "subfile.h"
#include "fs.h"
#include "toc.h"

static CPagesList listSubfiles;

//////////////////////////////////////////////
//
//  CPagesList
//
//////////////////////////////////////////////

CPagesList::~CPagesList()
{
    CPages* p;

    while (p = m_pFirst)
    {
        m_pFirst = p->m_pNext;
        delete p;
    }
}

CPages* CPagesList::GetPages( HASH hash )
{
    CPages * p;

    for (p = m_pFirst; p; p = p->m_pNext)
        if ( hash == p->m_hash )
            return p;

    p = new CPages( hash );
    p->m_pNext = m_pFirst;
    m_pFirst = p;

    return p;
}

////////////////////////////////////////
//
//  CPages
//
////////////////////////////////////////

CPages::CPages( HASH hash )
{
    m_pNext = 0;
    m_hash = hash;
    Flush();
}

void* CPages::Find(const CExTitle * pTitle, HASH hashPathname, DWORD dwPage)
{
  int i;
   
   for( i = 0; i < CACHE_PAGE_COUNT; i++ )
   {
      // test if lowest LRU
      if( m_pages[i].dwLRU < m_pages[m_dwLRUPage].dwLRU ) {
        m_dwLRUPage = i;
      }

      if( m_pages[i].dwLRU &&
          m_pages[i].hashPathname == hashPathname &&
          m_pages[i].dwPage == dwPage &&
          m_pages[i].pTitle == pTitle )
      {
         m_pages[i].dwLRU = ++m_dwLRUCount;  // update LRU
         return m_pages[i].rgb;
      }
   }

   return NULL;
}

void* CPages::Alloc(CExTitle * pTitle, HASH hashPathname, DWORD dwPage)
{
    // if we reached the max LRU number then flush the cache and start over
    if( m_dwLRUCount == ((DWORD) -1) )
      Flush();
    
    m_pages[m_dwLRUPage].dwLRU = ++m_dwLRUCount;
    m_pages[m_dwLRUPage].hashPathname = hashPathname;
    m_pages[m_dwLRUPage].dwPage = dwPage;
    m_pages[m_dwLRUPage].pTitle = pTitle;

    return m_pages[m_dwLRUPage].rgb;
}

void CPages::Flush(void)
{
    int i;

    for( i = 0; i < CACHE_PAGE_COUNT; i++ ) 
        m_pages[i].dwLRU = 0;
    m_dwLRUPage = 0;
    m_dwLRUCount = 0;
}

void CPages::Flush( CExTitle* pTitle )
{
    if( !pTitle )
      return;

    int i;

    for( i = 0; i < CACHE_PAGE_COUNT; i++ )
      if( m_pages[i].pTitle == pTitle )
        m_pages[i].dwLRU = 0;
}


//////////////////////////////////////////////
//
//  CPagedSubfile
//
//////////////////////////////////////////////

CPagedSubfile::CPagedSubfile()
{
   m_pCSubFileSystem = 0;          // type CSubFileSystem from fs.h/fs.cpp
   m_pTitle = 0;
   m_pPages = 0;
   m_cbSize = 0xFFFFFFFF;
}

CPagedSubfile::~CPagedSubfile()
{
   if ( m_pCSubFileSystem )
      delete m_pCSubFileSystem;

   // flush all of its owned pages since
   // the same pTitle value may be re-used and thus
   // the cache is invalid
   m_pPages->Flush( m_pTitle );
}

HRESULT CPagedSubfile::Open(CExTitle * pTitle, LPCSTR lpsz)
{
   if (m_pCSubFileSystem || m_pTitle || m_pPages || !pTitle->m_pCFileSystem )
      return E_FAIL;

   m_pTitle = pTitle;

   // hash the filename
   m_hashPathname = HashFromSz( lpsz );

#ifdef _DEBUG
   char sz[1024];
   wsprintf( sz, "Hash:%d File:%s\n", m_hashPathname, lpsz );
   OutputDebugString( sz );
#endif

#if defined( HH_FAST_CACHE )
   // keep CACHE_PAGE_COUNT small (2-3) and hash the title and the filename
   char szHash[MAX_PATH*2];
   strcpy( szHash, pTitle->GetInfo2()->GetShortName() );
   strcat( szHash, "::" );
   strcat( szHash, lpsz );
   m_hash = HashFromSz( szHash );
#elif defined ( HH_EFFICIENT_CACHE )
   // keep CACHE_PAGE_COUNT moderately low (3-5) and hash just the filename
   m_hash = HashFromSz( lpsz );
#else // HH_SHARED_CACHE
   // keep CACHE_PAGE_COUNT high (30+) and have only one shared cache group
   m_hash = HashFromSz( "HTMLHelpSharedCache" );
#endif

   if (!(m_pPages = listSubfiles.GetPages(m_hash)))
       return E_FAIL;

   m_pCSubFileSystem = new CSubFileSystem(pTitle->m_pCFileSystem);

   if(FAILED(m_pCSubFileSystem->OpenSub(lpsz)))
   {
       delete m_pCSubFileSystem;
       m_pCSubFileSystem = NULL;
       return E_FAIL;
   }
   m_cbSize = m_pCSubFileSystem->GetUncompressedSize();

   return S_OK;
}

void* CPagedSubfile::Offset(DWORD dwOffs)
{
   DWORD dwPage;
   void* pv;

   if (dwOffs >= m_cbSize)
       return NULL;

   dwPage = dwOffs / PAGE_SIZE;

   dwOffs -= (dwPage * PAGE_SIZE);

   if (pv = Page(dwPage))
      return (BYTE*)pv + dwOffs;
   else
      return NULL;
}

void* CPagedSubfile::Page(DWORD dwPage)
{
    void* pv;

    if (pv = m_pPages->Find(m_pTitle, m_hashPathname, dwPage))
        return pv;

    if (!(pv = m_pPages->Alloc(m_pTitle, m_hashPathname, dwPage)))
        return NULL;

    DWORD li = dwPage * PAGE_SIZE;

    if ( m_pCSubFileSystem->SeekSub(li,0) != li )
       return NULL;

    // init the page in case we don't read it all...
    //
#ifdef _DEBUG
    memset(pv,0xFF,PAGE_SIZE);
#endif

    ULONG cb;
    if (FAILED(m_pCSubFileSystem->ReadSub(pv, PAGE_SIZE, &cb)))
        return NULL;

   return pv;
}