/*
 * \brief  MuPDF for Genode
 * \author Norman Feske
 * \date   2012-01-09
 */

/*
 * Copyright (C) 2012 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

/* Genode includes */
#include <framebuffer_session/connection.h>
#include <base/sleep.h>
#include <input_session/connection.h>
#include <input/event.h>
#include <input/keycodes.h>
#include <timer_session/connection.h>

/* MuPDF includes */
extern "C" {
#include <fitz.h>
#include <mupdf.h>
#include <muxps.h>
#include <pdfapp.h>
}

/* libc includes */
#include <unistd.h>


/***************
 ** Dithering **
 ***************/

/*
 * XXX blatantly copied from 'demo/src/app/backdrop/main.cc'
 *
 * We should factor-out the dithering support into a separate header file.
 * But where is a good place to put it?
 */

enum { DITHER_SIZE = 16, DITHER_MASK = DITHER_SIZE - 1 };

static const int dither_matrix[DITHER_SIZE][DITHER_SIZE] = {
  {   0,192, 48,240, 12,204, 60,252,  3,195, 51,243, 15,207, 63,255 },
  { 128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127 },
  {  32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223 },
  { 160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95 },
  {   8,200, 56,248,  4,196, 52,244, 11,203, 59,251,  7,199, 55,247 },
  { 136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119 },
  {  40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215 },
  { 168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87 },
  {   2,194, 50,242, 14,206, 62,254,  1,193, 49,241, 13,205, 61,253 },
  { 130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125 },
  {  34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221 },
  { 162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93 },
  {  10,202, 58,250,  6,198, 54,246,  9,201, 57,249,  5,197, 53,245 },
  { 138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117 },
  {  42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213 },
  { 170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85 }
};


static inline uint16_t rgb565(int r, int g, int b)
{
	enum {
		R_MASK = 0xf800, R_LSHIFT = 8,
		G_MASK = 0x07e0, G_LSHIFT = 3,
		B_MASK = 0x001f, B_RSHIFT = 3
	};
	return ((r << R_LSHIFT) & R_MASK)
	     | ((g << G_LSHIFT) & G_MASK)
	     | ((b >> B_RSHIFT) & B_MASK);
}


static void convert_line_rgba_to_rgb565(const unsigned char *rgba_src,
                                        uint16_t *dst, int num_pixels, int line)
{
	using namespace Genode;

	enum { CHANNEL_MAX = 255 };

	int const *dm = dither_matrix[line & DITHER_MASK];

	for (int i = 0; i < num_pixels; i++) {
		int v = dm[i & DITHER_MASK] >> 5;

		*dst++ = rgb565(min(v + (int)rgba_src[0], (int)CHANNEL_MAX),
		                min(v + (int)rgba_src[1], (int)CHANNEL_MAX),
		                min(v + (int)rgba_src[2], (int)CHANNEL_MAX));

		/* we ignore the alpha channel */

		rgba_src += 4; /* next pixel */
	}
}


/**************
 ** PDF view **
 **************/

class Pdf_view
{
	public:

		/**
		 * Exception types
		 */
		class Non_supported_framebuffer_mode { };
		class Invalid_input_file_name { };
		class Unexpected_document_color_depth { };

	private:

		struct _Framebuffer : Framebuffer::Connection
		{
			typedef uint16_t pixel_t;

			Framebuffer::Mode mode;
			pixel_t          *base;

			_Framebuffer()
			:
				mode(Framebuffer::Connection::mode()),
				base(Genode::env()->rm_session()->attach(dataspace()))
			{
				PDBG("Framebuffer is %dx%d\n", mode.width(), mode.height());

				if (mode.format() != Framebuffer::Mode::RGB565) {
					PERR("Color modes other than RGB565 are not supported. Exiting.");
					throw Non_supported_framebuffer_mode();
				}
			}
		} _framebuffer;

		pdfapp_t _pdfapp;

	public:

		/**
		 * Constructor
		 *
		 * \throw Non_supported_framebuffer_mode
		 * \throw Invalid_input_file_name
		 * \throw Unexpected_document_color_depth
		 */
		Pdf_view(char const *file_name)
		{
			pdfapp_init(&_pdfapp);
			_pdfapp.userdata = this;
			_pdfapp.scrw       = _framebuffer.mode.width();
			_pdfapp.scrh       = _framebuffer.mode.height();
			_pdfapp.resolution = 75;   /* XXX read from config */
			_pdfapp.pageno     = 0;    /* XXX read from config */

			int fd = open(file_name, O_BINARY | O_RDONLY, 0666);
			if (fd < 0) {
				PERR("Could not open input file \"%s\", Exiting.", file_name);
				throw Invalid_input_file_name();
			}
			pdfapp_open(&_pdfapp, (char *)file_name, fd, 0);

			if (_pdfapp.image->n != 4) {
				PERR("Unexpected color depth, expected 4, got %d, Exiting.",
				     _pdfapp.image->n);
				throw Unexpected_document_color_depth();
			}
		}

