#include <atf-c.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <dev/wscons/wsconsio.h>

/*
 * Test program for wsdisplay access ops. Should be running on a real hardware,
 * supporting these ops.
 */

const char* wsdev = "/dev/ttyE0";

void 
do_ioctl(unsigned long req, void *data)
{
	int fd;

	fd = open(wsdev, O_RDWR);
	ATF_REQUIRE_MSG(fd > 0, "could not open the device file");

	if (ioctl(fd, req, data) == -1) {
		perror("ioctl");
		atf_tc_fail("Test failed due to ioctl error");
	}
	close(fd);
}

void 
get_cmap(struct wsdisplay_cmap *cmap, u_int size) 
{
	cmap->index = 0;
	cmap->count = size;
	cmap->red = calloc(size, sizeof(u_int)); 
	cmap->green = calloc(size, sizeof(u_int)); 
	cmap->blue = calloc(size, sizeof(u_int)); 

	do_ioctl(WSDISPLAYIO_GETCMAP, cmap);
}

void
free_cmap(struct wsdisplay_cmap *cmap) 
{
	free(cmap->red);
	free(cmap->green);
	free(cmap->blue);
}

ATF_TC(wsgtype);
ATF_TC_HEAD(wsgtype, tc)
{
	atf_tc_set_md_var(tc, "descr", "test for sanity of WSDISPLAYIO_GTYPE");
}

ATF_TC_BODY(wsgtype, tc)
{
	int gtype;

	do_ioctl(WSDISPLAYIO_GTYPE, &gtype);

	printf("WSDISPLAYIO_GTYPE: %d\n", gtype);

	ATF_REQUIRE_MSG(gtype > 0, "WSDISPLAYIO_GTYPE needs to be > 0");
}

ATF_TC(wsginfo);
ATF_TC_HEAD(wsginfo, tc)
{
	atf_tc_set_md_var(tc, "descr", "test for sanity of WSDISPLAYIO_GINFO");
}

ATF_TC_BODY(wsginfo, tc)
{
	struct wsdisplay_fbinfo fbinfo;

	do_ioctl(WSDISPLAYIO_GINFO, &fbinfo);

	printf("WSDISPLAYIO_FBINFO->height: %d\n", fbinfo.height);
	ATF_REQUIRE_MSG(fbinfo.height > 0, 
	    "WSDISPLAYIO_FBINFO->height needs to be > 0");
	printf("WSDISPLAYIO_FBINFO->width: %d\n", fbinfo.width);
	ATF_REQUIRE_MSG(fbinfo.width > 0, 
	    "WSDISPLAYIO_FBINFO->width needs to be > 0");
	printf("WSDISPLAYIO_FBINFO->depth: %d\n", fbinfo.depth);
	ATF_REQUIRE_MSG(fbinfo.depth > 0, 
	    "WSDISPLAYIO_FBINFO->depth needs to be > 0");
	printf("WSDISPLAYIO_FBINFO->cmsize: %d\n", fbinfo.cmsize);
	ATF_REQUIRE_MSG(fbinfo.cmsize > 0, 
	    "WSDISPLAYIO_FBINFO->cmsize needs to be > 0");
}

ATF_TC(wsgvideo);
ATF_TC_HEAD(wsgvideo, tc)
{
	atf_tc_set_md_var(tc, "descr", "test for sanity of WSDISPLAYIO_GVIDEO");
}

ATF_TC_BODY(wsgvideo, tc)
{
	int gvideo;

	do_ioctl(WSDISPLAYIO_GVIDEO, &gvideo);

	printf("WSDISPLAYIO_GVIDEO: %d\n", gvideo);
	/* XXX: we assume that the default state is ON */
	ATF_REQUIRE_MSG(gvideo == WSDISPLAYIO_VIDEO_ON,
	    "Display state is not WSDISPLAYIO_VIDEO_ON"); 
}

ATF_TC(wssvideo);
ATF_TC_HEAD(wssvideo, tc)
{
	atf_tc_set_md_var(tc, "descr", "test WSDISPLAYIO_SVIDEO call");
}

