/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <assert.h>
#include "os/mynewt.h"
#include "button/button.h"

#define _BUTTON_FSM_INIT                        0
#define _BUTTON_FSM_PRESSED                     1
#define _BUTTON_FSM_WAIT_DBLPRESSED             2
#define _BUTTON_FSM_DBLPRESSED                  3
#define _BUTTON_FSM_HOLD_OR_REPEAT              4

#define _BUTTON_RELEASED                        0
#define _BUTTON_PRESSED                         1
#define _BUTTON_TIMEOUT                         2

struct button_event {
    struct os_event os_event;
    uint8_t type;
    uint8_t flags;
};

static struct os_eventq *button_internal_evq = NULL;
static struct os_eventq *button_callback_default_evq = NULL;

#define BUTTON_POST_STATE_EVENT(button, flags)  \
    button_post_event(button, BUTTON_STATE_CHANGED, flags)

#define BUTTON_POST_ACTION_EVENT(button, flags) \
    button_post_event(button, BUTTON_ACTION, flags)

static struct button_event button_event[MYNEWT_VAL(BUTTON_EVENT_MAX)] = { 0 };
static button_callback_t button_callback = NULL;

static struct button_event *
button_alloc_event(void)
{
    int i;

    for (i = 0 ; i < MYNEWT_VAL(BUTTON_EVENT_MAX) ; i++) {
        if (!OS_EVENT_QUEUED(&button_event[i].os_event)) {
            return &button_event[i];
        }
    }

    return NULL;
}

/**
 * Process generated event, using the user defined callback.
 *
 * @param ev generated event
 */
static void
button_event_handler(struct os_event *ev)
{
    struct button_event *b_ev;
    button_t *button;

    b_ev   = (struct button_event *)ev;
    button = (button_t *)ev->ev_arg;
    button_callback(button->id, b_ev->type, b_ev->flags);
}

/**
 * Post an event to the default queue.
 * 
 * @param button        button generating the event
 * @param type          type of event (state or action)
 * @param flags         event description (BUTTON_FLG_*)
 *
 * @return              -1 unable to post event (not enough buffer)
 *                       0 event posted
 *                       1 event not posted due to filtering
 */
static int
button_post_event(button_t *button, uint8_t type, uint8_t flags)
{
    struct button_event *evt;

#if MYNEWT_VAL(BUTTON_USE_FILTERING)
    if (button->filter.enabled) {
        uint8_t filter = 0xff;
        switch(type) {
#if MYNEWT_VAL(BUTTON_EMIT_STATE_CHANGED)
        case BUTTON_STATE_CHANGED:
            filter = button->filter.state;
            break;
#endif
#if MYNEWT_VAL(BUTTON_EMIT_ACTION)
        case BUTTON_ACTION:
            filter = button->filter.action;
            break;
#endif
        }
        if (~filter & flags) {
            return 1;
        }
    }
#endif

    evt = button_alloc_event();
    if (evt) {  
        evt->os_event.ev_cb  = button_event_handler;
        evt->os_event.ev_arg = button;
        evt->type            = type;
        evt->flags           = flags;
        
#if MYNEWT_VAL(BUTTON_USE_PER_BUTTON_CALLBACK_EVENTQ) > 0
        os_eventq_put(button->eventq,              &evt->os_event);
#else
        os_eventq_put(button_callback_default_evq, &evt->os_event);
#endif
        return 0;
    } else {
        button->state |= BUTTON_FLG_MISSED;
        return -1;
    }
}

#if MYNEWT_VAL(BUTTON_USE_EMULATION)
/**
 * Perform button emulation.
 *
 * @param button        the button to emulate
 */
static void
button_emulating(button_t *button)
{
    bool pressed  = true;
    bool released = true;
    
    for (button_t **from = button->emulated ; *from ; from++) {
        if ((*from)->state & BUTTON_FLG_PRESSED) {
            released = false;
        } else {
            pressed  = false;
        }
    }
    
    if (pressed != !released)
        return;

    if (pressed || (released && button->emulating)) {
        button->emulating = pressed;
        button_set_low_level_state(button, pressed);
    }
}

/**
 * Process children of a button. Used for emulating button.
 *
 * @param button        the button to look for children
 */
static void
button_process_children(button_t *button)
{
    if (button->children == NULL)
        return;

    for (button_t **child = button->children ; *child ; child++)
        if ((*child)->emulated)
            button_emulating(*child);
}

/**
 * Check if one of the children is using the action, and so
 * we shouldn't generate one for ourself.
 *
 * @param button        the button to look for children
 */
