/*
 * 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 <string.h>

#include "os/mynewt.h"
#include <sys/queue.h>
#include <battery/battery.h>
#include <battery/battery_prop.h>
#include <battery/battery_drv.h>

#define BATTERY_MAX_COUNT MYNEWT_VAL(BATTERY_MAX_COUNT)

#define GET_BIT(arr, bit)   ((arr)[(bit) / 32] & (1 << ((bit) % 32)))

/*
 * Structure holds information about listener and what is it listening for.
 */
struct listener_data {
    /* The properties to monitor for change */
    uint32_t ld_prop_change_mask[BATTERY_PROPERTY_MASK_SIZE];
    /* The properties to monitor for periodic read */
    uint32_t ld_prop_read_mask[BATTERY_PROPERTY_MASK_SIZE];
    /* Pointer to listener provided by the application. */
    struct battery_prop_listener *ld_listener;
};

struct battery_manager {
    /* The lock for battery object */
    struct os_mutex bm_lock;

    struct battery *bm_batteries[BATTERY_MAX_COUNT];

    /** Event queue */
    struct os_eventq *bm_eventq;

    /** Manages poll rates of the batteries */
    struct os_callout bm_poll_callout;

} battery_manager;

struct os_eventq *
battery_mgr_evq_get(void)
{
    return (battery_manager.bm_eventq);
}

static void
battery_mgr_evq_set(struct os_eventq *evq)
{
    assert(evq != NULL);
    battery_manager.bm_eventq = evq;
}

static void battery_mgr_poll_battery(struct battery *battery);

static void
battery_poll_event_cb(struct os_event *ev)
{
    int i;
    int pflag;
    os_time_t next_poll = 0;
    os_time_t now = os_time_get();
    struct battery *bat;
    os_stime_t ticks;

    pflag = 0;
    for (i = 0; i < BATTERY_MAX_COUNT; ++i) {
        bat = battery_manager.bm_batteries[i];
        if (bat) {
            if (OS_TIME_TICK_GEQ(now, bat->b_next_run)) {
                bat->b_last_read_time = now;
                battery_mgr_poll_battery(battery_manager.bm_batteries[i]);
                bat->b_next_run = now + os_time_ms_to_ticks32(bat->b_poll_rate);
            }
            if ((pflag == 0) || OS_TIME_TICK_LT(bat->b_next_run, next_poll)) {
                pflag = 1;
                next_poll = bat->b_next_run;
            }
        }
    }

    if (pflag) {
        ticks = (os_stime_t)(next_poll - os_time_get());
        if (ticks < 0) {
            ticks = 1;
        }
        os_callout_reset(&battery_manager.bm_poll_callout, (os_time_t)ticks);
    }
}

void
battery_mgr_process_event(struct os_event *event)
{
    os_eventq_put(battery_manager.bm_eventq, event);
}

static void
battery_mgr_init(void)
{
#ifdef MYNEWT_VAL_BATTERY_MGR_EVQ
    battery_mgr_evq_set(MYNEWT_VAL(BATTERY_MGR_EVQ));
#else
    battery_mgr_evq_set(os_eventq_dflt_get());
#endif

    /**
     * Initialize battery manager polling callout.
     */
    os_callout_init(&battery_manager.bm_poll_callout,
            battery_mgr_evq_get(), battery_poll_event_cb,
            NULL);

    os_mutex_init(&battery_manager.bm_lock);
}

/* =================================================================
 * ====================== PKG ======================================
 * =================================================================
 */

void
battery_pkg_init(void)
{
    /* Ensure this function only gets called by sysinit. */
    SYSINIT_ASSERT_ACTIVE();

    battery_mgr_init();
}

int
battery_mgr_get_battery_count(void)
{
    int i;

    for (i = 0; i < BATTERY_MAX_COUNT; ++i) {
        if (battery_manager.bm_batteries[i] == NULL) {
            break;
        }
    }

    return i;
}