ATF_TC_BODY(wssvideo, tc)
{
	int svideo, gvideo;

	svideo = WSDISPLAYIO_VIDEO_OFF;
	do_ioctl(WSDISPLAYIO_SVIDEO, &svideo);
	sleep(2);

	do_ioctl(WSDISPLAYIO_GVIDEO, &gvideo);
	printf("WSDISPLAYIO_GVIDEO: %d\n", gvideo);
	ATF_REQUIRE_MSG(gvideo == WSDISPLAYIO_VIDEO_OFF,
	    "Display state is not WSDISPLAYIO_VIDEO_OFF"); 

	svideo = WSDISPLAYIO_VIDEO_ON;
	do_ioctl(WSDISPLAYIO_SVIDEO, &svideo);
	sleep(2);

	do_ioctl(WSDISPLAYIO_GVIDEO, &gvideo);
	printf("WSDISPLAYIO_GVIDEO: %d\n", gvideo);
	ATF_REQUIRE_MSG(gvideo == WSDISPLAYIO_VIDEO_ON,
	    "Display state is not WSDISPLAYIO_VIDEO_ON"); 

}

ATF_TC(wsgmode);
ATF_TC_HEAD(wsgmode, tc)
{
	atf_tc_set_md_var(tc, "descr", "test for sanity of WSDISPLAYIO_GMODE");
}

ATF_TC_BODY(wsgmode, tc)
{
	int gmode;

	do_ioctl(WSDISPLAYIO_GMODE, &gmode);

	/* XXX: we don't make assumptions about current mode */
	printf("WSDISPLAYIO_GMODE: %d\n", gmode);
	atf_tc_pass();
}

ATF_TC(wssmode);
ATF_TC_HEAD(wssmode, tc)
{
	atf_tc_set_md_var(tc, "descr", "test WSDISPLAYIO_SMODE call");
}

/* XXX: currently this ignores the DUMBFB mode, whatever it is. */
ATF_TC_BODY(wssmode, tc)
{
	int omode, gmode, smode;

	/* Store the original mode. */
	do_ioctl(WSDISPLAYIO_GMODE, &omode);

	/* Determine the new mode we will use. */
	if (omode == WSDISPLAYIO_MODE_EMUL)
		smode = WSDISPLAYIO_MODE_MAPPED;
	else
		smode = WSDISPLAYIO_MODE_EMUL;

	/* Set the new mode. */	
	do_ioctl(WSDISPLAYIO_SMODE, &smode);

	/* Check mode. */
	do_ioctl(WSDISPLAYIO_GMODE, &gmode);
	printf("WSDISPLAYIO_GMODE: %d\n", gmode);
	if (omode == WSDISPLAYIO_MODE_EMUL)
		ATF_REQUIRE_MSG(gmode == WSDISPLAYIO_MODE_MAPPED,
		    "Display state is not WSDISPLAYIO_MODE_MAPPED"); 
	else
		ATF_REQUIRE_MSG(gmode == WSDISPLAYIO_MODE_EMUL,
		    "Display state is not WSDISPLAYIO_MODE_EMUL"); 

	/* Restore original mode. */
	do_ioctl(WSDISPLAYIO_SMODE, &omode);
	/* Check if it is restored. */
	do_ioctl(WSDISPLAYIO_GMODE, &gmode);
	printf("WSDISPLAYIO_GMODE: %d\n", gmode);
	ATF_REQUIRE_MSG(gmode == omode,
	    "Display state was not restored properly"); 
}

ATF_TC(wsgetcmap);
ATF_TC_HEAD(wsgetcmap, tc)
{
	atf_tc_set_md_var(tc, "descr","test for sanity of WSDISPLAYIO_GETCMAP");
}

