/***************************************************************************
 *   crengine-ng                                                           *
 *   Copyright (C) 2007-2012 Vadim Lopatin <coolreader.org@gmail.com>      *
 *   Copyright (C) 2020 Jellby <jellby@yahoo.com>                          *
 *   Copyright (C) 2018-2021 poire-z <poire-z@users.noreply.github.com>    *
 *   Copyright (C) 2020,2021 Aleksey Chernov <valexlin@gmail.com>          *
 *                                                                         *
 *   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        *
 *   of the License, 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., 51 Franklin Street, Fifth Floor, Boston,            *
 *   MA 02110-1301, USA.                                                   *
 ***************************************************************************/

/**
 * \file lvpagesplitter.cpp
 * \brief page splitter implementation
 */

#include <lvpagesplitter.h>
#include <lvdocviewcallback.h>
#include <crlog.h>
#include <lvserialbuf.h>

// Uncomment for debugging page splitting algorithm:
// #define DEBUG_PAGESPLIT
// (Also change '#if 0' around 'For debugging lvpagesplitter.cpp'
// to '#if 1' in src/lvdocview.cpp to get a summary of pages.

int LVRendPageList::FindNearestPage(int y, int direction) {
    if (!length())
        return 0;
    for (int i = 0; i < length(); i++) {
        const LVRendPageInfo* pi = ((*this)[i]);
        if (y < pi->start) {
            if (i == 0 || direction >= 0)
                return i;
            else
                return i - 1;
        } else if (y < pi->start + pi->height) {
            if (i < length() - 1 && direction > 0)
                return i + 1;
            else if (i == 0 || direction >= 0)
                return i;
            else
                return i - 1;
        }
    }
    return length() - 1;
}

static LVRendPageContext* main_context = NULL;

LVRendPageContext::LVRendPageContext(LVRendPageList* pageList, int pageHeight, int docFontSize, bool gatherLines)
        : callback(NULL)
        , totalFinalBlocks(0)
        , renderedFinalBlocks(0)
        , lastPercent(-1)
        , page_list(pageList)
        , page_h(pageHeight)
        , doc_font_size(docFontSize)
        , gather_lines(gatherLines)
        , footNotes(64)
        , curr_note(NULL)
        , current_flow(0)
        , max_flow(0) {
    if (callback) {
        callback->OnFormatStart();
    }
}

bool LVRendPageContext::updateRenderProgress(int numFinalBlocksRendered) {
    if (!callback) {
        if (main_context) {
            main_context->updateRenderProgress(numFinalBlocksRendered);
        }
        return false;
    }
    if (!main_context && callback) {
        // Save the main context (with the progress callback), so other
        // flows' contexts can forward their progress to it
        main_context = this;
    }
    renderedFinalBlocks += numFinalBlocksRendered;
    int percent = totalFinalBlocks > 0 ? renderedFinalBlocks * 100 / totalFinalBlocks : 0;
    if (percent < 0)
        percent = 0;
    if (percent > 100)
        percent = 100;
    if (callback && percent > lastPercent + RENDER_PROGRESS_INTERVAL_PERCENT) {
        if (progressTimeout.expired()) {
            callback->OnFormatProgress(percent);
            progressTimeout.restart(RENDER_PROGRESS_INTERVAL_MILLIS);
            lastPercent = percent;
            return true;
        }
    }
    return false;
}

/// Get the number of links in the current line links list, or
// in link_ids when !gather_lines
int LVRendPageContext::getCurrentLinksCount() {
    if (!gather_lines) {
        return link_ids.length();
    }
    if (lines.empty())
        return 0;
    return lines.last()->getLinksCount();
}

/// append or insert footnote link to last added line
void LVRendPageContext::addLink(lString32 id, int pos) {
    if (!gather_lines) {
        if (pos >= 0) // insert at pos
            link_ids.insert(pos, id);
        else // append
            link_ids.add(id);
        return;
    }
    if (lines.empty())
        return;
    LVFootNoteRef note = getOrCreateFootNote(id);
    lines.last()->addLink(note.get(), pos);
}

/// mark start of foot note
void LVRendPageContext::enterFootNote(lString32 id) {
    if (!page_list)
        return;
    //CRLog::trace("enterFootNote( %s )", LCSTR(id) );
    if (curr_note != NULL) {
        CRLog::error("Nested entering note");
        return;
    }
    curr_note = getOrCreateFootNote(id).get();
}

