/* 
 * Copyright (C) 2006, iRex Technologies B.V.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
 
#include "ScribbleMgr.h"
#include "GtkMgr.h"
#include "controller.h"
#include "Settings.h"
#include "PDFViewerLog.h"
#include "utils.h"
#include "Gestures.h"
#include <math.h>
#include <string>
#include <iostream>
#include <fstream>

static const double e = 0.001;


CScribbleMgr::CScribbleMgr(void)
: doc(NULL)
, page(NULL)
, stroke(NULL)
, pageNumber(-1)
, bErased(gFalse)
, bScbDirty(gFalse)
{
    scb_init();
    lastPoint.x = 0; lastPoint.y = 0;
    timer = g_timer_new();
}

CScribbleMgr::~CScribbleMgr(void)
{
    g_timer_destroy(timer);
    scb_uninit();
}

//////////////////////////////////////////////////////////////////////////
// open scribble data file
void CScribbleMgr::open(const char *dirName)
{
    close();
    
    // construct scribble file name
    if (NULL == dirName) return;
    strncpy(path.scbname, dirName, SCB_MAX_PATH);
    char *pos = strrchr(path.scbname, '/');
    strcpy(++pos, "scribble.irx");      
    pos += strlen("scribble.irx");
    *pos = 0;

    // open file
    doc = scb_doc_make_sure_exist(&path);
}

void CScribbleMgr::close(GBool bSave)
{
    if (doc && bSave && isScbDirty())
    {
        PV_SCBPRINTF("Save scribble now!");
        scb_doc_save(doc);
    }
    scb_doc_free(doc);
    doc = NULL;
    memset(&path, 0, sizeof(path));
    page = NULL;
    stroke = NULL;
    setScbDirty(gFalse);
}

void CScribbleMgr::setScbDirty(const GBool b)
{ 
    bScbDirty = b; 
}

void CScribbleMgr::save()
{
    if (doc && isScbDirty())
    {
        PV_SCBPRINTF("Save scribble now!");
        scb_doc_save(doc);
    }
    setScbDirty(gFalse);
}

void CScribbleMgr::generatePageId(ScbPageIdPtr pid, const int pn)
{
    snprintf(pid->id, SCB_MAX_PAGEID_LEN, "%d", pn);
}

void CScribbleMgr::beginStroke(const int pn, Controller * ctrl, 
                               const int px, const int py,
                               const int x, const int y,
                               const int ox, const int oy)
{
    PV_SCBPRINTF("Create new stroke!");

    // Start timer for gestures
    isGesture = false;
    justStarted = true;
    g_timer_start(timer);

    // record current page number and page position
    pageNumber = pn;
    pagePoint.x = px; 
    pagePoint.y = py;

    // check page
    generatePageId(&id, pn);
    page = scb_doc_get_page(doc, &id);
    if (NULL == page)
    {
        // does not exist, needs to create a new one
        page = scb_page_new();
        scb_page_set_id(page, &id);
        page->style.orientation = ctrl->settings.getRotate();
    }

    // check stroke
    if (NULL == stroke)
    {
        stroke = scb_stroke_new_with_style(scb_doc_get_current_stroke_style(doc));
        stroke->style.zoom = ctrl->calZoom(ctrl->settings.getCurrentPage());
//         ScbStrokesPtr strokes = scb_page_get_strokes(page);
//         scb_strokes_add_stroke(strokes, stroke);
    }

    // stroke point data, in offset
    point.x = ox;
    point.y = oy;
    point.pressure = 0;
    scb_stroke_add_point(stroke, &point);
    
    // for drawing
    point.x = x;
    point.y = y;
    scb_stroke_fast_draw_point(stroke, &point);
    
    // set dirty flag now
    setScbDirty();  
}

void CScribbleMgr::addPoint(const int pn, Controller * ctrl, 
                            const int px, const int py,
                            const int x, const int y, 
                            const int ox, const int oy)
{
    point.x = ox;
    point.y = oy;
    scb_stroke_add_point(stroke, &point);
    
    point.x = x;
    point.y = y;
    scb_stroke_fast_draw_point(stroke, &point);
    
    // set dirty flag now
    setScbDirty();
}

void CScribbleMgr::endStroke(const int pn, Controller * ctrl, 
                             const int px, const int py,
                             const int x, const int y, 
                             const int ox, const int oy,
			     CPDFPortraitView * view)
{
    PV_SCBPRINTF("End stroke!");
    
    // stroke data
    point.x = ox;
    point.y = oy;
    scb_stroke_add_point(stroke, &point);
    
    // drawing
    point.x = x;
    point.y = y;
    scb_stroke_fast_draw_point_done(stroke, &point);
    
    // draw in display
    drawStroke(stroke, ctrl, px, py);

    // Rincewind
    // Perform the gesture
    if (isGesture) {
	performGesture(ctrl, pn, stroke, px, py, view);
    } else if (g_timer_elapsed(timer, NULL) > 0.75) {
	// ERASE operation
	eraseStroke(ctrl, pn, stroke, px, py);
	clearStroke(ctrl, stroke, px, py);
    } else {
	// Add the stroke to the page
        ScbStrokesPtr strokes = scb_page_get_strokes(page);
        scb_strokes_add_stroke(strokes, stroke);
    }
    
    // add & reset
    scb_doc_add_page(doc, page);
    page = NULL;
    stroke = NULL;
    pageNumber = -1;

    // set dirty flag now
    setScbDirty();
}

void CScribbleMgr::eraseStroke(Controller *ctrl,
			       const int pn,
			       const ScbStrokePtr stroke,
			       const int x, const int y) {

    // Loop over all points of the "erase stroke"
    int count  = scb_stroke_get_point_count(stroke);
    ScbPoint * pts = (ScbPoint *)scb_stroke_get_point_data(stroke);
    
    int elems = count/2;
    
    struct point_t {
	int x;
	int y;
    }  points[elems+1];
    
    for (int i=0; i<elems+1; i++) {
	points[i].x = pts[i].x;
	points[i].y = pts[i].y;
    }
    
    
    for (int i=0; i<elems; i++) {
	
	if (i == 0)

	    onErasePress(pn, ctrl,  
			 x, y,       
			 points[i].x, points[i].y);
	else
	    onEraseMove(pn, ctrl,  
			 x, y,       
			 points[i].x, points[i].y);
    }

    // End it as well
    // This one resets the page pointer, which screws us
    //onEraseRelease(pn, ctrl, x, y, points[elems].x, points[elems].y);
}

void CScribbleMgr::performGesture(Controller *ctrl,
				  const int pn,
				  const ScbStrokePtr stroke,
				  const int x, const int y,
				  CPDFPortraitView * view) {
    
    Gesture gst = discoverGesture(stroke);
    if (gst.numDirections() == 0) {
	printf("Unrecognized gesture\n");
	ctrl->channels.busy_off();
	return;
    }

    loadGestures();
    
    // Get the max values from the gesture!
    // Needed for zoom
    rectangle zoomBox = getBoxFromStroke(stroke);
    int size = stroke->style.penSize;
    int color = stroke->style.color;

    // Erase the stroke - if not it will be added to the page...
    //eraseStroke(ctrl, pn, stroke, x, y);
    clearStroke(ctrl, stroke, x, y);
    
    // Gesture is a list of directions, get the mapping to a function
    GestureFunction func = knownGestures[gst];
    if (func == FUNC_UNDEFINED) {
	// TODO: Give user some feedback?
	printf("Gesture not recognized\n");
	ctrl->channels.busy_off();
	return;
    }
    
    printf("Found a function: %d\n", func);
    
    // Perform
    switch(func) {
	// TODO: Do this nicer?
    case FUNC_INCREASE_PEN:
	switch(size){
	case 1:
	    ctrl->channels.onScribbleIconsClicked(iconID_pen3pixelLow, 1);
	    break;
	case 3:
	    ctrl->channels.onScribbleIconsClicked(iconID_pen5pixelLow, 1);
	    break;
	case 5:
	    ctrl->channels.onScribbleIconsClicked(iconID_pen7pixelLow, 1);
	    break;
	}
	break;
    case FUNC_DECREASE_PEN:
	switch(size){
	case 3:
	    ctrl->channels.onScribbleIconsClicked(iconID_pen1pixelLow, 1);
	    break;
	case 5:
	    ctrl->channels.onScribbleIconsClicked(iconID_pen3pixelLow, 1);
	    break;
	case 7:
	    ctrl->channels.onScribbleIconsClicked(iconID_pen5pixelLow, 1);
	    break;
	}
	break;
    case FUNC_DARKER_COLOR:
	switch(color){
	case 0:
	    ctrl->channels.onScribbleIconsClicked(iconID_penlightgreyLow, 1);
	    break;
	case 1:
	    ctrl->channels.onScribbleIconsClicked(iconID_pendarkgreyLow, 1);
	    break;
	case 2:
	    ctrl->channels.onScribbleIconsClicked(iconID_penblackLow, 1);
	    break;
	default:
	    printf("Unexpected darker color\n");
	}
	break;
    case FUNC_LIGHTER_COLOR:
	switch(color){
	case 1:
	    ctrl->channels.onScribbleIconsClicked(iconID_penwhiteLow, 1);
	    break;
	case 2:
	    ctrl->channels.onScribbleIconsClicked(iconID_penlightgreyLow, 1);
	    break;
	case 3:
	    ctrl->channels.onScribbleIconsClicked(iconID_pendarkgreyLow, 1);
	    break;
	default:
	    printf("Unexpected lighter color\n");
	}
	break;
	
    case FUNC_ZOOM_IN:
	printf("Ready to zoom in?\n");
        //view->adjustZoomRect(x, y);  // What do these do???
	view->zoomFromRect(zoomBox.left, zoomBox.top,
			   zoomBox.right, zoomBox.bottom);
	break;
    case FUNC_ZOOM_OUT:
	printf("Zooming out\n");
	view->zoomOut();
	break;
    case FUNC_REFRESH_DISPLAY:
	view->refreshDisplay();

    case FUNC_UNDEFINED:
	break;
    }

    ctrl->channels.busy_off();
}

rectangle CScribbleMgr::getBoxFromStroke(const ScbStrokePtr stroke) {

    int count  = scb_stroke_get_point_count(stroke);
    ScbPoint * pts = (ScbPoint *)scb_stroke_get_point_data(stroke);
    
    //Box box;
    rectangle box;
    box.bottom = box.left = 102400;  // "totally out of bounds"
    box.top = box.right = 0;
    
    for (int i=0; i<count; i++) {
	if (pts[i].x < box.left)
	    box.left = pts[i].x;
	if (pts[i].x > box.right)
	    box.right = pts[i].x;
	if (pts[i].y < box.bottom)
	    box.bottom = pts[i].y;
	if (pts[i].y > box.top)
	    box.top = pts[i].y;
    }

    return box;
}

Gesture CScribbleMgr::discoverGesture(const ScbStrokePtr stroke) {

    // Analyze the scribble to find out the shape...
    int count  = scb_stroke_get_point_count(stroke);
    ScbPoint * pts = (ScbPoint *)scb_stroke_get_point_data(stroke);


    int startx, starty;
    startx = pts[0].x;
    starty = pts[0].y;

    Gesture gst;

    // We create a virtual boundingbox around the first point.
    // When the cursor leaves the box, we see which edge it crosses
    // After that, create a new box, and repeat.
    // Each time a new box is created, the direction is added to the
    // gesture list.

    rectangle bbox;
    //Box bbox;
//     struct box_t {
// 	int top, bottom;
// 	int left, right;
//     } bbox;

    
    memset(&bbox, 0, sizeof(bbox));
    
    GBool first_run = true;
    
    Direction direction, last_direction;
    direction = last_direction = DIRECTION_NONE;
    
    printf("Trying to discover gesture\n");

    // Pen thickness
    for (int i=1; i<count; i++) {
	
	//printf("Analyzing point %d,%d\n",pts[i].x, pts[i].y);
	
	if (!first_run) {
	    
	    // Are we outside the box?
	    if ((pts[i].x >= bbox.right) &&
		(pts[i].y <= bbox.top) && 
		(pts[i].y >= bbox.bottom)) {
		
		direction = DIRECTION_RIGHT;
	    } else if ((pts[i].x <= bbox.left) &&
		       (pts[i].y <= bbox.top) &&
		       (pts[i].y >= bbox.bottom)) {
		direction = DIRECTION_LEFT;
	    } else if ((pts[i].y >= bbox.top) &&
		       (pts[i].x >= bbox.left) &&
		       (pts[i].x <= bbox.right)) {
		direction = DIRECTION_DOWN;
	    } else if ((pts[i].y <= bbox.bottom) &&
		       (pts[i].x >= bbox.left) &&
		       (pts[i].x <= bbox.right)) {
		direction = DIRECTION_UP;
	    }
	} 

	if ((direction != DIRECTION_NONE) && (direction != last_direction)) {
	    gst.addDirection(direction);
	}

	if (first_run || direction != DIRECTION_NONE) {

	    // Create new box
	    bbox.left = pts[i].x - BOUNDING_BOX_SIZE;
	    bbox.bottom = pts[i].y - BOUNDING_BOX_SIZE;
	    bbox.right = pts[i].x + BOUNDING_BOX_SIZE;
	    bbox.top = pts[i].y + BOUNDING_BOX_SIZE;
	    
	    //printf("Created new box l:%d,b:%d,r:%d,t:%d\n",bbox.left, bbox.bottom, bbox.right, bbox.top);
	    last_direction = direction;
	} 
	
	first_run = false;
	direction = DIRECTION_NONE;
    }

    return gst;
}


/**
 * Map functions from textual to numeric representation
 */