static bool
button_stealed_action(button_t *button)
{
    if (button->children == NULL)
        return false;

    for (button_t **child = button->children ; *child ; child++)
        if ((*child)->emulated && (*child)->emulating)
            return true;

    return false;
}
#endif

/**
 * Use low level action, to generated button state and action.
 * This one deal with simple state/action, where only pressed/released/click
 * are considered.
 *
 * @param button        button to process
 * @param action        low level action associated to the button
 *                      (_BUTTON_PRESSED, _BUTTON_RELEASED)
 */
static void
button_exec_simple(button_t *button, int action)
{
    assert(action != _BUTTON_TIMEOUT);

    switch(action) {
    case _BUTTON_PRESSED:
        button->state  =  BUTTON_FLG_PRESSED;
        break;
    case _BUTTON_RELEASED:
#if MYNEWT_VAL(BUTTON_EMIT_ACTION)
#if MYNEWT_VAL(BUTTON_USE_EMULATION)
        if (!button_stealed_action(button))
#endif
            BUTTON_POST_ACTION_EVENT(button, button->state);
#endif
        button->state &= ~BUTTON_FLG_PRESSED;
        break;
    }

#if MYNEWT_VAL(BUTTON_EMIT_STATE_CHANGED)
    BUTTON_POST_STATE_EVENT(button, button->state);
#endif
    
#if MYNEWT_VAL(BUTTON_USE_EMULATION)
    button_process_children(button);
#endif
}

#if MYNEWT_VAL(BUTTON_USE_DOUBLE) || \
    MYNEWT_VAL(BUTTON_USE_LONG  ) || \
    MYNEWT_VAL(BUTTON_USE_REPEAT)
/**
 * Use low level action, to generated button state and action.
 * This one deal with complexe action, such as generating double click, 
 * long click, repeating action, ...
 *
 * @param button        the button to process
 * @param action        low level action associated to the button
 *                      (_BUTTON_PRESSED, _BUTTON_RELEASED, _BUTTON_TIMEOUT)
 */
