#include "fitz.h"
#include "mupdf.h"

#include <windows.h>
#include <windowsx.h>

#include "npapi.h"
#include "npupp.h"

#define PAD 5

#define MSG(s) MessageBox(0,s,"MuPDF Debug",MB_OK)

typedef struct pdfmoz_s pdfmoz_t;
typedef struct page_s page_t;

struct page_s
{
    fz_obj *obj;
    pdf_page *page;
    fz_pixmap *image;
    int w, h;	/* page size in units */
    int px; /* pixel height */
};

struct pdfmoz_s
{
    NPP inst;
    HWND hwnd;
    HWND sbar;
    WNDPROC winproc;
    HCURSOR arrow, hand, wait;
    BITMAPINFO *dibinf;
    HBRUSH graybrush;

    int scrollpage;	/* scrollbar -> page (n) */
    int scrollyofs;	/* scrollbar -> page offset in pixels */

    int pagecount;
    page_t *pages;

    char *filename;
    char *doctitle;

    pdf_xref *xref;
    fz_renderer *rast;

    char error[1024];	/* empty if no error has occured */
};

void pdfmoz_warn(pdfmoz_t *moz, const char *fmt, ...)
{
    char buf[1024];
    va_list ap;
    va_start(ap, fmt);
    vsprintf(buf, fmt, ap);
    va_end(ap);
    strcpy(moz->error, buf);
    InvalidateRect(moz->hwnd, NULL, FALSE);
    NPN_Status(moz->inst, moz->error);
}

void pdfmoz_error(pdfmoz_t *moz, fz_error error)
{
    fz_catch(error, "unhandled error");
    InvalidateRect(moz->hwnd, NULL, FALSE);
    NPN_Status(moz->inst, "mupdf error");
}

void pdfmoz_open(pdfmoz_t *moz, char *filename)
{
    SCROLLINFO si;
    fz_error error;
    fz_obj *obj;
    char *password = "";
    pdf_pagetree *pages;
    fz_irect bbox;
    int rot;
    int i;

    strcpy(moz->error, "");

    error = fz_newrenderer(&moz->rast, pdf_devicergb, 0, 1024 * 512);
    if (error)
	pdfmoz_error(moz, error);

    /*
     * Open PDF and load xref table
     */

    moz->filename = filename;

    error = pdf_newxref(&moz->xref);
    if (error)
	pdfmoz_error(moz, error);

    error = pdf_loadxref(moz->xref, filename);
    if (error)
    {
	error = pdf_repairxref(moz->xref, filename);
	if (error)
	    pdfmoz_error(moz, error);
    }

    /*
     * Handle encrypted PDF files
     */

    error = pdf_decryptxref(moz->xref);
    if (error)
	pdfmoz_error(moz, error);

    if (moz->xref->crypt)
    {
	int okay = pdf_setpassword(moz->xref->crypt, password);
	if (!okay)
	    pdfmoz_warn(moz, "Invalid password.");
    }

    moz->pagecount = moz->xrex->pagecount;
    moz->pages = fz_malloc(sizeof(page_t) * moz->pagecount);

    for (i = 0; i < moz->pagecount; i++)
    {
	moz->pages[i].obj = fz_keepobj(pdf_getpageobject(pages, i));
	moz->pages[i].page = nil;
	moz->pages[i].image = nil;

	obj = fz_dictgets(moz->pages[i].obj, "CropBox");
	if (!obj)
	    obj = fz_dictgets(moz->pages[i].obj, "MediaBox");
	bbox = fz_roundrect(pdf_torect(obj));
	moz->pages[i].w = bbox.x1 - bbox.x0;
	moz->pages[i].h = bbox.y1 - bbox.y0;

	rot = fz_toint(fz_dictgets(moz->pages[i].obj, "Rotate"));
	if ((rot / 90) % 2)
	{
	    int t = moz->pages[i].w;
	    moz->pages[i].w = moz->pages[i].h;
	    moz->pages[i].h = t;
	}

	moz->pages[i].px = 1 + PAD;
    }

    pdf_droppagetree(pages);

    /*
     * Load meta information
     * TODO: move this into mupdf library
     */

    obj = fz_dictgets(moz->xref->trailer, "Root");
    moz->xref->root = fz_resolveindirect(obj);
    if (!moz->xref->root)
	pdfmoz_error(moz, fz_throw("syntaxerror: missing Root object"));
    if (moz->xref->root)
	fz_keepobj(moz->xref->root);

    obj = fz_dictgets(moz->xref->trailer, "Info");
    moz->xref->info = fz_resolveindirect(obj);
    if (moz->xref->info)
	fz_keepobj(moz->xref->info);

    moz->doctitle = filename;
    if (strrchr(moz->doctitle, '\\'))
	moz->doctitle = strrchr(moz->doctitle, '\\') + 1;
    if (strrchr(moz->doctitle, '/'))
	moz->doctitle = strrchr(moz->doctitle, '/') + 1;
    if (moz->xref->info)
    {
	obj = fz_dictgets(moz->xref->info, "Title");
	if (obj)
	{
	    error = pdf_toutf8(&moz->doctitle, obj);
	    if (error)
		pdfmoz_error(moz, error);
	}
    }

    /*
     * Start at first page
     */

    si.cbSize = sizeof(si);
    si.fMask = SIF_POS | SIF_RANGE; // XXX | SIF_PAGE;
    si.nPos = 0;
    si.nMin = 0;
    si.nMax = 1;
    si.nPage = 1;
    SetScrollInfo(moz->hwnd, SB_VERT, &si, TRUE);

    moz->scrollpage = 0;
    moz->scrollyofs = 0;

    InvalidateRect(moz->hwnd, NULL, FALSE);
}

