/* Copyright 2014 the SumatraPDF project authors (see AUTHORS file).
   License: GPLv3 */

#ifndef DisplayModel_h
#define DisplayModel_h

#include "ChmEngine.h"
#include "DisplayState.h"
#include "EngineManager.h"

// define the following if you want shadows drawn around the pages
// #define DRAW_PAGE_SHADOWS

#define INVALID_PAGE_NO     -1

extern bool gPredictiveRender;

/* Describes many attributes of one page in one, convenient place */
struct PageInfo {
    /* data that is constant for a given page. page size in document units */
    RectD           page;

    /* data that is calculated when needed. actual content size within a page (View target) */
    RectD           contentBox;

    /* 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() */
    RectI           pos;

    /* data that changes due to scrolling. Calculated in DisplayModel::RecalcVisibleParts() */
    float           visibleRatio; /* (0.0 = invisible, 1.0 = fully visible) */
    /* position of page relative to visible view port: pos.Offset(-viewPort.x, -viewPort.y) */
    RectI           pageOnScreen;
};

/* The current scroll state (needed for saving/restoring the scroll position) */
/* coordinates are in user space units (per page) */
struct ScrollState {
    explicit ScrollState(int page=0, double x=0, double y=0) : page(page), x(x), y(y) { }
    bool operator==(const ScrollState& other) const {
        return page == other.page && x == other.x && y == other.y;
    }

    int page;
    double x, y;
};

class DisplayModel;
class PageTextCache;
class TextSelection;
class TextSearch;
struct TextSel;

class DisplayModelCallback : public ChmNavigationCallback {
public:
    virtual void Repaint() = 0;
    virtual void UpdateScrollbars(SizeI canvas) = 0;
    virtual void RequestRendering(int pageNo) = 0;
    virtual void CleanUp(DisplayModel *dm) = 0;
};

// TODO: in hindsight, zoomVirtual is not a good name since it's either
// virtual zoom level OR physical zoom level. Would be good to find
// better naming scheme (call it zoomLevel?)

/* Information needed to drive the display of a given document on a screen.
   You can think of it as a model in the MVC pardigm.
   All the display changes should be done through changing this model via
   API and re-displaying things based on new display information */
class DisplayModel
{
public:
    DisplayModel(BaseEngine *engine, EngineType engineType, DisplayModelCallback *dmCb);
    ~DisplayModel();

    const WCHAR *FilePath() const { return engine->FileName(); }
    /* number of pages in the document */
    int  PageCount() const { return engine->PageCount(); }
    bool ValidPageNo(int pageNo) const { return 1 <= pageNo && pageNo <= engine->PageCount(); }

    /* current rotation selected by user */
    int Rotation() const { return rotation; }
    void SetRotation(int newRotation) { rotation = newRotation; }

    DisplayMode GetDisplayMode() const { return displayMode; }
    void ChangeDisplayMode(DisplayMode newDisplayMode);
    void SetPresentationMode(bool enable);

    /* 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,
       ZOOM_FIT_WIDTH or ZOOM_FIT_CONTENT, whose real value depends on draw area size */
    float ZoomVirtual() const { return zoomVirtual; }
    float ZoomReal() const { return zoomReal; }
    float ZoomReal(int pageNo);
    float ZoomAbsolute() const { return zoomReal * 100 / dpiFactor; }

    bool HasTocTree() const { return engine && engine->HasTocTree();}
    int CurrentPageNo() const;

    BaseEngine *    engine;
    EngineType      engineType;

    PageTextCache * textCache;
    TextSelection * textSelection;
    // access only from Search thread
    TextSearch *    textSearch;

    PageInfo *      GetPageInfo(int pageNo) const;

    /* size and position of the viewport on the canvas (resp size of the visible
       part of the canvase available for content (totalViewPortSize minus scroll bars)
       (canvasSize is always at least as big as viewPort.Size()) */
    RectI           viewPort;
protected:
    /* total size of view port (draw area), including scroll bars */
    SizeI           totalViewPortSize;

public:
    bool            NeedHScroll() const { return viewPort.dy < totalViewPortSize.dy; }
    bool            NeedVScroll() const { return viewPort.dx < totalViewPortSize.dx; }
    SizeI           GetCanvasSize() const { return canvasSize; }

    void            ChangeViewPortSize(SizeI newViewPortSize);

    bool            PageShown(int pageNo);
    bool            PageVisible(int pageNo);
    bool            PageVisibleNearby(int pageNo);
    int             FirstVisiblePageNo() const;
    bool            FirstBookPageVisible();
    bool            LastBookPageVisible();
    void            Relayout(float zoomVirtual, int rotation);

