#include <cstdlib>
#include <iostream>
#include <sys/mman.h>
#include <pthread.h>
#include <errno.h>
#include <sys/slog.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <screen/screen.h>

const int barwidth = 32;

int ndisplays = 0;

struct display_t {
	pthread_mutex_t mutex;
	pthread_cond_t cond;
	enum { detached, attached, focused } state;
};

display_t *displays;


screen_pixmap_t screen_pix = NULL;

screen_buffer_t screen_pbuf = NULL;

void pixmap(screen_context_t screen_ctx)
{
	int i, j;

	screen_create_pixmap(&screen_pix, screen_ctx);

	int format = SCREEN_FORMAT_UYVY;
	screen_set_pixmap_property_iv(screen_pix, SCREEN_PROPERTY_FORMAT, &format);

	int usage = SCREEN_USAGE_WRITE | SCREEN_USAGE_NATIVE;
	screen_set_pixmap_property_iv(screen_pix, SCREEN_PROPERTY_USAGE, &usage);

	int size[2] = { 100, 100 };
	screen_set_pixmap_property_iv(screen_pix, SCREEN_PROPERTY_BUFFER_SIZE, size);

	screen_create_pixmap_buffer(screen_pix);
	screen_get_pixmap_property_pv(screen_pix, SCREEN_PROPERTY_RENDER_BUFFERS, (void **)&screen_pbuf);

	unsigned char *ptr = NULL;
	screen_get_buffer_property_pv(screen_pbuf, SCREEN_PROPERTY_POINTER, (void **)&ptr);
	if( NULL == ptr )
	{
		exit( EXIT_FAILURE );
	}

	int stride = 0;
	screen_get_buffer_property_iv(screen_pbuf, SCREEN_PROPERTY_STRIDE, &stride);

	for (i = 0; i < size[1]; i++, ptr += stride) {
		for (j = 0; j < size[0]; j++) {
			ptr[j*4] = 0x0;
			ptr[j*4+1] = 0x0;
			ptr[j*4+2] = 0x0;
			ptr[j*4+3] = ((j >= i && j <= size[1]-i) || (j <= i && j >= size[1]-i)) ? 0xff : 0;
		}
	}
}

