#include "app_curtain_motor.h"

#include "access.h"
#include "access_config.h"

#include "utils.h"
#include "mesh_app_utils.h"
#include "sdk_config.h"
#include "generic_onoff_mc.h"
#include "generic_level_mc.h"
#include "generic_dtt_mc.h"
#if (SCENE_SETUP_SERVER_INSTANCES_MAX > 0)
#include "scene_mc.h"
#endif
#include "list.h"
#include "bearer_event.h"

#include "log.h"



static list_node_t *mp_app_curtain_motor_head;
static bearer_event_flag_t m_transition_abort_flag;


/*** Forward declaration */

static void generic_onoff_state_get_cb(const generic_onoff_server_t *p_self,
                                       const access_message_rx_meta_t *p_meta,
                                       generic_onoff_status_params_t *p_out);
static void generic_onoff_state_set_cb(const generic_onoff_server_t *p_self,
                                       const access_message_rx_meta_t *p_meta,
                                       const generic_onoff_set_params_t *p_in,
                                       const model_transition_t *p_in_transition,
                                       generic_onoff_status_params_t *p_out);

const generic_onoff_server_callbacks_t m_onoff_srv_cbs = {
    .onoff_cbs.set_cb = generic_onoff_state_set_cb,
    .onoff_cbs.get_cb = generic_onoff_state_get_cb
};


static void generic_level_state_get_cb(const generic_level_server_t *p_self,
                                       const access_message_rx_meta_t *p_meta,
                                       generic_level_status_params_t *p_out);
static void generic_level_state_set_cb(const generic_level_server_t *p_self,
                                       const access_message_rx_meta_t *p_meta,
                                       const generic_level_set_params_t *p_in,
                                       const model_transition_t *p_in_transition,
                                       generic_level_status_params_t *p_out);

static void generic_level_state_delta_set_cb(const generic_level_server_t *p_self,
                                             const access_message_rx_meta_t *p_meta,
                                             const generic_level_delta_set_params_t *p_in,
                                             const model_transition_t *p_in_transition,
                                             generic_level_status_params_t *p_out);

static void generic_level_state_move_set_cb(const generic_level_server_t *p_self,
                                            const access_message_rx_meta_t *p_meta,
                                            const generic_level_move_set_params_t *p_in,
                                            const model_transition_t *p_in_transition,
                                            generic_level_status_params_t *p_out);

static const generic_level_server_callbacks_t m_level_srv_cbs = {
    .level_cbs.get_cb = generic_level_state_get_cb,
    .level_cbs.set_cb = generic_level_state_set_cb,
    .level_cbs.delta_set_cb = generic_level_state_delta_set_cb,
    .level_cbs.move_set_cb = generic_level_state_move_set_cb
};


static void generic_dtt_server_get_cb(const generic_dtt_server_t *p_self,
                                      const access_message_rx_meta_t *p_meta,
                                      generic_dtt_status_params_t *p_out);
static void generic_dtt_server_set_cb(const generic_dtt_server_t *p_self,
                                      const access_message_rx_meta_t *p_meta,
                                      const generic_dtt_set_params_t *p_in,
                                      generic_dtt_status_params_t *p_out);

static const generic_dtt_server_callbacks_t m_dtt_srv_cbs = {
    .dtt_cbs.set_cb = generic_dtt_server_set_cb,
    .dtt_cbs.get_cb = generic_dtt_server_get_cb
};


#if SCENE_SETUP_SERVER_INSTANCES_MAX > 0
static void app_curtain_motor_scene_store(const app_scene_model_interface_t *p_app_scene_if,
                                          uint8_t scene_index);
static void app_curtain_motor_scene_recall(const app_scene_model_interface_t *p_app_scene_if,
                                           uint8_t scene_index,
                                           uint32_t delay_ms,
                                           uint32_t transition_time_ms);
static void app_curtain_motor_scene_delete(const app_scene_model_interface_t *p_app_scene_if,
                                         uint8_t scene_index);

static const app_scene_callbacks_t m_scene_curtain_motor_cbs = {
    .scene_store_cb = app_curtain_motor_scene_store,
    .scene_recall_cb = app_curtain_motor_scene_recall,
    .scene_delete_cb = app_curtain_motor_scene_delete
};
#endif

static uint32_t store_state(app_curtain_motor_server_t *p_app, uint16_t level_value);
static void transition_parameters_set(app_curtain_motor_server_t *p_app,
                                      int32_t delta,
                                      const model_transition_t *p_in_transition,
                                      app_transition_type_t transition_type);


/* value converter helpers */

