//
// Class: documentPageCache
//
// Cache that holds a number of pages in a document.
// Part of KDVI- A previewer for TeX DVI files.
//
// (C) 2004 Stefan Kebekus. Distributed under the GPL.


#include <kdebug.h>

#include "documentPagePixmap.h"
#include "documentPageCache.h"
#include "documentRenderer.h"

//#define documentPageCache_DEBUG


documentPageCache::documentPageCache() 
{
  renderer = 0;
  LRUCache.setAutoDelete(false);
  recycleBin.setAutoDelete(false);

  resolutionInDPI = 0.0;
  userPreferredSize.setPageSize(200,300);
  useDocumentSpecifiedSize = true;
}


documentPageCache::~documentPageCache()
{
  LRUCache.setAutoDelete(true);
  recycleBin.setAutoDelete(true);
}


void documentPageCache::setRenderer(documentRenderer *_renderer)
{
  clear();
  renderer = _renderer;
}


simplePageSize documentPageCache::sizeOfPage(PageNumber page) const
{
  // Paranoid safety checks
  if (!page.isValid()) {
    kdError(4300) << "documentPageCache::sizeOfPage( " <<  page << ") called with invalid page number." << endl;
    return simplePageSize();
  }
  if (renderer == 0) {
    kdError(4300) << "documentPageCache::sizeOfPage( " <<  page << ") called when no renderer was set." << endl;
    return simplePageSize();
  }

  simplePageSize s = renderer->sizeOfPage(page);
  if (!useDocumentSpecifiedSize || !s.isValid())
    s = userPreferredSize;

  return s;
}


QSize documentPageCache::sizeOfPageInPixel(PageNumber pg) const
{
  // Paranoid safety checks
  if (renderer == 0) {
    kdError(4300) << "documentPageCache::sizeOfPageInPixel( " << pg << " ) called but no renderer was set" << endl;
    return QSize();
  }
  if (pg.isInvalid()) {
    kdError(4300) << "documentPageCache::sizeOfPageInPixel( " << pg << " ) called with invalid argument" << endl;
    return QSize();
  }

  simplePageSize ps = sizeOfPage(pg);
  if (ps.isValid())
    return ps.sizeInPixel(resolutionInDPI);
  return userPreferredSize.sizeInPixel(resolutionInDPI); 
}


documentPagePixmap *documentPageCache::getPage(PageNumber pageNr)
{
#ifdef documentPageCache_DEBUG
  kdDebug(4300) << "documentPageCache::getPage( pageNr=" << pageNr << " )" << endl;
#endif

  // Paranoid checks
  if (renderer == 0) {
    kdError(4300) << "documentPageCache::getPage(..) called but no renderer was set" << endl;
    return 0;
  }
  if (!pageNr.isValid()) {
    kdError(4300) << "documentPageCache::getPage( " << pageNr << " ) called, with invalid argument." << endl;
    return 0;
  }
  if (renderer->totalPages() < pageNr) {
    kdError(4300) << "documentPageCache::getPage( " << pageNr << " ) called but document contains only " << renderer->totalPages() << " pages." << endl;
    return 0;
  }


  // First check if the page that we are looking for is in the cache
  documentPagePixmap *page;
  for ( page = LRUCache.first(); page; page = LRUCache.next() )
    if ((page->getPageNumber() == pageNr) && (page->isEmpty == false)) {
      // Page found! Move the page to the front of the list, and
      // return it.
      LRUCache.remove();
      LRUCache.prepend(page);
      return page;
    }

  // The page was not found in the cache, so we have to make a new
  // page and add this to the cache. We have several options where
  // this page is supposed to come from.

  // If the cache already contains the maximum number of elements, we
  // use the last recently element, move it to the front of the list
  // and use that.
  if (LRUCache.count() == maxSize) {
    page = LRUCache.getLast();
    LRUCache.removeLast();
    page->clear();
  }

  // If we haven't found a structure in the cache, perhaps there is a
  // page structure waiting in the recycleBin that we could use?
  if ((page == 0) && (recycleBin.isEmpty() == false)) {
    page = recycleBin.first();
    recycleBin.removeFirst();
  }
  
  // If we haven't found a structure, we allocate a new one
  if (page == 0)
    page = new documentPagePixmap();
  
  // If even that failed, issue an error message and quit.
  if (page == 0) {
    kdError(4300) << "documentPageCache::getPage(..) cannot allocate documentPage structure" << endl;
    return 0;
  }
  
  // Now 'page' contains a point to a page structure that we can
  // use. Add the page as the first element to the cache, and apply
  // the renderer to the page
  LRUCache.prepend(page);
  page->setPageNumber(pageNr);
  if (renderer != 0) {
    if (resolutionInDPI > 0.0) {
      page->resize(sizeOfPageInPixel(pageNr));
      renderer->drawPage(resolutionInDPI, page);
    }
    else
      kdError(4300) << "documentPageCache::getPage() called, but no resolution or negative resolution was set" << endl;
  }
  return page;
}


void documentPageCache::clear()
{
  documentPagePixmap *page;

  while( (page = LRUCache.first()) != 0 ) {
    LRUCache.removeFirst();
    page->clear();
    recycleBin.prepend(page);
  }
}


void documentPageCache::setUserPreferredSize(simplePageSize s)
{
  bool sizeChanged = !userPreferredSize.isNearlyEqual(s);
  userPreferredSize = s;

  if (sizeChanged)
    emit(paperSizeChanged());
}


void documentPageCache::setUseDocumentSpecifiedSize(bool b)
{
  bool valChanged = (useDocumentSpecifiedSize == b);

  useDocumentSpecifiedSize = b;
  if (valChanged)
    emit(paperSizeChanged());
}


QPixmap documentPageCache::createThumbnail(PageNumber pageNr, int width)
{
  // Paranoid checks
  if (renderer == 0) {
    kdError(4300) << "documentPageCache::createThumbnail(..) called but no renderer was set" << endl;
    thumbnailPage.resize(0,0);
    return thumbnailPage;
  }
  if (renderer->totalPages() < pageNr) {
    kdError(4300) << "documentPageCache::createThumbnail( " << pageNr << ", width ) called but document contains only " << renderer->totalPages() << " pages." << endl;
    thumbnailPage.resize(0,0);
    return thumbnailPage;
  }
  if (!pageNr.isValid()) {
    kdError(4300) << "documentPageCache::createThumbnail(..) called for page with invalid page specification" << endl;
    thumbnailPage.resize(0,0);
    return thumbnailPage;
  }
  if (!sizeOfPage().isValid()) {
    kdError(4300) << "documentPageCache::createThumbnail(..) called for page with invalid size" << endl;
    thumbnailPage.resize(0,0);
    return thumbnailPage;
  }
  
  thumbnailPage.setPageNumber(pageNr);
  thumbnailPage.resize(width, (int)(width/sizeOfPage(pageNr).aspectRatio() + 0.5 ) );
  renderer->drawThumbnail((double)(width)*25.4/sizeOfPage(pageNr).width_in_mm(), &thumbnailPage);
  
  return thumbnailPage;
}


#include "documentPageCache.moc"