/// mark end of foot note
void LVRendPageContext::leaveFootNote() {
    if (!page_list)
        return;
    //CRLog::trace("leaveFootNote()" );
    if (!curr_note) {
        CRLog::error("leaveFootNote() w/o current note set");
    }
    curr_note = NULL;
}

void LVRendPageContext::newFlow(bool nonlinear) {
    /// A new non-linear flow gets the next number
    /// A new linear flow simply gets appended to flow 0
    if (nonlinear) {
        max_flow++;
        current_flow = max_flow;
        page_list->setHasNonLinearFlows();
    } else {
        current_flow = 0;
    }
}

void LVRendPageContext::AddLine(int starty, int endy, int flags) {
#ifdef DEBUG_PAGESPLIT
    printf("PS: AddLine (%x, #%d): %d > %d (%d)\n", this, lines.length(), starty, endy, flags);
#endif
    if (curr_note != NULL)
        flags |= RN_SPLIT_FOOT_NOTE;
    LVRendLineInfo* line = new LVRendLineInfo(starty, endy, flags, current_flow);
    lines.add(line);
    if (curr_note != NULL) {
        //CRLog::trace("adding line to note (%d)", line->start);
        curr_note->addLine(line);
    }
}

// We use 1.0rem (1x root font size) as the footnote margin (vertical margin
// between text and first footnote)
#define FOOTNOTE_MARGIN_REM 1

// helper class
struct PageSplitState
{
public:
    int page_h;
    int doc_font_size;
    LVRendPageList* page_list;
    const LVRendLineInfo* pagestart;
    const LVRendLineInfo* pageend;
    const LVRendLineInfo* next;
    const LVRendLineInfo* last;
    int footheight;
    LVFootNote* footnote;
    const LVRendLineInfo* footstart;
    const LVRendLineInfo* footend;
    const LVRendLineInfo* footlast;
    LVArray<LVPageFootNoteInfo> footnotes;
    LVArray<LVFootNote*> page_footnotes;         // foonotes already on this page, to avoid duplicates
    LVPtrVector<const LVRendLineInfo> own_lines; // to store our own made lines, so we can clear them when done
    int lastpageend;
    int nb_lines;
    int nb_lines_rtl;
    int nb_footnotes_lines;
    int nb_footnotes_lines_rtl;
    int current_flow;

    PageSplitState(LVRendPageList* pl, int pageHeight, int docFontSize)
            : page_h(pageHeight)
            , doc_font_size(docFontSize)
            , page_list(pl)
            , pagestart(NULL)
            , pageend(NULL)
            , next(NULL)
            , last(NULL)
            , footheight(0)
            , footnote(NULL)
            , footstart(NULL)
            , footend(NULL)
            , footlast(NULL)
            , lastpageend(0)
            , nb_lines(0)
            , nb_lines_rtl(0)
            , nb_footnotes_lines(0)
            , nb_footnotes_lines_rtl(0)
            , current_flow(0) {
    }

    // The final list of pages can be listed by changing some '#if 0' to '#if 1'
    // in lvdocview.cpp LVDocView::Render()

    unsigned CalcSplitFlag(int flg1, int flg2) {
        if (flg1 == RN_SPLIT_AVOID || flg2 == RN_SPLIT_AVOID)
            return RN_SPLIT_AVOID;
        if (flg1 == RN_SPLIT_ALWAYS || flg2 == RN_SPLIT_ALWAYS)
            return RN_SPLIT_ALWAYS;
        return RN_SPLIT_AUTO;
    }

    void ResetLineAccount(bool reset_footnotes = false) {
        nb_lines = 0;
        nb_lines_rtl = 0;
        if (reset_footnotes) {
            nb_footnotes_lines = 0;
            nb_footnotes_lines_rtl = 0;
        }
    }
    void AccountLine(const LVRendLineInfo* line) {
        nb_lines++;
        if (line->flags & RN_LINE_IS_RTL)
            nb_lines_rtl++;
    }
    void AccountFootnoteLine(const LVRendLineInfo* line) {
        nb_footnotes_lines++;
        if (line->flags & RN_LINE_IS_RTL)
            nb_footnotes_lines_rtl++;
    }
    int getLineTypeFlags() {
        int flags = 0;
        if (nb_lines_rtl > nb_lines / 2)
            flags |= RN_PAGE_MOSTLY_RTL;
        if (nb_footnotes_lines_rtl > nb_footnotes_lines / 2)
            flags |= RN_PAGE_FOOTNOTES_MOSTLY_RTL;
        return flags;
    }

