/*
 *Copyright (c) 2015, The Linux Foundation. All rights reserved.
 *
 *Redistribution and use in source and binary forms, with or without
 *modification, are permitted provided that the following conditions are
 *met:
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials provided
 *      with the distribution.
 *    * Neither the name of The Linux Foundation nor the names of its
 *      contributors may be used to endorse or promote products derived
 *      from this software without specific prior written permission.
 *
 *THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
 *WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
 *ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 *BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 *BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 *WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 *OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 *IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <dirent.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <cutils/klog.h>
#include <batteryservice/BatteryService.h>
#include <cutils/android_reboot.h>
#include <healthd.h>
#include "minui/minui.h"
#include "healthd_msm.h"
#define ARRAY_SIZE(x)           (sizeof(x)/sizeof(x[0]))

#define HVDCP_CHARGER           "USB_HVDCP"
#define HVDCP_BLINK_TYPE        2

#define RED_LED_PATH            "/sys/class/leds/red/brightness"
#define GREEN_LED_PATH          "/sys/class/leds/green/brightness"
#define BLUE_LED_PATH           "/sys/class/leds/blue/brightness"
#define RED_LED_BLINK_PATH      "/sys/class/leds/red/blink"
#define GREEN_LED_BLINK_PATH    "/sys/class/leds/green/blink"
#define BACKLIGHT_PATH          "/sys/class/leds/lcd-backlight/brightness"

#define CHARGING_ENABLED_PATH   "/sys/class/power_supply/battery/charging_enabled"
#define CHARGER_TYPE_PATH       "/sys/class/power_supply/usb/type"

#define LOGE(x...) do { KLOG_ERROR("charger", x); } while (0)
#define LOGW(x...) do { KLOG_WARNING("charger", x); } while (0)
#define LOGV(x...) do { KLOG_DEBUG("charger", x); } while (0)

enum {
    RED_LED = 0x01 << 0,
    GREEN_LED = 0x01 << 1,
    BLUE_LED = 0x01 << 2,
};

struct led_ctl {
    int color;
    const char *path;
};

struct led_ctl leds[3] =
    {{RED_LED, RED_LED_PATH},
    {GREEN_LED, GREEN_LED_PATH},
    {BLUE_LED, BLUE_LED_PATH}};

#define HVDCP_COLOR_MAP         (RED_LED | GREEN_LED)

struct soc_led_color_mapping {
    int soc;
    int color;
};

struct soc_led_color_mapping soc_leds[3] = {
    {15, RED_LED},
    {90, RED_LED | GREEN_LED},
    {100, GREEN_LED},
};

static int write_file_int(char const* path, int value)
{
    int fd;
    char buffer[20];
    int rc = -1, bytes;

    fd = open(path, O_WRONLY);
    if (fd >= 0) {
        bytes = snprintf(buffer, sizeof(buffer), "%d\n", value);
        rc = write(fd, buffer, bytes);
        close(fd);
    }

    return rc > 0 ? 0 : -1;
}

static int set_tricolor_led(int on, int color)
{
    int fd, i;
    char buffer[10];

    for (i = 0; i < (int)ARRAY_SIZE(leds); i++) {
        if ((color & leds[i].color) && (access(leds[i].path, R_OK | W_OK) == 0)) {
            fd = open(leds[i].path, O_RDWR);
            if (fd < 0) {
                LOGE("Could not open red led node\n");
                goto cleanup;
            }
            if (on)
                snprintf(buffer, sizeof(int), "%d\n", 255);
            else
                snprintf(buffer, sizeof(int), "%d\n", 0);

            if (write(fd, buffer, strlen(buffer)) < 0)
                LOGE("Could not write to led node\n");
cleanup:
            if (fd >= 0)
                close(fd);
        }
    }

    return 0;
}

static bool is_hvdcp_inserted()
{
    bool hvdcp = false;
    char buff[12] = "\0";
    int fd, cnt;

    fd = open(CHARGER_TYPE_PATH, O_RDONLY);
    if (fd >= 0) {
        cnt = read(fd, buff, (sizeof(buff) - 1));
        if (cnt > 0 && !strncmp(buff, HVDCP_CHARGER, 9))
            hvdcp = true;
        close(fd);
    }

    return hvdcp;
}

static int leds_blink_for_hvdcp_allow(void)
{
    int rc = 0, bytes;
    int red_blink_fd = -1, green_blink_fd = -1, type_fd = -1;
    char buf[20];

    green_blink_fd = open(GREEN_LED_BLINK_PATH, O_RDWR);
    red_blink_fd = open(RED_LED_BLINK_PATH, O_RDWR);
    if (red_blink_fd < 0 && green_blink_fd < 0) {
        LOGE("Could not open red && green led blink node\n");
    } else {
        type_fd = open(CHARGER_TYPE_PATH, O_RDONLY);
        if (type_fd < 0) {
            LOGE("Could not open USB type node\n");
            close(red_blink_fd);
            close(green_blink_fd);
            return rc;
        } else {
            close(type_fd);
            if (red_blink_fd > 0) {
                rc |= RED_LED;
                bytes = snprintf(buf, sizeof(buf), "%d\n", 0);
                if (write(red_blink_fd, buf, bytes) < 0) {
                    LOGE("Fail to write: %s\n", RED_LED_BLINK_PATH);
                    rc = 0;
                }
                close(red_blink_fd);
            }
            if (green_blink_fd > 0) {
                rc |= GREEN_LED;
                bytes = snprintf(buf, sizeof(buf), "%d\n", 0);
                if (write(green_blink_fd, buf, bytes) < 0) {
                    LOGE("Fail to write: %s\n", GREEN_LED_BLINK_PATH);
                    rc = 0;
                }
                close(green_blink_fd);
            }
        }
    }

    return rc;
}

#define STR_LEN 8
void healthd_board_mode_charger_draw_battery(
                struct android::BatteryProperties *batt_prop)
{
    char cap_str[STR_LEN];
    int x, y;
    int str_len_px;
    static int char_height = -1, char_width = -1;

    if (char_height == -1 && char_width == -1)
        gr_font_size(&char_width, &char_height);
    snprintf(cap_str, (STR_LEN - 1), "%d%%", batt_prop->batteryLevel);
    str_len_px = gr_measure(cap_str);
    x = (gr_fb_width() - str_len_px) / 2;
    y = (gr_fb_height() + char_height) / 2;
    gr_color(0xa4, 0xc6, 0x39, 255);
    gr_text(x, y, cap_str, 0);
}

void healthd_board_mode_charger_battery_update(
                struct android::BatteryProperties *batt_prop)
{
    static int blink_for_hvdcp = -1;
    static int old_color = 0;
    int i, color, soc, rc;
    bool blink = false;

    if (blink_for_hvdcp == -1)
        blink_for_hvdcp = leds_blink_for_hvdcp_allow();

    if ((blink_for_hvdcp > 0) && is_hvdcp_inserted())
        blink = true;

    soc = batt_prop->batteryLevel;

    for (i = 0; i < ((int)ARRAY_SIZE(soc_leds) - 1); i++) {
        if (soc < soc_leds[i].soc)
            break;
    }
    color = soc_leds[i].color;

    if (old_color != color) {
        if ((color == HVDCP_COLOR_MAP) && blink) {
            if (blink_for_hvdcp & RED_LED) {
                rc = write_file_int(RED_LED_BLINK_PATH, HVDCP_BLINK_TYPE);
                if (rc < 0) {
                    LOGE("Fail to write: %s\n", RED_LED_BLINK_PATH);
                    return;
                }
            }
            if (blink_for_hvdcp & GREEN_LED) {
                rc = write_file_int(GREEN_LED_BLINK_PATH, HVDCP_BLINK_TYPE);
                if (rc < 0) {
                    LOGE("Fail to write: %s\n", GREEN_LED_BLINK_PATH);
                    return;
                }
            }
        } else {
                set_tricolor_led(0, old_color);
                set_tricolor_led(1, color);
                old_color = color;
                LOGV("soc = %d, set led color 0x%x\n", soc, soc_leds[i].color);
        }
    }
}

#define BACKLIGHT_ON_LEVEL    100
#define BACKLIGHT_OFF_LEVEL    0
void healthd_board_mode_charger_set_backlight(bool en)
{
    int fd;
    char buffer[10];

    if (access(BACKLIGHT_PATH, R_OK | W_OK) != 0)
    {
        LOGW("Backlight control not support\n");
        return;
    }

    memset(buffer, '\0', sizeof(buffer));
    fd = open(BACKLIGHT_PATH, O_RDWR);
    if (fd < 0) {
        LOGE("Could not open backlight node : %s\n", strerror(errno));
        goto cleanup;
    }
    LOGV("set backlight status to %d\n", en);
    if (en)
        snprintf(buffer, sizeof(buffer), "%d\n", BACKLIGHT_ON_LEVEL);
    else
        snprintf(buffer, sizeof(buffer), "%d\n", BACKLIGHT_OFF_LEVEL);

    if (write(fd, buffer,strlen(buffer)) < 0) {
        LOGE("Could not write to backlight node : %s\n", strerror(errno));
        goto cleanup;
    }
cleanup:
    if (fd >= 0)
        close(fd);
}

void healthd_board_mode_charger_init()
{
    int ret;
    char buff[8] = "\0";
    int charging_enabled = 0;
    int fd;

    /* check the charging is enabled or not */
    fd = open(CHARGING_ENABLED_PATH, O_RDONLY);
    if (fd < 0)
        return;
    ret = read(fd, buff, (sizeof(buff) - 1));
    close(fd);
    if (ret > 0) {
        buff[ret] = '\0';
        sscanf(buff, "%d\n", &charging_enabled);
        LOGW(CHGR_TAG, "android charging is %s\n",
                !!charging_enabled ? "enabled" : "disabled");
        /* if charging is disabled, reboot and exit power off charging */
        if (charging_enabled)
            return;
        LOGW("android charging is disabled, exit!\n");
        android_reboot(ANDROID_RB_RESTART, 0, 0);
    }
}

void healthd_board_init(struct healthd_config*)
{
    // use defaults
    power_off_alarm_init();
}

int healthd_board_battery_update(struct android::BatteryProperties*)
{
    // return 0 to log periodic polled battery status to kernel log
    return 1;
}