GestureFunction CScribbleMgr::stringToFunction(std::string str) {
    
    // TODO: Make this more elegant
    if (str == "")
	return FUNC_UNDEFINED;
    
    if (str == "INCREASE_PEN")
	return FUNC_INCREASE_PEN;

    if (str == "DECREAES_PEN")
	return FUNC_DECREASE_PEN;
    
    if (str == "DARKER_COLOR")
	return FUNC_DARKER_COLOR;
    
    if (str == "LIGHTER_COLOR")
	return FUNC_LIGHTER_COLOR;

    if (str == "ZOOM_IN")
	return FUNC_ZOOM_IN;

    if (str == "ZOOM_OUT")
	return FUNC_ZOOM_OUT;
    
    return FUNC_UNDEFINED;
}

/**
 * Map directions from textual to numerical
 */
Direction CScribbleMgr::stringToDirection(std::string str) {
    
    if (str == "RIGHT")
	return DIRECTION_RIGHT;
    if (str == "LEFT")
	return DIRECTION_LEFT;
    if (str == "UP")
	return DIRECTION_UP;
    if (str == "DOWN")
	return DIRECTION_DOWN;

    return DIRECTION_NONE;
}
	    
/**
 * Load gestures from file
 */
void CScribbleMgr::loadGestures() {

    if (knownGestures.size() > 0)
	return;

    std::ifstream is("/mnt/settings/.ipdfrc");

    if (!is) {
	// Use default gestures
	Gesture gst;
	// Pen size
	gst.addDirection(DIRECTION_RIGHT);
	gst.addDirection(DIRECTION_UP);
	knownGestures[gst] = FUNC_INCREASE_PEN;
	
	gst.clear();
	gst.addDirection(DIRECTION_RIGHT);
	gst.addDirection(DIRECTION_DOWN);
	knownGestures[gst] = FUNC_DECREASE_PEN;

	// Color
	gst.clear();
	gst.addDirection(DIRECTION_LEFT);
	gst.addDirection(DIRECTION_UP);
	knownGestures[gst] = FUNC_DARKER_COLOR;

	gst.clear();
	gst.addDirection(DIRECTION_LEFT);
	gst.addDirection(DIRECTION_DOWN);
	knownGestures[gst] = FUNC_LIGHTER_COLOR;

	// Zoom
	gst.clear();
	gst.addDirection(DIRECTION_RIGHT);
	gst.addDirection(DIRECTION_DOWN);
	gst.addDirection(DIRECTION_LEFT);
	gst.addDirection(DIRECTION_UP);
	knownGestures[gst] = FUNC_ZOOM_IN;

	gst.clear();
	gst.addDirection(DIRECTION_LEFT);
	gst.addDirection(DIRECTION_UP);
	gst.addDirection(DIRECTION_LEFT);
	knownGestures[gst] = FUNC_ZOOM_OUT;
	
	// Refresh page
	gst.clear();
	gst.addDirection(DIRECTION_DOWN);
	gst.addDirection(DIRECTION_UP);
	knownGestures[gst] = FUNC_REFRESH_DISPLAY;
	return;
    }
    
    printf("Reading file!\n");

    char buf[160];
    std::string line;

    GestureFunction func = FUNC_UNDEFINED;
    Gesture gst;

    while (!is.eof()) {

	is.getline(buf, sizeof(buf));
	
	if ((strlen(buf) == 0) || (buf[0] == '#'))
	    continue; // Empty line or comment
	
	line = buf;
	printf("Read a line: '%s'\n",line.c_str());
	
	// Split the string
	unsigned int idx;
	idx = line.find_first_of(':');
	if (idx == std::string::npos)
	    continue;  // Bad line
	
	func = stringToFunction(line.substr(idx+1));
	line = line.substr(0, idx);
		
	while (true) {
	    idx = line.find_first_of(',');
	    if(idx == std::string::npos ) {
		gst.addDirection(stringToDirection(line));
		break;
	    }

	    gst.addDirection(stringToDirection(line.substr(0, idx)));
	    line.erase(0, idx+1);
	}
    }

    // Got the function and the gesture, add to the map
    knownGestures[gst] = func;
}

