/*
 * Copyright (C) 2009-2010, Pino Toscano <pino@kde.org>
 * Copyright (C) 2017-2019, Albert Astals Cid <aacid@kde.org>
 * Copyright (C) 2017, Jason Alan Palmer <jalanpalmer@gmail.com>
 * Copyright (C) 2018, Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
 * Copyright (C) 2018, Adam Reichold <adam.reichold@t-online.de>
 * Copyright (C) 2018, Zsombor Hollay-Horvath <hollay.horvath@gmail.com>
 * Copyright (C) 2018, Aleksey Nikolaev <nae202@gmail.com>
 * Copyright (C) 2020, Jiri Jakes <freedesktop@jirijakes.eu>
 *
 * 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., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
 */

/**
 \file poppler-page.h
 */
#include "poppler-page.h"
#include "poppler-page-transition.h"

#include "poppler-document-private.h"
#include "poppler-page-private.h"
#include "poppler-private.h"

#include "TextOutputDev.h"

#include <algorithm>
#include <memory>
#include <utility>

using namespace poppler;

page_private::page_private(document_private *_doc, int _index)
    : doc(_doc)
    , page(doc->doc->getCatalog()->getPage(_index + 1))
    , index(_index)
    , transition(nullptr)
{
}

page_private::~page_private()
{
    delete transition;
}

/**
 \class poppler::page poppler-page.h "poppler/cpp/poppler-page.h"

 A page in a PDF %document.
 */

/**
 \enum poppler::page::orientation_enum

 The possible orientation of a page.
*/

/**
 \enum poppler::page::search_direction_enum

 The direction/action to follow when performing a text search.
*/

/**
 \enum poppler::page::text_layout_enum

 A layout of the text of a page.
*/


page::page(document_private *doc, int index)
    : d(new page_private(doc, index))
{
}

/**
 Destructor.
 */
page::~page()
{
    delete d;
}

/**
 \returns the orientation of the page
 */
page::orientation_enum page::orientation() const
{
    const int rotation = d->page->getRotate();
    switch (rotation) {
    case 90:
        return landscape;
        break;
    case 180:
        return upside_down;
        break;
    case 270:
        return seascape;
        break;
    default:
        return portrait;
    }
}

/**
 The eventual duration the page can be hinted to be shown in a presentation.

 If this value is positive (usually different than -1) then a PDF viewer, when
 showing the page in a presentation, should show the page for at most for this
 number of seconds, and then switch to the next page (if any). Note this is
 purely a presentation attribute, it has no influence on the behaviour.

 \returns the duration time (in seconds) of the page
 */
double page::duration() const
{
    return d->page->getDuration();
}

/**
 Returns the size of one rect of the page.

 \returns the size of the specified page rect
 */
rectf page::page_rect(page_box_enum box) const
{
    const PDFRectangle *r = nullptr;
    switch (box) {
    case media_box:
        r = d->page->getMediaBox();
        break;
    case crop_box:
        r = d->page->getCropBox();
        break;
    case bleed_box:
        r = d->page->getBleedBox();
        break;
    case trim_box:
        r = d->page->getTrimBox();
        break;
    case art_box:
        r = d->page->getArtBox();
        break;
    }
    if (r) {
        return detail::pdfrectangle_to_rectf(*r);
    }
    return rectf();
}

/**
 \returns the label of the page, if any
 */
ustring page::label() const
{
    GooString goo;
    if (!d->doc->doc->getCatalog()->indexToLabel(d->index, &goo)) {
        return ustring();
    }

    return detail::unicode_GooString_to_ustring(&goo);
}

/**
 The transition from this page to the next one.

 If it is set, then a PDF viewer in a presentation should perform the
 specified transition effect when switching from this page to the next one.

 \returns the transition effect for the switch to the next page, if any
 */
page_transition* page::transition() const
{
    if (!d->transition) {
        Object o = d->page->getTrans();
        if (o.isDict()) {
            d->transition = new page_transition(&o);
        }
    }
    return d->transition;
}

/**
 Search the page for some text.

 \param text the text to search
 \param[in,out] r the area where to start search, which will be set to the area
                  of the match (if any)
 \param direction in which direction search for text
 \param case_sensitivity whether search in a case sensitive way
 \param rotation the rotation assumed for the page
 */
bool page::search(const ustring &text, rectf &r, search_direction_enum direction,
                  case_sensitivity_enum case_sensitivity, rotation_enum rotation) const
{
    const size_t len = text.length();
    std::vector<Unicode> u(len);
    for (size_t i = 0; i < len; ++i) {
        u[i] = text[i];
    }

    const bool sCase = case_sensitivity == case_sensitive;
    const int rotation_value = (int)rotation * 90;

    bool found = false;
    double rect_left = r.left();
    double rect_top = r.top();
    double rect_right = r.right();
    double rect_bottom = r.bottom();

    TextOutputDev td(nullptr, true, 0, false, false);
    d->doc->doc->displayPage(&td, d->index + 1, 72, 72, rotation_value, false, true, false);
    TextPage *text_page = td.takeText();

    switch (direction) {
    case search_from_top:
        found = text_page->findText(&u[0], len,
                    true, true, false, false, sCase, false, false,
                    &rect_left, &rect_top, &rect_right, &rect_bottom);
        break;
    case search_next_result:
        found = text_page->findText(&u[0], len,
                    false, true, true, false, sCase, false, false,
                    &rect_left, &rect_top, &rect_right, &rect_bottom);
        break;
    case search_previous_result:
        found = text_page->findText(&u[0], len,
                    false, true, true, false, sCase, true, false,
                    &rect_left, &rect_top, &rect_right, &rect_bottom);
        break;
    }

    text_page->decRefCnt();
    r.set_left(rect_left);
    r.set_top(rect_top);
    r.set_right(rect_right);
    r.set_bottom(rect_bottom);

    return found;
}