static inline uint8_t generic_level_to_generic_onoff(int16_t value)
{
    return (value == INT16_MIN ? 0 : 1);
}

static inline int16_t curtain_motor_level_to_generic_level(uint16_t value)
{
    return value - GENERIC_LEVEL_MIN;
}

static inline uint16_t generic_level_to_curtain_motor_level(int16_t value)
{
    return value + GENERIC_LEVEL_MIN;
}


/* store state to the flash */
static uint32_t store_state(app_curtain_motor_server_t *p_app, uint16_t level_value)
{
    uint8_t generic_onoff_value;
    int16_t generic_level_value;
    uint32_t status;

    generic_level_value = curtain_motor_level_to_generic_level(level_value);
    generic_onoff_value = generic_level_to_generic_onoff(generic_level_value);

    if (p_app->state.initialized) {
        status = generic_onoff_mc_onoff_state_set(p_app->onoff_server.state_handle,
                                                  generic_onoff_value);
        if (status != NRF_SUCCESS) {
            return status;
        }
//        __LOG(LOG_SRC_APP, LOG_LEVEL_DBG1, "%s(): store generic_onoff=%d\n", __func__, generic_onoff_value);

        if (level_value >= CURTAIN_MOTOR_LEVEL_LAST_MIN) {
            status = generic_level_mc_level_state_set(p_app->level_server.state_handle,
                                                      generic_level_value);
            if (status != NRF_SUCCESS) {
                return status;
            }
//            __LOG(LOG_SRC_APP, LOG_LEVEL_DBG1, "%s(): store generic_level=%d\n", __func__, generic_level_value);
        }
    }

    return NRF_SUCCESS;
}



/* Generic OnOff model interface callbacks */

static void generic_onoff_state_get_cb(const generic_onoff_server_t *p_self,
                                       const access_message_rx_meta_t *p_meta,
                                       generic_onoff_status_params_t *p_out)
{
    app_curtain_motor_server_t *p_app = PARENT_BY_FIELD_GET(app_curtain_motor_server_t,
                                                            onoff_server, p_self);
    NRF_MESH_ASSERT(p_app && p_out);
    generic_level_status_params_t out_data;

    generic_level_state_get_cb(&p_app->level_server,
                               p_meta,
                               &out_data);

    p_out->present_on_off = generic_level_to_generic_onoff(out_data.present_level);
    p_out->target_on_off = generic_level_to_generic_onoff(out_data.target_level);
    p_out->remaining_time_ms = out_data.remaining_time_ms;
}


static void generic_onoff_state_set_cb(const generic_onoff_server_t *p_self,
                                       const access_message_rx_meta_t *p_meta,
                                       const generic_onoff_set_params_t *p_in,
                                       const model_transition_t *p_in_transition,
                                       generic_onoff_status_params_t *p_out)
{
    app_curtain_motor_server_t *p_app = PARENT_BY_FIELD_GET(app_curtain_motor_server_t,
                                                            onoff_server, p_self);
    int16_t last_generic_level;
    generic_level_set_params_t in_data = {0};
    generic_level_status_params_t out_data = {0};
    generic_level_status_params_t *p_out_data;
    model_transition_t *p_transition = NULL;
    model_transition_t transition;

    in_data.tid = p_in->tid;

    if (p_in->on_off) {
        ERROR_CHECK(generic_level_mc_level_state_get(p_app->level_server.state_handle, &last_generic_level));
        in_data.level = last_generic_level;
    } else {
        in_data.level = INT16_MIN;
    }

    if (p_in_transition != NULL) {
        p_transition = &transition;
        transition.delay_ms = p_in_transition->delay_ms;
        transition.transition_time_ms = p_in_transition->transition_time_ms;
    }

    p_out_data = p_out ? &out_data : NULL;
    generic_level_state_set_cb(&p_app->level_server,
                               p_meta,
                               &in_data,
                               p_transition,
                               p_out_data);

    if (p_out != NULL && p_out_data != NULL) {
        p_out->present_on_off = generic_level_to_generic_onoff(p_out_data->present_level);
        p_out->target_on_off = generic_level_to_generic_onoff(p_out_data->target_level);
        p_out->remaining_time_ms = p_out_data->remaining_time_ms;
    }
}



/* Generic Level model interface callbacks */