// end stroke without adding point
void CScribbleMgr::endStroke(Controller * ctrl, 
                const int px, const int py,
		const int x, const int y,
		CPDFPortraitView* view)
{
    // drawing
    point.x = x;
    point.y = y;
    scb_stroke_fast_draw_point_done(stroke, &point);
    
    // draw
    drawStroke(stroke, ctrl, px, py);

    // add & reset
    scb_doc_add_page(doc, page);
    page = NULL;
    stroke = NULL;
    pageNumber = -1;
}

void CScribbleMgr::onScribblePress(const int pn, Controller * ctrl, 
                                 const int px, const int py, 
                                 const int x, const int y, 
                                 const int ox, const int oy)
{
    lastPoint.x = x; lastPoint.y = y;

    if (pn > 0 && page && stroke)
    {
        addPoint(pn, ctrl, px, py,  x, y, ox, oy);
        return;
    }
    
    if (pn > 0 && NULL == page && NULL == stroke)
    {            
        beginStroke(pn, ctrl, px, py, x, y, ox, oy);
    }        
}

//////////////////////////////////////////////////////////////////////////
// 1. move from page to window(out of page)
// 2. move from window(out of page) to page
// 3. move from one page to the other
void CScribbleMgr::onScribbleMove(const int pn, Controller * ctrl,  
                                const int px, const int py,
                                const int x, const int y, 
				const int ox, const int oy,
				CPDFPortraitView * view)
{

    if (abs(lastPoint.x - x) + abs(lastPoint.y - y) > 4) {
	
	// Is this a gesture?
	if (justStarted && !isGesture) {
	    if (g_timer_elapsed(timer, NULL) > 0.75) {
		isGesture = TRUE;
		ctrl->channels.busy_on();
	    }
	}

	justStarted = false;

	g_timer_start(timer);
    }
    

    lastPoint.x = x; lastPoint.y = y;

    // out of page, end stroke now.
    if (pn < 0 && stroke && page)
    {
        // end without adding it
        endStroke(ctrl, pagePoint.x, pagePoint.y, x, y, view);
        return;
    }

    // re-enter page bound
    if (pn > 0 && stroke == NULL && page == NULL)
    {
        beginStroke(pn, ctrl, px, py, x, y, ox, oy);
        return;
    }

    // from one page to the other, should use old px and py
    if (pn > 0 && pageNumber > 0 && pn != pageNumber && stroke && page)
    {
        endStroke(ctrl, pagePoint.x, pagePoint.y, x, y, view);
        beginStroke(pn, ctrl, px, py, x, y, ox, oy);
        return;
    }

    // scribble mode
    if (stroke)
    {
        addPoint(pn, ctrl, px, py, x, y, ox, oy);
    }
}