static void decodescroll(pdfmoz_t *moz, int spos)
{
    int i, y = 0;
    moz->scrollpage = 0;
    moz->scrollyofs = 0;
    for (i = 0; i < moz->pagecount; i++)
    {
	if (spos >= y && spos < y + moz->pages[i].px)
	{
	    moz->scrollpage = i;
	    moz->scrollyofs = spos - y;
	    return;
	}
	y += moz->pages[i].px;
    }
}

fz_matrix pdfmoz_pagectm(pdfmoz_t *moz, int pagenum)
{
    page_t *page = moz->pages + pagenum;
    fz_matrix ctm;
    float zoom;
    RECT rc;

    GetClientRect(moz->hwnd, &rc);

    zoom = (rc.right - rc.left) / (float) page->w;

    ctm = fz_identity();
    ctm = fz_concat(ctm, fz_translate(0, -page->page->mediabox.y1));
    ctm = fz_concat(ctm, fz_scale(zoom, -zoom));
    ctm = fz_concat(ctm, fz_rotate(page->page->rotate));

    return ctm;
}

void pdfmoz_loadpage(pdfmoz_t *moz, int pagenum)
{
    page_t *page = moz->pages + pagenum;
    fz_error error;

    if (page->page)
	return;

    error = pdf_loadpage(&page->page, moz->xref, page->obj);
    if (error)
	pdfmoz_error(moz, error);
}

void pdfmoz_drawpage(pdfmoz_t *moz, int pagenum)
{
    page_t *page = moz->pages + pagenum;
    fz_error error;
    fz_matrix ctm;
    fz_rect bbox;

    if (page->image)
	return;

    ctm = pdfmoz_pagectm(moz, pagenum);
    bbox = fz_transformaabb(ctm, page->page->mediabox);

    error = fz_rendertree(&page->image, moz->rast, page->page->tree,
	    ctm, fz_roundrect(bbox), 1);
    if (error)
	pdfmoz_error(moz, error);
}

void pdfmoz_gotouri(pdfmoz_t *moz, fz_obj *uri)
{
    char buf[2048];
    memcpy(buf, fz_tostrbuf(uri), fz_tostrlen(uri));
    buf[fz_tostrlen(uri)] = 0;
    NPN_GetURL(moz->inst, buf, "_blank");
}

int pdfmoz_getpagenum(pdfmoz_t *moz, fz_obj *obj)
{
    fz_error error;
    int page;
    int i, y = 0;

    error = pdf_findpageobject(moz->xref, obj, &page);
    if (error)
	pdfmoz_error(moz, error);

    return page;
}

