/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */
#include <pj/timer.h>
#include <pj/pool.h>
#include <pj/assert.h>
#include <pj/errno.h>
#include <pj/lock.h>

#include "os_symbian.h"


#define DEFAULT_MAX_TIMED_OUT_PER_POLL  (64)

// Maximum number of miliseconds that RTimer.At() supports
#define MAX_RTIMER_INTERVAL             2147

/* Absolute maximum number of timer entries */
#ifndef PJ_SYMBIAN_TIMER_MAX_COUNT
#  define PJ_SYMBIAN_TIMER_MAX_COUNT    65535
#endif

/* Get the number of free slots in the timer heap */
#define FREECNT(th)     (th->max_size - th->cur_size)

// Forward declaration
class CPjTimerEntry;

/**
 * The implementation of timer heap.
 */
struct pj_timer_heap_t
{
    /** Maximum size of the heap. */
    pj_size_t max_size;

    /** Current size of the heap. */
    pj_size_t cur_size;

    /** Array of timer entries. A scheduled timer will occupy one slot, and
     *  the slot number will be saved in entry->_timer_id
     */
    CPjTimerEntry **entries;
    
    /** Array of free slot indexes in the "entries" array */
    int *free_slots;
};

/**
 * Active object for each timer entry.
 */
class CPjTimerEntry : public CActive 
{
public:
    pj_timer_entry  *entry_;
    
    static CPjTimerEntry* NewL( pj_timer_heap_t *timer_heap,
                                pj_timer_entry *entry,
                                const pj_time_val *delay);
    
    ~CPjTimerEntry();
    
    virtual void RunL();
    virtual void DoCancel();

private:        
    pj_timer_heap_t *timer_heap_;
    RTimer           rtimer_;
    pj_uint32_t      interval_left_;
    
    CPjTimerEntry(pj_timer_heap_t *timer_heap, pj_timer_entry *entry);
    void ConstructL(const pj_time_val *delay);
    void Schedule();
};

//////////////////////////////////////////////////////////////////////////////
/*
 * Implementation.
 */

/* Grow timer heap to the specified size */
static pj_status_t realloc_timer_heap(pj_timer_heap_t *th, pj_size_t new_size)
{
    typedef CPjTimerEntry *entry_ptr;
    CPjTimerEntry **entries = NULL;
    int *free_slots = NULL;
    unsigned i, j;
 
    if (new_size > PJ_SYMBIAN_TIMER_MAX_COUNT) {
        /* Just some sanity limit */
        new_size = PJ_SYMBIAN_TIMER_MAX_COUNT;
        if (new_size <= th->max_size) {
            /* We've grown large enough */
            pj_assert(!"Too many timer heap entries");
            return PJ_ETOOMANY;
        }
    }
    
    /* Allocate entries, move entries from the old array if there is one */
    entries = new entry_ptr[new_size];
    if (th->entries) {
        pj_memcpy(entries, th->entries, th->max_size * sizeof(th->entries[0]));
    }
    /* Initialize the remaining new area */
    pj_bzero(&entries[th->max_size], 
            (new_size - th->max_size) * sizeof(th->entries[0]));
    
    /* Allocate free slots array */
    free_slots = new int[new_size];
    if (th->free_slots) {
        pj_memcpy(free_slots, th->free_slots, 
                  FREECNT(th) * sizeof(th->free_slots[0]));
    }
    /* Initialize the remaining new area */
    for (i=FREECNT(th), j=th->max_size; j<new_size; ++i, ++j) {
        free_slots[i] = j;
    }
    for ( ; i<new_size; ++i) {
        free_slots[i] = -1;
    }
    
    /* Apply */
    delete [] th->entries;
    th->entries = entries;
    th->max_size = new_size;
    delete [] th->free_slots;
    th->free_slots = free_slots;

    return PJ_SUCCESS;
}

/* Allocate and register an entry to timer heap for newly scheduled entry */
static pj_status_t add_entry(pj_timer_heap_t *th, CPjTimerEntry *entry)
{
    pj_status_t status;
    int slot;
    
    /* Check that there's still capacity left in the timer heap */
    if (FREECNT(th) < 1) {
        // Grow the timer heap twice the capacity
        status = realloc_timer_heap(th, th->max_size * 2);
        if (status != PJ_SUCCESS)
            return status;
    }
    
    /* Allocate one free slot. Use LIFO */
    slot = th->free_slots[FREECNT(th)-1];
    PJ_ASSERT_RETURN((slot >= 0) && (slot < (int)th->max_size) && 
                     (th->entries[slot]==NULL), PJ_EBUG);
    
    th->free_slots[FREECNT(th)-1] = -1;
    th->entries[slot] = entry;
    entry->entry_->_timer_id = slot;
    ++th->cur_size;
    
    return PJ_SUCCESS;
}