/**
 Returns the text in the page, in its physical layout.

 \param r if not empty, it will be extracted the text in it; otherwise, the
          text of the whole page

 \returns the text of the page in the specified rect or in the whole page
 */
ustring page::text(const rectf &r) const
{
    return text(r, physical_layout);
}

static void appendToGooString(void *stream, const char *text, int len) {
  ((GooString *) stream)->append(text, len);
}

/**
 Returns the text in the page.

 \param rect if not empty, it will be extracted the text in it; otherwise, the
             text of the whole page
 \param layout_mode the layout of the text

 \returns the text of the page in the specified rect or in the whole page

 \since 0.16
 */
ustring page::text(const rectf &r, text_layout_enum layout_mode) const
{
    std::unique_ptr<GooString> out(new GooString());
    const bool use_raw_order = (layout_mode == raw_order_layout); 
    const bool use_physical_layout = (layout_mode == physical_layout);
    TextOutputDev td(&appendToGooString, out.get(), use_physical_layout, 0, use_raw_order, false);
    if (r.is_empty()) {
        d->doc->doc->displayPage(&td, d->index + 1, 72, 72, 0, false, true, false);
    } else {
        d->doc->doc->displayPageSlice(&td, d->index + 1, 72, 72, 0, false, true, false,
				      r.left(), r.top(), r.width(), r.height());
    }
    return ustring::from_utf8(out->c_str());
}

/*
 * text_box object for page::text_list()
 */
text_box_data::~text_box_data() = default;

text_box::~text_box() = default;

text_box& text_box::operator=(text_box&& a) noexcept = default;
text_box::text_box(text_box&& a) noexcept = default;

text_box::text_box(text_box_data *data) : m_data{data}
{
}

ustring text_box::text() const
{
    return m_data->text;
}

rectf text_box::bbox() const
{
    return m_data->bbox;
}

int text_box::rotation() const
{
    return m_data->rotation;
}

rectf text_box::char_bbox(size_t i) const
{
    if (i < m_data->char_bboxes.size())
        return m_data->char_bboxes[i];
    return rectf(0, 0, 0, 0);
}

bool text_box::has_space_after() const
{
    return m_data->has_space_after;
}

std::vector<text_box> page::text_list() const
{
    std::vector<text_box>  output_list;

    /* config values are same with Qt5 Page::TextList() */
    auto output_dev = std::make_unique<TextOutputDev>(
	nullptr,    /* char* fileName */
	false,  /* bool physLayoutA */
	0,       /* double fixedPitchA */
	false,  /* bool rawOrderA */
	false  /* bool append */
    );

    /*
     * config values are same with Qt5 Page::TextList(),
     * but rotation is fixed to zero.
     * Few people use non-zero values.
     */
    d->doc->doc->displayPageSlice(output_dev.get(),
                                  d->index + 1,           /* page */
                                  72, 72, 0,              /* hDPI, vDPI, rot */
                                  false, false, false,    /* useMediaBox, crop, printing */
                                  -1, -1, -1, -1,         /* sliceX, sliceY, sliceW, sliceH */
                                  nullptr, nullptr,       /* abortCheckCbk(), abortCheckCbkData */
                                  nullptr, nullptr,       /* annotDisplayDecideCbk(), annotDisplayDecideCbkData */
                                  true);                  /* copyXRef */

    if (std::unique_ptr< TextWordList > word_list{output_dev->makeWordList()}) {

        output_list.reserve(word_list->getLength());
        for (int i = 0; i < word_list->getLength(); i ++) {
            TextWord *word = word_list->get(i);

            std::unique_ptr<GooString> gooWord{word->getText()};
            ustring ustr = ustring::from_utf8(gooWord->c_str());

            double xMin, yMin, xMax, yMax;
            word->getBBox(&xMin, &yMin, &xMax, &yMax);

            text_box tb{new text_box_data{
                ustr,
                {xMin, yMin, xMax-xMin, yMax-yMin},
                word->getRotation(),
                {},
                word->hasSpaceAfter() == true
            }};

            tb.m_data->char_bboxes.reserve(word->getLength());
            for (int j = 0; j < word->getLength(); j ++) {
                word->getCharBBox(j, &xMin, &yMin, &xMax, &yMax);
                tb.m_data->char_bboxes.emplace_back(xMin, yMin, xMax-xMin, yMax-yMin);
            }

            output_list.push_back(std::move(tb));
        }
    }

    return output_list;
}