void *display(void *arg)
{
	const int idx = (int)arg;
	int rect[4] = { 0, 0 };
	int realized = 0;
	int pos = 0;
	int attached;
	int focus;
	int i;

	screen_context_t screen_ctx;
	screen_create_context(&screen_ctx, SCREEN_APPLICATION_CONTEXT);

	screen_display_t *screen_dpy = (screen_display_t *)calloc(ndisplays, sizeof(screen_display_t));
	screen_get_context_property_pv(screen_ctx, SCREEN_PROPERTY_DISPLAYS, (void **)screen_dpy);

	screen_window_t screen_win;
	screen_create_window(&screen_win, screen_ctx);
	screen_set_window_property_pv(screen_win, SCREEN_PROPERTY_DISPLAY, (void **)&screen_dpy[idx]);

	int usage = SCREEN_USAGE_NATIVE;
	screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_USAGE, &usage);

	attached = displays[idx].state != display_t::detached ? 1 : 0;
	focus = displays[idx].state == display_t::focused ? 1 : 0;

	screen_event_t screen_ev;
	screen_create_event(&screen_ev);

	screen_buffer_t screen_buf[2];
	if( -1 == screen_create_buffer( &screen_buf[0] ) )
	{
		exit( EXIT_FAILURE );
	}

	if( -1 == screen_create_buffer( &screen_buf[1] ) )
	{
		exit( EXIT_FAILURE );
	}

	if( -1 == screen_attach_window_buffers( screen_win, 2, screen_buf ) )
	{
		printf( "%s() Unable to attach buffers\n", __func__ );
		exit( EXIT_FAILURE );
	}

	while (1) {
		if (attached) {
			if (!realized) {
				screen_get_display_property_iv(screen_dpy[idx], SCREEN_PROPERTY_SIZE, rect+2);
				screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, rect+2);

#ifdef MANUAL_ALLOCATE
				size_t i = 0;
				for( ; i < sizeof(screen_buf)/sizeof(screen_buf[0]); ++i )
				{
					uint32_t memorysize = 1200*1600*16;
					uintptr_t virtAddr = (uintptr_t)mmap64( 0,
							memorysize,
							PROT_NOCACHE | PROT_READ | PROT_WRITE,
							MAP_ANON | MAP_PHYS,
							NOFD,
							0 );

					if ( (int)virtAddr == (int)MAP_FAILED ) {
						printf("%s() mmap64 failed!  Error: %s  Errno: %d  Buffer Size requested: %d ", __func__, strerror( errno ), errno, memorysize );
						exit( EXIT_FAILURE );
					}

					off64_t  physLocation = 0;
					size_t maxContigLen =0;
					if( -1 == mem_offset64( (void *)virtAddr,
									  NOFD,
									  memorysize,
									  &physLocation,
									  &maxContigLen ) )
					{
						printf("%s() mem_offset64() failed!  Error: %s  Errno: %d Buffer Size: %d  Virtual Address: 0x%x\n", __func__, strerror( errno ), errno, memorysize, virtAddr);
						exit( EXIT_FAILURE );
					}

					int format = SCREEN_FORMAT_RGB565;
					screen_set_buffer_property_iv( screen_buf[i], SCREEN_PROPERTY_FORMAT, (int *)&format );
					screen_set_buffer_property_iv( screen_buf[i], SCREEN_PROPERTY_SIZE, (int *) rect+2 );

					memset( (void *)virtAddr, 0xff55bbcc, memorysize );

					int bppc = 1;
					screen_set_buffer_property_iv( screen_buf[i], SCREEN_PROPERTY_PHYSICALLY_CONTIGUOUS, &bppc );
					screen_set_buffer_property_pv( screen_buf[i], SCREEN_PROPERTY_POINTER, (void **)&virtAddr );

//					if( -1 == screen_set_buffer_property_llv( screen_buf[i],
//							SCREEN_PROPERTY_PHYSICAL_ADDRESS,
//							(const long long *)&physLocation ) )
//					{
//						printf( "%s() screen_get_buffer_property_liv(..., SCREEN_PROPERTY_PHYSICAL_ADDRESS, ...) failed!  Error: %s  Errno: %d Buffer Size: %d  Virtual Address: 0x%x\n", __func__, strerror( errno ), errno, memorysize, (uint32_t)virtAddr );
//						exit( EXIT_FAILURE );
//					}
				}
#else
				if( -1 == screen_create_window_buffers(screen_win, 2) )
				{
					printf( "%s() Unable to create window buffers buffers\n", __func__ );
					exit( EXIT_FAILURE );
				}

				screen_get_window_property_pv( screen_win, SCREEN_PROPERTY_RENDER_BUFFERS, (void **)screen_buf );

				size_t i = 0;
				for( ; i < sizeof(screen_buf)/sizeof(screen_buf[0]); ++i )
				{
					uintptr_t virtAddr;
					off64_t  physLocation;

					int format;
					screen_get_buffer_property_iv( screen_buf[i], SCREEN_PROPERTY_FORMAT, (int *)&format );
					screen_get_buffer_property_iv( screen_buf[i], SCREEN_PROPERTY_SIZE, (int *) rect+2 );

					int bppc;
					screen_get_buffer_property_iv( screen_buf[i], SCREEN_PROPERTY_PHYSICALLY_CONTIGUOUS, &bppc );
					screen_get_buffer_property_pv( screen_buf[i], SCREEN_PROPERTY_POINTER, (void **)&virtAddr );

					if( -1 == screen_get_buffer_property_llv( screen_buf[i],
							SCREEN_PROPERTY_PHYSICAL_ADDRESS,
							(long long *)&physLocation ) )
					{
						printf( "%s() screen_get_buffer_property_liv(..., SCREEN_PROPERTY_PHYSICAL_ADDRESS, ...) failed!  Error: %s  Errno: %d Virtual Address: 0x%x\n", __func__, strerror( errno ), errno, (uint32_t)virtAddr );
						exit( EXIT_FAILURE );
					}

					physLocation = physLocation;
				}

#endif
				realized = 1;
			}

			int bg[] = { SCREEN_BLIT_COLOR, (int)0xffffff00, SCREEN_BLIT_END };
			screen_fill(screen_ctx, screen_buf[0], bg);

			if (focus > 0) {
				int bar[] = {
					SCREEN_BLIT_COLOR, (int)0xff00f0ff,
					SCREEN_BLIT_DESTINATION_X, pos,
					SCREEN_BLIT_DESTINATION_WIDTH, barwidth,
					SCREEN_BLIT_END };

				screen_fill(screen_ctx, screen_buf[0], bar);

				if (++pos > rect[2] - barwidth) {
					for (i = (idx+1) % ndisplays; i != idx; i = (i+1) % ndisplays) {
						pthread_mutex_lock(&displays[i].mutex);
						if (displays[i].state == attached) {
							displays[i].state = display_t::focused;
							pthread_cond_signal(&displays[i].cond);
							pthread_mutex_unlock(&displays[i].mutex);
							break;
						}
						pthread_mutex_unlock(&displays[i].mutex);
					}
					if (i != idx) {
						pthread_mutex_lock(&displays[idx].mutex);
						displays[idx].state = display_t::attached;
						pthread_mutex_unlock(&displays[idx].mutex);
						focus = -1;
					}
					pos = 0;
				}
			} else {
				focus = 0;
			}

			int hg[] = {
				SCREEN_BLIT_SOURCE_WIDTH, 100,
				SCREEN_BLIT_SOURCE_HEIGHT, 100,
				SCREEN_BLIT_DESTINATION_X, 10,
				SCREEN_BLIT_DESTINATION_Y, 10,
				SCREEN_BLIT_DESTINATION_WIDTH, 100,
				SCREEN_BLIT_DESTINATION_HEIGHT, 100,
				SCREEN_BLIT_TRANSPARENCY, SCREEN_TRANSPARENCY_SOURCE_OVER,
				SCREEN_BLIT_END
			};

			screen_blit(screen_ctx, screen_buf[0], screen_pbuf, hg);
			screen_post_window(screen_win, screen_buf[0], 1, rect, 0);
		}

		if (!attached && realized) {
			screen_destroy_window_buffers(screen_win);
			screen_flush_context(screen_ctx, 0);
			realized = 0;
		}

		if (focus != -1) {
			pthread_mutex_lock(&displays[idx].mutex);
			if (!focus) {
				printf("%s[%d]: idx=%d\n", __FUNCTION__, __LINE__, idx);
				pthread_cond_wait(&displays[idx].cond, &displays[idx].mutex);
				pos = 0;
			}
			attached = displays[idx].state != display_t::detached ? 1 : 0;
			focus = displays[idx].state == display_t::focused ? 1 : 0;
			pthread_mutex_unlock(&displays[idx].mutex);
		}
	}

	free(screen_dpy);

	return NULL;
}