void CScribbleMgr::onScribbleRelease(const int pn, Controller * ctrl, 
                                   const int px, const int py,
                                   const int x, const int y,    
				   const int ox, const int oy,
				   CPDFPortraitView * view)
{
    if (pn > 0 && stroke && doc && page)
    {
        endStroke(pn, ctrl, px, py, x, y, ox, oy, view);
    }
}

void CScribbleMgr::setEraseDirty(GBool b)
{
    bErased = b; 
    if (b)
    {
        setScbDirty();
    }        
}     

//////////////////////////////////////////////////////////////////////////
// when erasing, no need to check whether or not go into another page
void CScribbleMgr::onErasePress(const int pn, Controller * ctrl,  
                     const int x, const int y,       // for erase redrawing
                     const int ox, const int oy)
{
    if (pn < 0) return;
    scb_page_erase_init(&eraseCtx);
    generatePageId(&id, pn);
    page = scb_doc_get_page(doc, &id);
    if (NULL == page) return;

    // record
    pageNumber = pn;
    point.x = ox; point.y = oy;
    eraseCtx.zoom = ctrl->calZoom(ctrl->settings.getCurrentPage());
    ScbStrokesPtr strokes = scb_page_erase_hit_test(page, &point, &eraseCtx);
    if (strokes)
    {
        PV_LOGPRINTF("Erase strokes!");
        
        // draw and free
        if (ctrl->settings.getRotate() == 270)
        {
            drawErasedStrokesLandscape(strokes, ctrl, x, y);
        }
        else
        {
            drawErasedStrokesPortrait(strokes, ctrl, x, y);
        }
        
        // set dirty flag
        setEraseDirty();
               
        // free
        scb_strokes_free(strokes);
    }
}