struct os_dev *
battery_get_battery(int bat_num)
{
    assert(bat_num < BATTERY_MAX_COUNT);
    assert(battery_manager.bm_batteries[bat_num]);

    return &battery_manager.bm_batteries[bat_num]->b_dev;
}

static int
battery_get_num(struct battery *battery)
{
    int i;

    /* For one battery, there is no need to specify battery at all */
    if (battery == NULL) {
        return 0;
    }
    for (i = 0; i < BATTERY_MAX_COUNT; ++i) {
        if (battery_manager.bm_batteries[i] == battery) {
            return i;
        }
    }
    assert(0);
    return 0;
}

struct battery_driver *
battery_get_driver(struct os_dev *battery,
                   const char *dev_name)
{
    int i;
    assert(battery);
    struct battery *bat = (struct battery *)battery;
    for (i = 0; i < BATTERY_DRIVERS_MAX && bat->b_drivers[i]; ++i) {
        if (strcmp(bat->b_drivers[i]->dev.od_name, dev_name) == 0) {
            return bat->b_drivers[i];
        }
    }
    /* No device found */
    assert(0);
    return NULL;
}

static void
set_bit(uint32_t *mask, int bit)
{
    mask[bit / (sizeof(*mask) * 8)] |=
        1 << (bit & ((sizeof(*mask) * 8) - 1));
}

static void
clear_bit(uint32_t *mask, int bit)
{
    mask[bit / (sizeof(*mask) * 8)] &=
        ~(1 << (bit & ((sizeof(*mask) * 8) - 1)));
}

static struct battery_property *
find_driver_property(struct battery *bat, struct battery_driver *driver,
                     battery_property_type_t type, battery_property_flags_t flags)
{
    struct battery_property *prop = bat->b_properties + driver->bd_first_property;
    int i;
    assert(driver);

    for (i = 0; i < driver->bd_property_count; ++i, ++prop) {
        if (prop->bp_type == type && prop->bp_flags == flags) {
            return prop;
        }
    }
    return NULL;
}

static struct battery_property *
find_hardware_property(struct battery *battery, struct battery_driver *driver,
                       battery_property_type_t type, battery_property_flags_t flags)
{
    struct battery_property *res = NULL;
    int i;

    /* Search driver properties first */
    if (driver) {
        res = find_driver_property(battery, driver, type, flags);
    } else {
        for (i = 0; res == NULL && i < BATTERY_DRIVERS_MAX; ++i) {
            if (battery->b_drivers[i]) {
                res = find_driver_property(battery, battery->b_drivers[i],
                                           type, flags);
            }
        }
    }
    return res;
}

struct battery_property *
battery_find_property(struct os_dev *battery,
                      battery_property_type_t type, battery_property_flags_t flags,
                      const char *dev_name)
{
    struct battery_property *res = NULL;
    struct battery_property *prop;
    struct battery *bat = (struct battery *)battery;
    int i;
    struct battery_driver *driver = NULL;

    if (dev_name) {
        driver = battery_get_driver(battery, dev_name);
        assert(driver);
    }
    /* Search hardware properties first */
    res = find_hardware_property(bat, driver, type, flags);
    if (!res) {
        /* Search battery manager created properties */
        for (i = 0; i < bat->b_all_property_count; ++i) {
            prop = &bat->b_properties[i];
            if (prop->bp_type == type &&
                prop->bp_flags == flags &&
                (driver == NULL ||
                 bat->b_drivers[prop->bp_drv_num] == driver)) {
                res = prop;
                break;
            }
        }
    }
    if (!res) {
        /* Create software threshold */
        if ((flags & BATTERY_PROPERTY_FLAGS_CREATE) != 0 &&
            (flags & BATTERY_PROPERTY_FLAGS_ALARM_THREASH) != 0) {
            /* Find base property that threshold is for */
            prop = find_hardware_property(bat, driver, type,
                    BATTERY_PROPERTY_FLAGS_NONE);
            /* Base property found, create derived one */
            if (prop) {
                struct battery_property *p =
                    calloc(1, sizeof(struct battery_property));
                if (!p) {
                    /* If an allocation fails, return NULL */
                    return NULL;
                }
                if (prop->bp_prop_num == 0) {
                    prop->bp_prop_num = ++bat->b_all_property_count;
                    assert(bat->b_all_property_count <= BATTERY_MAX_PROPERTY_COUNT);
                }
                /* Base property is marked as base for others
                 */
                prop->bp_base = 1;
                p->bp_type = type;
                p->bp_flags = flags;
                p->bp_bat_num = prop->bp_bat_num;
                p->bp_drv_num = prop->bp_drv_num;
                p->bp_prop_num = ++bat->b_all_property_count;
                assert(bat->b_all_property_count <= BATTERY_MAX_PROPERTY_COUNT);
                res = p;
            }
        }
    }

    return res;
}