static void
button_exec_fsm(button_t *button, int action)
{
    struct os_callout *callout;

    callout = &button->callout;
    
    switch (button->fsm_state) {
    case _BUTTON_FSM_INIT:
        switch(action) {
        case _BUTTON_PRESSED:
            goto do_pressed;
        case _BUTTON_RELEASED:
            goto do_nothing;
        case _BUTTON_TIMEOUT:
            goto do_assert;
        }
        break;

    case _BUTTON_FSM_PRESSED:
        switch(action) {
        case _BUTTON_PRESSED:
            goto do_nothing;
        case _BUTTON_RELEASED:
#if MYNEWT_VAL(BUTTON_USE_DOUBLE)
            if (button->mode & BUTTON_FLG_DOUBLED) {
                goto to_wait_dblpressed;
            } else {
#endif
                goto do_release;
#if MYNEWT_VAL(BUTTON_USE_DOUBLE)
            }
#endif
        case _BUTTON_TIMEOUT:
#if MYNEWT_VAL(BUTTON_USE_LONG)
            if (button->mode & BUTTON_FLG_LONG) {
                goto do_longpress;
#if MYNEWT_VAL(BUTTON_USE_REPEAT)
            } else if (button->mode & BUTTON_FLG_REPEATING) {
                goto do_repeat;
#endif
            } else {
                goto do_assert;
            }
#elif MYNEWT_VAL(BUTTON_USE_REPEAT)
            goto do_repeat;
#else
            goto do_assert;
#endif
        }
        break;
        
#if MYNEWT_VAL(BUTTON_USE_DOUBLE)
    case _BUTTON_FSM_WAIT_DBLPRESSED:
        switch(action) {
        case _BUTTON_TIMEOUT:
            goto do_release;
        case _BUTTON_PRESSED:
            goto do_dblpressed;
        case _BUTTON_RELEASED:
            goto do_nothing;
        }
        break;

    case _BUTTON_FSM_DBLPRESSED:
        switch(action) {
        case _BUTTON_TIMEOUT :
#if MYNEWT_VAL(BUTTON_USE_LONG)
            if (button->mode & BUTTON_FLG_LONG) {
                goto do_longpress;
#if MYNEWT_VAL(BUTTON_USE_REPEAT)
            } else if (button->mode & BUTTON_FLG_REPEATING) {
                goto do_repeat;
#endif
            } else {
                goto do_assert;
            }
#elif MYNEWT_VAL(BUTTON_USE_REPEAT)
            goto do_repeat;
#else
            goto do_assert;
#endif
        case _BUTTON_RELEASED: goto do_release;
        case _BUTTON_PRESSED : goto do_nothing;
        } 
        break;
#endif

#if MYNEWT_VAL(BUTTON_USE_LONG  ) || \
    MYNEWT_VAL(BUTTON_USE_REPEAT)
    case _BUTTON_FSM_HOLD_OR_REPEAT:
        switch(action) {
        case _BUTTON_TIMEOUT:
#if MYNEWT_VAL(BUTTON_USE_REPEAT)
            if (button->mode & BUTTON_FLG_REPEATING) {
                goto do_repeat;
            } else {
#endif
                goto do_assert;
#if MYNEWT_VAL(BUTTON_USE_REPEAT)
            }
#endif
        case _BUTTON_RELEASED:
            goto do_release;
        case _BUTTON_PRESSED:
            goto do_nothing;
        }
        break;
#endif
    }

 do_assert:
    assert(0);
    
 do_nothing:
    return;
    
 do_pressed:
#if MYNEWT_VAL(BUTTON_USE_LONG)
    if (button->mode & BUTTON_FLG_LONG) {
        os_callout_reset(callout, os_time_ms_to_ticks32(MYNEWT_VAL(BUTTON_LONGHOLD_TIME_MS)));
#if MYNEWT_VAL(BUTTON_USE_REPEAT)
    } else if (button->mode & BUTTON_FLG_REPEATING) {
        os_callout_reset(callout, os_time_ms_to_ticks32(MYNEWT_VAL(BUTTON_REPEAT_FIRST_TIME_MS)));
#endif
    }
#elif MYNEWT_VAL(BUTTON_USE_REPEAT)
    os_callout_reset(callout, os_time_ms_to_ticks32(MYNEWT_VAL(BUTTON_REPEAT_FIRST_TIME_MS)));
#endif
    button->state = BUTTON_FLG_PRESSED;
#if MYNEWT_VAL(BUTTON_EMIT_STATE_CHANGED)
    BUTTON_POST_STATE_EVENT(button, button->state);
#endif
#if MYNEWT_VAL(BUTTON_USE_EMULATION)
    button_process_children(button);
#endif
    button->fsm_state = _BUTTON_FSM_PRESSED;
    return;
    
#if MYNEWT_VAL(BUTTON_USE_DOUBLE)
 to_wait_dblpressed:
    os_callout_reset(callout, os_time_ms_to_ticks32(MYNEWT_VAL(BUTTON_DBLCLICK_TIMEOUT_MS)));
    button->fsm_state = _BUTTON_FSM_WAIT_DBLPRESSED;
    return;
    
 do_dblpressed:
#if MYNEWT_VAL(BUTTON_USE_LONG)
    if (button->mode & BUTTON_FLG_LONG) {
        os_callout_reset(callout, os_time_ms_to_ticks32(MYNEWT_VAL(BUTTON_LONGHOLD_TIME_MS)));
#if MYNEWT_VAL(BUTTON_USE_REPEAT)
    } else if (button->mode & BUTTON_FLG_REPEATING) {
        os_callout_reset(callout, os_time_ms_to_ticks32(MYNEWT_VAL(BUTTON_REPEAT_FIRST_TIME_MS)));
#endif
    } else {
        os_callout_stop(callout);
    }   
#elif MYNEWT_VAL(BUTTON_USE_REPEAT)
    os_callout_reset(callout, os_time_ms_to_ticks32(MYNEWT_VAL(BUTTON_REPEAT_FIRST_TIME_MS)));
#else
    os_callout_stop(callout);
#endif
    button->state |= BUTTON_FLG_DOUBLED;
#if MYNEWT_VAL(BUTTON_EMIT_STATE_CHANGED)
    BUTTON_POST_STATE_EVENT(button, button->state);
#endif
    button->fsm_state = _BUTTON_FSM_DBLPRESSED;
    return;
#endif

#if MYNEWT_VAL(BUTTON_USE_LONG)
 do_longpress:
#if MYNEWT_VAL(BUTTON_USE_REPEAT)
    if (button->mode & BUTTON_FLG_REPEATING)
        os_callout_reset(callout, os_time_ms_to_ticks32(MYNEWT_VAL(BUTTON_REPEAT_FIRST_TIME_MS)));
#endif
    button->state |= BUTTON_FLG_LONG;
#if MYNEWT_VAL(BUTTON_EMIT_STATE_CHANGED)
    BUTTON_POST_STATE_EVENT(button, button->state);
#endif
    button->fsm_state = _BUTTON_FSM_HOLD_OR_REPEAT;
    return;
#endif
    
#if MYNEWT_VAL(BUTTON_USE_REPEAT)
 do_repeat:
    os_callout_reset(callout, os_time_ms_to_ticks32(MYNEWT_VAL(BUTTON_REPEAT_TIME_MS)));
    
#if MYNEWT_VAL(BUTTON_EMIT_ACTION)
#if MYNEWT_VAL(BUTTON_USE_EMULATION)
    if (!button_stealed_action(button))
#endif
        BUTTON_POST_ACTION_EVENT(button, button->state);
#endif
    
    if (!(button->state & BUTTON_FLG_REPEATING)) {
        button->state |= BUTTON_FLG_REPEATING;
#if MYNEWT_VAL(BUTTON_EMIT_STATE_CHANGED)
        BUTTON_POST_STATE_EVENT(button, button->state);
#endif
    }
    return;
#endif
    
 do_release:
    os_callout_stop(callout);

#if MYNEWT_VAL(BUTTON_EMIT_ACTION)
#if MYNEWT_VAL(BUTTON_USE_EMULATION)
    if (!button_stealed_action(button)) {
#endif
#if MYNEWT_VAL(BUTTON_USE_REPEAT)
        if (!(button->state & BUTTON_FLG_REPEATING))
#endif
            BUTTON_POST_ACTION_EVENT(button, button->state);
#if MYNEWT_VAL(BUTTON_USE_EMULATION)
    }
#endif
#endif
    button->state &= ~BUTTON_FLG_PRESSED;
#if MYNEWT_VAL(BUTTON_EMIT_STATE_CHANGED)
    BUTTON_POST_STATE_EVENT(button, button->state);
#endif
#if MYNEWT_VAL(BUTTON_USE_EMULATION)
    button_process_children(button);
#endif
    button->fsm_state = _BUTTON_FSM_INIT;
    return;
}


