/* This file is part of the KDE project
 *
 * Copyright (C) 2000 Waldo Bastian <bastian@kde.org>
 * Copyright (C) 2007 Nick Shaforostoff <shafff@ukr.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "khtml_pagecache.h"

#include <kfilterdev.h>
#include <QTemporaryFile>
#include <kstandarddirs.h>

#include <QQueue>
#include <QHash>
#include <QList>
#include <QtCore/QTimer>
#include <QtCore/QFile>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <assert.h>

// We keep 12 pages in memory.
#ifndef KHTML_PAGE_CACHE_SIZE
#define KHTML_PAGE_CACHE_SIZE 12
#endif

template class QList<KHTMLPageCacheDelivery*>;
class KHTMLPageCacheEntry
{
  friend class KHTMLPageCache;
public:
  KHTMLPageCacheEntry(long id);

  ~KHTMLPageCacheEntry();

  void addData(const QByteArray &data);
  void endData();

  bool isComplete() const {return m_complete;}
  QString fileName() const {return m_fileName;}

  KHTMLPageCacheDelivery *fetchData(QObject *recvObj, const char *recvSlot);
private:
  long m_id;
  bool m_complete;
  QByteArray m_buffer;
  QIODevice* m_file;
  QString m_fileName;
};

class KHTMLPageCachePrivate
{
public:
  long newId;
  bool deliveryActive;
  QHash<int, KHTMLPageCacheEntry*> dict;
  QList<KHTMLPageCacheDelivery*> delivery;
  QQueue<long> expireQueue;
};

KHTMLPageCacheEntry::KHTMLPageCacheEntry(long id)
    : m_id(id)
    , m_complete(false)
{
  //get tmp file name
  QTemporaryFile* f=new QTemporaryFile(KStandardDirs::locateLocal("tmp", "")+"khtmlcacheXXXXXX.tmp");
  f->open();
  m_fileName=f->fileName();
  f->setAutoRemove(false);
  delete f;

  m_file = KFilterDev::deviceForFile(m_fileName, "application/x-gzip"/*,false*/);
  m_file->open(QIODevice::WriteOnly);
}

KHTMLPageCacheEntry::~KHTMLPageCacheEntry()
{
  delete m_file;
  QFile::remove(m_fileName);
}


void
KHTMLPageCacheEntry::addData(const QByteArray &data)
{
    m_buffer+=data;
}

void
KHTMLPageCacheEntry::endData()
{
  m_complete = true;
 m_file->write(m_buffer);
 m_buffer.clear();
 m_file->close();
}


KHTMLPageCacheDelivery *
KHTMLPageCacheEntry::fetchData(QObject *recvObj, const char *recvSlot)
{
  // Duplicate fd so that entry can be safely deleted while delivering the data.
  KHTMLPageCacheDelivery *delivery=new KHTMLPageCacheDelivery(
                                                              KFilterDev::deviceForFile (m_fileName, "application/x-gzip")
                                                             );
  delivery->file->open(QIODevice::ReadOnly);

  recvObj->connect(delivery, SIGNAL(emitData(const QByteArray&)), recvSlot);
  delivery->recvObj = recvObj;
  return delivery;
}

KHTMLPageCache *
KHTMLPageCache::self()
{
  K_GLOBAL_STATIC(KHTMLPageCache, _self)
  return _self;
}

KHTMLPageCache::KHTMLPageCache()
	:d( new KHTMLPageCachePrivate)
{
  d->newId = 1;
  d->deliveryActive = false;
}

KHTMLPageCache::~KHTMLPageCache()
{
  qDeleteAll(d->dict);
  qDeleteAll(d->delivery);
  delete d;
}

long
KHTMLPageCache::createCacheEntry()
{

  KHTMLPageCacheEntry *entry = new KHTMLPageCacheEntry(d->newId);
  d->dict.insert(d->newId, entry);
  d->expireQueue.append(d->newId);
  if (d->expireQueue.count() > KHTML_PAGE_CACHE_SIZE)
     delete d->dict.take(d->expireQueue.dequeue());
  return (d->newId++);
}

void
KHTMLPageCache::addData(long id, const QByteArray &data)
{

  KHTMLPageCacheEntry *entry = d->dict.value( id );
  if (entry)
     entry->addData(data);
}

void
KHTMLPageCache::endData(long id)
{
  KHTMLPageCacheEntry *entry = d->dict.value( id );
  if (entry)
     entry->endData();
}

void
KHTMLPageCache::cancelEntry(long id)
{
  KHTMLPageCacheEntry *entry = d->dict.take( id );
  if (entry)
  {
     d->expireQueue.removeAll(entry->m_id);
     delete entry;
  }
}

bool
KHTMLPageCache::isValid(long id)
{
  return d->dict.contains(id);
}

bool
KHTMLPageCache::isComplete(long id)
{
  KHTMLPageCacheEntry *entry = d->dict.value( id );
  if (entry)
     return entry->isComplete();
  return false;
}

void
KHTMLPageCache::fetchData(long id, QObject *recvObj, const char *recvSlot)
{
  KHTMLPageCacheEntry *entry = d->dict.value( id );
  if (!entry || !entry->isComplete()) return;

  // Make this entry the most recent entry.
  d->expireQueue.removeAll(entry->m_id);
  d->expireQueue.enqueue(entry->m_id);

  d->delivery.append( entry->fetchData(recvObj, recvSlot) );
  if (!d->deliveryActive)
  {
     d->deliveryActive = true;
     QTimer::singleShot(20, this, SLOT(sendData()));
  }
}

void
KHTMLPageCache::cancelFetch(QObject *recvObj)
{
  QMutableListIterator<KHTMLPageCacheDelivery*> it( d->delivery );
  while (it.hasNext()) {
      KHTMLPageCacheDelivery* delivery = it.next();
      if (delivery->recvObj == recvObj)
      {
         delete delivery;
         it.remove();
      }
  }
}

void
KHTMLPageCache::sendData()
{
  if (d->delivery.isEmpty())
  {
     d->deliveryActive = false;
     return;
  }

  KHTMLPageCacheDelivery *delivery = d->delivery.takeFirst();
  assert(delivery);

  QByteArray byteArray(delivery->file->read(64*1024));
  delivery->emitData(byteArray);

  //put back in queue
  if (delivery->file->atEnd())
  {
    // done.
    delivery->file->close();
    delivery->emitData(QByteArray()); // Empty array
    delete delivery;
  }
  else
    d->delivery.append( delivery );

  QTimer::singleShot(0, this, SLOT(sendData()));
}

void
KHTMLPageCache::saveData(long id, QDataStream *str)
{
  assert(d->dict.contains( id ));
  KHTMLPageCacheEntry *entry = d->dict.value( id );

  if (!entry->isComplete())
  {
      QTimer::singleShot(20, this, SLOT(saveData()));
      return;
  }

  QIODevice* file = KFilterDev::deviceForFile (entry->fileName(), "application/x-gzip");
  if (!file->open(QIODevice::ReadOnly))
    return;

  QByteArray byteArray(file->readAll());
  file->close();

  str->writeRawData(byteArray.constData(), byteArray.length());

}

KHTMLPageCacheDelivery::~KHTMLPageCacheDelivery()
{
  file->close();
  delete file;
}

#include "khtml_pagecache.moc"