void pdfmoz_gotopage(pdfmoz_t *moz, fz_obj *obj)
{
    fz_error error;
    int page;
    int i, y = 0;

    error = pdf_findpageobject(moz->xref, obj, &page);
    if (error)
	pdfmoz_error(moz, error);

    for (i = 0; i < page; i++)
	y += moz->pages[i].px;

    SetScrollPos(moz->hwnd, SB_VERT, y, TRUE);
    InvalidateRect(moz->hwnd, NULL, FALSE);
}

void pdfmoz_onmouse(pdfmoz_t *moz, int x, int y, int click)
{
    char buf[512];
    pdf_link *link;
    fz_matrix ctm;
    fz_point p;
    int pi;
    int py;

    if (!moz->pages)
	return;

    pi = moz->scrollpage;
    py = -moz->scrollyofs;
    while (pi < moz->pagecount)
    {
	if (!moz->pages[pi].image)
	    return;
	if (y > py && y < moz->pages[pi].px)
	    break;
	py += moz->pages[pi].px;
	pi ++;
    }
    if (pi == moz->pagecount)
	return;

    p.x = x + moz->pages[pi].image->x;
    p.y = y + moz->pages[pi].image->y - py;

    ctm = pdfmoz_pagectm(moz, pi);
    ctm = fz_invertmatrix(ctm);

    p = fz_transformpoint(ctm, p);

    for (link = moz->pages[pi].page->links; link; link = link->next)
    {
	if (p.x >= link->rect.x0 && p.x <= link->rect.x1)
	    if (p.y >= link->rect.y0 && p.y <= link->rect.y1)
		break;
    }

    if (link)
    {
	SetCursor(moz->hand);
	if (click)
	{
	    if (fz_isstring(link->dest))
		pdfmoz_gotouri(moz, link->dest);
	    if (fz_isindirect(link->dest))
		pdfmoz_gotopage(moz, link->dest);
	    return;
	}
	else
	{
	    if (fz_isstring(link->dest))
	    {
		memcpy(buf, fz_tostrbuf(link->dest), fz_tostrlen(link->dest));
		buf[fz_tostrlen(link->dest)] = 0;
		NPN_Status(moz->inst, buf);
	    }
	    else if (fz_isindirect(link->dest))
	    {
		sprintf(buf, "Go to page %d",
			pdfmoz_getpagenum(moz, link->dest) + 1);
		NPN_Status(moz->inst, buf);
	    }
	    else
		NPN_Status(moz->inst, "Say what?");
	}
    }
    else
    {
	sprintf(buf, "Page %d of %d", moz->scrollpage + 1, moz->pagecount);
	NPN_Status(moz->inst, buf);
	SetCursor(moz->arrow);
    }
}

static void drawimage(HDC hdc, pdfmoz_t *moz, fz_pixmap *image, int yofs)
{
    int bmpstride = ((image->w * 3 + 3) / 4) * 4;
    unsigned char *bmpdata = fz_malloc(image->h * bmpstride);
    int x, y;

    if (!bmpdata)
	return;

    for (y = 0; y < image->h; y++)
    {
	unsigned char *p = bmpdata + y * bmpstride;
	unsigned char *s = image->samples + y * image->w * 4;
	for (x = 0; x < image->w; x++)
	{
	    p[x * 3 + 0] = s[x * 4 + 3];
	    p[x * 3 + 1] = s[x * 4 + 2];
	    p[x * 3 + 2] = s[x * 4 + 1];
	}
    }

    moz->dibinf->bmiHeader.biWidth = image->w;
    moz->dibinf->bmiHeader.biHeight = -image->h;
    moz->dibinf->bmiHeader.biSizeImage = image->h * bmpstride;
    SetDIBitsToDevice(hdc,
	    0, /* destx */
	    yofs, /* desty */
	    image->w, /* destw */
	    image->h, /* desth */
	    0, /* srcx */
	    0, /* srcy */
	    0, /* startscan */
	    image->h, /* numscans */
	    bmpdata, /* pBits */
	    moz->dibinf, /* pInfo */
	    DIB_RGB_COLORS /* color use flag */
		     );

    fz_free(bmpdata);
}