int
battery_get_property_count(struct os_dev *battery,
                           struct battery_driver *driver)
{
    struct battery *bat = (struct battery *)battery;
    int i;

    if (driver == NULL) {
        return bat->b_all_property_count;
    } else {
        for (i = 0; i < BATTERY_DRIVERS_MAX && bat->b_drivers[i]; ++i) {
            if (bat->b_drivers[i] == driver) {
                return bat->b_drivers[i]->bd_property_count;
            }
        }
    }

    return 0;
}

struct battery_property *
battery_enum_property(struct os_dev *battery, struct battery_driver *driver,
                      uint8_t prop_num)
{
    struct battery *bat = (struct battery *)battery;
    struct battery_property *prop = NULL;
    int i;

    if (driver == NULL) {
        if (prop_num < bat->b_all_property_count) {
            prop = &bat->b_properties[prop_num];
        }
    } else {
        for (i = 0; i < BATTERY_DRIVERS_MAX && bat->b_drivers[i]; ++i) {
            if (bat->b_drivers[i] == driver) {
                if (prop_num < driver->bd_property_count) {
                    prop = &bat->b_properties[prop_num + driver->bd_first_property];
                }
            }
        }
    }

    return prop;
}

char *
battery_prop_get_name(const struct battery_property *prop, char *buf,
                      size_t buf_size)
{
    struct battery *bat = battery_manager.bm_batteries[prop->bp_bat_num];
    struct battery_driver *driver = bat->b_drivers[prop->bp_drv_num];
    const struct battery_driver_property *driver_prop =
        &driver->bd_driver_properties[prop->bp_prop_num - driver->bd_first_property];

    strncpy(buf, driver_prop->bdp_name, buf_size - 1);
    buf[buf_size - 1] = '\0';

    return buf;
}

struct battery_property *
battery_find_property_by_name(struct os_dev *battery, const char *name)
{
    struct battery *bat = (struct battery *)battery;
    char buf[20];
    int i;

    for (i = 0; i < bat->b_all_property_count; ++i) {
        battery_prop_get_name(&bat->b_properties[i], buf, sizeof(buf));
        if (strcmp(buf, name) == 0) {
            return &bat->b_properties[i];
        }
    }
    return NULL;
}

static void
battery_mgr_poll_battery_driver(struct battery *bat,
                                struct battery_driver *drv, uint32_t changed[], uint32_t queried[])
{
    int i;
    battery_property_value_t old_val;
    struct battery_property *prop = &bat->b_properties[drv->bd_first_property];

    /* Read requested properties */
    for (i = 0; i < drv->bd_property_count; ++i, ++prop) {
        if (!GET_BIT(bat->b_polled_properties, i)) {
            continue;
        }

        if (!driver_property(prop)) {
            continue;
        }

        old_val = prop->bp_value;
        if (drv->bd_funcs->bdf_property_get(drv, prop, 100)) {
            prop->bp_valid = 0;
        } else {
            prop->bp_valid = 1;
            if (memcmp(&old_val, &prop->bp_value, sizeof(old_val))) {
                set_bit(changed, prop->bp_prop_num);
            }
            set_bit(queried, prop->bp_prop_num);
        }
    }
}