static void generic_level_state_get_cb(const generic_level_server_t *p_self,
                                       const access_message_rx_meta_t *p_meta,
                                       generic_level_status_params_t *p_out)
{
    app_curtain_motor_server_t *p_app = PARENT_BY_FIELD_GET(app_curtain_motor_server_t,
                                                            level_server, p_self);
    NRF_MESH_ASSERT(p_app && p_out);
    uint16_t present_level;
    app_transition_params_t *p_params = app_transition_ongoing_get(&p_app->state.transition);

    p_app->get_cb(p_app, &present_level);

    p_out->present_level = curtain_motor_level_to_generic_level(present_level);

    if (p_params->transition_type == APP_TRANSITION_TYPE_MOVE_SET) {
        p_out->target_level =
            curtain_motor_level_to_generic_level(p_app->state.target_level > 0 ? UINT16_MAX : 0);
        p_out->remaining_time_ms = (p_params->transition_time_ms == 0 || p_params->required_delta == 0) ?
                                    0 : MODEL_TRANSITION_TIME_UNKNOWN;
    } else {
        p_out->target_level = curtain_motor_level_to_generic_level(p_app->state.target_level);
        p_out->remaining_time_ms = app_transition_remaining_time_get(&p_app->state.transition);
    }

    __LOG(LOG_SRC_APP, LOG_LEVEL_DBG1, "GET Level: pr: %d tgt: %d tt: %d\n",
          p_out->present_level, p_out->target_level, p_out->remaining_time_ms);
}


static void generic_level_state_set_cb(const generic_level_server_t *p_self,
                                       const access_message_rx_meta_t *p_meta,
                                       const generic_level_set_params_t *p_in,
                                       const model_transition_t *p_in_transition,
                                       generic_level_status_params_t *p_out)
{
    app_curtain_motor_server_t *p_app = PARENT_BY_FIELD_GET(app_curtain_motor_server_t,
                                                            level_server, p_self);
    app_transition_params_t *p_params = app_transition_requested_get(&p_app->state.transition);
    NRF_MESH_ASSERT(p_app && p_in);
    uint16_t present_level;
    uint32_t transition_time_ms;

    p_app->get_cb(p_app, &present_level);

    p_app->state.target_snapshot = generic_level_to_curtain_motor_level(p_in->level);
    p_app->state.init_present_snapshot = p_app->state.present_level;

    ERROR_CHECK(store_state(p_app, p_app->state.target_snapshot));

    if (!p_app->state.initialized || (present_level != p_app->state.target_snapshot)) {
        (void) transition_parameters_set(p_app,
                                         (int32_t)p_app->state.target_snapshot - (int32_t)present_level,
                                         p_in_transition,
                                         APP_TRANSITION_TYPE_SET);

        transition_time_ms = p_params->transition_time_ms;
        __LOG(LOG_SRC_APP, LOG_LEVEL_INFO,
              "SET: target level: %d  delay: %d  tt: %d  req-delta: %d \n",
              p_app->state.target_snapshot,
              p_app->state.transition.delay_ms,
              p_params->transition_time_ms,
              p_params->required_delta);

        app_transition_trigger(&p_app->state.transition);

#if SCENE_SETUP_SERVER_INSTANCES_MAX > 0
        app_scene_model_scene_changed(p_app->p_app_scene);
#endif
    } else {
        transition_time_ms = 0;
    }

    if (p_out != NULL) {
        p_out->present_level = curtain_motor_level_to_generic_level(p_app->state.present_level);
        p_out->target_level = curtain_motor_level_to_generic_level(p_app->state.target_snapshot);
        p_out->remaining_time_ms = transition_time_ms;
    }
}


static void generic_level_state_delta_set_cb(const generic_level_server_t *p_self,
                                             const access_message_rx_meta_t *p_meta,
                                             const generic_level_delta_set_params_t *p_in,
                                             const model_transition_t *p_in_transition,
                                             generic_level_status_params_t *p_out)
{
    app_curtain_motor_server_t *p_app = PARENT_BY_FIELD_GET(app_curtain_motor_server_t,
                                                            level_server, p_self);
    NRF_MESH_ASSERT(p_app && p_in);
    app_transition_params_t *p_params = app_transition_requested_get(&p_app->state.transition);
    int32_t target_level;

    p_app->state.new_tid = model_transaction_is_new(&p_app->level_server.tid_tracker);
    __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "tid: %d %s\n", p_in->tid, p_app->state.new_tid ?
          "New TID" : "Same TID, cumulative delta set");

    p_app->get_cb(p_app, &p_app->state.present_level);

    if (p_app->state.new_tid) {
        p_app->state.init_present_snapshot = p_app->state.present_level;
    }

    target_level = p_app->state.new_tid ?
                    (int32_t)p_app->state.present_level + p_in->delta_level :
                    (int32_t)p_app->state.init_present_snapshot + p_in->delta_level;


    /* There is a special case for target level values of less than or equal to `0`.
     * If calculated target is less than or equal to zero, target should be zero. */
    p_app->state.target_snapshot = target_level <= 0 ? 0 :
                                   target_level > UINT16_MAX ? UINT16_MAX :
                                   target_level;

    __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "present_level=%u, target_level=%u\n",
          p_app->state.present_level,
          p_app->state.target_snapshot);

    ERROR_CHECK(store_state(p_app, p_app->state.target_snapshot));

    (void) transition_parameters_set(p_app,
                                     p_in->delta_level,
                                     p_in_transition,
                                     APP_TRANSITION_TYPE_DELTA_SET);
    __LOG(LOG_SRC_APP, LOG_LEVEL_INFO,
          "Delta SET: delta: %d  delay: %d  tt: %d target: %d\n",
          p_in->delta_level,
          p_app->state.transition.delay_ms,
          p_params->transition_time_ms,
          p_app->state.target_snapshot);

    app_transition_trigger(&p_app->state.transition);