int main(int argc, char **argv)
{
	int i, j, idx = -1;

	screen_context_t screen_ctx;
	screen_create_context(&screen_ctx, SCREEN_APPLICATION_CONTEXT);
	screen_get_context_property_iv(screen_ctx, SCREEN_PROPERTY_DISPLAY_COUNT, &ndisplays);

	pixmap(screen_ctx);

	screen_display_t *screen_dpy = (screen_display_t *) calloc(ndisplays, sizeof(screen_display_t));
	screen_get_context_property_pv(screen_ctx, SCREEN_PROPERTY_DISPLAYS, (void **)screen_dpy);

	displays = (display_t *)calloc(ndisplays, sizeof(*displays));
	for (i = 0; i < ndisplays; i++) {
		int active = 0;
		screen_get_display_property_iv(screen_dpy[i], SCREEN_PROPERTY_ATTACHED, &active);
		if (active) {
			if (idx == -1) {
				displays[i].state = display_t::focused;
				idx = i;
			} else {
				displays[i].state = display_t::attached;
			}
		} else {
			displays[i].state = display_t::detached;
		}

		pthread_mutex_init(&displays[i].mutex, NULL);
		pthread_cond_init(&displays[i].cond, NULL);

		pthread_t thread;
		pthread_create(&thread, NULL, display, (void *)i);
	}

	screen_event_t screen_ev;
	screen_create_event(&screen_ev);

	while (1) {
		int type = SCREEN_EVENT_NONE;
		screen_get_event(screen_ctx, screen_ev, ~0);
		screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_TYPE, &type);
		if (type == SCREEN_EVENT_DISPLAY) {
			screen_display_t tmp = NULL;
			screen_get_event_property_pv(screen_ev, SCREEN_PROPERTY_DISPLAY, (void **)&tmp);
			for (i = 0; i < ndisplays; i++) {
				if (tmp == screen_dpy[i]) {
					int active = 0;
					screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_ATTACHED, &active);

					if (active) {
						int size[2];
						screen_get_display_property_iv(tmp, SCREEN_PROPERTY_SIZE, size);
						if (size[0] == 0 || size[1] == 0) {
							active = 0;
						}
					}

					pthread_mutex_lock(&displays[i].mutex);
					if ((active && displays[i].state == display_t::detached) ||
						(!active && displays[i].state != display_t::detached)) {
						if (active) {
							for (j = 0; j < ndisplays; j++) {
								printf("%s[%d]: j=%d\n", __FUNCTION__, __LINE__, j);
								if (i != j) {
									pthread_mutex_lock(&displays[j].mutex);
									if (displays[j].state == display_t::focused) {
										displays[i].state = display_t::attached;
										pthread_mutex_unlock(&displays[j].mutex);
										break;
									}
									pthread_mutex_unlock(&displays[j].mutex);
								}
							}
							if (displays[i].state == display_t::detached) {
								displays[i].state = display_t::focused;
							}
						} else {
							if (displays[i].state == display_t::focused) {
								for (j = (i+1) % ndisplays; j != i; j = (j+1) % ndisplays) {
									printf("%s[%d]: j=%d\n", __FUNCTION__, __LINE__, j);
									pthread_mutex_lock(&displays[j].mutex);
									if (displays[j].state == display_t::attached) {
										displays[j].state = display_t::focused;
										pthread_cond_signal(&displays[j].cond);
										pthread_mutex_unlock(&displays[j].mutex);
										break;
									}
									pthread_mutex_unlock(&displays[j].mutex);
								}
							}
							displays[i].state = display_t::detached;
						}
						pthread_cond_signal(&displays[i].cond);
					}
					pthread_mutex_unlock(&displays[i].mutex);

					break;
				}
			}
		}
	}

	screen_destroy_pixmap(screen_pix);
	screen_destroy_event(screen_ev);
	screen_destroy_context(screen_ctx);

	free(displays);

	return EXIT_SUCCESS;
}