static void
battery_mgr_poll_battery(struct battery *battery)
{
    uint32_t changed[BATTERY_PROPERTY_MASK_SIZE] = {0};
    uint32_t queried[BATTERY_PROPERTY_MASK_SIZE] = {0};
    uint32_t masked;
    int first_one;
    struct listener_data *ld;
    struct battery_driver *driver;
    int i;
    int j;

    /* Poll battery drivers */
    for (i = 0; i < BATTERY_DRIVERS_MAX; ++i) {
        driver = battery->b_drivers[i];
        if (driver) {
            battery_mgr_poll_battery_driver(battery, driver, changed, queried);
        }
    }

    /* Notify listeners about property changes */
    for (i = 0; i < battery->b_listener_count; ++i) {
        ld = &battery->b_listeners[i];
        for (j = 0; j < BATTERY_PROPERTY_MASK_SIZE; ++j) {
            masked = changed[j] & ld->ld_prop_change_mask[j];
            while (masked) {
                /* Find first set bit */
                first_one = __builtin_ffs(masked) - 1;
                /* Clear it for next loop */
                masked ^= (1 << first_one);
                driver = battery->b_drivers[i];
                /* Notify listener */
                ld->ld_listener->bpl_prop_changed(ld->ld_listener,
                                                  battery->b_properties +
                                                  first_one + j * 32);
            }
        }
    }

    /* Notify listeners about periodic reads */
    for (i = 0; i < battery->b_listener_count; ++i) {
        ld = &battery->b_listeners[i];
        for (j = 0; j < BATTERY_PROPERTY_MASK_SIZE; ++j) {
            masked = queried[j] & ld->ld_prop_read_mask[j];
            while (masked) {
                /* Find first set bit */
                first_one = __builtin_ffs(masked) - 1;
                /* Clear it for next loop */
                masked ^= (1 << first_one);
                driver = battery->b_drivers[i];
                /* Notify listener */
                ld->ld_listener->bpl_prop_read(ld->ld_listener,
                                          battery->b_properties +
                                          first_one + j * 32);
            }
        }
    }
}

int
battery_set_poll_rate_ms(struct os_dev *battery, uint32_t poll_rate)
{
    return battery_set_poll_rate_ms_delay(battery, poll_rate, 0);
}

int
battery_set_poll_rate_ms_delay(struct os_dev *battery, uint32_t poll_rate,
                               uint32_t start_delay)
{
    struct battery *bat = (struct battery *)battery;

    if (bat == NULL) {
        return -1;
    }

    if (poll_rate == 0) {
        bat->b_poll_rate = 0;
        os_callout_stop(&battery_manager.bm_poll_callout);
        return 0;
    }

    bat->b_poll_rate = poll_rate;
    bat->b_next_run = os_time_get();
    os_callout_reset(&battery_manager.bm_poll_callout,
                     os_time_ms_to_ticks32(start_delay));

    return 0;
}

int
battery_add_driver(struct os_dev *battery, struct battery_driver *driver)
{
    struct battery *bat = (struct battery *)battery;
    const struct battery_driver_property *drv_prop;
    struct battery_property *prop;
    int bat_num;
    int drv_num;
    int i;
    int j;

    assert(battery);
    assert(driver);

    bat_num = battery_get_num(bat);
    /* Find first slot */
    for (i = 0; i < BATTERY_DRIVERS_MAX; ++i) {
        /* Just make sure that driver is not added twice */
        assert(bat->b_drivers[i] != driver);
        if (bat->b_drivers[i] == NULL) {
            break;
        }
    }
    assert(i < BATTERY_DRIVERS_MAX);
    drv_num = i;
    bat->b_drivers[i] = driver;

    drv_prop = driver->bd_driver_properties;
    for (i = 0; drv_prop->bdp_type; ++i) {
        ++drv_prop;
    }
    driver->bd_property_count = i;
    bat->b_properties = realloc(bat->b_properties,
                                ((i + bat->b_all_property_count) *
                                 sizeof(struct battery_property)));

    j = bat->b_all_property_count;
    driver->bd_first_property = (uint8_t)j;
    /* Initialize driver properties with battery manager data */
    for (i = 0; i < driver->bd_property_count; ++i, ++j) {
        prop = bat->b_properties + j;
        prop->bp_type = driver->bd_driver_properties[i].bdp_type;
        prop->bp_flags = driver->bd_driver_properties[i].bdp_flags;
        prop->bp_valid = 0;
        prop->bp_value.bpv_i32 = 0;
        prop->bp_drv_prop_num = (uint8_t)i;
        prop->bp_drv_num = drv_num;
        prop->bp_bat_num = bat_num;
        prop->bp_prop_num = j;
    }
    bat->b_all_property_count = j;

    return 0;
}

