/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed 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.
 */

#define LOG_TAG "lights"

#include <cutils/log.h>

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>

#include <sys/ioctl.h>
#include <sys/types.h>

#include <hardware/lights.h>
#include <hardware/hardware.h>

#define ALOG_ONCE(mask, op, ...)			\
	do {					\
		if (!(mask & op)) {		\
			ALOGE(__VA_ARGS__);	\
			mask |= op;		\
		}				\
	} while (0);
#define OP_WRITE_OPEN		(1 << 0)
#define OP_BRIGHTNESS_PATH	(1 << 1)
#define OP_BRIGHTNESS_VALUE	(1 << 2)
#define OP_BRIGHTNESS_WRITE	(1 << 3)
#define OP_MAX_BRIGHTNESS_PATH	(1 << 4)
#define OP_MAX_BRIGHTNESS_OPEN	(1 << 5)
#define OP_MAX_BRIGHTNESS_READ	(1 << 6)

struct dragon_lights {
	struct light_device_t base;

	pthread_mutex_t lock;
	char const *sysfs_path;

	int max_brightness;

	unsigned long logged_failures;
};

static char const * kBacklightPath =
	"/sys/class/backlight/lpm102a188a-backlight";
static const int kNumBrightnessLevels = 16;
static const int kBrightnessLevels[] =
	{8, 25, 30, 40, 55, 65, 75, 85, 95, 105, 120, 135, 160, 180, 200, 220};

static struct dragon_lights *to_dragon_lights(struct light_device_t *dev)
{
	return (struct dragon_lights *)dev;
}

static int write_brightness(struct dragon_lights *lights, int brightness)
{
	char buffer[20], path[PATH_MAX];
	int fd, bytes, amt, ret = 0;

	bytes = snprintf(path, sizeof(path), "%s/brightness",
			 lights->sysfs_path);
	if (bytes < 0 || (size_t)bytes >= sizeof(path)) {
		ALOG_ONCE(lights->logged_failures, OP_BRIGHTNESS_PATH,
			  "failed to create brightness path %d\n", bytes);
		return -EINVAL;
	}

	fd = open(path, O_RDWR);
	if (fd < 0) {
		ALOG_ONCE(lights->logged_failures, OP_WRITE_OPEN,
			  "write_int failed to open %s/%d\n", path, errno);
		return -errno;
	}

	bytes = snprintf(buffer, sizeof(buffer), "%d\n", brightness);
	if (bytes < 0 || (size_t)bytes >= sizeof(buffer)) {
		ALOG_ONCE(lights->logged_failures, OP_BRIGHTNESS_VALUE,
			  "failed to create brightness value %d/%d\n",
			  brightness, bytes);
		ret = -EINVAL;
		goto out;
	}
	amt = write(fd, buffer, bytes);
	if (amt != bytes) {
		ALOG_ONCE(lights->logged_failures, OP_BRIGHTNESS_WRITE,
			  "failed to write brightness value %d/%d\n", amt,
			  bytes);
		ret = amt == -1 ? -errno : -EINVAL;
		goto out;
	}

out:
	close(fd);
	return ret;
}

static int read_max_brightness(struct dragon_lights *lights, int *value)
{
	char buffer[20], path[PATH_MAX];
	int ret = 0, fd, bytes;

	bytes = snprintf(path, sizeof(path), "%s/max_brightness",
			 lights->sysfs_path);
	if (bytes < 0 || (size_t)bytes >= sizeof(path)) {
		ALOG_ONCE(lights->logged_failures, OP_MAX_BRIGHTNESS_PATH,
			  "failed to create max_brightness path %d\n", bytes);
		return -EINVAL;
	}

	fd = open(path, O_RDONLY);
	if (fd < 0) {
		ALOG_ONCE(lights->logged_failures, OP_MAX_BRIGHTNESS_OPEN,
			  "failed to open max_brightness %s/%d\n", path, errno);
		return -errno;
	}

	bytes = read(fd, buffer, sizeof(buffer));
	if (bytes <= 0) {
		ALOG_ONCE(lights->logged_failures, OP_MAX_BRIGHTNESS_READ,
			  "failed to read max_brightness %s/%d\n", path, errno);
		ret = -errno;
		goto out;
	}

	*value = atoi(buffer);

out:
	close(fd);
	return ret;
}

static int rgb_to_brightness(struct light_state_t const *state)
{
	int color = state->color & 0x00ffffff;
	return ((77 * ((color >> 16) & 0x00ff))
			+ (150 * ((color >> 8) & 0x00ff)) +
			(29 * (color & 0x00ff))) >> 8;
}

static int set_light_backlight(struct light_device_t *dev,
			       struct light_state_t const *state)
{
	struct dragon_lights *lights = to_dragon_lights(dev);
	int err, brightness_idx;
	int brightness = rgb_to_brightness(state);

	if (brightness > 0) {
		// Get the bin number for brightness (0 to kNumBrightnessLevels - 1)
		brightness_idx = (brightness - 1) * kNumBrightnessLevels / 0xff;

		// Get brightness level
		brightness = kBrightnessLevels[brightness_idx];
	}

	pthread_mutex_lock(&lights->lock);
	err = write_brightness(lights, brightness);
	pthread_mutex_unlock(&lights->lock);

	return err;
}

static int close_lights(struct hw_device_t *dev)
{
	struct dragon_lights *lights = (struct dragon_lights *)dev;
	if (lights)
		free(lights);
	return 0;
}

static int open_lights(const struct hw_module_t *module, char const *name,
		       struct hw_device_t **device)
{
	struct dragon_lights *lights;
	int ret;

	// Only support backlight at the moment
	if (strcmp(LIGHT_ID_BACKLIGHT, name))
		return -EINVAL;

	lights = malloc(sizeof(*lights));
	if (lights == NULL) {
		ALOGE("failed to allocate lights memory");
		return -ENOMEM;
	}
	memset(lights, 0, sizeof(*lights));

	pthread_mutex_init(&lights->lock, NULL);

	lights->sysfs_path = kBacklightPath;

	ret = read_max_brightness(lights, &lights->max_brightness);
	if (ret) {
		close_lights((struct hw_device_t *)lights);
		return ret;
	}

	lights->base.common.tag = HARDWARE_DEVICE_TAG;
	lights->base.common.version = 0;
	lights->base.common.module = (struct hw_module_t *)module;
	lights->base.common.close = close_lights;
	lights->base.set_light = set_light_backlight;

	*device = (struct hw_device_t *)lights;
	return 0;
}

static struct hw_module_methods_t lights_methods = {
	.open =  open_lights,
};

struct hw_module_t HAL_MODULE_INFO_SYM = {
	.tag = HARDWARE_MODULE_TAG,
	.version_major = 1,
	.version_minor = 0,
	.id = LIGHTS_HARDWARE_MODULE_ID,
	.name = "dragon lights module",
	.author = "Google, Inc.",
	.methods = &lights_methods,
};