/***************************************************************************
**
**  This file is part of QGpCoreMath.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2009-04-11
**  Copyright: 2009-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "AbstractNumericalCache.h"
#include "AbstractNumericalKey.h"

namespace QGpCoreMath {

// QHash does not work with a pointer nor with a virtual class as key
class QGPCOREMATH_EXPORT HashNumericalKey
{
public:
  HashNumericalKey() {_key=0;}
  HashNumericalKey(AbstractNumericalKey * key) {_key=key;}

  void operator=(const HashNumericalKey& o) {_key=o._key;}
  inline bool operator==(const HashNumericalKey& o) const;
  int hash() const {return _key ? _key->hash() : 0;}
private:
  AbstractNumericalKey * _key;
};

inline bool HashNumericalKey::operator==(const HashNumericalKey& o) const
{
  if(_key && o._key) {
    if(_key->id()==o._key->id()) {
      return *_key==*o._key;
    } else {
      return false;
    }
  } else {
    return _key==o._key;
  }
}

int qHash(const HashNumericalKey& key)
{
  return key.hash();
}

/*!
  \class AbstractNumericalCache AbstractNumericalCache.h
  \brief Cache any numerical computation

  A maximum of 200 keys is currently allowed.
*/

Mutex AbstractNumericalCache::_mutex;
QHash<HashNumericalKey, AbstractNumericalCache *> AbstractNumericalCache::_caches;

/*!
  Takes the ownership over \a key. If a new cache is created, the cache takes its ownership.
*/
const AbstractNumericalCache * AbstractNumericalCache::begin(AbstractNumericalKey * key)
{
  AbstractNumericalCache * cache;
  HashNumericalKey k(key);
  _mutex.MUTEX_LOCK;
  QHash<HashNumericalKey, AbstractNumericalCache *>::iterator it=_caches.find(k);
  if(it!=_caches.end()) {
    cache=it.value();
    cache->_locked.lockForRead();
    _mutex.unlock();
    delete key; // A similar key already exist, drop this one
  } else {
    cache=add(key);
    cache->_locked.lockForWrite();
    _mutex.unlock();
    cache->init();
    cache->_locked.unlock();
    _mutex.MUTEX_LOCK;
    cache->_locked.lockForRead();
    _mutex.unlock();
  }
  return cache;
}

void AbstractNumericalCache::end(const AbstractNumericalCache * cache)
{
  cache->_locked.unlock();
}

AbstractNumericalCache * AbstractNumericalCache::add(AbstractNumericalKey * key)
{
  AbstractNumericalCache * cache=key->createCache();
  // Ensures deletion at the end of application
  CoreApplication::addGlobalObject(cache);
  _caches.insert(HashNumericalKey(key), cache);
  if(_caches.count()>200) {
    // Finds the oldest unlocked cache entry
    // This private function is called only while _mutex is locked, hence
    // a cache entry can never be unlocked during this function call.
    QHash<HashNumericalKey, AbstractNumericalCache *>::iterator it=_caches.begin();
    QHash<HashNumericalKey, AbstractNumericalCache *>::iterator itOld=it;
    clock_t t=it.value()->lastUse();
    for(++it; it!=_caches.end(); it++) {
      AbstractNumericalCache * c=it.value();
      if(c->lastUse()<t && c->_locked.tryLockForWrite()) {
        itOld=it;
        t=c->lastUse();
        c->_locked.unlock();
      }
    }
    // Prevents deletion at the end of application
    // It is safe to delete the oldest cache entry here because _mutex is locked
    // tryLockForWrite returned immediately true hence no other thread had locked
    // it for read.
    AbstractNumericalCache * lostCache=_caches.take(itOld.key());
    CoreApplication::removeGlobalObject(lostCache);
    delete lostCache;
  }
  return cache;
}

} // namespace QGpCoreMath
