
#include "event_log.h"

#include <string.h>
#include "ble.h"
#include "nrf_error.h"
#include "pstorage.h"
#include "app_util_platform.h"

#include "watchdog.h"
#include "bluetooth_control.h"
#include "debug_util.h"
#include "configuration.h"

#define RET_WRONG_INDEX     -2
#define RET_NOT_AVAILABLE   -1
#define RET_SUCCESS         0

typedef struct {
    uint8_t enable;
    uint16_t start;
    uint16_t index_of_first;
} app_request_t;

// Fixed size circular queue for events.
// See comment in `event_log_add` for further information.
static const uint8_t MAX_EVENT_QUEUE = 10;
static const uint8_t MAX_RETRIES = 4;
static event_log_entry_t m_event_queue[MAX_EVENT_QUEUE];
static uint8_t m_event_idx = 0;

static app_request_t m_app_req;
static event_log_t m_event_log;

// Callback for pstorage operations.
static void event_log_pstorage_cb_handler(pstorage_handle_t * handle, uint8_t op_code,
    uint32_t result, uint8_t * p_data, uint32_t data_len)
{
    if (result != NRF_SUCCESS) {
        debug_printf("CRITICAL: pstorage error\n\r");
    }
}

// Write the global instance of event attributes to the flash.
static void update_attrs()
{
    uint32_t retval;
    pstorage_handle_t dest_block;
    retval = pstorage_block_identifier_get(&m_event_log.attrs_handle, 0, &dest_block);
    
    if (retval == NRF_SUCCESS) {
        pstorage_update(&dest_block, (uint8_t *) &m_event_log.attrs,
            sizeof(m_event_log.attrs), sizeof(uint32_t));
    }
}

// Clear the flash and store default event attributes.
static uint32_t store_default_log_attrs()
{
    uint32_t retval;
    uint32_t magic_no;
    pstorage_handle_t dest_block;

    m_event_log.attrs.watchdog_counter = 0;
    m_event_log.attrs.power_cycle_counter = 0;
    m_event_log.attrs.event_id_counter = 0;
    m_event_log.attrs.buffer_index = 0;
    
    retval = pstorage_block_identifier_get(&m_event_log.attrs_handle, 0, &dest_block);
    if (retval != NRF_SUCCESS) {
        return retval;
    }

    retval = pstorage_clear(&m_event_log.attrs_handle,(sizeof (m_event_log.attrs) +
                            sizeof (uint32_t)));
    if (retval != NRF_SUCCESS) {
        return retval;
    }

    magic_no = EVENT_ATTR_MAGIC_NUMBER;
    retval = pstorage_store(&dest_block, (uint8_t *) &magic_no,
                            sizeof(uint32_t), 0);
    if (retval != NRF_SUCCESS) {
        return retval;
    }

    retval = pstorage_store(&dest_block, (uint8_t *) &m_event_log.attrs,
                            sizeof(m_event_log.attrs), sizeof(uint32_t));
    if (retval != NRF_SUCCESS) {
        return retval;
    }

    debug_printf("INFO: Store event attributes magic no.: %u\n\r"
                "INFO: Default event attributes stored.\n\r", magic_no);
    
    retval = pstorage_block_identifier_get(&m_event_log.entries_handle, 0, &dest_block);
    if (retval != NRF_SUCCESS) {
        return retval;
    }

    retval = pstorage_clear(&m_event_log.entries_handle,
        (sizeof (event_log_entry_t) * EVENT_MAX_ENTRIES));
    if (retval != NRF_SUCCESS) {
        return retval;
    }
    
    return NRF_SUCCESS;
}

// Retrieve an event directly from flash
static uint32_t get_entry_at(uint32_t index, event_log_entry_t * entry) 
{
    uint32_t retval;
    pstorage_handle_t source_block;
    
    retval = pstorage_block_identifier_get(&m_event_log.entries_handle, index, &source_block);
    if (retval != NRF_SUCCESS) {
        return retval;
    }

    retval = pstorage_load((uint8_t *) entry, &source_block, sizeof(event_log_entry_t), 0);
    if (retval != NRF_SUCCESS) {
        return retval;
    }
    
    return NRF_SUCCESS;
}