#if SCENE_SETUP_SERVER_INSTANCES_MAX > 0
    if (p_in->delta_level != 0) {
        app_scene_model_scene_changed(p_app->p_app_scene);
    }
#endif

    if (p_out != NULL) {
        p_out->present_level = curtain_motor_level_to_generic_level(p_app->state.present_level);
        p_out->target_level = curtain_motor_level_to_generic_level(p_app->state.target_snapshot);
        p_out->remaining_time_ms = p_params->transition_time_ms;
    }
}


static void generic_level_state_move_set_cb(const generic_level_server_t *p_self,
                                            const access_message_rx_meta_t *p_meta,
                                            const generic_level_move_set_params_t *p_in,
                                            const model_transition_t *p_in_transition,
                                            generic_level_status_params_t *p_out)
{
    app_curtain_motor_server_t *p_app = PARENT_BY_FIELD_GET(app_curtain_motor_server_t,
                                                            level_server, p_self);
    NRF_MESH_ASSERT(p_app && p_in);
    app_transition_params_t *p_params = app_transition_requested_get(&p_app->state.transition);

    transition_parameters_set(p_app,
                              (int32_t)p_in->move_level,
                              p_in_transition,
                              APP_TRANSITION_TYPE_MOVE_SET);

    p_app->get_cb(p_app, &p_app->state.present_level);

    if (p_in->move_level > 0 && p_app->state.transition.requested_params.transition_time_ms > 0) {
        p_app->state.target_snapshot = UINT16_MAX;
    } else if (p_in->move_level < 0 && p_app->state.transition.requested_params.transition_time_ms > 0) {
        p_app->state.target_snapshot = 0;
    } else {
        p_app->state.target_snapshot = p_app->state.present_level;
    }
    p_app->state.init_present_snapshot = p_app->state.present_level;

    __LOG(LOG_SRC_APP, LOG_LEVEL_INFO,
          "MOVE SET: move-level: %d  delay: %d  tt: %d \n",
          p_in->move_level,
          p_app->state.transition.delay_ms,
          p_params->transition_time_ms);

    app_transition_trigger(&p_app->state.transition);

#if SCENE_SETUP_SERVER_INSTANCES_MAX > 0
    if ((p_in->move_level != 0) && (p_app->state.transition.requested_params.transition_time_ms > 0)) {
        app_scene_model_scene_changed(p_app->p_app_scene);
    }
#endif

    /* Prepare response */
    if (p_out != NULL) {
        p_out->present_level = curtain_motor_level_to_generic_level(p_app->state.present_level);
        p_out->target_level = curtain_motor_level_to_generic_level(p_app->state.target_level);
        p_out->remaining_time_ms = (p_params->transition_time_ms == 0 || p_in->move_level == 0) ?
                                    0 : MODEL_TRANSITION_TIME_UNKNOWN;
    }
}


/* Generci DTT Interface functions */

static void generic_dtt_server_get_cb(const generic_dtt_server_t *p_self,
                                      const access_message_rx_meta_t *p_meta,
                                      generic_dtt_status_params_t *p_out)
{
    app_curtain_motor_server_t *p_app = PARENT_BY_FIELD_GET(app_curtain_motor_server_t,
                                                            dtt_server, p_self);

    ERROR_CHECK(generic_dtt_mc_dtt_state_get(p_app->dtt_server.state_handle, &p_out->transition_time_ms));
}