static int
get_listener_index(struct battery *battery,
                   struct battery_prop_listener *listener)
{
    int i;

    assert(listener);
    assert(battery);

    /* Find existing listener */
    for (i = 0; i < battery->b_listener_count; ++i) {
        if (battery->b_listeners[i].ld_listener == listener) {
            break;
        }
    }
    /* If listener was not yet added, create space for it and fill
     * masks with zeros so they will be filled when listener is actually
     * added to properties.
     */
    if (i >= battery->b_listener_count) {
        i = battery->b_listener_count++;
        battery->b_listeners = realloc(battery->b_listeners,
                sizeof(struct listener_data) * battery->b_listener_count);
        if (battery->b_listeners == NULL) {
            return -1;
        }
        battery->b_listeners[i].ld_listener = listener;
        memset(battery->b_listeners[i].ld_prop_change_mask, 0,
               sizeof(battery->b_listeners[i].ld_prop_change_mask));
        memset(battery->b_listeners[i].ld_prop_read_mask, 0,
               sizeof(battery->b_listeners[i].ld_prop_read_mask));
    }

    return i;
}

static int
battery_update_polled_properties(struct battery *battery)
{
    struct listener_data *ld;
    int i;
    int j;

    for (i = 0; i < BATTERY_PROPERTY_MASK_SIZE; ++i) {
        battery->b_polled_properties[i] = 0;
        for (j = 0; j < battery->b_listener_count; ++j) {
            ld = &battery->b_listeners[j];
            battery->b_polled_properties[i] |= ld->ld_prop_read_mask[i];
            battery->b_polled_properties[i] |= ld->ld_prop_change_mask[i];
        }
    }

    return 0;
}

int
battery_prop_change_subscribe(struct battery_prop_listener *listener,
                              struct battery_property *prop)
{
    struct battery *battery;
    int listener_index;

    assert(listener);
    assert(prop);

    battery = (struct battery *)battery_get_battery(prop->bp_bat_num);
    listener_index = get_listener_index(battery, listener);

    set_bit(battery->b_listeners[listener_index].ld_prop_change_mask,
            prop->bp_prop_num);

    battery_update_polled_properties(battery);

    return 0;
}

int
battery_prop_change_unsubscribe(struct battery_prop_listener *listener,
                                struct battery_property *prop)
{
    struct battery *battery;
    int i;
    int j;

    if (prop) {
        /* Single propety supplied */
        battery = (struct battery *)battery_get_battery(prop->bp_bat_num);
        for (i = 0; i < battery->b_listener_count; ++i) {
            if (listener == battery->b_listeners[i].ld_listener) {
                break;
            }
        }
        if (i >= battery->b_listener_count) {
            return -1;
        }

        clear_bit(battery->b_listeners[i].ld_prop_change_mask,
                  prop->bp_prop_num);
    } else {
        /* No property supplied, remove listener from all subscriptions */
        for (i = 0; i < BATTERY_MAX_COUNT; ++i) {
            battery = battery_manager.bm_batteries[i];
            for (j = 0; j < battery->b_listener_count; ++i) {
                if (battery->b_listeners[j].ld_listener == listener) {
                    /* Reduce number of listeners */
                    battery->b_listener_count--;
                    if (j < battery->b_listener_count) {
                        /* It was not last listener,
                         * move last to emptied space */
                        battery->b_listeners[j] =
                            battery->b_listeners[battery->b_listener_count];
                    }
                    battery->b_listeners =
                        realloc(battery->b_listeners,
                                sizeof(struct listener_data) *
                                battery->b_listener_count);
                    break;
                }
            }
        }
    }

