/* Copyright Krzysztof Kowalczyk 2006-2007
   License: GPLv2 */
#ifndef _DISPLAY_MODEL_H_
#define _DISPLAY_MODEL_H_

#include "base_util.h"
#include "geom_util.h"
#include "DisplayState.h"
#include "PdfEngine.h"

class GooString;
class Link;
class Links;
class TextPage;
class UGooString;
class SplashBitmap;

#define INVALID_ZOOM        -99
#define INVALID_BIG_ZOOM    999999.0   /* arbitrary but big */

typedef struct DisplaySettings {
    int     paddingPageBorderTop;
    int     paddingPageBorderBottom;
    int     paddingPageBorderLeft;
    int     paddingPageBorderRight;
    int     paddingBetweenPagesX;
    int     paddingBetweenPagesY;
} DisplaySettings;

/* the default distance between a page and window border edges, in pixels */
#ifdef _WIN32
  #define PADDING_PAGE_BORDER_TOP_DEF      6
#else
  #define PADDING_PAGE_BORDER_TOP_DEF      4
#endif
#define PADDING_PAGE_BORDER_BOTTOM_DEF   PADDING_PAGE_BORDER_TOP_DEF
#define PADDING_PAGE_BORDER_LEFT_DEF     2
#define PADDING_PAGE_BORDER_RIGHT_DEF    2
/* the distance between pages in x axis, in pixels. Only applicable if
   columns > 1 */
#define PADDING_BETWEEN_PAGES_X_DEF      4
/* the distance between pages in y axis, in pixels. Only applicable if
   more than one page in y axis (continuous mode) */
#define PADDING_BETWEEN_PAGES_Y_DEF      PADDING_PAGE_BORDER_TOP_DEF * 2

#define PADDING_PAGE_BORDER_TOP      gDisplaySettings.paddingPageBorderTop
#define PADDING_PAGE_BORDER_BOTTOM   gDisplaySettings.paddingPageBorderBottom
#define PADDING_PAGE_BORDER_LEFT     gDisplaySettings.paddingPageBorderLeft
#define PADDING_PAGE_BORDER_RIGHT    gDisplaySettings.paddingPageBorderRight
#define PADDING_BETWEEN_PAGES_X      gDisplaySettings.paddingBetweenPagesX
#define PADDING_BETWEEN_PAGES_Y      gDisplaySettings.paddingBetweenPagesY

#define POINT_OUT_OF_PAGE			0

/* Describes a link on PDF page. */
typedef struct PdfLink {
    int             pageNo;     /* on which Pdf page the link exists. 1..pageCount */
    // TODO: remove it from common code, only splash needs it
    Link *          link;       /* a reference to a link; we don't own it */
    RectD           rectPage;   /* position of the link on the page */
    RectI           rectCanvas; /* position of the link on canvas */
} PdfLink;

/* Describes many attributes of one page in one, convenient place */
typedef struct PdfPageInfo {
    /* data that is constant for a given page. page size and rotation
       recorded in PDF file */
    double          pageDx; // TODO: consider SizeD instead of pageDx/pageDy
    double          pageDy;
    int             rotation;

    /* data that needs to be set before DisplayModel_relayout().
       Determines whether a given page should be shown on the screen. */
    bool            shown;

    /* data that changes when zoom and rotation changes */
    /* position and size within total area after applying zoom and rotation.
       Represents display rectangle for a given page.
       Calculated in DisplayModel_relayout() */

    /* TODO: change it to RectD ?*/
    double          currDx;
    double          currDy;
    double          currPosX;
    double          currPosY;

    /* data that changes due to scrolling. Calculated in DisplayModel_RecalcVisibleParts() */
    bool            visible; /* is currently visible on the page ? */
    /* part of the image that should be shown */
    int             bitmapX, bitmapY, bitmapDx, bitmapDy;
    /* where it should be blitted on the screen */
    int             screenX, screenY;

    // TODO: remove it from common code, only splash needs it
    Links *         links;
    TextPage *      textPage;
} PdfPageInfo;

/* When searching, we can be in one of those states. The state determines what
   will happen after searching for next or previous term.
   */
enum SearchState {
    /* Search hasn't started yet. 'Next' will start searching from the top
       of current page, searching forward. 'Previous' will start searching from
       the top of current page, searching backward. */
    eSsNone,
    /* We searched the whole document and didn't find the search term at all. 'Next'
       and 'prev' do nothing. */
    eSsNotFound,
    /* Previous 'next' search found the term, without wrapping. 'Next' will
       continue searching forward from the current position. 'Previous' will
       search backward from the current position.*/
    eSsFoundNext,
    /* Like eSsFoundNext but we wrapped past last page. In that case we show
       a message about being wrapped and continuing from top. */
    eSsFoundNextWrapped,
    /* Previous 'prev' search found the term, without wrapping. 'Next' will
       search forward from the current position. 'Prev' will continue searching
       backward */
    eSsFoundPrev,
    /* Like eSsFoundPrev, but wrapped around first page. */
    eSsFoundPrevWrapped,
/*   TODO: add eSsFoundOnlyOne as optimization i.e. if the hit is the same
   as previous hit, 'next' and 'previous' will do nothing, to avoid (possibly
   long) no-op search.
    eSsFoundOnlyOne */
};