static void generic_dtt_server_set_cb(const generic_dtt_server_t *p_self,
                                      const access_message_rx_meta_t *p_meta,
                                      const generic_dtt_set_params_t *p_in,
                                      generic_dtt_status_params_t *p_out)
{
    app_curtain_motor_server_t *p_app = PARENT_BY_FIELD_GET(app_curtain_motor_server_t,
                                                            dtt_server, p_self);

    p_out->transition_time_ms = p_in->transition_time_ms;
    ERROR_CHECK(generic_dtt_mc_dtt_state_set(p_app->dtt_server.state_handle, p_out->transition_time_ms));
}



/* Transition application */

static bool m_transition_abort_flag_cb(void)
{
    LIST_FOREACH(p_iter, mp_app_curtain_motor_head) {
        app_curtain_motor_server_t *p_app = PARENT_BY_FIELD_GET(app_curtain_motor_server_t,
                                                                node,
                                                                p_iter);
        if (p_app->abort_move) {
            __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "Element %d move stopped\n", p_app->element_index);
            p_app->abort_move = false;
            app_transition_abort(&p_app->state.transition);
        }
    }
    return true;
}


static void publish_bound_states(app_curtain_motor_server_t *p_app,
                                 const generic_level_status_params_t *p_level_params)
{
    generic_onoff_status_params_t onoff_params;

    (void) generic_level_server_status_publish(&p_app->level_server, p_level_params);

    onoff_params.present_on_off = generic_level_to_generic_onoff(p_level_params->present_level);
    onoff_params.target_on_off = generic_level_to_generic_onoff(p_level_params->target_level);
    onoff_params.remaining_time_ms = p_level_params->remaining_time_ms;
    (void) generic_onoff_server_status_publish(&p_app->onoff_server, &onoff_params);
}


static void publish_level(app_curtain_motor_server_t *p_app, uint32_t remaining_time_ms)
{
    generic_level_status_params_t level_params;

    level_params.present_level = curtain_motor_level_to_generic_level(p_app->state.present_level);
    level_params.target_level = curtain_motor_level_to_generic_level(p_app->state.target_level);
    level_params.remaining_time_ms = remaining_time_ms;
    publish_bound_states(p_app, &level_params);
}


static inline app_curtain_motor_server_t *transition_to_app(const app_transition_t *p_transition)
{
    app_curtain_motor_state_t *p_state = PARENT_BY_FIELD_GET(app_curtain_motor_state_t,
                                                             transition,
                                                             p_transition);
    return PARENT_BY_FIELD_GET(app_curtain_motor_server_t,
                               state,
                               p_state);
}


static void transition_parameters_set(app_curtain_motor_server_t *p_app,
                                      int32_t delta,
                                      const model_transition_t *p_in_transition,
                                      app_transition_type_t transition_type)
{
    app_transition_params_t *p_params = app_transition_requested_get(&p_app->state.transition);

    p_params->transition_type = transition_type;
    p_params->required_delta = delta;
    p_params->minimum_step_ms = TRANSITION_STEP_MIN_MS;

    if (p_in_transition == NULL) {
        p_app->state.transition.delay_ms = 0;
        p_params->transition_time_ms = 0;
        ERROR_CHECK(generic_dtt_mc_dtt_state_get(p_app->dtt_server.state_handle,
                                                 &p_params->transition_time_ms));
    } else {
        p_app->state.transition.delay_ms = p_in_transition->delay_ms;
        p_params->transition_time_ms = p_in_transition->transition_time_ms;
    }
}


static void transition_delay_start_cb(const app_transition_t *p_transition)
{
#if NRF_MESH_LOG_ENABLE
    app_curtain_motor_server_t *p_app = transition_to_app(p_transition);

    __LOG(LOG_SRC_APP, LOG_LEVEL_INFO,
          "Element %d: starting delay, present-level: %u\n",
          p_app->element_index,
          p_app->state.present_level);
#endif /* NRF_MESH_LOG_ENABLE */
}


static void transition_start_cb(const app_transition_t *p_transition)
{
    app_curtain_motor_server_t * p_app;

    p_app = transition_to_app(p_transition);
    app_transition_params_t *p_params = app_transition_ongoing_get(&p_app->state.transition);

    if((p_app->state.transition.requested_params.transition_type == APP_TRANSITION_TYPE_DELTA_SET && p_app->state.new_tid) ||
       (p_app->state.transition.requested_params.transition_type == APP_TRANSITION_TYPE_SET) ||
       (p_app->state.transition.requested_params.transition_type == APP_TRANSITION_TYPE_MOVE_SET))
    {
       p_app->state.initial_present_level = p_app->state.init_present_snapshot;
    }
    p_app->state.target_level = p_app->state.target_snapshot;

    p_app->state.published_ms = 0;

    __LOG(LOG_SRC_APP, LOG_LEVEL_INFO,
          "Element %d: starting transition: initial-l: %d delta: %d tt: %d\n",
          p_app->element_index,
          p_app->state.initial_present_level,
          p_params->required_delta,
          p_params->transition_time_ms);

    if (p_app->transition_cb != NULL) {
        p_app->transition_cb(p_app,
                             p_params->transition_time_ms,
                             p_app->state.target_level);
    }

}