    battery_update_polled_properties(battery);

    return 0;
}

int
battery_prop_poll_subscribe(struct battery_prop_listener *listener,
                            struct battery_property *prop)
{
    struct battery *battery;
    int listener_index;

    assert(listener);
    assert(prop);

    battery = (struct battery *)battery_get_battery(prop->bp_bat_num);
    listener_index = get_listener_index(battery, listener);

    set_bit(battery->b_listeners[listener_index].ld_prop_read_mask,
            prop->bp_prop_num);

    battery_update_polled_properties(battery);

    return 0;
}

int
battery_prop_poll_unsubscribe(struct battery_prop_listener *listener,
                              struct battery_property *prop)
{
    struct battery *battery;
    int i;
    int j;

    if (prop) {
        battery = (struct battery *)battery_get_battery(prop->bp_bat_num);
        for (i = 0; i < battery->b_listener_count; ++i) {
            if (listener == battery->b_listeners[i].ld_listener) {
                break;
            }
        }
        if (i >= battery->b_listener_count) {
            return -1;
        }

        clear_bit(battery->b_listeners[i].ld_prop_read_mask,
                  prop->bp_prop_num);
    } else {
        for (i = 0; i < BATTERY_MAX_COUNT; ++i) {
            battery = battery_manager.bm_batteries[i];
            for (j = 0; j < battery->b_listener_count; ++i) {
                if (battery->b_listeners[j].ld_listener == listener) {
                    battery->b_listener_count--;
                    if (j < battery->b_listener_count) {
                        battery->b_listeners[j] =
                            battery->b_listeners[battery->b_listener_count];
                    }
                    battery->b_listeners =
                        realloc(battery->b_listeners,
                                sizeof(struct listener_data) *
                                battery->b_listener_count);
                    break;
                }
            }
        }
    }

    battery_update_polled_properties(battery);

    return 0;
}

static int
battery_open(struct os_dev *dev, uint32_t timeout, void *arg)
{
    struct battery *bat = (struct battery *)dev;
    struct os_dev *drv_dev;
    int rollback = -1;
    int i;
    int rc = 0;

    for (i = 0; i < BATTERY_DRIVERS_MAX && bat->b_drivers[i]; ++i) {
        drv_dev = os_dev_open(bat->b_drivers[i]->dev.od_name, 0, NULL);
        assert(drv_dev == &bat->b_drivers[i]->dev);
        if (drv_dev != &bat->b_drivers[i]->dev) {
            rollback = i - 1;
            rc = -1;
            break;
        }
    }
    for (i = rollback; i >= 0; --i) {
        os_dev_close(&bat->b_drivers[i]->dev);
    }
    return rc;
}

static int
battery_close(struct os_dev *dev)
{
    struct battery *bat = (struct battery *)dev;
    int i;

    for (i = 0; i < BATTERY_DRIVERS_MAX && bat->b_drivers[i]; ++i) {
        os_dev_close(&bat->b_drivers[i]->dev);
    }
    return 0;
}

int
battery_init(struct os_dev *dev, void *arg)
{
    int i;
    struct battery *bat = (struct battery *)dev;

    OS_DEV_SETHANDLERS(dev, battery_open, battery_close);

    os_mutex_pend(&battery_manager.bm_lock, OS_WAIT_FOREVER);
    for (i = 0; i < BATTERY_MAX_COUNT; ++i) {
        if (battery_manager.bm_batteries[i] == NULL) {
            break;
        }
    }
    os_mutex_release(&battery_manager.bm_lock);

    assert(i < BATTERY_MAX_COUNT);
    battery_manager.bm_batteries[i] = bat;

    memset(bat->b_drivers, 0, sizeof(bat->b_drivers));

    return 0;
}