    void StartPage(const LVRendLineInfo* line) {
#ifdef DEBUG_FOOTNOTES
        if (!line) {
            CRLog::trace("StartPage(NULL)");
        }
        if (CRLog::isTraceEnabled())
            CRLog::trace("StartPage(%d)", line ? line->start : -111111111);
#endif
        // A "line" is a slice of the document, it's a unit of what can be stacked
        // into pages. It has a y coordinate as start and an other at end,
        // that make its height.
        // It's usually a line of text, or an image, but we also have one
        // for each non-zero vertical margin, border and padding above and
        // below block elements.
        // A single "line" can also include multiple lines of text that have
        // to be considered as a slice-unit for technical reasons: this happens
        // with table rows (TR), so a table row will usually not be splitted
        // among multiple pages, but pushed to the next page (except when a single
        // row can't fit on a page: we'll then split inside that unit of slice).
        pagestart = line; // First line of the new future page
        pageend = NULL;   // No end of page yet (pagestart will be used if no pageend set)
        next = NULL;      // Last known line that we can split on
// We should keep current 'last' (we'll use its ->getEnd()) and will
// compare its flags to next coming line). We don't want to reset
// it in the one case we add a past line: when using 'StartPage(next)',
// when there is AVOID between 'last' and the current line. We don't
// want to reset 'last' to be this past line!
#ifdef DEBUG_PAGESPLIT
        printf("PS:           new current page %d>%d h=%d\n",
               pagestart ? pagestart->getStart() : -111111111,
               last ? last->getEnd() : -111111111,
               pagestart && last ? last->getEnd() - pagestart->getStart() : -111111111);
#endif
        ResetLineAccount();
        if (line)
            AccountLine(line);
    }
    void AddToList() {
        bool hasFootnotes = footnotes.length() > 0;
        if (!pageend)
            pageend = pagestart;
        if (!pagestart && !hasFootnotes)
            return;
        int start = (pagestart && pageend) ? pagestart->getStart() : lastpageend;
        int h = (pagestart && pageend) ? pageend->getEnd() - pagestart->getStart() : 0;
#ifdef DEBUG_FOOTNOTES
        if (CRLog::isTraceEnabled()) {
            if (pagestart && pageend)
                CRLog::trace("AddToList(%d, %d) footnotes: %d  pageHeight=%d",
                             pagestart->start, pageend->start + pageend->height, footnotes.length(), h);
            else
                CRLog::trace("AddToList(Only footnote: %d) footnotes: %d  pageHeight=%d",
                             lastpageend, footnotes.length(), h);
        }
#endif
#ifdef DEBUG_PAGESPLIT
        printf("PS: ========= ADDING PAGE %d: %d > %d h=%d",
               page_list->length(), start, start + h, h);
        if (footheight || hasFootnotes)
            printf(" (+ %d footnotes, fh=%d => h=%d)", footnotes.length(),
                   footheight, h + footheight + FOOTNOTE_MARGIN_REM * doc_font_size);
        printf(" [rtl l:%d/%d fl:%d/%d]\n", nb_lines_rtl, nb_lines, nb_footnotes_lines_rtl, nb_footnotes_lines);
#endif
        LVRendPageInfo* page = new LVRendPageInfo(start, h, page_list->length());
        lastpageend = start + h;
        if (footnotes.length() > 0) {
            page->footnotes.add(footnotes);
            footnotes.clear();
            footheight = 0;
        }
        if (page_footnotes.length() > 0) {
            page_footnotes.clear();
        }
        if (footnote) {
            // If we're not yet done adding this footnote (so it will continue
            // on the new page), consider it already present in this new page
            // (even if one won't see the starting text and footnote number,
            // it's better than seeing again the same duplicated footnote text)
            page_footnotes.add(footnote);
        }
        page->flags |= getLineTypeFlags();
        if (pagestart)
            current_flow = pagestart->flow;
        page->flow = current_flow;
        ResetLineAccount(true);
        page_list->add(page);
    }
    int currentFootnoteHeight() {
        if (!footstart)
            return 0;
        int h = 0;
        h = (footlast ? footlast : footstart)->getEnd() - footstart->getStart();
        return h;
    }
    int currentHeight(const LVRendLineInfo* line = NULL) {
        if (line == NULL)
            line = last;
        int h;
        if (line && pagestart)
            h = line->getEnd() - pagestart->getStart();
        else if (line)
            h = line->getHeight();
        else
            h = 0;
        int footh = 0 /*currentFootnoteHeight()*/ + footheight;
        if (footh)
            h += FOOTNOTE_MARGIN_REM * doc_font_size + footh;
        return h;
    }
    void SplitLineIfOverflowPage(LVRendLineInfo* line) {
        // If page_h <= 0 this function will fall into an infinite loop
        // in which it will allocate memory until it runs out.
        if (page_h <= 0)
            return;
        // A 'line' is usually a line of text from a paragraph, but
        // can be an image, or a fully rendered table row (<tr>), and
        // can have a huge height, possibly overflowing page height.
        // This line (rendered block) is at start of page. If its
        // height is greater than page height, we need to split it
        // and put slices on new pages. We have no knowledge of what
        // this 'line' contains (text, image...), so we can't really
        // find a better 'y' to cut at than the page height. We may
        // then cut in the middle of a text line, and have halves
        // displayed on different pages (although it seems crengine
        // deals with displaying the cut line fully at start of
        // next page).
        // we don't take the current footnotes height into account
        // here to get the maximum vertical space for this line (to
        // avoid more splits if we were to display the accumulated
        // footnotes with the first slice). They will be displayed
        // on the page with the last slice of the overflowed 'line'.

        // If we have a previous pagestart (which will be 'line' if
        // we are called following a StartPage(line), take it as
        // part of the first slice
        int slice_start = pagestart ? pagestart->getStart() : line->getStart();
#ifdef DEBUG_PAGESPLIT
        if (line->getEnd() - slice_start > page_h) {
            printf("PS:     line overflows page: %d > %d\n",
                   line->getEnd() - slice_start, page_h);
        }
#endif
        bool did_slice = false;
        while (line->getEnd() - slice_start > page_h) {
            if (did_slice)
                AccountLine(line);
            // Greater than page height: we need to cut
            LVRendPageInfo* page = new LVRendPageInfo(slice_start, page_h, page_list->length());
#ifdef DEBUG_PAGESPLIT
            printf("PS: ==== SPLITTED AS PAGE %d: %d > %d h=%d\n",
                   page_list->length(), slice_start, slice_start + page_h, page_h);
#endif
            page->flags |= getLineTypeFlags();
            page->flow = line->flow;
            ResetLineAccount();
            page_list->add(page);
            slice_start += page_h;
            lastpageend = slice_start;
            last = new LVRendLineInfo(slice_start, line->getEnd(), line->flags, line->flow);
            own_lines.add(last); // so we can have it 'delete'd in Finalize()
            pageend = last;
            did_slice = true;
        }
        if (did_slice) {
            // We did cut slices: we made a virtual 'line' with the last slice,
            // and as it fits on a page, use it at start of next page, to
            // possibly have other lines added after it on this page.
            StartPage(last);
            // This 'last' we made is the good 'last' to use from now on.
        }
        // Else, keep current 'last' (which must have been set to 'line'
        // before we were called)
    }
    void AddLine(LVRendLineInfo* line) {
#ifdef DEBUG_PAGESPLIT
        printf("PS: Adding line %d>%d h=%d, flags=<%d|%d>",
               line->getStart(), line->getEnd(), line->getHeight(),
               line->getSplitBefore(), line->getSplitAfter());
#endif
        if (pagestart == NULL) { // first line added,
                                 // or new page created by addition of footnotes
            int footnotes_h = currentHeight(NULL);
            if (footnotes_h > 0) {                              // we have some footnote on this new page
                if (footnotes_h + line->getHeight() > page_h) { // adding this line would overflow
#ifdef DEBUG_PAGESPLIT
                    printf("   overflow over previous footnotes, letting footnotes on their own page\n");
#endif
                    AddToList(); //create a page with only the footnotes
                }
            }
            if (line->flags & RN_SPLIT_DISCARD_AT_START) {
// Don't put this line at start of new page (this flag
// is set on a margin line when page split auto, as it should
// be seen when inside page, but should not be seen on top of
// page - and can be kept if it fits at the end of previous page)
#ifdef DEBUG_PAGESPLIT
                printf("PS:   discarded discardable line at start of page %d\n", page_list->length());
#endif
                return;
            }
#ifdef DEBUG_PAGESPLIT
            printf("   starting page with it\n");
#endif
            last = line; // 'last' should never be NULL from now on
                         // (but it can still happen when footnotes are enabled,
                         // with long footnotes spanning multiple pages)
            StartPage(line);
            SplitLineIfOverflowPage(line); // may update 'last'
        } else {
#ifdef DEBUG_PAGESPLIT
            printf("   to current page %d>%d h=%d\n",
                   pagestart->getStart(), last->getEnd(),
                   last->getEnd() - pagestart->getStart());
#endif
            // Check if line has some backward part
            if (line->getEnd() < last->getEnd()) {
#ifdef DEBUG_PAGESPLIT
                printf("PS:   FULLY BACKWARD, IGNORED\n");
#endif
                return; // for table cells
            } else if (line->getStart() < last->getEnd()) {
#ifdef DEBUG_PAGESPLIT
                printf("PS:   SOME PART BACKWARD, CROPPED TO FORWARD PART\n");
#endif
                // Make a new line with the forward part only, and avoid a
                // split with previous line (which might not be enough if
                // the backward spans over multiple past lines which had
                // SPLIT_AUTO - but we can't change the past...)
                lUInt16 flags = line->flags & ~RN_SPLIT_BEFORE_ALWAYS & RN_SPLIT_BEFORE_AVOID;
                line = new LVRendLineInfo(last->getEnd(), line->getEnd(), flags);
                own_lines.add(line); // so we can have it 'delete'd in Finalize()
            }
            unsigned flgSplit = CalcSplitFlag(last->getSplitAfter(), line->getSplitBefore());
            //bool flgFit = currentHeight( next ? next : line ) <= page_h;
            bool flgFit = currentHeight(line) <= page_h;

            if (!flgFit && flgSplit == RN_SPLIT_AVOID && pageend && next && line->getHeight() <= page_h) {
                // (But not if the line itself is taller than page height
                // and would be split again: keeping it on current page
                // (with its preceeding line) may prevent some additional
                // splits.)
#ifdef DEBUG_PAGESPLIT
                printf("PS:   does not fit but split avoid\n");
#endif
                // This new line doesn't fit, but split should be avoided between
                // 'last' and this line - and we have a previous line where a split
                // is allowed (pageend and next were reset on StartPage(),
                // and were only updated below when flgSplit==RN_SPLIT_AUTO).
                // Let AddToList() use current pagestart and pageend,
                // and StartPage on this old 'next'.
                AddToList();
                StartPage(next);
                // 'next' fitted previously on a page, so it must still fit now that it's
                // the first line in a new page: no need for SplitLineIfOverflowPage(next)
                // We keep the current 'last' (which can be 'next', or can be a line
                // met after 'next').
                // Recompute flgFit (if it still doesn't fit, it will be splitted below)
                flgFit = currentHeight(line) <= page_h;
                // What happens after now:
                // 'last' fitted before, so it still fits now.
                // We still have RN_SPLIT_AVOID between 'last' and 'line'.
                // - If !flgFit ('last'+'line' do not fit), we'll go below in the if (!flgFit)
                //   and we will split between 'last' and 'line' (in spite of RN_SPLIT_AVOID): OK
                // - If flgFit ('last'+'line' now does fit), we'll go below in the 'else'
                //   where 'next' and 'pageend' are not set, so they'll stay NULL.
                //   - If an upcoming line is not AVOID: good, we'll have a 'next' and
                //     'pageend', and we can start again doing what we just did when
                //     we later meet a SPLIT_AVOID that does not fit
                //   - If the upcoming lines are all SPLIT_AVOID:
                //     - as long as pagestart+..+upcoming fit: OK, but still no 'next' and 'pageend'.
                //     - when comes the point where pagestart+..+upcoming do not fit: OK
                //       no 'next' and 'pageend', so we split between 'last' and upcoming in
                //       the 'if (!flgFit)' in spite of RN_SPLIT_AVOID.
            }

            if (!flgFit && line->getHeight() > page_h) {
                // If it doesn't fit and if the line itself is taller than
                // page height and would be split again, keep it on current
                // page to possibly avoid additional splits.
                SplitLineIfOverflowPage(line); // may update 'last'
            } else if (!flgFit) {
#ifdef DEBUG_PAGESPLIT
                printf("PS:   does not fit but split ");
                if (flgSplit == RN_SPLIT_AUTO) {
                    printf("is allowed\n");
                } else if (flgSplit == RN_SPLIT_ALWAYS) {
                    printf("is mandatory\n");
                } else if (flgSplit == RN_SPLIT_AVOID) {
                    printf("can't be avoided\n");
                } else
                    printf("????\n");
#endif
                // Doesn't fit, but split is allowed (or mandatory) between
                // last and this line - or we don't have a previous line
                // where split is allowed: split between last and this line
                next = line; // Not useful anyway, as 'next' is not used
                             // by AddToList() and reset by StartPage()
                pageend = last;
                AddToList();
                if (flgSplit == RN_SPLIT_AUTO && (line->flags & RN_SPLIT_DISCARD_AT_START)) {
                    // Don't put this line at start of new page (this flag
                    // is set on a margin line when page split auto, as it should
                    // be seen when inside page, but should not be seen on top of
                    // page - and can be kept if it fits at the end of previous page)
                    StartPage(NULL);
                    last = NULL; // and don't even put it on last page if it ends the book
#ifdef DEBUG_PAGESPLIT
                    printf("PS:   discarded discardable line at start of page %d\n", page_list->length());
#endif
                } else {
                    last = line;
                    StartPage(line);
                    SplitLineIfOverflowPage(line); // may update 'last'
                }
            } else if (flgSplit == RN_SPLIT_ALWAYS) {
// Fits, but split is mandatory
#ifdef DEBUG_PAGESPLIT
                printf("PS:   fit but split mandatory\n");
#endif
                if (next == NULL) { // Not useful anyway, as 'next' is not
                    next = line;    // used by AddToList() and reset by StartPage()
                }
                pageend = last;
                AddToList();
                last = line;
                StartPage(line);
                SplitLineIfOverflowPage(line); // may update 'last'
            } else if (flgSplit == RN_SPLIT_AUTO) {
#ifdef DEBUG_PAGESPLIT
                printf("PS:   fit, split allowed\n");
#endif
                // Fits, split is allowed, but we don't split yet.
                // Update split candidate for when it's needed.
                pageend = last;
                next = line;
                last = line;
                AccountLine(line); // might be wrong if we split backward, but well...
            } else if (flgSplit == RN_SPLIT_AVOID) {
#ifdef DEBUG_PAGESPLIT
                printf("PS:   fit, split to avoid\n");
#endif
                // Don't update previous 'next' and 'pageend' (they
                // were updated on last AUTO or ALWAYS), so we know
                // we can still split on these if needed.
                last = line;
                AccountLine(line); // might be wrong if we split backward, but well...
            } else {               // should not happen
#ifdef DEBUG_PAGESPLIT
                printf("PS:   fit, unknown case");
#endif
                last = line;
            }
        }
    }
    void Finalize() {
        if (last == NULL)
            return;
        // Add remaining line to current page
        pageend = last;
        AddToList();
        own_lines.clear();
    }
    void StartFootNote(LVFootNote* note) {
#ifdef DEBUG_FOOTNOTES
        CRLog::trace("StartFootNote(%d)", note->getLines().length());
#endif
        if (!note || note->getLines().length() == 0)
            return;
        footnote = note;
        //footstart = footnote->getLines()[0];
        //footlast = footnote->getLines()[0];
        footend = NULL;
    }
    void AddFootnoteFragmentToList() {
        if (footstart == NULL)
            return; // no data
        if (footend == NULL)
            footend = footstart;
        //CRLog::trace("AddFootnoteFragmentToList(%d, %d)", footstart->start, footend->end );
        int h = footend->getEnd() - footstart->getStart(); // currentFootnoteHeight();
        if (h > 0 && h < page_h) {
            footheight += h;
#ifdef DEBUG_FOOTNOTES
            CRLog::trace("AddFootnoteFragmentToList(%d, %d)", footstart->getStart(), h);
#endif
            footnotes.add(LVPageFootNoteInfo(footstart->getStart(), h));
        }
        footstart = footend = NULL;
    }
    /// footnote is finished
    void EndFootNote() {
#ifdef DEBUG_FOOTNOTES
        CRLog::trace("EndFootNote()");
#endif
        footend = footlast;
        AddFootnoteFragmentToList();
        footnote = NULL;
        footstart = footend = footlast = NULL;
    }
    void AddFootnoteLine(LVRendLineInfo* line) {
        int dh = line->getEnd() - (footstart ? footstart->getStart() : line->getStart()) + (footheight == 0 ? FOOTNOTE_MARGIN_REM * doc_font_size : 0);
        int h = currentHeight(NULL); //next
#ifdef DEBUG_FOOTNOTES
        CRLog::trace("Add footnote line %d  footheight=%d  h=%d  dh=%d  page_h=%d",
                     line->start, footheight, h, dh, page_h);
#endif
#ifdef DEBUG_PAGESPLIT
        printf("PS: Adding footnote line h=%d => current footnotes height=%d (available: %d)\n",
               line->getEnd() - line->getStart(), dh, page_h - h);
#endif
        if (h + dh > page_h) {
#ifdef DEBUG_FOOTNOTES
            CRLog::trace("No current page space for this line, %s",
                         (footstart ? "footstart is not null" : "footstart is null"));
#endif
#ifdef DEBUG_PAGESPLIT
            if (footstart)
                printf("PS:   does not fit, splitting current footnote\n");
            else
                printf("PS:   does not fit, starting footnote on next page\n");
                // printf("PS:       pageend=%d, next=%d, last=%d\n",
                //    pageend?pageend->getEnd():-1, next?next->getStart():-1, last->getStart());
#endif
            if (footstart) { // Add what fitted to current page
                AddFootnoteFragmentToList();
            }

            // Forget about SPLIT_AVOID when footnotes are in the way,
            // keeping things simpler and natural.
            // (But doing that will just cut an ongoing float...
            // not obvious how to do that well: the alternative
            // below seems sometimes better, sometimes worse...)
            pageend = last;
            AddToList(); // create a page with current text and footnotes
            StartPage(NULL);

            /* Alternative, splitting on previous allowed position (pageend)
               if we're in SPLIT_AVOID:
            AddToList(); // create a page with current text (up to 'pagenend'
                         // or 'last') and footnotes
            // StartPage( last );
            // We shouldn't use 'last', as we could lose some lines if
            // we're into some SPLIT_AVOID, and footnotes fill current
            // height before we get the chance to deal with it with
            // normal lines in AddLine().
            if (pageend && next)
                // Safer to use 'next' if there is one to not lose any
                // normal lines (footnotes for what will be pushed to next
                // page might then be displayed on the previous page... but
                // this somehow avoid pushing stuff too much on new pages)
                StartPage(next);
            else
                // Otherwise, use NULL instead of 'last': 'last' is already
                // on the previous page, and if coming foonotes span multiple
                // page, 'last' might be displayed on each of these pages!
                StartPage(NULL);
            */

            footstart = footlast = line;
            footend = NULL;
            AccountFootnoteLine(line);
            return;
        }
        if (footstart == NULL) {
#ifdef DEBUG_PAGESPLIT
            printf("PS:   fit, footnote started, added to current page\n");
#endif
            footstart = footlast = line;
            footend = line;
        } else {
#ifdef DEBUG_PAGESPLIT
            printf("PS:   fit, footnote continued, added to current page\n");
#endif
            footend = line;
            footlast = line;
        }
        AccountFootnoteLine(line);
    }
    bool IsFootNoteInCurrentPage(LVFootNote* note) {
        if (page_footnotes.length() > 0)
            for (int n = 0; n < page_footnotes.length(); n++)
                if (note == page_footnotes[n])
                    return true;
        // Assume calling code will add it to this page, so remember it
        page_footnotes.add(note);
        return false;
    }
};