void CScribbleMgr::onEraseMove(const int pn, Controller * ctrl, 
                    const int x, const int y, 
                    const int ox, const int oy)
{
    if (pn < 0) return;
    if (pageNumber != pn)
    {
        pageNumber = pn;
        scb_page_erase_init(&eraseCtx);
        eraseCtx.zoom = ctrl->calZoom(ctrl->settings.getCurrentPage());
    }

    generatePageId(&id, pn);
    page = scb_doc_get_page(doc, &id);
    if (NULL == page) return;
     
    point.x = ox; point.y = oy;

    ScbStrokesPtr strokes = scb_page_erase_hit_test(page, &point, &eraseCtx);
    if (strokes)
    {
        PV_LOGPRINTF("Erase strokes!");
        if (ctrl->settings.getRotate() == 270)
        {
            drawErasedStrokesLandscape(strokes, ctrl, x, y);
        }
        else
        {
            drawErasedStrokesPortrait(strokes, ctrl, x, y);
        }

        // set dirty flag
        setEraseDirty();

        // free 
        scb_strokes_free(strokes);
    }
}

void CScribbleMgr::clearStroke(Controller * ctrl, 
			       const ScbStrokePtr stroke, 
			       const int x, const int y) {

    if (ctrl->settings.getRotate() == 270)
        {
            drawErasedStrokesLandscape(stroke, ctrl, x, y);
        }
    else
        {
            drawErasedStrokesPortrait(stroke, ctrl, x, y);
        }
    
    // set dirty flag
    setEraseDirty();
    
    // free 
    scb_stroke_free(stroke);
}

void CScribbleMgr::onEraseRelease(const int pn, Controller * ctrl,
                       const int x, const int y, 
                       const int ox, const int oy)
{
    if (pn < 0 || NULL == page) return;
    point.x = ox; point.y = oy;
    ScbStrokesPtr strokes = scb_page_erase_hit_test(page, &point, &eraseCtx);

    // reset
    page = NULL;
    if (strokes) 
    {
        PV_LOGPRINTF("Erase strokes!");
        if (ctrl->settings.getRotate() == 270)
        {
            drawErasedStrokesLandscape(strokes, ctrl, x, y);
        }
        else
        {
            drawErasedStrokesPortrait(strokes, ctrl, x, y);
        }
        
        // set dirty flag
        setEraseDirty();
        
        // draw and free
        scb_strokes_free(strokes);
    }                
}