    void            GoToPage(int pageNo, int scrollY, bool addNavPt=false, int scrollX=-1);
    bool            GoToPrevPage(int scrollY);
    bool            GoToNextPage(int scrollY);
    bool            GoToFirstPage();
    bool            GoToLastPage();

    void            ScrollXTo(int xOff);
    void            ScrollXBy(int dx);

    void            ScrollYTo(int yOff);
    void            ScrollYBy(int dy, bool changePage);

    void            ZoomTo(float zoomVirtual, PointI *fixPt=NULL);
    void            ZoomBy(float zoomFactor, PointI *fixPt=NULL);
    float           NextZoomStep(float towardsLevel);
    void            RotateBy(int rotation);

    WCHAR *         GetTextInRegion(int pageNo, RectD region);
    bool            IsOverText(PointI pt);
    PageElement *   GetElementAtPos(PointI pt);

    const WindowMargin *GetWindowMargin() const { return &windowMargin; }

    int             GetPageNoByPoint(PointI pt);
    int             GetPageNextToPoint(PointI pt);
    PointI          CvtToScreen(int pageNo, PointD pt);
    RectI           CvtToScreen(int pageNo, RectD r);
    PointD          CvtFromScreen(PointI pt, int pageNo=INVALID_PAGE_NO);
    RectD           CvtFromScreen(RectI r, int pageNo=INVALID_PAGE_NO);

    bool            ShowResultRectToScreen(TextSel *res);

    ScrollState     GetScrollState();
    void            SetScrollState(ScrollState state);

    bool            CanNavigate(int dir) const;
    void            Navigate(int dir);
    void            CopyNavHistory(DisplayModel& orig);

    void            DisplayStateFromModel(DisplayState *ds);
    void            SetInitialViewSettings(DisplayMode displayMode, int newStartPage, SizeI viewPort, int screenDPI);
    void            SetDisplayR2L(bool r2l) { displayR2L = r2l; }
    bool            GetDisplayR2L() const { return displayR2L; }

    // called when we decide that the display needs to be redrawn
    void            RepaintDisplay() { dmCb->Repaint(); }

    ChmEngine *     AsChmEngine() const;

protected:

    void            BuildPagesInfo();
    float           ZoomRealFromVirtualForPage(float zoomVirtual, int pageNo);
    SizeD           PageSizeAfterRotation(int pageNo, bool fitToContent=false);
    void            ChangeStartPage(int startPage);
    PointI          GetContentStart(int pageNo);
    void            SetZoomVirtual(float zoomVirtual);
    void            RecalcVisibleParts();
    void            RenderVisibleParts();

    void            AddNavPoint();
    RectD           GetContentBox(int pageNo, RenderTarget target=Target_View);

    void            ZoomToChm(float zoomLevel);

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

    DisplayMode     displayMode;
    /* In non-continuous mode is the first page from a file that we're
       displaying.
       No meaning in continous mode. */
    int             startPage;

    /* A callback to notify UI about required changes */
    DisplayModelCallback *dmCb;

    /* size of virtual canvas containing all rendered pages. */
    SizeI           canvasSize;

    WindowMargin    windowMargin;
    SizeI           pageSpacing;

    /* real zoom value calculated from zoomVirtual. Same as
       zoomVirtual * 0.01 * dpiFactor
       except for ZOOM_FIT_PAGE, ZOOM_FIT_WIDTH and ZOOM_FIT_CONTENT */
    float           zoomReal;
    float           zoomVirtual;
    int             rotation;
    /* dpi correction factor by which _zoomVirtual has to be multiplied in
       order to get _zoomReal */
    float           dpiFactor;
    /* whether to display pages Left-to-Right or Right-to-Left.
       this value is extracted from the PDF document */
    bool            displayR2L;

    /* when we're in presentation mode, _pres* contains the pre-presentation values */
    bool            presentationMode;
    float           presZoomVirtual;
    DisplayMode     presDisplayMode;

    Vec<ScrollState>navHistory;
    /* index of the "current" history entry (to be updated on navigation),
       resp. number of Back history entries */
    size_t          navHistoryIx;

public:
    /* allow resizing a window without triggering a new rendering (needed for window destruction) */
    bool            dontRenderFlag;
};

bool    IsContinuous(DisplayMode displayMode);
bool    IsSingle(DisplayMode displayMode);
bool    IsFacing(DisplayMode displayMode);
bool    DisplayModeShowCover(DisplayMode displayMode);
int     NormalizeRotation(int rotation);
bool    IsValidZoom(float zoomLevel);

#endif