/* Free a slot when an entry's timer has elapsed or cancel */
static pj_status_t remove_entry(pj_timer_heap_t *th, CPjTimerEntry *entry)
{
    int slot = entry->entry_->_timer_id;
    
    PJ_ASSERT_RETURN(slot >= 0 && slot < (int)th->max_size, PJ_EBUG);
    PJ_ASSERT_RETURN(FREECNT(th) < th->max_size, PJ_EBUG);
    PJ_ASSERT_RETURN(th->entries[slot]==entry, PJ_EBUG);
    PJ_ASSERT_RETURN(th->free_slots[FREECNT(th)]==-1, PJ_EBUG);
    
    th->entries[slot] = NULL;
    th->free_slots[FREECNT(th)] = slot;
    entry->entry_->_timer_id = -1;
    --th->cur_size;
    
    return PJ_SUCCESS;
}


CPjTimerEntry::CPjTimerEntry(pj_timer_heap_t *timer_heap,
                             pj_timer_entry *entry)
: CActive(PJ_SYMBIAN_TIMER_PRIORITY), entry_(entry), timer_heap_(timer_heap), 
  interval_left_(0)
{
}

CPjTimerEntry::~CPjTimerEntry() 
{
    Cancel();
    rtimer_.Close();
}

void CPjTimerEntry::Schedule()
{
    pj_int32_t interval;
    
    if (interval_left_ > MAX_RTIMER_INTERVAL) {
        interval = MAX_RTIMER_INTERVAL;
    } else {
        interval = interval_left_;
    }
    
    interval_left_ -= interval;
    rtimer_.After(iStatus, interval * 1000);
    SetActive();
}

void CPjTimerEntry::ConstructL(const pj_time_val *delay) 
{
    rtimer_.CreateLocal();
    CActiveScheduler::Add(this);
    
    interval_left_ = PJ_TIME_VAL_MSEC(*delay);
    Schedule();
}

CPjTimerEntry* CPjTimerEntry::NewL(pj_timer_heap_t *timer_heap,
                                   pj_timer_entry *entry,
                                   const pj_time_val *delay) 
{
    CPjTimerEntry *self = new CPjTimerEntry(timer_heap, entry);
    CleanupStack::PushL(self);
    self->ConstructL(delay);
    CleanupStack::Pop(self);

    return self;
}

void CPjTimerEntry::RunL() 
{
    if (interval_left_ > 0) {
        Schedule();
        return;
    }
    
    remove_entry(timer_heap_, this);
    entry_->cb(timer_heap_, entry_);
    
    // Finger's crossed!
    delete this;
}

void CPjTimerEntry::DoCancel() 
{
    /* It's possible that _timer_id is -1, see schedule(). In this case,
     * the entry has not been added to the timer heap, so don't remove
     * it.
     */
    if (entry_ && entry_->_timer_id != -1)
        remove_entry(timer_heap_, this);
    
    rtimer_.Cancel();
}


//////////////////////////////////////////////////////////////////////////////


/*
 * Calculate memory size required to create a timer heap.
 */
PJ_DEF(pj_size_t) pj_timer_heap_mem_size(pj_size_t count)
{
    return /* size of the timer heap itself: */
           sizeof(pj_timer_heap_t) + 
           /* size of each entry: */
           (count+2) * (sizeof(void*)+sizeof(int)) +
           /* lock, pool etc: */
           132;
}

/*
 * Create a new timer heap.
 */
PJ_DEF(pj_status_t) pj_timer_heap_create( pj_pool_t *pool,
                                          pj_size_t size,
                                          pj_timer_heap_t **p_heap)
{
    pj_timer_heap_t *ht;
    pj_status_t status;

    PJ_ASSERT_RETURN(pool && p_heap, PJ_EINVAL);

    *p_heap = NULL;

    /* Allocate timer heap data structure from the pool */
    ht = PJ_POOL_ZALLOC_T(pool, pj_timer_heap_t);
    if (!ht)
        return PJ_ENOMEM;

    /* Allocate slots */
    status = realloc_timer_heap(ht, size);
    if (status != PJ_SUCCESS)
        return status;

    *p_heap = ht;
    return PJ_SUCCESS;
}