LRESULT CALLBACK
MozWinProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    pdfmoz_t *moz = (pdfmoz_t*) GetWindowLongPtr(hwnd, GWLP_USERDATA);
    char buf[256];

    int x = (signed short) LOWORD(lParam);
    int y = (signed short) HIWORD(lParam);
    int i, h;

    SCROLLINFO si;
    PAINTSTRUCT ps;
    HDC hdc;
    RECT rc;
    RECT pad;
    WORD sendmsg;
    float zoom;

    GetClientRect(hwnd, &rc);
    h = rc.bottom - rc.top;

    if (strlen(moz->error))
    {
	if (msg == WM_PAINT)
	{
	    hdc = BeginPaint(hwnd, &ps);
	    FillRect(hdc, &rc, GetStockBrush(WHITE_BRUSH));
	    rc.top += 10;
	    rc.bottom -= 10;
	    rc.left += 10;
	    rc.right -= 10;
	    DrawText(hdc, moz->error, strlen(moz->error), &rc, 0);
		// DT_SINGLELINE|DT_CENTER|DT_VCENTER);
	    EndPaint(hwnd, &ps);
	}
	if (msg == WM_MOUSEMOVE)
	{
	    SetCursor(moz->arrow);
	}
	return 0;
    }

    switch (msg)
    {

    case WM_PAINT:
	GetClientRect(moz->hwnd, &rc);

	si.cbSize = sizeof(si);
	si.fMask = SIF_ALL;
	GetScrollInfo(hwnd, SB_VERT, &si);

	decodescroll(moz, si.nPos);

	/* evict out-of-range images and pages */
	for (i = 0; i < moz->pagecount; i++)
	{
	    if (i < moz->scrollpage - 2 || i > moz->scrollpage + 6)
	    {
		if (moz->pages[i].page)
		{
		    pdf_droppage(moz->pages[i].page);
		    moz->pages[i].page = nil;
		}
	    }
	    if (i < moz->scrollpage - 1 || i > moz->scrollpage + 3)
	    {
		if (moz->pages[i].image)
		{
		    fz_droppixmap(moz->pages[i].image);
		    moz->pages[i].image = nil;
		}
	    }
	}

	i = moz->scrollpage;

	pdfmoz_loadpage(moz, i);
	if (moz->error[0]) return 0;

	pdfmoz_drawpage(moz, i);
	if (moz->error[0]) return 0;

	y = -moz->scrollyofs;
	while (y < h && i < moz->pagecount)
	{
	    pdfmoz_loadpage(moz, i);
	    if (moz->error[0]) return 0;
	    pdfmoz_drawpage(moz, i);
	    if (moz->error[0]) return 0;
	    y += moz->pages[i].image->h;
	    i ++;
	}

	hdc = BeginPaint(hwnd, &ps);

	pad.left = rc.left;
	pad.right = rc.right;

	i = moz->scrollpage;
	y = -moz->scrollyofs;
	while (y < h && i < moz->pagecount)
	{
	    drawimage(hdc, moz, moz->pages[i].image, y);
	    y += moz->pages[i].image->h;
	    i ++;

	    pad.top = y;
	    pad.bottom = y + PAD;
	    FillRect(hdc, &pad, moz->graybrush);
	    y += PAD;
	}

	if (y < h)
	{
	    pad.top = y;
	    pad.bottom = h;
	    FillRect(hdc, &pad, moz->graybrush);
	}

	EndPaint(hwnd, &ps);

	return 0;

    case WM_SIZE:
	ShowScrollBar(moz->hwnd, SB_VERT, TRUE);
	GetClientRect(moz->hwnd, &rc);

	si.cbSize = sizeof(si);
	si.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
	si.nPos = 0;
	si.nMin = 0;
	si.nMax = 0;
	// si.nPage = MAX(30, rc.bottom - rc.top - 30);
	si.nPage = rc.bottom - rc.top;

	for (i = 0; i < moz->pagecount; i++)
	{
	    zoom = (rc.right - rc.left) / (float) moz->pages[i].w;
	    moz->pages[i].px = zoom * moz->pages[i].h + PAD;

	    if (moz->scrollpage == i)
	    {
		si.nPos = si.nMax;
		if (moz->pages[i].image)
		{
		    si.nPos +=
			moz->pages[i].px *
			moz->scrollyofs /
			moz->pages[i].image->h + 1;
		}
	    }

	    if (moz->pages[i].image)
	    {
		fz_droppixmap(moz->pages[i].image);
		moz->pages[i].image = nil;
	    }

	    si.nMax += moz->pages[i].px;
	}

	si.nMax --;

	SetScrollInfo(moz->hwnd, SB_VERT, &si, TRUE);

	break;

    case WM_MOUSEMOVE:
	pdfmoz_onmouse(moz, x, y, 0);
	break;

    case WM_LBUTTONDOWN:
	SetFocus(hwnd);
	pdfmoz_onmouse(moz, x, y, 1);
	break;

    case WM_VSCROLL:

	si.cbSize = sizeof(si);
	si.fMask = SIF_ALL;
	GetScrollInfo(hwnd, SB_VERT, &si);

	switch (LOWORD(wParam))
	{
	case SB_BOTTOM: si.nPos = si.nMax; break;
	case SB_TOP: si.nPos = 0; break;
	case SB_LINEUP: si.nPos -= 50; break;
	case SB_LINEDOWN: si.nPos += 50; break;
	case SB_PAGEUP: si.nPos -= si.nPage; break;
	case SB_PAGEDOWN: si.nPos += si.nPage; break;
	case SB_THUMBTRACK: si.nPos = si.nTrackPos; break;
	case SB_THUMBPOSITION: si.nPos = si.nTrackPos; break;
	}

	si.fMask = SIF_POS;
	si.nPos = MAX(0, MIN(si.nPos, si.nMax));
	SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

	InvalidateRect(moz->hwnd, NULL, FALSE);

	decodescroll(moz, si.nPos);
	sprintf(buf, "Page %d of %d", moz->scrollpage + 1, moz->pagecount);
	NPN_Status(moz->inst, buf);

	return 0;

    case WM_MOUSEWHEEL:
	if ((signed short)HIWORD(wParam) > 0)
	    SendMessage(hwnd, WM_VSCROLL, MAKELONG(SB_LINEUP, 0), 0);
	else
	    SendMessage(hwnd, WM_VSCROLL, MAKELONG(SB_LINEDOWN, 0), 0);
	break;

    case WM_KEYDOWN:
	sendmsg = 0xFFFF;

	switch (wParam)
	{
	case VK_UP: sendmsg = SB_LINEUP; break;
	case VK_PRIOR: sendmsg = SB_PAGEUP; break;
	case ' ':
	case VK_NEXT: sendmsg = SB_PAGEDOWN; break;
	case '\r':
	case VK_DOWN: sendmsg = SB_LINEDOWN; break;
	case VK_HOME: sendmsg = SB_TOP; break;
	case VK_END: sendmsg = SB_BOTTOM; break;
	}

	if (sendmsg != 0xFFFF)
	    SendMessage(hwnd, WM_VSCROLL, MAKELONG(sendmsg, 0), 0);

	/* ick! someone eats events instead of bubbling... not my fault! */

	break;

    default:
	break;

    }

    return moz->winproc(hwnd, msg, wParam, lParam);
}

