/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2016 Amaury Pouly
 *
 * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "system.h"
#include "power-nwz.h"
#include "button-target.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/reboot.h>

static int power_fd = -1; /* file descriptor */

void power_init(void)
{
    power_fd = open(NWZ_POWER_DEV, O_RDWR);
}

void power_close(void)
{
    close(power_fd);
}

int nwz_power_get_status(void)
{
    int status;
    if(ioctl(power_fd, NWZ_POWER_GET_STATUS, &status) < 0)
        return -1;
    return status;
}

static int nwz_power_adval_to_mv(int adval, int ad_base)
{
    if(adval == -1)
        return -1;
    /* the AD base corresponds to the millivolt value if adval was 255 */
    return (adval * ad_base) / 255;
}

int nwz_power_get_vbus_adval(void)
{
    int status;
    if(ioctl(power_fd, NWZ_POWER_GET_VBUS_ADVAL, &status) < 0)
        return -1;
    return status;
}

int nwz_power_get_vbus_voltage(void)
{
    return nwz_power_adval_to_mv(nwz_power_get_vbus_adval(), NWZ_POWER_AD_BASE_VBUS);
}

int nwz_power_get_vbus_limit(void)
{
    int status;
    if(ioctl(power_fd, NWZ_POWER_GET_VBUS_LIMIT, &status) < 0)
        return -1;
    return status;
}

int nwz_power_get_charge_switch(void)
{
    int status;
    if(ioctl(power_fd, NWZ_POWER_GET_CHARGE_SWITCH, &status) < 0)
        return -1;
    return status;
}

int nwz_power_get_charge_current(void)
{
    int status;
    if(ioctl(power_fd, NWZ_POWER_GET_CHARGE_CURRENT, &status) < 0)
        return -1;
    return status;
}

int nwz_power_get_battery_gauge(void)
{
    int status;
    if(ioctl(power_fd, NWZ_POWER_GET_BAT_GAUGE, &status) < 0)
        return -1;
    return status;
}

int nwz_power_get_battery_adval(void)
{
    int status;
    if(ioctl(power_fd, NWZ_POWER_GET_BAT_ADVAL, &status) < 0)
        return -1;
    return status;
}

int nwz_power_get_battery_voltage(void)
{
    return nwz_power_adval_to_mv(nwz_power_get_battery_adval(), NWZ_POWER_AD_BASE_VBAT);
}

int nwz_power_get_vbat_adval(void)
{
    int status;
    if(ioctl(power_fd, NWZ_POWER_GET_VBAT_ADVAL, &status) < 0)
        return -1;
    return status;
}

int nwz_power_get_vbat_voltage(void)
{
    return nwz_power_adval_to_mv(nwz_power_get_vbat_adval(), NWZ_POWER_AD_BASE_VBAT);
}

int nwz_power_get_sample_count(void)
{
    int status;
    if(ioctl(power_fd, NWZ_POWER_GET_SAMPLE_COUNT, &status) < 0)
        return -1;
    return status;
}

int nwz_power_get_vsys_adval(void)
{
    int status;
    if(ioctl(power_fd, NWZ_POWER_GET_VSYS_ADVAL, &status) < 0)
        return -1;
    return status;
}

int nwz_power_get_vsys_voltage(void)
{
    return nwz_power_adval_to_mv(nwz_power_get_vsys_adval(), NWZ_POWER_AD_BASE_VSYS);
}

int nwz_power_get_acc_charge_mode(void)
{
    int status;
    if(ioctl(power_fd, NWZ_POWER_GET_ACCESSARY_CHARGE_MODE, &status) < 0)
        return -1;
    return status;
}

int nwz_power_is_fully_charged(void)
{
    int status;
    if(ioctl(power_fd, NWZ_POWER_IS_FULLY_CHARGED, &status) < 0)
        return -1;
    return status;
}

static int write_string_to_file(const char *file, const char *msg)
{
    int fd = open(file, O_WRONLY);
    if(fd < 0)
        return -1;
    const int len = strlen(msg);
    int res = write(fd, msg, len);
    close(fd);
    return res == len ? 0 : -1;
}

int do_nwz_power_suspend(void)
{
    /* older devices use /proc/pm, while the new one use the standard sys file */
    if(write_string_to_file("/proc/pm", "S3") == 0)
        return 0;
    return write_string_to_file("/sys/power/state", "mem");
}

int nwz_power_suspend(void)
{
    int ret = do_nwz_power_suspend();
    /* the button driver tracks button status using events, but the kernel does
     * not generate an event if a key is changed during suspend, so make sure we
     * reload as much information as possible */
    nwz_button_reload_after_suspend();
    return ret;
}

int nwz_power_shutdown(void)
{
    sync(); /* man page advises to sync to avoid data loss */
    return reboot(RB_POWER_OFF);
}

int nwz_power_restart(void)
{
    sync(); /* man page advises to sync to avoid data loss */
    return reboot(RB_AUTOBOOT);
}