// Initialize the flash storage for events and event attributes.
static uint32_t init_storage()
{
    uint32_t retval;
    pstorage_module_param_t param;

    // Register flash block for event log attributes 
    // (indices, power cycle counter etc)
    param.block_size = sizeof(uint32_t) + sizeof(m_event_log.attrs);
    param.block_count = 1;
    param.cb = event_log_pstorage_cb_handler;

    retval = pstorage_register(&param, &m_event_log.attrs_handle);
    if (retval != NRF_SUCCESS) {
        return retval;
    }
    
    // Register flash block for event entries
    param.block_size = sizeof(event_log_entry_t);
    param.block_count = EVENT_MAX_ENTRIES;
    
    retval = pstorage_register(&param, &m_event_log.entries_handle);
    if (retval != NRF_SUCCESS) {
        return retval;
    }
    
    pstorage_handle_t source_block;
    retval = pstorage_block_identifier_get(&m_event_log.attrs_handle, 0, &source_block);
    if (retval != NRF_SUCCESS) {
        return retval;
    }

    // Read magic number (combination of a constant and the magic number
    // of the event log entry format. The the number stored in flash and 
    // the one calculated here doesn't match, this it either first power up,
    // or the event log format has changed.
    
    uint32_t magic_no;
    retval = pstorage_load((uint8_t *) &magic_no, &source_block, sizeof(uint32_t), 0);
    if (retval != NRF_SUCCESS) {
       return retval;
    }

    if (magic_no != EVENT_ATTR_MAGIC_NUMBER) {
        retval = store_default_log_attrs();
        if (retval != NRF_SUCCESS) {
            return retval;
        }
    } else {
        retval = pstorage_load((uint8_t *) &m_event_log.attrs, &source_block,
            sizeof(m_event_log.attrs), sizeof(uint32_t));
        if (retval != NRF_SUCCESS) {
            return retval;
        }
    }
    
    return NRF_SUCCESS;
}

// Returns id of the next event (top of flash)
static uint16_t get_next_id()
{
    return m_event_log.attrs.event_id_counter;
}

// Store the given event in flash. The page written to is cleared
// before storing (update operation).
static uint32_t store_entry(const event_log_entry_t * entry)
{
    uint32_t retval;
    pstorage_handle_t dest_block;

    //uint16_t index = get_index_from_id(entry->event_id);
    uint16_t index = m_event_log.attrs.buffer_index;
    
    m_event_log.attrs.buffer_index++;
    if (m_event_log.attrs.buffer_index >= EVENT_MAX_ENTRIES) {
        m_event_log.attrs.buffer_index = 0;
    }
    
    retval = pstorage_block_identifier_get(&m_event_log.entries_handle, index, &dest_block);
    if (retval != NRF_SUCCESS) {
        return retval;
    }
    
    retval = pstorage_update(&dest_block, (uint8_t *) entry, sizeof(*entry), 0);
    if (retval != NRF_SUCCESS) {
        return retval;
    } 
    
    if (m_event_log.verbose) {
        debug_printf("INFO: Event %d @ %d (T: %d, O: %d, B:%d)\n\r", entry->event_id, index, 
            entry->type, entry->origin, entry->brightness);
    }
   
    update_attrs();
    return NRF_SUCCESS;
}

/**
 * Calculate the oldest available id.
 */
static uint32_t get_oldest_id() 
{
    int32_t id = m_event_log.attrs.event_id_counter - EVENT_MAX_ENTRIES;
    if (id < 0) {
        return 0;
    }
    return id;
}

/**
 * Check if the given event log entry is valid.
 */
static bool is_valid(uint32_t expected_id, event_log_entry_t* entry) 
{
    return (entry->magic_number != 0x00 && 
            entry->magic_number != 0x07 && 
            entry->event_id == expected_id);
}

/**
 * Get an event log entry by id.
 * 
 * \return RET_WRONG_INDEX, RET_NOT_AVAILABLE, or RET_SUCCESS
 */
static int32_t get_by_id(uint32_t id, event_log_entry_t* entry)
{
    if (id >= m_event_log.attrs.event_id_counter) {
        return RET_WRONG_INDEX;
    }
    
    uint32_t idx = 0;
    uint32_t age = m_event_log.attrs.event_id_counter - id;
    
    if (age <= EVENT_MAX_ENTRIES) {
        idx = id % EVENT_MAX_ENTRIES;
        if (get_entry_at(idx, entry) == NRF_SUCCESS) {
            // Sanity check the data read from the flash
            // (check magic number, and expected id)
            if (is_valid(id, entry)) {
                return RET_SUCCESS;
            }
        }
    }
    
    return RET_NOT_AVAILABLE;
}

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