//////////////////////////////////////////////////////////////////////////
// x & y is the page start position in screen
void CScribbleMgr::drawScribblePage(const int pn, Controller * ctrl, 
                                    const int x, const int y)
{
#if (PV_PROFILE_ON)
    int t1 = ctrl->getHighTimeStamp();
#endif 

    // page from page id
    generatePageId(&id, pn);
    ScbPagePtr ptr = scb_doc_get_page(doc, &id);
    if (NULL == ptr) 
    {
        PV_LOGPRINTF("page %s not found!", id.id);
        return;
    }

    if (ctrl->settings.getRotate() == 270)
    {
        drawScribblePageLandscape(ptr, ctrl, x, y);
    }
    else
    {
        drawScribblePagePortrait(ptr, ctrl, x, y);
    }

#if (PV_PROFILE_ON)
        int t2 = ctrl->getHighTimeStamp();
        PV_DUMP("Scribble Manager drawScribblePage uses %d\n", t2 - t1);
#endif 
}


int CScribbleMgr::calLineSize(const int penSize, const double strokeZoom, const double dispZoom)
{
    if (fabs(strokeZoom - dispZoom) < e)
    {
        return penSize;
    }
    int ret = (int)(penSize * strokeZoom / dispZoom);
    if (ret < 1) return 1;
    if (ret > 10) return 10;
    return ret;
}

void CScribbleMgr::drawScribblePagePortrait(ScbPagePtr ptr, Controller * ctrl, 
                                 const int x, const int y)
{
    ScbStrokesPtr strokes = scb_page_get_strokes(ptr);
    double z = ctrl->calZoom(ctrl->settings.getCurrentPage());
    if (strokes)
    {
        ScbStrokePtr stroke = NULL;
        GList *ptr = g_list_first(strokes->strokes);
        while (ptr)
        {
            stroke = (ScbStrokePtr)ptr->data;
            if (stroke)
            {
                // point data
                int count  = scb_stroke_get_point_count(stroke);
                ScbPoint * pts = (ScbPoint *)scb_stroke_get_point_data(stroke);

                // select color. 
                ScbColor color;
                scb_dev_color_to_color(&color, stroke->style.color);
                ctrl->gtkMgr.setLineColor(color.pixel);

                // select pen size
                ctrl->gtkMgr.setLineAttributes(
                    calLineSize(stroke->style.penSize, 
                    z, stroke->style.zoom));

                // draw lines now
                if (fabs(z - stroke->style.zoom) < e)
                {
                    int i = 0;
                    int x1 = x + pts[i].x, y1 = y + pts[i].y; ++i;
                    int x2, y2;
                    while (i < count)
                    {
                        x2 = x + pts[i].x, y2 = y + pts[i].y; ++i;
                        ctrl->gtkMgr.drawLine(x1, y1, x2, y2);
                        x1 = x2; y1 = y2;
                    }
                    
                    if (count <= 1)
                    {
                        ctrl->gtkMgr.drawLine(x1, y1, x1, y1);
                    }
                }
                else
                {
                    double ratio = z / stroke->style.zoom;
                    int i = 0;
                    int x1 = x + (int)(ratio * pts[i].x), y1 = y + (int)(ratio * pts[i].y); ++i;
                    int x2, y2;
                    while (i < count)
                    {
                        x2 = x + (int)(ratio * pts[i].x); y2 = y + (int)(ratio * pts[i].y); ++i;
                        ctrl->gtkMgr.drawLine(x1, y1, x2, y2);
                        x1 = x2; y1 = y2;
                    }

                    if (count <= 1)
                    {
                        ctrl->gtkMgr.drawLine(x1, y1, x1, y1);
                    }
                }
            }
            ptr = g_list_next(ptr);
        }
    }
}