NPError
NPP_New(NPMIMEType mime, NPP inst, uint16 mode,
	int16 argc, char *argn[], char *argv[], NPSavedData *saved)
{
    pdfmoz_t *moz;

    //MSG("NPP_New");

    moz = fz_malloc(sizeof(pdfmoz_t));
    if (!moz)
	return NPERR_OUT_OF_MEMORY_ERROR;

    memset(moz, 0, sizeof(pdfmoz_t));

    sprintf(moz->error, "MuPDF is loading the file...");

    moz->inst = inst;

    moz->arrow = LoadCursor(NULL, IDC_ARROW);
    moz->hand = LoadCursor(NULL, IDC_HAND);
    moz->wait = LoadCursor(NULL, IDC_WAIT);

    moz->dibinf = fz_malloc(sizeof(BITMAPINFO) + 12);
    if (!moz->dibinf)
	return NPERR_OUT_OF_MEMORY_ERROR;

    moz->dibinf->bmiHeader.biSize = sizeof(moz->dibinf->bmiHeader);
    moz->dibinf->bmiHeader.biPlanes = 1;
    moz->dibinf->bmiHeader.biBitCount = 24;
    moz->dibinf->bmiHeader.biCompression = BI_RGB;
    moz->dibinf->bmiHeader.biXPelsPerMeter = 2834;
    moz->dibinf->bmiHeader.biYPelsPerMeter = 2834;
    moz->dibinf->bmiHeader.biClrUsed = 0;
    moz->dibinf->bmiHeader.biClrImportant = 0;
    moz->dibinf->bmiHeader.biClrUsed = 0;

    moz->graybrush = CreateSolidBrush(RGB(0x70,0x70,0x70));

    inst->pdata = moz;

    return NPERR_NO_ERROR;
}