static void transition_tick_cb(const app_transition_t *p_transition)
{
    app_curtain_motor_server_t *p_app = transition_to_app(p_transition);
    int32_t present_level;
    uint32_t elapsed_ms;
    uint32_t remaining_ms;
    app_transition_params_t * p_params = app_transition_ongoing_get(&p_app->state.transition);

    elapsed_ms = app_transition_elapsed_time_get((app_transition_t *)p_transition);
    remaining_ms = app_transition_remaining_time_get((app_transition_t *)p_transition);

    /* Calculate new value using linear interpolation and provide to the application. */
    if (p_params->transition_type != APP_TRANSITION_TYPE_MOVE_SET) {
        int32_t delta = (p_app->state.target_level - p_app->state.initial_present_level);
        present_level = p_app->state.initial_present_level +
            (delta * (int64_t)elapsed_ms / (int32_t)p_params->transition_time_ms);
    } else {
        present_level = p_app->state.initial_present_level +
            (((int64_t)elapsed_ms * (int64_t)p_params->required_delta) /
                (int64_t)p_params->transition_time_ms);
    }

    p_app->state.present_level = MIN((int32_t)MAX(present_level, 0), UINT16_MAX);

    if (p_app->set_cb != NULL) {
        p_app->set_cb(p_app, p_app->state.present_level);
    }

    if (p_params->transition_type == APP_TRANSITION_TYPE_MOVE_SET &&
        ((p_params->required_delta > 0 && p_app->state.present_level == UINT16_MAX) ||
        (p_params->required_delta < 0 && p_app->state.present_level == 0)))
    {
        /* stop transition */
        p_app->abort_move = true;
        bearer_event_flag_set(m_transition_abort_flag);
    }

    /* While the transition has at least one half second remaining,
       publish intermediate levels at one second intervals. */
    if ((remaining_ms > 500) && (elapsed_ms - p_app->state.published_ms > 1000)){
        p_app->state.published_ms = elapsed_ms;
        publish_level(p_app, remaining_ms);
    }
}


static void transition_complete_cb(const app_transition_t *p_transition)
{
    app_curtain_motor_server_t *p_app = transition_to_app(p_transition);
    app_transition_params_t * p_params = app_transition_ongoing_get(&p_app->state.transition);

    p_app->state.target_level = p_app->state.target_snapshot;
    if (p_params->transition_type != APP_TRANSITION_TYPE_MOVE_SET) {
        p_app->state.present_level = p_app->state.target_level;
    }

    if (p_app->set_cb != NULL) {
        p_app->set_cb(p_app, p_app->state.present_level);
    }

    __LOG(LOG_SRC_APP, LOG_LEVEL_INFO,
      "Element %d: transition completed, present-L: %d\n",
      p_app->element_index,
      p_app->state.present_level);

    /* transition is complete */
    if (p_app->transition_cb != NULL) {
        p_app->transition_cb(p_app,
                             p_params->transition_time_ms,
                             p_app->state.target_level);
    }

    ERROR_CHECK(store_state(p_app, p_app->state.present_level));

    publish_level(p_app, 0);
}



/* Scene Interface functions */

#if SCENE_SETUP_SERVER_INSTANCES_MAX > 0

static void app_curtain_motor_scene_store(const app_scene_model_interface_t *p_app_scene_if,
                                          uint8_t scene_index)
{
    app_curtain_motor_server_t *p_app = PARENT_BY_FIELD_GET(app_curtain_motor_server_t, scene_if, p_app_scene_if);

    ERROR_CHECK(generic_level_mc_scene_level_store(p_app->level_server.model_handle, scene_index,
                                                   curtain_motor_level_to_generic_level(p_app->state.present_level)));
    __LOG(LOG_SRC_APP, LOG_LEVEL_DBG1,
            "SCENE STORE: handle: %d  scene index: %d  present level: %d\n",
            p_app->level_server.state_handle,
            scene_index,
            p_app->state.present_level);
}


