///###////////////////////////////////////////////////////////////////////////
//
// Burton Computer Corporation
// http://www.burton-computer.com
// http://www.cooldevtools.com
// $Id: FrequencyDBImpl_cache.cc 272 2007-01-06 19:37:27Z brian $
//
// Copyright (C) 2007 Burton Computer Corporation
// ALL RIGHTS RESERVED
//
// This program is open source software; you can redistribute it
// and/or modify it under the terms of the Q Public License (QPL)
// version 1.0. Use of this software in whole or in part, including
// linking it (modified or unmodified) into other programs is
// subject to the terms of the QPL.
//
// This program 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
// Q Public License for more details.
//
// You should have received a copy of the Q Public License
// along with this program; see the file LICENSE.txt.  If not, visit
// the Burton Computer Corporation or CoolDevTools web site
// QPL pages at:
//
//    http://www.burton-computer.com/qpl.html
//    http://www.cooldevtools.com/qpl.html
//

#include <unistd.h>
#include <fcntl.h>
#include "WordData.h"
#include "FrequencyDBImpl_cache.h"

static const string DATABASE_TYPE("-cached");

FrequencyDBImpl_cache::FrequencyDBImpl_cache(FrequencyDBImpl *db,
                                             int max_size)
  : m_db(db), m_cache(max_size)
{
  assert(db);
}

FrequencyDBImpl_cache::~FrequencyDBImpl_cache()
{
  close();
}

bool FrequencyDBImpl_cache::open(const string &filename,
                                 bool read_only,
                                 int create_mode)
{
  close();
  return m_db->open(filename, read_only, create_mode);
}

void FrequencyDBImpl_cache::close()
{
  m_db->close();
  clear();
}

void FrequencyDBImpl_cache::flush()
{
  // PBL performance poor for large transactions on large databases
  // 100 seems to be a good limit for records per transaction
  const int MAX_RECORDS_PER_TRANSACTION = 100;
  int num_writes = 0;
  m_db->beginTransaction();
  for (IteratorType i = m_cache.begin(), limit = m_cache.end(); i != limit; ++i) {
    if (i.isLocked()) {
      m_db->writeWord(i.key(), i.value()->counts);
      m_cache.unlock(i.key());
      if (++num_writes >= MAX_RECORDS_PER_TRANSACTION) {
        m_db->endTransaction(true);
        m_db->beginTransaction();
        num_writes = 0;
      }
    }
  }
  m_db->endTransaction(true);
  m_db->flush();
}

void FrequencyDBImpl_cache::addWordData(const string &word,
                                        bool is_dirty,
                                        bool is_shared,
                                        const WordData &counts)
{
  m_cache.put(word, make_ref(new CacheEntry(is_shared, counts)), is_dirty);
}

void FrequencyDBImpl_cache::clear()
{
  m_cache.clear();
}

void FrequencyDBImpl_cache::writeWord(const string &word,
                                      const WordData &counts)
{
  if (m_cache.lockedCount() >= m_cache.maxSize()) {
    if (is_debug) {
      cerr << "MAX CACHE TERMS EXCEEDED - FLUSHING CACHE" << endl;
    }
    flush();
  }

  if (!m_db->canCacheTerm(word)) {
    if (is_debug) {
      cerr << "UNCACHED TERM " << word << endl;
    }
    m_db->writeWord(word, counts);
  } else {
    Ref<CacheEntry> entry;
    if (!m_cache.get(word, entry)) {
      addWordData(word, true, false, counts);
      if (is_debug) {
        cerr << "CACHED TERM INSERTED " << word << endl;
      }
    } else if ((entry->counts).equals(counts)) {
      if (is_debug) {
        cerr << "CACHED TERM UNCHANGED " << word << endl;
      }
    } else if (entry->is_shared && entry->counts.hasSameCounts(counts)) {
      // Do nothing because we don't want terms migrating from shared to 
      // private database if only the timestamp changed.
      if (is_debug) {
        cerr << "CACHED TERM FROM SHARED " << word << endl;
      }
    } else {
      entry->is_shared = false;
      entry->counts = counts;
      m_cache.lock(word);
      if (is_debug) {
        cerr << "CACHED TERM UPDATED " << word << endl;
      }
    }
  }
  if (is_debug) {
    cerr << "CACHE SIZE LOCKED: " << m_cache.lockedCount() << " TOTAL " << m_cache.size() << " MAX " << m_cache.maxSize() << endl;
  }
}

bool FrequencyDBImpl_cache::readWord(const string &word,
                                     WordData &counts)
{
  Ref<CacheEntry> entry;
  if (m_cache.get(word, entry)) {
    if (is_debug) {
      cerr << "READ CACHED TERM " << word << endl;
    }
    counts = entry->counts;
    return true;
  }

  bool is_shared = false;
  if (m_db->readWord(word, counts, is_shared)) {
    if (m_db->canCacheTerm(word)) {
      addWordData(word, false, is_shared, counts);
      if (is_debug) {
        cerr << "CACHED UNMODIFIED TERM " << word << endl;
      }
    }
    return true;
  }

  return false;
}

bool FrequencyDBImpl_cache::firstWord(string &word,
                                      WordData &counts)
{
  flush();
  clear();
  return m_db->firstWord(word, counts);
}

bool FrequencyDBImpl_cache::nextWord(string &word,
                                     WordData &counts)
{
  return m_db->nextWord(word, counts);
}

string FrequencyDBImpl_cache::getDatabaseType() const
{
  return m_db->getDatabaseType() + DATABASE_TYPE;
}

void FrequencyDBImpl_cache::sweepOutOldTerms(const CleanupManager &cleanman)
{
  flush();
  clear();
  m_db->sweepOutOldTerms(cleanman);
}


syntax highlighted by Code2HTML, v. 0.9.1