/* timeoutstore.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2009 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "timeoutstore.hh"

/*
 * this is a very simple interval based timeout store.
 * it will just return the gcd of all timeouts to be sure
 * to not miss one.
 * TODO: a better implementation would store each timeout start time
 * and interval and postpone everytime a timeout is expired
 */

TimeoutStore::TimeoutStore() : m_timeout_gcd( 0 )
{
    gettimeofday( &m_time_reference, NULL );
}

TimeoutStore::~TimeoutStore()
{
}

void TimeoutStore::addTimeout( loff_t timeout_ms )
{
    m_lock.lock();

    if ( m_timeout_store.count( timeout_ms ) > 0 ) {
        m_timeout_store[ timeout_ms ] += 1;
    } else {
        m_timeout_store[ timeout_ms ] = 1;
    }

    update();

    m_lock.unlock();
}

void TimeoutStore::clearTimeout( loff_t timeout_ms )
{
    m_lock.lock();

    if ( m_timeout_store.count( timeout_ms ) > 0 ) {
        if ( m_timeout_store[ timeout_ms ] > 1 ) {
            m_timeout_store[ timeout_ms ] -= 1;
        } else {
            m_timeout_store.erase( timeout_ms );
        }

        update();
    }

    m_lock.unlock();
}

loff_t TimeoutStore::getNextTimeout()
{
    m_lock.lock();

    loff_t res = 0;

    if ( m_timeout_gcd > 0 ) {
        struct timeval now;
        loff_t start_ms, now_ms, diff;

        gettimeofday( &now, NULL );

        start_ms = m_time_reference.tv_sec * 1000 +
            m_time_reference.tv_usec / 1000;
        now_ms = now.tv_sec * 1000 +
            now.tv_usec / 1000;

        if ( now_ms < start_ms ) {
            // time drift?
            // just set the start time again
            start_ms = now_ms;
            m_time_reference = now;
        }

        diff = now_ms - start_ms;
        diff /= m_timeout_gcd;
        diff += 1;
        diff *= m_timeout_gcd;

        res = ( start_ms + diff ) - now_ms;

        // check some limits
        if ( res < 1 ) res = 1;
        else if ( res > ( 60 * 1000 ) ) res = 60000;
    }

    m_lock.unlock();
    return res;
}

loff_t TimeoutStore::gcd( loff_t v1, loff_t v2 )
{
    // euclid
    while ( v1 ) {
        loff_t v1_new = v2 % v1;
        v2 = v1;
        v1 = v1_new;
    }
    return v2;
}

void TimeoutStore::update()
{
    std::map< loff_t, int >::const_iterator it1;
    loff_t v1;

    it1 = m_timeout_store.begin();
    if ( it1 == m_timeout_store.end() ) {
        m_timeout_gcd = 0;
    } else {
        v1 = it1->first;
        it1++;
        for ( ; it1 != m_timeout_store.end(); it1++ ) {
            v1 = gcd( v1, it1->first );
        }
        m_timeout_gcd = v1;
    }
}