static void app_curtain_motor_scene_recall(const app_scene_model_interface_t *p_app_scene_if,
                                           uint8_t scene_index,
                                           uint32_t delay_ms,
                                           uint32_t transition_time_ms)
{
    app_curtain_motor_server_t *p_app = PARENT_BY_FIELD_GET(app_curtain_motor_server_t, scene_if, p_app_scene_if);
    uint16_t present_level;
    int16_t recall_level;

    p_app->get_cb(p_app, &present_level);

    ERROR_CHECK(generic_level_mc_scene_level_recall(p_app->level_server.model_handle,
                                                    scene_index,
                                                    &recall_level));

    p_app->state.target_snapshot = generic_level_to_curtain_motor_level(recall_level);
    p_app->state.init_present_snapshot = p_app->state.present_level;

    ERROR_CHECK(store_state(p_app, p_app->state.target_snapshot));

    if (!p_app->state.initialized || (present_level != p_app->state.target_snapshot)) {
        model_transition_t in_transition = {
            .delay_ms = delay_ms,
            .transition_time_ms = transition_time_ms
        };

        (void) transition_parameters_set(p_app,
                                         (int32_t)p_app->state.target_snapshot - (int32_t)present_level,
                                         &in_transition,
                                         APP_TRANSITION_TYPE_SET);

        __LOG(LOG_SRC_APP, LOG_LEVEL_INFO,
              "SCENE RECALL: target level: %d  delay: %d  tt: %d\n",
              p_app->state.target_snapshot,
              in_transition.delay_ms,
              in_transition.transition_time_ms);

        app_transition_trigger(&p_app->state.transition);
    }
}


static void app_curtain_motor_scene_delete(const app_scene_model_interface_t *p_app_scene_if,
                                           uint8_t scene_index)
{
    app_curtain_motor_server_t *p_app = PARENT_BY_FIELD_GET(app_curtain_motor_server_t, scene_if, p_app_scene_if);
    app_transition_abort(&p_app->state.transition);

    /* no need to do anything else */

    __LOG(LOG_SRC_APP, LOG_LEVEL_DBG1, "%s():\n", __func__);
}

#endif /* SCENE_SETUP_SERVER_INSTANCES_MAX */


/*** Interface functions */

uint32_t app_curtain_motor_init(app_curtain_motor_server_t *p_app, uint8_t element_index)
{
    uint32_t status = NRF_ERROR_INTERNAL;

    if (p_app == NULL) {
        return NRF_ERROR_NULL;
    }

    if ((p_app->get_cb == NULL) || (p_app->set_cb == NULL)) {
        return NRF_ERROR_NULL;
    }

    p_app->onoff_server.settings.p_callbacks = &m_onoff_srv_cbs;
    status = generic_onoff_server_init(&p_app->onoff_server, element_index);
    if (status != NRF_SUCCESS) {
        return status;
    }

    p_app->level_server.settings.p_callbacks = &m_level_srv_cbs;
    status = generic_level_server_init(&p_app->level_server, element_index);
    if (status != NRF_SUCCESS) {
        return status;
    }

    /* share Generic On/Off and Generic Lever subscription list */
    status = access_model_subscription_list_dealloc(p_app->level_server.model_handle);
    if (status != NRF_SUCCESS) {
        return status;
    }

    status = access_model_subscription_lists_share(p_app->onoff_server.model_handle,
                                                   p_app->level_server.model_handle);
    if (status != NRF_SUCCESS) {
        return status;
    }

    /* generic dtt server */
    p_app->dtt_server.settings.p_callbacks = &m_dtt_srv_cbs;
    status = generic_dtt_server_init(&p_app->dtt_server, element_index);
    if (status != NRF_SUCCESS) {
        return status;
    }
    status = access_model_subscription_list_dealloc(p_app->dtt_server.model_handle);
    if (status != NRF_SUCCESS) {
        return status;
    }
    status = access_model_subscription_lists_share(p_app->onoff_server.model_handle,
                                                   p_app->dtt_server.model_handle);
    if (status != NRF_SUCCESS) {
        return status;
    }

    /* set the default state */
    status = generic_onoff_mc_open(&p_app->onoff_server.state_handle);
    if (status != NRF_SUCCESS) {
        return status;
    }

    status = generic_level_mc_open(&p_app->level_server.state_handle);
    if (status != NRF_SUCCESS) {
        return status;
    }

    status = generic_dtt_mc_open(&p_app->dtt_server.state_handle);
    if (status != NRF_SUCCESS) {
        return status;
    }


#if SCENE_SETUP_SERVER_INSTANCES_MAX > 0
    p_app->scene_if.p_callbacks = &m_scene_curtain_motor_cbs;
#endif

    p_app->element_index = element_index;
    p_app->state.initialized = false;

    p_app->state.transition.delay_start_cb = transition_delay_start_cb;
    p_app->state.transition.transition_start_cb = transition_start_cb;
    p_app->state.transition.transition_tick_cb = transition_tick_cb;
    p_app->state.transition.transition_complete_cb = transition_complete_cb;
    p_app->state.transition.p_context = p_app;
    p_app->abort_move = false;

    status = app_transition_init(&p_app->state.transition);
    if (status != NRF_SUCCESS) {
        return status;
    }

    list_add(&mp_app_curtain_motor_head, &p_app->node);
    m_transition_abort_flag = bearer_event_flag_add(m_transition_abort_flag_cb);

    return NRF_SUCCESS;
}


