/*
 * test.c
 *
 * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com>
 * Copyright (C) 2009-2012 Tomi Valkeinen

 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <time.h>

#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include <linux/fb.h>

#include "common.h"

static struct fb_info fb_info;

static void draw_pixel(struct fb_info *fb_info, int x, int y, unsigned color)
{
	void *fbmem;

	fbmem = fb_info->ptr;
	if (fb_info->var.bits_per_pixel == 8) {
		unsigned char *p;

		fbmem += fb_info->fix.line_length * y;

		p = fbmem;

		p += x;

		*p = color;
	} else if (fb_info->var.bits_per_pixel == 16) {
		unsigned short c;
		unsigned r = (color >> 16) & 0xff;
		unsigned g = (color >> 8) & 0xff;
		unsigned b = (color >> 0) & 0xff;
		unsigned short *p;

		r = r * 32 / 256;
		g = g * 64 / 256;
		b = b * 32 / 256;

		c = (r << 11) | (g << 5) | (b << 0);

		fbmem += fb_info->fix.line_length * y;

		p = fbmem;

		p += x;

		*p = c;
	} else {
		unsigned int *p;

		fbmem += fb_info->fix.line_length * y;

		p = fbmem;

		p += x;

		*p = color;
	}
}

static void fill_screen(struct fb_info *fb_info)
{
	unsigned x, y;
	unsigned h = fb_info->var.yres_virtual;
	unsigned w = fb_info->var.xres_virtual;

	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			if (x < 20 && y < 20)
				draw_pixel(fb_info, x, y, 0xffffff);
			else if (x < 20 && (y > 20 && y < h - 20))
				draw_pixel(fb_info, x, y, 0xff);
			else if (y < 20 && (x > 20 && x < w - 20))
				draw_pixel(fb_info, x, y, 0xff00);
			else if (x > w - 20 && (y > 20 && y < h - 20))
				draw_pixel(fb_info, x, y, 0xff0000);
			else if (y > h - 20 && (x > 20 && x < w - 20))
				draw_pixel(fb_info, x, y, 0xffff00);
			else if (x == 20 || x == w - 20 ||
					y == 20 || y == h - 20)
				draw_pixel(fb_info, x, y, 0xffffff);
			else if (x == y || w - x == h - y)
				draw_pixel(fb_info, x, y, 0xff00ff);
			else if (w - x == y || x == h - y)
				draw_pixel(fb_info, x, y, 0x00ffff);
			else if (x > 20 && y > 20 && x < w - 20 && y < h - 20) {
				int t = x * 3 / w;
				unsigned r = 0, g = 0, b = 0;
				unsigned c;
				if (fb_info->var.bits_per_pixel == 16) {
					if (t == 0)
						b = (y % 32) * 256 / 32;
					else if (t == 1)
						g = (y % 64) * 256 / 64;
					else if (t == 2)
						r = (y % 32) * 256 / 32;
				} else {
					if (t == 0)
						b = (y % 256);
					else if (t == 1)
						g = (y % 256);
					else if (t == 2)
						r = (y % 256);
				}
				c = (r << 16) | (g << 8) | (b << 0);
				draw_pixel(fb_info, x, y, c);
			} else {
				draw_pixel(fb_info, x, y, 0);
			}
		}

	}

	fb_put_string(fb_info, w / 3 * 2, 30, "RED", 3, 0xffffff, 1, 3);
	fb_put_string(fb_info, w / 3, 30, "GREEN", 5, 0xffffff, 1, 5);
	fb_put_string(fb_info, 20, 30, "BLUE", 4, 0xffffff, 1, 4);
}

void fill_screen_solid(struct fb_info *fb_info, unsigned int color)
{

	unsigned x, y;
	unsigned h = fb_info->var.yres_virtual;
	unsigned w = fb_info->var.xres_virtual;


	if ( (fb_info->h && fb_info->l) != 0 ) {
		if (((fb_info->y + fb_info->h) <= h) && ((fb_info->x + fb_info->l) <= w)) { 
			for (y = fb_info->y; y < (fb_info->y + fb_info->h); y++) {
				for (x = fb_info->x; x < (fb_info->x + fb_info->l); x++)
					draw_pixel(fb_info, x, y, color);
			}
		} else {
			printf("Maxwell display does not support this size\n"); 
		}
	} else {
		for (y = 0; y < h; y++) {
			for (x = 0; x < w; x++)
				draw_pixel(fb_info, x, y, color);
		}
	}
}

static void do_fill_screen(struct fb_info *fb_info, int pattern)
{

	switch (pattern) {
	case 1:
		fill_screen_solid(fb_info, 0xff0000);
		break;
	case 2:
		fill_screen_solid(fb_info, 0x00ff00);
		break;
	case 3:
		fill_screen_solid(fb_info, 0x0000ff);
		break;
	case 4:
		fill_screen_solid(fb_info, 0xffffff);
		break;
	case 5: 
		fill_screen_solid(fb_info, 0x000000);
                break;
	case 0:
	default:
		fill_screen(fb_info);
		break;
	}
}

void show_help(void)
{
	printf("Usage: fb-test -f fbnum -r -g -b -w -p pattern\n");
	printf("Where -f fbnum   = framebuffer device number\n");
	printf("      -r         = fill framebuffer with red\n");
	printf("      -g         = fill framebuffer with green\n");
	printf("      -b         = fill framebuffer with blue\n");
	printf("      -w         = fill framebuffer with white\n");
	printf("      -B         = fill framebuffer with Black\n");
	printf("      -x         = start x point on display\n");
	printf("      -y         = start y point on display\n");
	printf("      -h         = height of pattern\n");
	printf("      -l         = length of pattern\n");
	printf("      -p pattern = fill framebuffer with pattern number\n");
}

int main(int argc, char **argv)
{
	int opt;
	int req_fb = 0;
	int req_pattern = 0;

	printf("fb-test %d.%d.%d (%s)\n", VERSION, PATCHLEVEL, SUBLEVEL,
		VERSION_NAME);

	while ((opt = getopt(argc, argv, "vrBgbwp:f:x:y:h:l:")) != -1) {
		switch (opt) {
		case 'f':
			req_fb = atoi(optarg);
			break;
		case 'p':
			req_pattern = atoi(optarg);
			break;
		case 'r':
			req_pattern = 1;
			break;
		case 'g':
			req_pattern = 2;
			break;
		case 'b':
			req_pattern = 3;
			break;
		case 'B':
			req_pattern = 5;
			break;
		case 'x':
			fb_info.x = atoi(optarg);
			break;
		case 'y':
			fb_info.y =  atoi(optarg);
			break;
		case 'h':
			fb_info.h =  atoi(optarg);
			break;
		case 'l':
			fb_info.l =  atoi(optarg);
			break;
		case 'w':
			req_pattern = 4;
			break;
		case 'v':
			show_help();
			return 0;
		default:
			exit(EXIT_FAILURE);
		}
	}

	fb_open(req_fb, &fb_info);

	do_fill_screen(&fb_info, req_pattern);

	return 0;
}