void LVRendPageContext::split() {
    if (!page_list)
        return;
    PageSplitState s(page_list, page_h, doc_font_size);
#ifdef DEBUG_PAGESPLIT
    printf("PS: splitting lines into pages, page height=%d\n", page_h);
#endif

    int lineCount = lines.length();

    LVRendLineInfo* line = NULL;
    for (int lindex = 0; lindex < lineCount; lindex++) {
        line = lines[lindex];
        s.AddLine(line);
        // add footnotes for line, if any...
        if (line->getLinks()) {
            // These are not needed: we shouldn't mess here with last/next that
            // are correctly managed by s.AddLine() just above.
            // s.last = line;
            // s.next = lindex<lineCount-1?lines[lindex+1]:line;
            bool foundFootNote = false;
            //if ( CRLog::isTraceEnabled() && line->getLinks()->length()>0 ) {
            //    CRLog::trace("LVRendPageContext::split() line %d: found %d links", lindex, line->getLinks()->length() );
            // }
            for (int j = 0; j < line->getLinks()->length(); j++) {
                LVFootNote* note = line->getLinks()->get(j);
                if (note->getLines().length()) {
                    // Avoid duplicated footnotes in the same page
                    if (s.IsFootNoteInCurrentPage(note))
                        continue;
                    foundFootNote = true;
                    s.StartFootNote(note);
                    for (int k = 0; k < note->getLines().length(); k++) {
                        s.AddFootnoteLine(note->getLines()[k]);
                    }
                    s.EndFootNote();
                }
            }
            if (!foundFootNote)
                line->flags = line->flags & ~RN_SPLIT_FOOT_LINK;
        }
    }
    s.Finalize();
}