uint32_t app_curtain_motor_binding_setup(app_curtain_motor_server_t *p_app)
{
    bool generic_onoff_last;
    generic_level_set_params_t in_data = {0};
    uint32_t status;

    if (p_app == NULL) {
        return NRF_ERROR_NULL;
    }

    status = generic_onoff_mc_onoff_state_get(p_app->onoff_server.state_handle,
                                              &generic_onoff_last);
    if (status != NRF_SUCCESS) {
        return status;
    }

//    __LOG(LOG_SRC_APP, LOG_LEVEL_DBG1, "%s(): generic_onoff_last=%d\n", __func__, generic_onoff_last);

    if (generic_onoff_last) {
        status = generic_level_mc_level_state_get(p_app->level_server.state_handle,
                                              &in_data.level);
        if (status != NRF_SUCCESS) {
            return status;
        }
    } else {
        in_data.level = INT16_MIN;
    }

//    __LOG(LOG_SRC_APP, LOG_LEVEL_DBG1, "%s(): generic_level_last=%d\n", __func__, in_data.level);

    generic_level_state_set_cb(&p_app->level_server,
                               NULL,
                               &in_data,
                               NULL,
                               NULL);

    p_app->state.initialized = true;

    return NRF_SUCCESS;
}


uint32_t app_curtain_motor_direct_actual_set(app_curtain_motor_server_t *p_app, uint16_t level_value)
{
    if (p_app == NULL) {
        return NRF_ERROR_NULL;
    }

#if SCENE_SETUP_SERVER_INSTANCES_MAX > 0
    uint16_t old_present_level = p_app->state.present_level;
#endif

    p_app->state.present_level = level_value;

    if (p_app->set_cb != NULL) {
        p_app->set_cb(p_app, p_app->state.present_level);
    }

    app_transition_params_t *p_params = app_transition_requested_get(&p_app->state.transition);

    /* transition is complete */
    app_transition_abort(&p_app->state.transition);
    if (p_app->transition_cb != NULL) {
        p_app->transition_cb(p_app,
                             p_params->transition_time_ms,
                             level_value);
    }

    store_state(p_app, level_value);

#if SCENE_SETUP_SERVER_INSTANCES_MAX > 0
    if (old_present_level != p_app->state.present_level) {
        app_scene_model_scene_changed(p_app->p_app_scene);
    }
#endif

    return NRF_SUCCESS;
}


uint32_t app_curtain_motor_current_value_publish(app_curtain_motor_server_t *p_app)
{
    if (p_app == NULL) {
        return NRF_ERROR_NULL;
    }

    app_transition_abort(&p_app->state.transition);

#if SCENE_SETUP_SERVER_INSTANCES_MAX > 0
    uint16_t old_present_level = p_app->state.present_level;
#endif

    p_app->get_cb(p_app, &p_app->state.present_level);

    ERROR_CHECK(store_state(p_app, p_app->state.present_level));

#if SCENE_SETUP_SERVER_INSTANCES_MAX > 0
    if (old_present_level != p_app->state.present_level) {
        app_scene_model_scene_changed(p_app->p_app_scene);
    }
#endif

    publish_level(p_app, 0);

    return NRF_SUCCESS;
}


#if SCENE_SETUP_SERVER_INSTANCES_MAX > 0
uint32_t app_curtain_motor_scene_context_set(app_curtain_motor_server_t *p_app,
                                             app_scene_setup_server_t *p_app_scene)
{
    if (p_app == NULL || p_app_scene == NULL)
    {
        return NRF_ERROR_NULL;
    }

    p_app->p_app_scene = p_app_scene;
    return NRF_SUCCESS;
}
#endif
