/*
 * Copyright (C) 2013 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.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>

#include <pthread.h>

#include <hardware/hardware.h>
#include <hardware/gralloc.h>
#include <hardware/hwcomposer.h>

#include <system/window.h>
#include <cutils/native_handle.h>

// normalize and shorten type names
typedef struct android_native_base_t aBase;
typedef struct ANativeWindowBuffer aBuffer;
typedef struct ANativeWindow aWindow;

static int trace_level = 1;

#define _TRACE(n,fmt...) \
	do { if (trace_level >= n) fprintf(stderr, "CNW: " fmt); } while (0)

#define ERROR(fmt...) _TRACE(0, fmt)
#define INFO(fmt...) _TRACE(1, fmt)
#define LOG(fmt...) _TRACE(2, fmt)
#define TRACE(fmt...) _TRACE(3, fmt)

#define QCT_WORKAROUND 1

typedef struct CNativeBuffer {
	aBuffer base;
	struct CNativeBuffer *next;
	struct CNativeBuffer *prev;
	int ffd;
} CNativeBuffer;

typedef struct CNativeWindow {
	aWindow base;

	hwc_composer_device_1_t *hwc;
	framebuffer_device_t *fb;
	alloc_device_t *gr;

	pthread_mutex_t lock;
	pthread_cond_t cvar;

	aBuffer *front;
	aBuffer *spare;

	CNativeBuffer free_buffer_queue;

	unsigned width;
	unsigned height;
	unsigned xdpi;
	unsigned ydpi;
	unsigned format;

	hwc_display_contents_1_t *dclist[HWC_NUM_PHYSICAL_DISPLAY_TYPES];

	hwc_display_contents_1_t dc;
	hwc_layer_1_t layer[4];
} CNativeWindow;

static inline CNativeBuffer *from_abuffer(aBuffer *buf) {
	return (CNativeBuffer*) buf;
}

static CNativeBuffer *get_front(struct CNativeBuffer *queue) {
	CNativeBuffer *buf = queue->next;
	if (buf == queue)
		return 0;
	buf->next->prev = queue;
	queue->next = buf->next;
	buf->next = buf->prev = 0;
	return buf;
}

static void put_front(struct CNativeBuffer *queue, aBuffer *_buf) {
	struct CNativeBuffer *buf = (struct CNativeBuffer *) _buf;
	buf->prev = queue;
	buf->next = queue->next;
	queue->next->prev = buf;
	queue->next = buf;
}

static void put_back(struct CNativeBuffer *queue, aBuffer *_buf) {
	struct CNativeBuffer *buf = (struct CNativeBuffer *) _buf;
	buf->next = queue;
	buf->prev = queue->prev;
	queue->prev->next = buf;
	queue->prev = buf;
}

static void cnw_inc_ref(aBase *base) { TRACE("buf %p ref++\n",base); }
static void cnw_dec_ref(aBase *base) { TRACE("buf %p ref--\n",base); }

static inline CNativeWindow *from_base(aWindow *base) {
	return (CNativeWindow *) base;
}

static inline CNativeWindow *from_base_const(const aWindow *base) {
	return (CNativeWindow *) base;
}

static int cnw_set_swap_interval(aWindow *base, int interval) {
	CNativeWindow *win = from_base(base);
	if (win->fb && win->fb->setSwapInterval)
		return win->fb->setSwapInterval(win->fb, interval);
	return 0;
}

static int cnw_dequeue_buffer1(aWindow *base, aBuffer **buf, int *ffd) {
	CNativeWindow *win = from_base(base);
	CNativeBuffer *cnb;

	pthread_mutex_lock(&win->lock);

	while ((cnb = get_front(&win->free_buffer_queue)) == 0) {
		pthread_cond_wait(&win->cvar, &win->lock);
	}

	*ffd = cnb->ffd;
	*buf = &cnb->base;
	cnb->ffd = -1;
	LOG("<< dequeue buffer %p %d\n", *buf, *ffd);

	pthread_mutex_unlock(&win->lock);
	return 0;
}

static int cnw_lock_buffer0(aWindow *base, aBuffer *buffer) {
	return 0;
}

static void set_layer(hwc_layer_1_t *dl, aBuffer *buf, int ffd) {
	int right = buf->width;
	int bottom = buf->height;

	dl->compositionType = HWC_FRAMEBUFFER;
	dl->hints = 0;
	dl->flags = 0;

	dl->handle = buf->handle;
	dl->transform = 0;
	dl->blending = HWC_BLENDING_NONE;
	dl->sourceCrop.left = 0;
	dl->sourceCrop.top = 0;
	dl->sourceCrop.right = right;
	dl->sourceCrop.bottom = bottom;
	dl->displayFrame.left = 0;
	dl->displayFrame.top = 0;
	dl->displayFrame.right = right;
	dl->displayFrame.bottom = bottom;
	dl->visibleRegionScreen.numRects = 1;
	dl->visibleRegionScreen.rects = &dl->displayFrame;

	dl->acquireFenceFd = ffd;
	dl->releaseFenceFd = -1;
}

static void hwc_post(CNativeWindow *win, aBuffer *buf, int ffd) {
	hwc_composer_device_1_t *hwc = win->hwc;
	hwc_display_contents_1_t *dc = &(win->dc);
	hwc_layer_1_t *dl = win->dc.hwLayers;
	int r, i;

	dc->retireFenceFd = -1;
	dc->outbufAcquireFenceFd = -1;
	dc->flags = HWC_GEOMETRY_CHANGED;
	dc->numHwLayers = 1;

	// some hwcomposers fail if these are NULL
	dc->dpy = (void*) 0xdeadbeef;
	dc->sur = (void*) 0xdeadbeef;

	set_layer(&dl[0], buf, ffd);

	if (QCT_WORKAROUND) {
		set_layer(&dl[1], win->spare, -1);
		dl[1].compositionType = HWC_FRAMEBUFFER_TARGET;
		dc->numHwLayers++;
	}

	r = hwc->prepare(hwc, HWC_NUM_PHYSICAL_DISPLAY_TYPES, win->dclist);
	if (r) {
		ERROR("hwc->prepare failed r=%d\n",r);
		return;
	}

//	for (i = 0; i < dc->numHwLayers; i++)
//		LOG("dl[%d] ctype=0x%08x hints=0x%08x flags=0x%08x\n", i,
//			dl[i].compositionType, dl[0].hints, dl[0].flags);

	r = hwc->set(hwc, HWC_NUM_PHYSICAL_DISPLAY_TYPES, win->dclist);
	if (r) {
		ERROR("hwc->set failed, r=%d\n", r);
		return;
	}

	if (dc->retireFenceFd != -1)
		close(dc->retireFenceFd);
	if (dl->releaseFenceFd != -1) {
		CNativeBuffer *cnb = from_abuffer(buf);
		cnb->ffd = dl->releaseFenceFd;
	}
	if (QCT_WORKAROUND)
		if (dl[1].releaseFenceFd != -1)
			close(dl[1].releaseFenceFd);
}

static int cnw_queue_buffer1(aWindow *base, aBuffer *buffer, int ffd) {
	CNativeWindow *win = from_base(base);
	int res;
	LOG(">> queue buffer %p %d\n", buffer, ffd);
	if (win->fb) {
		res = win->fb->post(win->fb, buffer->handle);
		if (ffd != -1)
			close(ffd);
	} else {
		hwc_post(win, buffer, ffd);
		res = 0;
	}
	pthread_mutex_lock(&win->lock);
	if (win->front)
		put_back(&win->free_buffer_queue, win->front);
	win->front = buffer;
	pthread_cond_signal(&win->cvar);
	pthread_mutex_unlock(&win->lock);

	return res;
}

static int cnw_cancel_buffer1(aWindow *base, aBuffer *buf, int ffd) {
	CNativeWindow *win = from_base(base);
	CNativeBuffer *cnb = from_abuffer(buf);
	LOG("<< cancel buffer %p %d\n", buf, ffd);
	cnb->ffd = ffd;
	pthread_mutex_lock(&win->lock);
	put_front(&win->free_buffer_queue, buf);
	pthread_mutex_unlock(&win->lock);
	return 0;
}

static int cnw_dequeue_buffer0(aWindow *base, aBuffer **buf) {
	int ffd = -1;
	int r;
	r = cnw_dequeue_buffer1(base, buf, &ffd);
	if (ffd != -1)
		close(ffd);
	return r;
}

static int cnw_queue_buffer0(aWindow *base, aBuffer *buf) {
	return cnw_queue_buffer1(base, buf, -1);
}

static int cnw_cancel_buffer0(aWindow *base, aBuffer *buf) {
	return cnw_cancel_buffer1(base, buf, -1);
}

static int cnw_query(const aWindow *base, int what, int *value) {
	CNativeWindow *win = from_base_const(base);

	switch (what) {
	case NATIVE_WINDOW_WIDTH:
	case NATIVE_WINDOW_DEFAULT_WIDTH:
		*value = win->width;
		TRACE("query window width: %d\n", *value);
		return 0;
	case NATIVE_WINDOW_HEIGHT:
	case NATIVE_WINDOW_DEFAULT_HEIGHT:
		*value = win->height;
		TRACE("query window height: %d\n", *value);
		return 0;
	case NATIVE_WINDOW_FORMAT:
		*value = win->format;
		TRACE("query window format: %d\n", *value);
		return 0;
	case NATIVE_WINDOW_TRANSFORM_HINT:
		TRACE("query transform hint: 0\n");
		*value = 0;
		return 0;
	case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
		TRACE("query min undequeued buffers: 1\n");
		*value = 1;
		return 0;
	default:
		*value = 0;
		ERROR("query %d unknown!\n", what);
		return -EINVAL;
	}
}

static int cnw_perform(aWindow *base, int op, ...) {
	CNativeWindow *win = from_base(base);
	va_list ap;
	va_start(ap, op);

	switch (op) {
	case NATIVE_WINDOW_SET_USAGE:
		TRACE("set usage %d\n", va_arg(ap,int));
		return 0;
	case NATIVE_WINDOW_CONNECT:
	case NATIVE_WINDOW_DISCONNECT:
	case NATIVE_WINDOW_API_CONNECT:
	case NATIVE_WINDOW_API_DISCONNECT:
		return 0;
	case NATIVE_WINDOW_SET_BUFFERS_FORMAT:
		TRACE("set buffers format %d\n", va_arg(ap,int));
		return 0;
	case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM:
		TRACE("set buffers transform %d\n", va_arg(ap,int));
		return 0;
	case NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP:
		TRACE("set buffers timestamp %lld\n", va_arg(ap,long long));
		return 0;
	case NATIVE_WINDOW_SET_SCALING_MODE:
		TRACE("set scaling mode %d\n", va_arg(ap,int));
		return 0;
	case NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS: {
		int w = va_arg(ap,int);
		int h = va_arg(ap,int);
		if ((w == win->width) && (h == win->height)) {
			TRACE("set buffers dimensions %d x %d\n", w, h);
			return 0;
		}
		ERROR("cannot resize buffers to %d x %d\n", w, h);
		return -1;
	}
	default:
		ERROR("perform %d unknown!\n", op);
		return -ENODEV;
	}
}

static void hwc_invalidate(const struct hwc_procs *procs) {}
static void hwc_vsync(const struct hwc_procs *procs, int disp, int64_t ts) {}
static void hwc_hotplug(const struct hwc_procs *procs, int disp, int conn) {}

struct hwc_procs hprocs = {
	.invalidate = hwc_invalidate,
	.vsync = hwc_vsync,
	.hotplug = hwc_hotplug,
};

uint32_t attrs[] = {
	HWC_DISPLAY_WIDTH,
	HWC_DISPLAY_HEIGHT,
	HWC_DISPLAY_VSYNC_PERIOD,
	HWC_DISPLAY_DPI_X,
	HWC_DISPLAY_DPI_Y,
	HWC_DISPLAY_NO_ATTRIBUTE,
};

static int hwc_init(CNativeWindow *win) {
	hw_module_t const* module;
	hwc_composer_device_1_t *hwc;
	unsigned i;
	int r;
	uint32_t configs[32];
	uint32_t numconfigs = 32;
	int32_t values[8];

	if (hw_get_module(HWC_HARDWARE_MODULE_ID, &module) != 0) {
		ERROR("cannot open hw composer module\n");
		return -ENODEV;
	}

	if (hwc_open_1(module, &hwc)) {
		ERROR("cannot open hwc device\n");
		return -ENODEV;
	}
	win->hwc = hwc;

	LOG("hwc version 0x%08x\n", hwc->common.version);

	if ((hwc->common.version & 0xFFFF0000) < 0x01010000) {
		ERROR("hwc version less than 1.1\n");
		hwc_close_1(hwc);
		return -ENODEV;
	}

	hwc->registerProcs(hwc, &hprocs);

	if (hwc->getDisplayConfigs(hwc, 0, configs, &numconfigs)) {
		ERROR("cannot get configs\n");
		return -ENODEV;
	}
	for (i = 0; i < numconfigs; i++)
		LOG("cfg[%d] = 0x%08x\n", i, configs[i]);

	if ((r = hwc->getDisplayAttributes(hwc, 0, configs[0], attrs, values))) {
		ERROR("cannot get attributes %d\n", r);
		return -ENODEV;
	}

	win->width = values[0];
	win->height = values[1];
	win->xdpi = values[3];
	win->ydpi = values[4];
	win->format = HAL_PIXEL_FORMAT_RGBA_8888;

	hwc->blank(hwc, 0, 0);

	win->dclist[0] = &(win->dc);
	return 0;
}

static aBuffer *cnw_alloc(CNativeWindow *win, unsigned format, unsigned usage) {
	CNativeBuffer *cnb;
	aBuffer *buf;
	int err;

	if (!(cnb = malloc(sizeof(CNativeBuffer))))
		return 0;

	buf = &cnb->base;
	cnb->ffd = -1;

	buf->common.magic = ANDROID_NATIVE_BUFFER_MAGIC;
	buf->common.version = sizeof(aBuffer);
	buf->common.incRef = cnw_inc_ref;
	buf->common.decRef = cnw_dec_ref;

	buf->width = win->width;
	buf->height = win->height;
	buf->format = format;
	buf->usage = usage;

	err = win->gr->alloc(win->gr, win->width, win->height,
		format, usage, &buf->handle, &buf->stride);
	if (err) {
		ERROR("gralloc of %d x %d failed: err=%d\n",
			win->width, win->height, err);
		free(buf);
		return 0;
	}
	INFO("alloc buffer %p %d x %d\n", buf, win->width, win->height);
	return buf;
}

static int cnw_init(CNativeWindow *win) {
	hw_module_t const* module;
	framebuffer_device_t *fb = NULL;
	alloc_device_t *gr;
	int err, i, n;
	unsigned usage, format;

	memset(win, 0, sizeof(CNativeWindow));

	win->free_buffer_queue.next = &(win->free_buffer_queue);
	win->free_buffer_queue.prev = &(win->free_buffer_queue);

	if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module) != 0) {
		ERROR("cannot open gralloc module\n");
		return -ENODEV;
	}

	if (hwc_init(win)) {
		ERROR("cannot open hwcomposer, trying legacy fb HAL\n");
		err = framebuffer_open(module, &fb);
		if (err) {
			ERROR("cannot open fb HAL (%s)", strerror(-err));
			return -ENODEV;
		}
		win->width = fb->width;
		win->height = fb->height;
		win->format = fb->format;
		win->xdpi = fb->xdpi;
		win->ydpi = fb->ydpi;
		win->fb = fb;
	}

	INFO("display %d x %d fmt=%d\n",
		win->width, win->height, win->format);

	err = gralloc_open(module, &gr);
	if (err) {
		ERROR("couldn't open gralloc HAL (%s)", strerror(-err));
		return -ENODEV;
	}
	win->gr = gr;

	usage = GRALLOC_USAGE_HW_FB |
		GRALLOC_USAGE_HW_COMPOSER |
		GRALLOC_USAGE_HW_RENDER;

	for (i = 0; i < 2; i++) {
		aBuffer *buf = cnw_alloc(win, win->format, usage);
		if (!buf)
			return -ENOMEM;
		put_back(&win->free_buffer_queue, buf);
	}

	if (!win->fb && QCT_WORKAROUND) {
		win->spare = cnw_alloc(win, win->format, usage);
		if (!win->spare)
			return -ENOMEM;
	}

	// Disgusting, but we need to init these "const" fields
	// and unlike C++ we can't use const_cast<>
	*((float*) &win->base.xdpi) = win->xdpi;
	*((float*) &win->base.ydpi) = win->ydpi;
	*((int*) &win->base.minSwapInterval) = 1;
	*((int*) &win->base.maxSwapInterval) = 1;

	win->base.common.magic = ANDROID_NATIVE_WINDOW_MAGIC;
	win->base.common.version = sizeof(aWindow);
	win->base.common.incRef = cnw_inc_ref;
	win->base.common.decRef = cnw_dec_ref;

	win->base.setSwapInterval = cnw_set_swap_interval;
	win->base.dequeueBuffer_DEPRECATED = cnw_dequeue_buffer0;
	win->base.lockBuffer_DEPRECATED = cnw_lock_buffer0;
	win->base.queueBuffer_DEPRECATED = cnw_queue_buffer0;
	win->base.query = cnw_query;
	win->base.perform = cnw_perform;
	win->base.cancelBuffer_DEPRECATED = cnw_cancel_buffer0;
	win->base.dequeueBuffer = cnw_dequeue_buffer1;
	win->base.queueBuffer = cnw_queue_buffer1;
	win->base.cancelBuffer = cnw_cancel_buffer1;

	pthread_mutex_init(&win->lock, NULL);
	pthread_cond_init(&win->cvar, NULL);

	return 0;
}

void cnw_destroy(CNativeWindow *win) {
	if (win->fb)
		framebuffer_close(win->fb);
	if (win->hwc)
		hwc_close_1(win->hwc);
	if (win->gr)
		gralloc_close(win->gr);
	free(win);
}

CNativeWindow *cnw_create(void) {
	CNativeWindow *win;
	char *x;
	if ((x = getenv("CNWDEBUG")))
		trace_level = atoi(x);
	if (!(win = malloc(sizeof(CNativeWindow))))
		return NULL;
	if (cnw_init(win)) {
		cnw_destroy(win);
		return NULL;
	}
	return win;
}

void cnw_info(CNativeWindow *win, unsigned *w, unsigned *h, unsigned *fmt) {
	*w = win->width;
	*h = win->height;
	*fmt = win->format;
}