/*
   This file is part of the KDE libraries
   Copyright (C) 2008 Christian Ehrlicher <ch.ehrlicher@gmx.de>

   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 "kmemfile.h"

#include <QtCore/QSharedMemory>
#include <QtCore/QCryptographicHash>
#include <QtCore/QFile>
#include <QtCore/QDir>

#include "klocalizedstring.h"

class KMemFile::Private
{
public:
  struct sharedInfoData {
    int  shmCounter;
    qint64 shmDataSize;

    sharedInfoData() {
      memset ( this, 0, sizeof ( this ) );
    }
  };
  Private ( KMemFile *_parent ) : readWritePos ( 0 ), shmDataSize ( 0 ), parent ( _parent ) {}

  QString getShmKey ( int iCounter = -1 );
  static QString getShmKey ( const QString &filename, int iCounter = -1 );
  bool loadContentsFromFile();
  void close();

  QString filename;
  QSharedMemory shmInfo;
  QSharedMemory shmData;
  qint64 readWritePos;
  qint64 shmDataSize;

  KMemFile *parent;
};

QString KMemFile::Private::getShmKey ( int iCounter )
{
  return getShmKey ( filename, iCounter );
}

QString KMemFile::Private::getShmKey ( const QString &filename, int iCounter )
{
  QByteArray tmp = QString ( QDir ( filename ).canonicalPath() + QString::number ( iCounter ) ).toUtf8();
  return QString::fromAscii ( QCryptographicHash::hash ( tmp, QCryptographicHash::Sha1 ) );
}

bool KMemFile::Private::loadContentsFromFile()
{
  QFile f ( filename );
  if ( !f.exists() ) {
    close();
    parent->setErrorString ( i18n ( "File %1 does not exist" , filename ) );
    return false;
  }
  if ( !f.open ( QIODevice::ReadOnly ) ) {
    close();
    parent->setErrorString ( i18n ( "Cannot open %1 for reading" , filename ) );
    return false;
  }

  sharedInfoData *infoPtr = static_cast<sharedInfoData*> ( shmInfo.data() );

  infoPtr->shmDataSize = f.size();
  shmData.setKey ( getShmKey ( infoPtr->shmCounter ) );
  if ( !shmData.create ( infoPtr->shmDataSize ) ) {
    close();
    parent->setErrorString ( i18n ( "Cannot create memory segment for file %1" , filename ) );
    return false;
  }
  shmData.lock();
  qint64 size = 0;
  qint64 bytesRead;
  char *data = static_cast<char*> ( shmData.data() );
  bytesRead = f.read ( data, infoPtr->shmDataSize );
  if ( bytesRead != infoPtr->shmDataSize ) {
    close();
    parent->setErrorString ( i18n ( "Could not read data from %1 into shm" , filename ) );
    return false;
  }
  shmDataSize = size;
  shmData.unlock();
  return true;
}

void KMemFile::Private::close()
{
  shmData.unlock();
  shmData.detach();
  shmInfo.unlock();
  shmInfo.detach();
  readWritePos = 0;
  shmDataSize = 0;
}

KMemFile::KMemFile ( const QString &filename, QObject *parent )
    : QIODevice ( parent ), d ( new Private ( this ) )
{
  d->filename = filename;
}

KMemFile::~KMemFile()
{
  close();
  delete d;
}

void KMemFile::close ()
{
  QIODevice::close();
  if ( !isOpen() )
    return;
  d->close();
}

bool KMemFile::isSequential () const
{
  return false;
}

bool KMemFile::open ( OpenMode mode )
{
  if ( isOpen() ) {
    QIODevice::open ( mode );
    return false;
  }

  if ( mode != QIODevice::ReadOnly ) {
    setErrorString ( i18n ( "Only 'ReadOnly' allowed" ) );
    return false;
  }

  if ( !QFile::exists ( d->filename ) ) {
    setErrorString ( i18n ( "File %1 does not exist" , d->filename ) );
    return false;
  }

  QSharedMemory lock ( QDir ( d->filename ).canonicalPath() );
  lock.lock();

  Private::sharedInfoData *infoPtr;
  d->shmInfo.setKey ( d->getShmKey() );
  // see if it's already in memory
  if ( !d->shmInfo.attach ( QSharedMemory::ReadWrite ) ) {
    if ( !d->shmInfo.create ( sizeof ( Private::sharedInfoData ) ) ) {
      lock.unlock();
      setErrorString ( i18n ( "Cannot create memory segment for file %1" , d->filename ) );
      return false;
    }
    d->shmInfo.lock();
    // no -> create it
    infoPtr = static_cast<Private::sharedInfoData*> ( d->shmInfo.data() );
    memset ( infoPtr, 0, sizeof ( Private::sharedInfoData ) );
    infoPtr->shmCounter = 1;
    if ( !d->loadContentsFromFile() ) {
      d->shmInfo.unlock();
      d->shmInfo.detach();
      lock.unlock();
      return false;
    }
  } else {
    d->shmInfo.lock();
    infoPtr = static_cast<Private::sharedInfoData*> ( d->shmInfo.data() );
    d->shmData.setKey ( d->getShmKey ( infoPtr->shmCounter ) );
    if ( !d->shmData.attach ( QSharedMemory::ReadOnly ) ) {
      if ( !d->loadContentsFromFile() ) {
        d->shmInfo.unlock();
        d->shmInfo.detach();
        lock.unlock();
        return false;
      }
    }
  }
  d->shmDataSize = infoPtr->shmDataSize;
  d->shmInfo.unlock();
  lock.unlock();

  setOpenMode ( mode );
  return true;
}

bool KMemFile::seek ( qint64 pos )
{
  if ( d->shmDataSize < pos ) {
    setErrorString ( i18n ( "Cannot seek past eof" ) );
    return false;
  }
  d->readWritePos = pos;
  QIODevice::seek ( pos );
  return true;
}

qint64 KMemFile::size () const
{
  return d->shmDataSize;
}

qint64 KMemFile::readData ( char * data, qint64 maxSize )
{
  if ( ( openMode() & QIODevice::ReadOnly ) == 0 )
    return -1;

  qint64 maxRead = size() - d->readWritePos;
  qint64 bytesToRead = qMin ( maxRead, maxSize );
  const char *src = static_cast<const char*> ( d->shmData.data() );
  memcpy ( data, &src[d->readWritePos], bytesToRead );
  d->readWritePos += bytesToRead;
  return bytesToRead;
}

qint64 KMemFile::writeData ( const char *, qint64 )
{
  return -1;
}

void KMemFile::fileContentsChanged ( const QString &filename )
{
  QSharedMemory lock ( QDir ( filename ).canonicalPath() );
  lock.lock();

  QSharedMemory shmData ( Private::getShmKey ( filename ) );
  if ( !shmData.attach() )
    return;
  shmData.lock();
  Private::sharedInfoData *infoPtr = static_cast<Private::sharedInfoData*> ( shmData.data() );
  infoPtr->shmCounter++;
  infoPtr->shmDataSize = 0;
  shmData.unlock();
}