void LVRendPageContext::Finalize() {
    split();
    lines.clear();
    footNotes.clear();
    if (main_context == this) {
        main_context = NULL;
    }
}

static const char* pagelist_magic = "PageList";

bool LVRendPageList::serialize(SerialBuf& buf) {
    if (buf.error())
        return false;
    buf.putMagic(pagelist_magic);
    int pos = buf.pos();
    buf << (lUInt32)length();
    for (int i = 0; i < length(); i++) {
        get(i)->serialize(buf);
    }
    buf.putMagic(pagelist_magic);
    buf.putCRC(buf.pos() - pos);
    return !buf.error();
}

bool LVRendPageList::deserialize(SerialBuf& buf) {
    if (buf.error())
        return false;
    if (!buf.checkMagic(pagelist_magic))
        return false;
    clear();
    int pos = buf.pos();
    lUInt32 len;
    buf >> len;
    clear();
    reserve(len);
    for (lUInt32 i = 0; i < len; i++) {
        LVRendPageInfo* item = new LVRendPageInfo();
        item->deserialize(buf);
        item->index = i;
        add(item);
        if (item->flow > 0)
            has_nonlinear_flows = true;
    }
    if (!buf.checkMagic(pagelist_magic))
        return false;
    buf.checkCRC(buf.pos() - pos);
    return !buf.error();
}