/**
 * Callout function used for processing timeout.
 * Used for processing complexe action.
 *
 * @param ev            event from callout
 */
static void
button_fsm_callout(struct os_event *ev)
{
    button_t *button;

    button = (button_t *)ev->ev_arg;
    button_exec_fsm(button, _BUTTON_TIMEOUT);
}
#endif

void
button_init(button_t *buttons, unsigned int count, button_callback_t cb)
{
#if MYNEWT_VAL(BUTTON_USE_DOUBLE) ||                            \
    MYNEWT_VAL(BUTTON_USE_LONG  ) ||                            \
    MYNEWT_VAL(BUTTON_USE_REPEAT) ||                            \
    MYNEWT_VAL(BUTTON_USE_PER_BUTTON_CALLBACK_EVENTQ) > 0
    unsigned int i;
#endif
    
    if (button_internal_evq == NULL) {
        button_internal_evq = os_eventq_dflt_get();
    }
    if (button_callback_default_evq == NULL) {
        button_callback_default_evq = os_eventq_dflt_get();
    }
    
    button_callback = cb;

#if MYNEWT_VAL(BUTTON_USE_DOUBLE) || \
    MYNEWT_VAL(BUTTON_USE_LONG  ) || \
    MYNEWT_VAL(BUTTON_USE_REPEAT)
    for (i = 0 ; i < count ; i++) {
        button_t *button = &buttons[i];
        os_callout_init(&button->callout, button_internal_evq,
                        button_fsm_callout, button);
    }
#endif

#if MYNEWT_VAL(BUTTON_USE_PER_BUTTON_CALLBACK_EVENTQ) > 0
    for (i = 0 ; i < count ; i++) {
        button_t *button = &buttons[i];
        if (button->eventq == NULL) {
            button->eventq = button_callback_default_evq;
        }
    }
#endif
}

void
button_set_low_level_state(button_t *button, bool pressed)
{
    int action;

    action = pressed ? _BUTTON_PRESSED : _BUTTON_RELEASED;
#if MYNEWT_VAL(BUTTON_USE_DOUBLE) || \
    MYNEWT_VAL(BUTTON_USE_LONG  ) || \
    MYNEWT_VAL(BUTTON_USE_REPEAT)
    if (button->mode & ~(BUTTON_FLG_PRESSED)) {
        button_exec_fsm(button, action);
    } else {
        button_exec_simple(button, action);
    }
#else
    button_exec_simple(button, action);
#endif
}

void
button_internal_evq_set(struct os_eventq *evq)
{
    button_internal_evq = evq;
}

void
button_callback_default_evq_set(struct os_eventq *evq)
{
    button_callback_default_evq = evq;
}