/* The current state of searching */
typedef struct SearchStateData {
    /* String we search for, both regular and unicode versions */
    GooString *     str;
    UGooString *    strU;
    /* The page on which we started the search */
    int             startPage;
    /* did we wrap (crossed last page when searching forward or first page
       when searching backwards) */
    BOOL            wrapped;
    SearchState     searchState;
    BOOL            caseSensitive;
    int             currPage; /* page for the last hit */
} SearchStateData;

class DisplayModel
{
public:
    DisplayModel(DisplayMode displayMode);
    virtual ~DisplayModel();

    RenderedBitmap *renderBitmap(int pageNo, double zoomReal, int rotation,
                         BOOL (*abortCheckCbkA)(void *data),
                         void *abortCheckCbkDataA) {
        if (!_pdfEngine) return NULL;
        return _pdfEngine->renderBitmap(pageNo, zoomReal, rotation, abortCheckCbkA, abortCheckCbkDataA);
    }

    PdfEngine *pdfEngine() { return _pdfEngine; }

    /* number of pages in PDF document */
    int  pageCount() const { return _pdfEngine->pageCount(); }
    bool load(const char *fileName, int startPage, WindowInfo *win);
    bool validPageNo(int pageNo) const { return _pdfEngine->validPageNo(pageNo); }

    /* current rotation selected by user */
    int rotation(void) const { return _rotation; }
    void setRotation(int rotation) { _rotation = rotation; }
    DisplayMode displayMode() const { return _displayMode; }

    void changeDisplayMode(DisplayMode displayMode);
    const char *fileName(void) const { return _pdfEngine->fileName(); }

    bool fullScreen(void) const { return _fullScreen; }
    void setFullScreen(bool fullScreen) { _fullScreen = fullScreen; }

    /* a "virtual" zoom level. Can be either a real zoom level in percent
       (i.e. 100.0 is original size) or one of virtual values ZOOM_FIT_PAGE
       or ZOOM_FIT_WIDTH, whose real value depends on draw area size */
    double zoomVirtual(void) const { return _zoomVirtual; }
    void setZoomVirtual(double zoomVirtual);

    double zoomReal(void) const { return _zoomReal; }

    int startPage(void) const { return _startPage; }

    /* TODO: should become non-virtual */
    int currentPageNo(void) const;

    /* an arbitrary pointer that can be used by an app e.g. a multi-window GUI
       could link this to a data describing window displaying  this document */
    void * appData() const { return _appData; }

    void setAppData(void *appData) { _appData = appData; }

    /* TODO: rename to pageInfo() */
    PdfPageInfo * getPageInfo(int pageNo) const;

    /* an array of PdfPageInfo, len of array is pageCount */
    PdfPageInfo *   pagesInfo;

    /* areaOffset is "polymorphic". If drawAreaSize.dx > totalAreSize.dx then
       areaOffset.x is offset of total area rect inside draw area, otherwise
       an offset of draw area inside total area.
       The same for areaOff.y, except it's for dy */
    PointD          areaOffset;

    /* size of draw area i.e. _totalDrawAreaSize minus scrollbarsSize (if
       they're shown) */
    SizeD           drawAreaSize;

    SearchStateData searchState;

    int             searchHitPageNo;
    RectD           searchHitRectPage;
    RectI           searchHitRectCanvas;

    void            setScrollbarsSize(int scrollbarXDy, int scrollbarYDx) {
        _scrollbarXDy = scrollbarXDy;
        _scrollbarYDx = scrollbarYDx;
    }

    void            setTotalDrawAreaSize(SizeD size) {
        _totalDrawAreaSize = size;
        drawAreaSize = SizeD(size.dx() - _scrollbarYDx, size.dy() - _scrollbarXDy);
    }

    void            changeTotalDrawAreaSize(SizeD totalDrawAreaSize);

    bool            pageShown(int pageNo);
    bool            pageVisible(int pageNo);
    bool            pageVisibleNearby(int pageNo);
    void            relayout(double zoomVirtual, int rotation);

    void            goToPage(int pageNo, int scrollY, int scrollX=-1);
    bool            goToPrevPage(int scrollY);
    bool            goToNextPage(int scrollY);
    bool            goToFirstPage(void);
    bool            goToLastPage(void);

    void            scrollXTo(int xOff);
    void            scrollXBy(int dx);

    void            scrollYTo(int yOff);
    void            scrollYBy(int dy, bool changePage);
    void            scrollYByAreaDy(bool forward, bool changePage);

    void            zoomTo(double zoomVirtual);
    void            zoomBy(double zoomFactor);
    void            rotateBy(int rotation);