NPError
NPP_Destroy(NPP inst, NPSavedData **saved)
{
    pdfmoz_t *moz = inst->pdata;
    int i;

    //MSG("NPP_Destroy");

    inst->pdata = NULL;

    DeleteObject(moz->graybrush);

    DestroyCursor(moz->arrow);
    DestroyCursor(moz->hand);
    DestroyCursor(moz->wait);

    fz_free(moz->dibinf);

    for (i = 0; i < moz->pagecount; i++)
    {
	if (moz->pages[i].obj)
	    fz_dropobj(moz->pages[i].obj);
	if (moz->pages[i].page)
	    pdf_droppage(moz->pages[i].page);
	if (moz->pages[i].image)
	    fz_droppixmap(moz->pages[i].image);
    }

    fz_free(moz->pages);

    if (moz->xref)
    {
	if (moz->xref->store)
	{
	    pdf_dropstore(moz->xref->store);
	    moz->xref->store = nil;
	}

	pdf_closexref(moz->xref);
    }

    fz_free(moz);

    return NPERR_NO_ERROR;
}

NPError
NPP_SetWindow(NPP inst, NPWindow *npwin)
{
    pdfmoz_t *moz = inst->pdata;

    if (moz->hwnd != npwin->window)
    {
	moz->hwnd = npwin->window;
	SetWindowLongPtr(moz->hwnd, GWLP_USERDATA, (LONG_PTR)moz);
	moz->winproc = (WNDPROC)
	    SetWindowLongPtr(moz->hwnd, GWLP_WNDPROC, (LONG_PTR)MozWinProc);
    }

    SetFocus(moz->hwnd);

    return NPERR_NO_ERROR;
}

NPError
NPP_NewStream(NPP inst, NPMIMEType type,
	NPStream* stream, NPBool seekable,
	uint16* stype)
{
    //MSG("NPP_NewStream");
    *stype = NP_ASFILE;
    return NPERR_NO_ERROR;
}

NPError
NPP_DestroyStream(NPP inst, NPStream* stream, NPReason reason)
{
    //MSG("NPP_DestroyStream");
    return NPERR_NO_ERROR;
}

int32
NPP_WriteReady(NPP inst, NPStream* stream)
{
    //MSG("NPP_WriteReady");
    return 2147483647;
}

int32
NPP_Write(NPP inst, NPStream* stream, int32 offset, int32 len, void* buffer)
{
    //MSG("NPP_Write");
    return len;
}

void
NPP_StreamAsFile(NPP inst, NPStream* stream, const char* fname)
{
    pdfmoz_t *moz = inst->pdata;
    //MSG("NPP_StreamAsFile");
    pdfmoz_open(moz, (char*)fname);
}

void
NPP_Print(NPP inst, NPPrint* platformPrint)
{
    MSG("Sorry, printing is not supported.");
}

int16
NPP_HandleEvent(NPP inst, void* event)
{
    MSG("handle event\n");
    return 0;
}

void
NPP_URLNotify(NPP inst, const char* url,
	NPReason reason, void* notifyData)
{
    MSG("notify url\n");
}

NPError
NPP_GetValue(void* inst, NPPVariable variable, void *value)
{
    return NPERR_NO_ERROR;
}

NPError
NPP_SetValue(void* inst, NPNVariable variable, void *value)
{
    return NPERR_NO_ERROR;
}

void* NPP_GetJavaClass(void)
{
    return 0;
}

NPError
NPP_Initialize(void)
{
    //	MSG("NPP_Initialize");
    return NPERR_NO_ERROR;
}

void
NPP_Shutdown(void)
{
    //	MSG("NPP_Shutdown");
}