void CScribbleMgr::drawScribblePageLandscape(ScbPagePtr ptr, Controller * ctrl, 
                                  const int x, const int y)
{
    ScbStrokesPtr strokes = scb_page_get_strokes(ptr);
    double z = ctrl->calZoom(ctrl->settings.getCurrentPage());
    if (strokes)
    {
        ScbStrokePtr stroke = NULL;
        GList *ptr = g_list_first(strokes->strokes);
        while (ptr)
        {
            stroke = (ScbStrokePtr)ptr->data;
            if (stroke)
            {
                // point data
                int count  = scb_stroke_get_point_count(stroke);
                ScbPoint * pts = (ScbPoint *)scb_stroke_get_point_data(stroke);

                // select color.  
                ScbColor color;
                scb_dev_color_to_color(&color, stroke->style.color);
                ctrl->gtkMgr.setLineColor(color.pixel);

                // select pen size
                ctrl->gtkMgr.setLineAttributes(
                    calLineSize(stroke->style.penSize,
                    z, stroke->style.zoom));

                // draw lines now
                if (fabs(z - stroke->style.zoom) < e)
                {
                    int i = 0;
                    int x1 = y - pts[i].x, y1 = x + pts[i].y; ++i;
                    int x2, y2;
                    while (i < count)
                    {
                        x2 = y - pts[i].x, y2 = x + pts[i].y; ++i;
                        ctrl->gtkMgr.drawLine(y1, x1, y2, x2);  // swap
                        x1 = x2; y1 = y2;
                    }
                    
                    // single point
                    if (1 >= count)
                    {
                        ctrl->gtkMgr.drawLine(y1, x1, y1, x1);
                    }
                }
                else
                {
                    double ratio = z / stroke->style.zoom;
                    int i = 0;
                    int x1 = y - (int)(ratio * pts[i].x), y1 = x + (int)(ratio * pts[i].y); ++i;
                    int x2, y2;
                    while (i < count)
                    {
                        x2 = y - (int)(ratio * pts[i].x), y2 = x + (int)(ratio * pts[i].y); ++i;
                        ctrl->gtkMgr.drawLine(y1, x1, y2, x2);
                        x1 = x2; y1 = y2;
                    }

                    // single point
                    if (1 >= count)
                    {
                        ctrl->gtkMgr.drawLine(y1, x1, y1, x1);
                    }
                }
            }
            ptr = g_list_next(ptr);
        }
    }
}

// Draw a single erased stroke

void  CScribbleMgr::drawErasedStrokesPortrait(ScbStrokePtr stroke, Controller * ctrl, 
                const int x, const int y)
{
    double z = ctrl->calZoom(ctrl->settings.getCurrentPage());

    if (stroke)
	{
	    // adjust position
	    int count  = scb_stroke_get_point_count(stroke);
	    ScbPoint * pts = (ScbPoint *)scb_stroke_get_point_data(stroke);
            
	    // draw                
	    if (fabs(z - stroke->style.zoom) < e)
                {
                    int i = 0;
                    while (i < count)
			{
			    pts[i].x += x, pts[i].y += y; ++i;
			}                        
                }
	    else
                {
                    double ratio = z / stroke->style.zoom;
                    int i = 0;
                    while (i < count)
			{
			    pts[i].x = x + (int)(pts[i].x * ratio);
			    pts[i].y = y + (int)(pts[i].y * ratio);
			    ++i;
			}                        
                }
	    stroke->style.color = SCB_DEV_COLOR_WHITE;
	    scb_stroke_fast_draw(stroke);
	    drawStrokeDirectly(stroke, ctrl);                
	}
}

//////////////////////////////////////////////////////////////////////////
// redraw stroke by driver
void  CScribbleMgr::drawErasedStrokesPortrait(ScbStrokesPtr strokes, Controller * ctrl, 
                const int x, const int y)
{
    double z = ctrl->calZoom(ctrl->settings.getCurrentPage());
    if (strokes)
    {
        ScbStrokePtr stroke = NULL;
        GList *ptr = g_list_first(strokes->strokes);
        while (ptr)
        {
            stroke = (ScbStrokePtr)ptr->data;
            if (stroke)
            {
                // adjust position
                int count  = scb_stroke_get_point_count(stroke);
                ScbPoint * pts = (ScbPoint *)scb_stroke_get_point_data(stroke);
                
                // draw                
                if (fabs(z - stroke->style.zoom) < e)
                {
                    int i = 0;
                    while (i < count)
                    {
                        pts[i].x += x, pts[i].y += y; ++i;
                    }                        
                }
                else
                {
                    double ratio = z / stroke->style.zoom;
                    int i = 0;
                    while (i < count)
                    {
                        pts[i].x = x + (int)(pts[i].x * ratio);
                        pts[i].y = y + (int)(pts[i].y * ratio);
                        ++i;
                    }                        
                }
                stroke->style.color = SCB_DEV_COLOR_WHITE;
                scb_stroke_fast_draw(stroke);
                drawStrokeDirectly(stroke, ctrl);                
            }
            ptr = g_list_next(ptr);
        }
    }
}

void CScribbleMgr::drawErasedStrokesLandscape(ScbStrokePtr stroke, Controller * ctrl, 
                const int x, const int y)
{
    double z = ctrl->calZoom(ctrl->settings.getCurrentPage());

    if (stroke)
	{
	    // adjust position
	    int count  = scb_stroke_get_point_count(stroke);
	    ScbPoint * pts = (ScbPoint *)scb_stroke_get_point_data(stroke);
            
	    // draw                
	    if (fabs(z - stroke->style.zoom) < e)
                {
                    int i = 0;
                    while (i < count)
			{
			    pts[i].x = y - pts[i].x;
			    pts[i].y = x + pts[i].y;
			    swap(pts[i].x, pts[i].y);
			    ++i;
			}                        
                }
	    else
                {
                    double ratio = z / stroke->style.zoom;
                    int i = 0;
                    while (i < count)
			{
			    pts[i].x = y - (int)(pts[i].x * ratio);
			    pts[i].y = x + (int)(pts[i].y * ratio);
			    swap(pts[i].x, pts[i].y);
			    ++i;
			}                        
                }
	    stroke->style.color = SCB_DEV_COLOR_WHITE;
	    scb_stroke_fast_draw(stroke);
	    drawStrokeDirectly(stroke, ctrl);
	}
}