ATF_TC_BODY(wsgetcmap, tc)
{
	int i;
	struct wsdisplay_cmap cmap;
	struct wsdisplay_fbinfo fbinfo;

	do_ioctl(WSDISPLAYIO_GINFO, &fbinfo);
	printf("WSDISPLAYIO_GINFO->cmsize: %d\n", fbinfo.cmsize);

	get_cmap(&cmap, fbinfo.cmsize);

	printf("WSDISPLAYIO_GETCMAP->index: %d\n", cmap.index);
	printf("WSDISPLAYIO_GETCMAP->count: %d\n", cmap.count);

	printf("cmap: ");
	for (i = 0; i < fbinfo.cmsize; i++) {
		printf("[%d = R%x, G%x, B%x] ", i, cmap.red[i], cmap.green[i], 
		    cmap.blue[i]);
	}

	free_cmap(&cmap);
	atf_tc_pass();
}

ATF_TC(wslinebytes);
ATF_TC_HEAD(wslinebytes, tc)
{
	atf_tc_set_md_var(tc, "descr", "test WSDISPLAYIO_LINEBYTES call");
}

ATF_TC_BODY(wslinebytes, tc)
{
	u_int linebytes;
	
	do_ioctl(WSDISPLAYIO_LINEBYTES, &linebytes);

	printf("WSDISPLAYIO_LINEBYTES: %d\n", linebytes);
	ATF_REQUIRE_MSG(linebytes > 0,
	    "WSDISPLAYIO_LINEBYTES must be > 0"); 
}

ATF_TC(wsputcmap);
ATF_TC_HEAD(wsputcmap, tc)
{
	atf_tc_set_md_var(tc, "descr", "test WSDISPLAYIO_SETCMAP call");
}

ATF_TC_BODY(wsputcmap, tc)
{
	int i;
	struct wsdisplay_cmap ocmap, ncmap, tcmap;
	struct wsdisplay_fbinfo fbinfo;

	do_ioctl(WSDISPLAYIO_GINFO, &fbinfo);
	printf("WSDISPLAYIO_GINFO->cmsize: %d\n", fbinfo.cmsize);

	/* Save original colormap (to restore it later). */
	get_cmap(&ocmap, fbinfo.cmsize);

	/* Obtain current colormap. */
	get_cmap(&ncmap, fbinfo.cmsize);	
	/* Modify a few entries. */
	ncmap.red[0] = 0xFF;
	ncmap.green[0] = 0x00;
	ncmap.blue[0] = 0x00;
	ncmap.red[1] = 0x00;
	ncmap.green[1] = 0x00;
	ncmap.blue[1] = 0xFF;
	ncmap.red[2] = 0x00;
	ncmap.green[2] = 0xFF;
	ncmap.blue[2] = 0x00;
	/* Put the modified map. */	
	do_ioctl(WSDISPLAYIO_PUTCMAP, &ncmap);
	sleep(1); /* Look at the screen ;) */
	/* Re-read the modified map. */
	get_cmap(&tcmap, fbinfo.cmsize);

	/* Restore the original colormap before checking the cmaps. */
	do_ioctl(WSDISPLAYIO_PUTCMAP, &ocmap);

	/* Compare the cmap values. */
	for (i = 0; i < fbinfo.cmsize; i++) {
		ATF_CHECK_EQ(ncmap.red[i], tcmap.red[i]);
		ATF_CHECK_EQ(ncmap.green[i], tcmap.green[i]);
		ATF_CHECK_EQ(ncmap.blue[i], tcmap.blue[i]);
	}

	free_cmap(&ocmap);
	free_cmap(&ncmap);
	free_cmap(&tcmap);
}

ATF_TP_ADD_TCS(tp)
{
	ATF_TP_ADD_TC(tp, wsgtype);
	ATF_TP_ADD_TC(tp, wsginfo);
	ATF_TP_ADD_TC(tp, wsgvideo);
	ATF_TP_ADD_TC(tp, wssvideo);
	ATF_TP_ADD_TC(tp, wsgmode);
	ATF_TP_ADD_TC(tp, wssmode);
	ATF_TP_ADD_TC(tp, wsgetcmap);
	ATF_TP_ADD_TC(tp, wsputcmap);
	ATF_TP_ADD_TC(tp, wslinebytes);

	return atf_no_error();
}