uint32_t event_log_init(uint8_t reset_by_wdt)
{
    uint32_t retval;
    
    m_app_req.enable = 0;
    m_event_log.verbose = CONFIG_EVENT_LOG_VERBOSE;
    
    retval = event_clock_init();
    if (retval != NRF_SUCCESS) {
        return retval;
    }

    retval = init_storage();
    if (retval != NRF_SUCCESS) {
        return retval;
    }
    
    // Increment counters
    m_event_log.attrs.power_cycle_counter++;
    if (reset_by_wdt) {
        m_event_log.attrs.watchdog_counter++;
    }
    
    // Remember the ID of the first event in the current power cycle
    // Used when transmitting events to flag them as old or from current
    // power cycle.
    m_event_log.power_cycle_start_event = m_event_log.attrs.event_id_counter;
    
    update_attrs();
    
    // XXX Only print if verbose?
    debug_printf("INFO: Current power cycle: %u\r\n"
                "INFO: WDT Resets: %u\n\r"
                "INFO: Next Event ID: %u\n\r"
                "INFO: Next Buffer Index: %u\n\r",
                m_event_log.attrs.power_cycle_counter,
                m_event_log.attrs.watchdog_counter,
                m_event_log.power_cycle_start_event,
                m_event_log.attrs.buffer_index);
 
    return NRF_SUCCESS;
}

void event_log_add(uint8_t origin, uint8_t type, uint8_t brightness, 
    uint8_t slave_connected) 
{   
    // The pstorage API requires the data to be written to the flash,
    // to be available all the time while writing it (i.e. the pstorage
    // API itself does not make a copy of the data).
    //
    // As we're are uncertain when the previous write to the flash has
    // finished, we *just* store the event information that has to be
    // written, in another static variable (another index in the queue
    // array).
    
    event_log_entry_t* entry = &m_event_queue[m_event_idx++];
    if (m_event_idx == MAX_EVENT_QUEUE) {
        m_event_idx = 0;
    }
    
    entry->magic_number = EVENT_MAGIC_NUMBER;
    entry->timestamp = event_clock_get();
    entry->event_id = m_event_log.attrs.event_id_counter++;
    
    entry->brightness = brightness;
    entry->slave_connected = slave_connected;
    entry->origin = origin;
    entry->type = type;
    
    // XXX How should be react if storing an event actually fails?
    // One option is to ignore it, and possibly decrement the global 
    // event counter - in that way, the client (smart phone) will never
    // notice it.
    store_entry(entry);
}

void event_log_request_events_since(uint16_t id)
{
    uint32_t oldest_id = get_oldest_id();
    
    m_app_req.start = (id < oldest_id) ? oldest_id : id;
    m_app_req.enable = 1;
    
    if (m_event_log.verbose) {
        debug_printf("INFO: Request events %d (actual %d)\n\r", 
            id, m_app_req.start);
    }
}

void event_log_stop_sending()
{
    m_app_req.enable = 0;
}

uint8_t event_log_has_pending_events() 
{
    return ( m_app_req.enable && m_app_req.start <= get_next_id() );
}

void event_log_send_pending_events()
{
    uint32_t retval;
    static event_log_entry_t entry;
    static uint8_t retry_count = 0;
    
    // Skip the time if the pstorage API is busy.
    pstorage_access_status_get(&retval);
    if (retval > 0) {
        return;
    } 
    
    int32_t ret = get_by_id(m_app_req.start, &entry);
    if (ret == RET_SUCCESS) {
        entry.current_cycle = (entry.event_id >= m_event_log.power_cycle_start_event);
        // Try to send event. We may be out of the transmit buffers, so this operation
        // may run for a couple of times before succedding.
        if (bluetooth_send_event(&entry) == NRF_SUCCESS) {
            if (m_event_log.verbose) {
                debug_printf("INFO: Send event %d (%d)\n\r", 
                    entry.event_id, entry.current_cycle);
            }
            retry_count = 0;
            m_app_req.start++;
        } else {
            // POSSIBLE ERROR!
            // This error is probably because no transmit buffers
            // are available - so just try again next time.
        }
    } else if (ret == RET_NOT_AVAILABLE) {
        // The event is not available, possible because it hasn't been
        // written to the flash yet? Retry up to `MAX_RETRIES` times.
        if (++retry_count > MAX_RETRIES) {
            retry_count = 0;
            
            // Event was not available - just skip it and hope
            // the next is available
            m_app_req.start++;
        } 
    } else {
       // Wrong index
    }
}