void CScribbleMgr::drawErasedStrokesLandscape(ScbStrokesPtr strokes, Controller * ctrl, 
                const int x, const int y)
{
    double z = ctrl->calZoom(ctrl->settings.getCurrentPage());
    if (strokes)
    {
        ScbStrokePtr stroke = NULL;
        GList *ptr = g_list_first(strokes->strokes);
        while (ptr)
        {
            stroke = (ScbStrokePtr)ptr->data;
            if (stroke)
            {
                // adjust position
                int count  = scb_stroke_get_point_count(stroke);
                ScbPoint * pts = (ScbPoint *)scb_stroke_get_point_data(stroke);
                
                // draw                
                if (fabs(z - stroke->style.zoom) < e)
                {
                    int i = 0;
                    while (i < count)
                    {
                        pts[i].x = y - pts[i].x;
                        pts[i].y = x + pts[i].y;
                        swap(pts[i].x, pts[i].y);
                        ++i;
                    }                        
                }
                else
                {
                    double ratio = z / stroke->style.zoom;
                    int i = 0;
                    while (i < count)
                    {
                        pts[i].x = y - (int)(pts[i].x * ratio);
                        pts[i].y = x + (int)(pts[i].y * ratio);
                        swap(pts[i].x, pts[i].y);
                        ++i;
                    }                        
                }
                stroke->style.color = SCB_DEV_COLOR_WHITE;
                scb_stroke_fast_draw(stroke);
                drawStrokeDirectly(stroke, ctrl);
            }
            ptr = g_list_next(ptr);
        }
    }
}

void CScribbleMgr::drawStroke(ScbStrokePtr stroke, Controller * ctrl, 
                const int px, const int py)
{
    if (ctrl->settings.getRotate() == 0)
    {
        // point data
        int count  = scb_stroke_get_point_count(stroke);
        ScbPoint * pts = (ScbPoint *)scb_stroke_get_point_data(stroke);

        // select color. 
        ScbColor color;
        scb_dev_color_to_color(&color, stroke->style.color);
        ctrl->gtkMgr.setLineColor(color.pixel);

        // select pen size
        ctrl->gtkMgr.setLineAttributes(stroke->style.penSize);

        int i = 0;
        int x1 = px + pts[i].x, y1 = py + pts[i].y; ++i;
        int x2, y2;

        while (i < count)
        {
            x2 = px + pts[i].x, y2 = py + pts[i].y; ++i;
            ctrl->gtkMgr.drawLine(x1, y1, x2, y2);
            x1 = x2; y1 = y2;
        }
        
        if (count <= 1)
        {
            ctrl->gtkMgr.drawLine(x1, y1, x1, y1);
        }            
    }
    else if (ctrl->settings.getRotate() == 270)
    {
        // point data
        int count  = scb_stroke_get_point_count(stroke);
        ScbPoint * pts = (ScbPoint *)scb_stroke_get_point_data(stroke);

        // select color. 
        ScbColor color;
        scb_dev_color_to_color(&color, stroke->style.color);
        ctrl->gtkMgr.setLineColor(color.pixel);

        // select pen size
        ctrl->gtkMgr.setLineAttributes(stroke->style.penSize);
        int i = 0;
        int x1 = py - pts[i].x, y1 = px + pts[i].y; ++i;
        int x2, y2;
        while (i < count)
        {
            x2 = py - pts[i].x, y2 = px + pts[i].y; ++i;
            ctrl->gtkMgr.drawLine(y1, x1, y2, x2);    // swap x & y
            x1 = x2; y1 = y2;
        }
         
        if (count <= 1)
        {
            ctrl->gtkMgr.drawLine(y1, x1, y1, x1);
        }            
    }
}

void CScribbleMgr::drawStrokeDirectly(ScbStrokePtr stroke, Controller * ctrl)               
{
    // point data
    int count  = scb_stroke_get_point_count(stroke);
    ScbPoint * pts = (ScbPoint *)scb_stroke_get_point_data(stroke);

    // select color. 
    ScbColor color;
    scb_dev_color_to_color(&color, stroke->style.color);
    ctrl->gtkMgr.setLineColor(color.pixel);

    // select pen size
    ctrl->gtkMgr.setLineAttributes(stroke->style.penSize);

    for(int i = 0; i < count - 1; ++i)
    {
        ctrl->gtkMgr.drawLine(pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y);
    }
}

                