    virtual int     getTextInRegion(int pageNo, RectD *region, unsigned short *buf, int buflen) = 0;

    void            clearSearchHit(void);
    void            setSearchHit(int pageNo, RectD *hitRect);
    void            recalcLinksCanvasPos(void);

    int             linkCount(void) const { return _linkCount; }
    PdfLink *       link(int linkNo) const { return &(_links[linkNo]); }
    PdfLink *       linkAtPosition(int x, int y);

    virtual void    handleLink(PdfLink *pdfLink) = 0;

    virtual void    cvtUserToScreen(int pageNo, double *x, double *y) = 0;
    virtual void    cvtScreenToUser(int *pageNo, double *x, double *y) = 0;
    void            rectCvtUserToScreen(int pageNo, RectD *r);
    void            rectCvtScreenToUser(int *pageNo, RectD *r);

protected:
    int             getPageNoByPoint (double x, double y);

    void            startRenderingPage(int pageNo);

    bool            buildPagesInfo(void);
    double          zoomRealFromFirtualForPage(double zoomVirtual, int pageNo);
    int             firstVisiblePageNo(void) const;
    void            changeStartPage(int startPage);
    void            recalcVisibleParts(void);
    void            recalcSearchHitCanvasPos(void);
    void            renderVisibleParts(void);
    /* Those need to be implemented somewhere else by the GUI */
    void            setScrollbarsState(void);
    /* called when a page number changes */
    void            pageChanged(void);
    /* called when we decide that the display needs to be redrawn */
    void            repaintDisplay(bool delayed);

    void            showBusyCursor();
    void            showNormalCursor();

    PdfEngine *     _pdfEngine;
    DisplayMode     _displayMode; /* TODO: not used yet */
    bool            _fullScreen;
    /* In non-continuous mode is the first page from a PDF file that we're
       displaying.
       No meaning in continous mode. */
    int             _startPage;
    void *          _appData;

    /* size of scrollbars */
    int             _scrollbarXDy;
    int             _scrollbarYDx;

    /* size of total draw area (i.e. window size) */
    SizeD           _totalDrawAreaSize;

    /* size of virtual canvas containing all rendered pages.
       TODO: re-consider, 32 signed number should be large enough for everything. */
    SizeD           _canvasSize;

    /* real zoom value calculated from zoomVirtual. Same as zoomVirtual except
       for ZOOM_FIT_PAGE and ZOOM_FIT_WIDTH */
    double          _zoomReal;
    double          _zoomVirtual;
    int             _rotation;

    /* total number of links */
    int             _linkCount;
    /* an array of 'totalLinksCount' size, each entry describing a link */
    PdfLink *       _links;
};

bool                validZoomReal(double zoomReal);
bool                displayModeContinuous(DisplayMode displayMode);
bool                displayModeFacing(DisplayMode displayMode);
DisplaySettings *   globalDisplaySettings(void);
int                 columnsFromDisplayMode(DisplayMode displayMode);
void                pageSizeAfterRotation(PdfPageInfo *pageInfo, int rotation, double *pageDxOut, double *pageDyOut);
bool                displayStateFromDisplayModel(DisplayState *ds, DisplayModel *dm);

extern DisplaySettings gDisplaySettings;

/* must be implemented somewhere else */
//extern void         LaunchBrowser(const char *uri);

/* We keep a cache of rendered bitmaps. BitmapCacheEntry keeps data
   that uniquely identifies rendered page (dm, pageNo, rotation, zoomReal)
   and corresponding rendered bitmap.
*/
typedef struct {
  DisplayModel * dm;
  int            pageNo;
  int            rotation;
  double         zoomLevel;
  RenderedBitmap *bitmap;
  double         renderTime;
} BitmapCacheEntry;

typedef struct {
    DisplayModel *  dm;
    int             pageNo;
    double          zoomLevel;
    int             rotation;
    int             abort;
} PageRenderRequest;

/* Lock protecting both bitmap cache and page render queue */
void              LockCache();
void              UnlockCache();

void              RenderQueue_Add(DisplayModel *dm, int pageNo);
extern void       RenderQueue_RemoveForDisplayModel(DisplayModel *dm);
extern void       cancelRenderingForDisplayModel(DisplayModel *dm);

BitmapCacheEntry *BitmapCache_Find(DisplayModel *dm, int pageNo, double zoomLevel, int rotation);
BitmapCacheEntry *BitmapCache_Find(DisplayModel *dm, int pageNo);
bool              BitmapCache_Exists(DisplayModel *dm, int pageNo, double zoomLevel, int rotation);
void              BitmapCache_Add(DisplayModel *dm, int pageNo, double zoomLevel, int rotation,
                                  RenderedBitmap *bitmap, double renderTime);
void              BitmapCache_FreeAll(void);
bool              BitmapCache_FreeForDisplayModel(DisplayModel *dm);
bool              BitmapCache_FreeNotVisible(void);

#endif