PJ_DEF(void) pj_timer_heap_destroy( pj_timer_heap_t *ht )
{
    /* Cancel and delete pending active objects */
    if (ht->entries) {
        unsigned i;
        for (i=0; i<ht->max_size; ++i) {
            if (ht->entries[i]) {
                ht->entries[i]->entry_ = NULL;
                ht->entries[i]->Cancel();
                delete ht->entries[i];
                ht->entries[i] = NULL;
            }
        }
    }
    
    delete [] ht->entries;
    delete [] ht->free_slots;
    
    ht->entries = NULL;
    ht->free_slots = NULL;
}

PJ_DEF(void) pj_timer_heap_set_lock(  pj_timer_heap_t *ht,
                                      pj_lock_t *lock,
                                      pj_bool_t auto_del )
{
    PJ_UNUSED_ARG(ht);
    if (auto_del)
        pj_lock_destroy(lock);
}


PJ_DEF(unsigned) pj_timer_heap_set_max_timed_out_per_poll(pj_timer_heap_t *ht,
                                                          unsigned count )
{
    /* Not applicable */
    PJ_UNUSED_ARG(count);
    return ht->max_size;
}

PJ_DEF(pj_timer_entry*) pj_timer_entry_init( pj_timer_entry *entry,
                                             int id,
                                             void *user_data,
                                             pj_timer_heap_callback *cb )
{
    pj_assert(entry && cb);

    entry->_timer_id = -1;
    entry->id = id;
    entry->user_data = user_data;
    entry->cb = cb;

    return entry;
}

PJ_DEF(pj_status_t) pj_timer_heap_schedule( pj_timer_heap_t *ht,
                                            pj_timer_entry *entry, 
                                            const pj_time_val *delay)
{
    CPjTimerEntry *timerObj;
    pj_status_t status;
    
    PJ_ASSERT_RETURN(ht && entry && delay, PJ_EINVAL);
    PJ_ASSERT_RETURN(entry->cb != NULL, PJ_EINVAL);

    /* Prevent same entry from being scheduled more than once */
    PJ_ASSERT_RETURN(entry->_timer_id < 1, PJ_EINVALIDOP);

    entry->_timer_id = -1;
    
    timerObj = CPjTimerEntry::NewL(ht, entry, delay);
    status = add_entry(ht, timerObj);
    if (status != PJ_SUCCESS) {
        timerObj->Cancel();
        delete timerObj;
        return status;
    }
    
    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pj_timer_heap_schedule_w_grp_lock(pj_timer_heap_t *ht,
                                                      pj_timer_entry *entry,
                                                      const pj_time_val *delay,
                                                      int id_val,
                                                      pj_grp_lock_t *grp_lock)
{
    pj_status_t status;
            
    PJ_UNUSED_ARG(grp_lock);

    status = pj_timer_heap_schedule(ht, entry, delay);
    
    if (status == PJ_SUCCESS)
        entry->id = id_val;
    
    return status;
}

PJ_DEF(int) pj_timer_heap_cancel( pj_timer_heap_t *ht,
                                  pj_timer_entry *entry)
{
    PJ_ASSERT_RETURN(ht && entry, PJ_EINVAL);
    
    if (entry->_timer_id >= 0 && entry->_timer_id < (int)ht->max_size) {
        CPjTimerEntry *timerObj = ht->entries[entry->_timer_id];
        if (timerObj) {
            timerObj->Cancel();
            delete timerObj;
            return 1;
        } else {
            return 0;
        }
    } else {
        return 0;
    }
}

PJ_DEF(int) pj_timer_heap_cancel_if_active(pj_timer_heap_t *ht,
                                           pj_timer_entry *entry,
                                           int id_val)
{
    int count = pj_timer_heap_cancel(ht, entry);
    if (count == 1)
        entry->id = id_val;
    
    return count;
}

PJ_DEF(unsigned) pj_timer_heap_poll( pj_timer_heap_t *ht, 
                                     pj_time_val *next_delay )
{
    /* Polling is not necessary on Symbian, since all async activities
     * are registered to active scheduler.
     */
    PJ_UNUSED_ARG(ht);
    if (next_delay) {
        next_delay->sec = 1;
        next_delay->msec = 0;
    }
    return 0;
}

PJ_DEF(pj_size_t) pj_timer_heap_count( pj_timer_heap_t *ht )
{
    PJ_ASSERT_RETURN(ht, 0);

    return ht->cur_size;
}

PJ_DEF(pj_status_t) pj_timer_heap_earliest_time( pj_timer_heap_t * ht,
                                                 pj_time_val *timeval)
{
    /* We don't support this! */
    PJ_UNUSED_ARG(ht);
    
    timeval->sec = 1;
    timeval->msec = 0;
    
    return PJ_SUCCESS;
}