		void show();

		void handle_key(int ascii)
		{
			pdfapp_onkey(&_pdfapp, ascii);
		}
};


void Pdf_view::show()
{
	int const x_max = Genode::min(_framebuffer.mode.width(),  _pdfapp.image->w);
	int const y_max = Genode::min(_framebuffer.mode.height(), _pdfapp.image->h);

	Genode::size_t src_line_bytes   = _pdfapp.image->n * _pdfapp.image->w;
	unsigned char *src_line         = _pdfapp.image->samples;

	Genode::size_t dst_line_width   = _framebuffer.mode.width(); /* in pixels */
	_Framebuffer::pixel_t *dst_line = _framebuffer.base;

	for (int y = 0; y < y_max; y++) {
		convert_line_rgba_to_rgb565(src_line, dst_line, x_max, y);
		src_line += src_line_bytes;
		dst_line += dst_line_width;
	}

	_framebuffer.refresh(0, 0, _framebuffer.mode.width(), _framebuffer.mode.height());
}


extern "C" void _sigprocmask()
{
	/* suppress debug message by default "not-implemented" implementation */
}


/**************************
 ** Called from pdfapp.c **
 **************************/

void winrepaint(pdfapp_t *pdfapp)
{
	PDBG("called");
	Pdf_view *pdf_view = (Pdf_view *)pdfapp->userdata;
	pdf_view->show();
}


void winrepaintsearch(pdfapp_t *)
{
	PDBG("not implemented");
}


void wincursor(pdfapp_t *, int curs)
{
	PDBG("curs=%d - not implemented", curs);
}


void winerror(pdfapp_t *, fz_error error)
{
	PDBG("error=%d", error);
	Genode::sleep_forever();
}


void winwarn(pdfapp_t *, char *msg)
{
	PWRN("MuPDF: %s", msg);
}


void winhelp(pdfapp_t *)
{
	PDBG("not implemented");
}


char *winpassword(pdfapp_t *, char *)
{
	PDBG("not implemented");
	return NULL;
}


void winclose(pdfapp_t *app)
{
	PDBG("not implemented");
}


void winreloadfile(pdfapp_t *)
{
	PDBG("not implemented");
}


void wintitle(pdfapp_t *app, char *s)
{
	PDBG("s=\"%s\" - not implemented", s);
}


void winresize(pdfapp_t *app, int w, int h)
{
	PDBG("not implemented, w=%d, h=%d", w, h);
}


/******************
 ** Main program **
 ******************/

static int keycode_to_ascii(int code)
{
	switch (code) {
	case Input::KEY_LEFT:      return 'h';
	case Input::KEY_RIGHT:     return 'l';
	case Input::KEY_DOWN:      return 'j';
	case Input::KEY_UP:        return 'k';
	case Input::KEY_PAGEDOWN:
	case Input::KEY_ENTER:     return ' ';
	case Input::KEY_PAGEUP:
	case Input::KEY_BACKSPACE: return 'b';
	default:                   return 0;
	}
}


int main(int, char **)
{
	char const *file_name = "test.pdf"; /* XXX read from config */

	static Pdf_view pdf_view(file_name);

	static Input::Connection input;
	static Timer::Connection timer;

	Input::Event *ev_buf = Genode::env()->rm_session()->attach(input.dataspace());
	int key_cnt = 0;

	/*
	 * Input event loop
	 */
	for (;;) {
		while (!input.is_pending()) timer.msleep(20);

		for (int i = 0, num_ev = input.flush(); i < num_ev; i++) {

			Input::Event const &ev = ev_buf[i];

			if (ev.type() == Input::Event::PRESS)   key_cnt++;
			if (ev.type() == Input::Event::RELEASE) key_cnt--;

			if (ev.type() == Input::Event::PRESS && key_cnt == 1) {

				PDBG("key %d pressed", ev.keycode());

				int const ascii = keycode_to_ascii(ev.keycode());
				if (ascii)
					pdf_view.handle_key(ascii);
			}
		}
	}
	Genode::sleep_forever();
	return 0;
}