bool LVRendPageInfo::serialize(SerialBuf& buf) {
    if (buf.error())
        return false;
    buf << (lUInt32)start;  /// start of page
    buf << (lUInt16)height; /// height of page, does not include footnotes
    buf << (lUInt8)flags;   /// RN_PAGE_*
    buf << (lUInt16)flow;   /// flow the page belongs to
    lUInt16 len = footnotes.length();
    buf << len;
    for (int i = 0; i < len; i++) {
        buf << (lUInt32)footnotes[i].start;
        buf << (lUInt32)footnotes[i].height;
    }
    return !buf.error();
}

bool LVRendPageInfo::deserialize(SerialBuf& buf) {
    if (buf.error())
        return false;
    lUInt32 n1;
    lUInt16 n2;
    lUInt8 n3;
    lUInt16 n4;

    buf >> n1 >> n2 >> n3 >> n4; /// start of page

    start = n1;
    height = n2;
    flags = n3;
    flow = n4;

    lUInt16 len;
    buf >> len;
    footnotes.clear();
    if (len) {
        footnotes.reserve(len);
        for (int i = 0; i < len; i++) {
            lUInt32 n1;
            lUInt32 n2;
            buf >> n1;
            buf >> n2;
            footnotes.add(LVPageFootNoteInfo(n1, n2));
        }
    }
    return !buf.error();
}
