/**
 * @file
 * SVG <hatch> implementation
 */
/*
 * Authors:
 *   Tomasz Boczkowski <penginsbacon@gmail.com>
 *   Jon A. Cruz <jon@joncruz.org>
 *
 * Copyright (C) 2014 Tomasz Boczkowski
 *
 * Released under GNU GPL, read the file 'COPYING' for more information
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <cstring>
#include <string>
#include <2geom/transforms.h>
#include <sigc++/functors/mem_fun.h>

#include "svg/svg.h"
#include "display/cairo-utils.h"
#include "display/drawing-context.h"
#include "display/drawing-surface.h"
#include "display/drawing.h"
#include "display/drawing-pattern.h"
#include "attributes.h"
#include "document-private.h"
#include "uri.h"
#include "style.h"
#include "sp-hatch.h"
#include "sp-hatch-path.h"
#include "xml/repr.h"

#include "sp-factory.h"

namespace {

SPObject* createHatch() {
    return new SPHatch();
}

bool hatchRegistered = SPFactory::instance().registerObject("svg:hatch", createHatch);

} // namespace

SPHatch::SPHatch()
    : SPPaintServer(),
      href(),
      ref(NULL), // avoiding 'this' in initializer list
      _hatchUnits(UNITS_OBJECTBOUNDINGBOX),
      _hatchUnits_set(false),
      _hatchContentUnits(UNITS_USERSPACEONUSE),
      _hatchContentUnits_set(false),
      _hatchTransform(Geom::identity()),
      _hatchTransform_set(false),
      _x(),
      _y(),
      _pitch(),
      _rotate(),
      _modified_connection(),
      _display()
{
    ref = new SPHatchReference(this);
    ref->changedSignal().connect(sigc::mem_fun(this, &SPHatch::_onRefChanged));

    // TODO check that these should start already as unset:
    _x.unset();
    _y.unset();
    _pitch.unset();
    _rotate.unset();
}

SPHatch::~SPHatch() {
}

void SPHatch::build(SPDocument* doc, Inkscape::XML::Node* repr)
{
    SPPaintServer::build(doc, repr);

    readAttr("hatchUnits");
    readAttr("hatchContentUnits");
    readAttr("hatchTransform");
    readAttr("x");
    readAttr("y");
    readAttr("pitch");
    readAttr("rotate");
    readAttr("xlink:href");
    readAttr( "style" );

    // Register ourselves
    doc->addResource("hatch", this);
}

void SPHatch::release()
{
    if (document) {
        // Unregister ourselves
        document->removeResource("hatch", this);
    }

    std::vector<SPHatchPath *> children(hatchPaths());
    for (ViewIterator view_iter = _display.begin(); view_iter != _display.end(); ++view_iter) {
        for (ChildIterator child_iter = children.begin(); child_iter != children.end(); ++child_iter) {
            SPHatchPath *child = *child_iter;
            child->hide(view_iter->key);
        }
        delete view_iter->arenaitem;
        view_iter->arenaitem = NULL;
    }

    if (ref) {
        _modified_connection.disconnect();
        ref->detach();
        delete ref;
        ref = NULL;
    }

    SPPaintServer::release();
}

void SPHatch::child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref)
{
    SPObject::child_added(child, ref);

    SPHatchPath *path_child = dynamic_cast<SPHatchPath *>(document->getObjectByRepr(child));

    if (path_child) {
        for (ViewIterator iter = _display.begin(); iter != _display.end(); iter++) {
            Geom::OptInterval extents = _calculateStripExtents(iter->bbox);
            Inkscape::DrawingItem *ac = path_child->show(iter->arenaitem->drawing(), iter->key, extents);

            path_child->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            if (ac) {
                iter->arenaitem->prependChild(ac);
            }
        }
    }
    //FIXME: notify all hatches that refer to this child set
}

void SPHatch::set(unsigned int key, const gchar* value)
{
    switch (key) {
    case SP_ATTR_HATCHUNITS:
        if (value) {
            if (!strcmp(value, "userSpaceOnUse")) {
                _hatchUnits = UNITS_USERSPACEONUSE;
            } else {
                _hatchUnits = UNITS_OBJECTBOUNDINGBOX;
            }

            _hatchUnits_set = true;
        } else {
            _hatchUnits_set = false;
        }

        requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
        break;

    case SP_ATTR_HATCHCONTENTUNITS:
        if (value) {
            if (!strcmp(value, "userSpaceOnUse")) {
                _hatchContentUnits = UNITS_USERSPACEONUSE;
            } else {
                _hatchContentUnits = UNITS_OBJECTBOUNDINGBOX;
            }

            _hatchContentUnits_set = true;
        } else {
            _hatchContentUnits_set = false;
        }

        requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
        break;

    case SP_ATTR_HATCHTRANSFORM: {
        Geom::Affine t;

        if (value && sp_svg_transform_read(value, &t)) {
            _hatchTransform = t;
            _hatchTransform_set = true;
        } else {
            _hatchTransform = Geom::identity();
            _hatchTransform_set = false;
        }

        requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
        break;
    }
    case SP_ATTR_X:
        _x.readOrUnset(value);
        requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
        break;

    case SP_ATTR_Y:
        _y.readOrUnset(value);
        requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
        break;

    case SP_ATTR_PITCH:
        _pitch.readOrUnset(value);
        requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
        break;

    case SP_ATTR_ROTATE:
        _rotate.readOrUnset(value);
        requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
        break;

    case SP_ATTR_XLINK_HREF:
        if (value && href == value) {
            // Href unchanged, do nothing.
        } else {
            href.clear();

            if (value) {
                // First, set the href field; it's only used in the "unchanged" check above.
                href = value;
                // Now do the attaching, which emits the changed signal.
                if (value) {
                    try {
                        ref->attach(Inkscape::URI(value));
                    } catch (Inkscape::BadURIException &e) {
                        g_warning("%s", e.what());
                        ref->detach();
                    }
                } else {
                    ref->detach();
                }
            }
        }
        requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
        break;

    default:
        if (SP_ATTRIBUTE_IS_CSS(key)) {
            sp_style_read_from_object(style, this);
            requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
        } else {
            SPPaintServer::set(key, value);
        }
        break;
    }
}

bool SPHatch::_hasHatchPatchChildren(SPHatch const *hatch)
{
    bool matched = false;
    for (SPObject const *child = hatch->firstChild(); child && !matched; child = child->getNext() ) {
        SPHatchPath const *hatchPath = dynamic_cast<SPHatchPath const *>(child);
        if (hatchPath) {
            matched = true;
        }
    }
    return matched;
}

std::vector<SPHatchPath*> SPHatch::hatchPaths()
{
    std::vector<SPHatchPath*> list;
    SPHatch *src = chase_hrefs<SPHatch>(this, sigc::ptr_fun(&_hasHatchPatchChildren));

    if (src) {
        for (SPObject *child = src->firstChild(); child; child = child->getNext()) {
            SPHatchPath *hatchPath = dynamic_cast<SPHatchPath *>(child);
            if (hatchPath) {
                list.push_back(hatchPath);
            }
        }
    }
    return list;
}

std::vector<SPHatchPath const*> SPHatch::hatchPaths() const
{
    std::vector<SPHatchPath const*> list;
    SPHatch const *src = chase_hrefs<SPHatch const>(this, sigc::ptr_fun(&_hasHatchPatchChildren));

    if (src) {
        for (SPObject const *child = src->firstChild(); child; child = child->getNext()) {
            SPHatchPath const *hatchPath = dynamic_cast<SPHatchPath const*>(child);
            if (hatchPath) {
                list.push_back(hatchPath);
            }
        }
    }
    return list;
}

// TODO: ::remove_child and ::order_changed handles - see SPPattern


void SPHatch::update(SPCtx* ctx, unsigned int flags)
{
    typedef std::list<SPHatch::View>::iterator ViewIterator;

    if (flags & SP_OBJECT_MODIFIED_FLAG) {
        flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
    }

    flags &= SP_OBJECT_MODIFIED_CASCADE;

    std::vector<SPHatchPath *> children(hatchPaths());

    for (ChildIterator iter = children.begin(); iter != children.end(); ++iter) {
        SPHatchPath* child = *iter;

        sp_object_ref(child, NULL);

        for (ViewIterator view_iter = _display.begin(); view_iter != _display.end(); ++view_iter) {
            Geom::OptInterval strip_extents = _calculateStripExtents(view_iter->bbox);
            child->setStripExtents(view_iter->key, strip_extents);
        }

        if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {

            child->updateDisplay(ctx, flags);
        }

        sp_object_unref(child, NULL);
    }

    for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) {
        _updateView(*iter);
    }
}

void SPHatch::modified(unsigned int flags)
{
    if (flags & SP_OBJECT_MODIFIED_FLAG) {
        flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
    }

    flags &= SP_OBJECT_MODIFIED_CASCADE;

    std::vector<SPHatchPath *> children(hatchPaths());

    for (ChildIterator iter = children.begin(); iter != children.end(); ++iter) {
        SPObject *child = *iter;

        sp_object_ref(child, NULL);

        if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
            child->emitModified(flags);
        }

        sp_object_unref(child, NULL);
    }
}

void SPHatch::_onRefChanged(SPObject *old_ref, SPObject *ref)
{
    typedef std::list<SPHatch::View>::iterator ViewIterator;

    if (old_ref) {
        _modified_connection.disconnect();
    }

    SPHatch *hatch = dynamic_cast<SPHatch *>(ref);
    if (hatch) {
        _modified_connection = ref->connectModified(sigc::mem_fun(this, &SPHatch::_onRefModified));
    }

    if (!_hasHatchPatchChildren(this)) {
        SPHatch *old_shown = NULL;
        SPHatch *new_shown = NULL;
        std::vector<SPHatchPath *> oldhatchPaths;
        std::vector<SPHatchPath *> newhatchPaths;

        SPHatch *old_hatch = dynamic_cast<SPHatch *>(old_ref);
        if (old_hatch) {
            old_shown = old_hatch->rootHatch();
            oldhatchPaths = old_shown->hatchPaths();
        }
        if (hatch) {
            new_shown = hatch->rootHatch();
            newhatchPaths = new_shown->hatchPaths();
        }
        if (old_shown != new_shown) {

            for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) {
                Geom::OptInterval extents = _calculateStripExtents(iter->bbox);

                for (ChildIterator child_iter = oldhatchPaths.begin(); child_iter != oldhatchPaths.end(); ++child_iter) {
                    SPHatchPath *child = *child_iter;
                    child->hide(iter->key);
                }
                for (ChildIterator child_iter = newhatchPaths.begin(); child_iter != newhatchPaths.end(); ++child_iter) {
                    SPHatchPath *child = *child_iter;
                    Inkscape::DrawingItem *cai = child->show(iter->arenaitem->drawing(), iter->key, extents);
                    child->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
                    if (cai) {
                        iter->arenaitem->appendChild(cai);
                    }

                }
            }
        }
    }

    _onRefModified(ref, 0);
}

void SPHatch::_onRefModified(SPObject */*ref*/, guint /*flags*/)
{
    requestModified(SP_OBJECT_MODIFIED_FLAG);
    // Conditional to avoid causing infinite loop if there's a cycle in the href chain.
}


SPHatch *SPHatch::rootHatch()
{
    SPHatch *src = chase_hrefs<SPHatch>(this, sigc::ptr_fun(&_hasHatchPatchChildren));
    return src ? src : this; // document is broken, we can't get to root; but at least we can return pat which is supposedly a valid hatch
}

// Access functions that look up fields up the chain of referenced hatchs and return the first one which is set
// FIXME: all of them must use chase_hrefs as children() and rootHatch()

SPHatch::HatchUnits SPHatch::hatchUnits() const
{
    HatchUnits units = _hatchUnits;
    for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) {
        if (pat_i->_hatchUnits_set) {
            units = pat_i->_hatchUnits;
            break;
        }
    }
    return units;
}

SPHatch::HatchUnits SPHatch::hatchContentUnits() const
{
    HatchUnits units = _hatchContentUnits;
    for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) {
        if (pat_i->_hatchContentUnits_set) {
            units = pat_i->_hatchContentUnits;
            break;
        }
    }
    return units;
}

Geom::Affine const &SPHatch::hatchTransform() const
{
    for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) {
        if (pat_i->_hatchTransform_set) {
            return pat_i->_hatchTransform;
        }
    }
    return _hatchTransform;
}

gdouble SPHatch::x() const
{
    gdouble val = 0;
    for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) {
        if (pat_i->_x._set) {
            val = pat_i->_x.computed;
            break;
        }
    }
    return val;
}

gdouble SPHatch::y() const
{
    gdouble val = 0;
    for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) {
        if (pat_i->_y._set) {
            val = pat_i->_y.computed;
            break;
        }
    }
    return val;
}

gdouble SPHatch::pitch() const
{
    gdouble val = 0;
    for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) {
        if (pat_i->_pitch._set) {
            val = pat_i->_pitch.computed;
            break;
        }
    }
    return val;
}

gdouble SPHatch::rotate() const
{
    gdouble val = 0;
    for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) {
        if (pat_i->_rotate._set) {
            val = pat_i->_rotate.computed;
            break;
        }
    }
    return val;
}

bool SPHatch::isValid() const
{
    bool valid = false;

    if (pitch() > 0) {
        std::vector<SPHatchPath const *> children(hatchPaths());
        if (!children.empty()) {
            valid = true;
            for (ConstChildIterator iter = children.begin(); (iter != children.end()) && valid; ++iter) {
                SPHatchPath const *child = *iter;
                valid = child->isValid();
            }
        }
    }

    return valid;
}

Inkscape::DrawingPattern *SPHatch::show(Inkscape::Drawing &drawing, unsigned int key, Geom::OptRect bbox)
{
    Inkscape::DrawingPattern *ai = new Inkscape::DrawingPattern(drawing);
    //TODO: set some debug flag to see DrawingPattern
    _display.push_front(View(ai, key));
    _display.front().bbox = bbox;

    std::vector<SPHatchPath *> children(hatchPaths());

    Geom::OptInterval extents = _calculateStripExtents(bbox);
    for (ChildIterator iter = children.begin(); iter != children.end(); ++iter) {
        SPHatchPath *child = *iter;
        Inkscape::DrawingItem *cai = child->show(drawing, key, extents);
        if (cai) {
            ai->appendChild(cai);
        }
    }

    View& view = _display.front();
    _updateView(view);

    return ai;
}

void SPHatch::hide(unsigned int key)
{
    std::vector<SPHatchPath *> children(hatchPaths());

    for (ChildIterator iter = children.begin(); iter != children.end(); ++iter) {
        SPHatchPath *child = *iter;
        child->hide(key);
    }

    for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) {
        if (iter->key == key) {
            delete iter->arenaitem;
            _display.erase(iter);
            return;
        }
    }

    g_assert_not_reached();
}


Geom::Interval SPHatch::bounds() const
{
    Geom::Interval result;
    std::vector<SPHatchPath const *> children(hatchPaths());

    for (ConstChildIterator iter = children.begin(); iter != children.end(); ++iter) {
        SPHatchPath const *child = *iter;
        if (result.extent() == 0) {
            result = child->bounds();
        } else {
            result |= child->bounds();
        }
    }
    return result;
}

SPHatch::RenderInfo SPHatch::calculateRenderInfo(unsigned key) const
{
    RenderInfo info;
    for (ConstViewIterator iter = _display.begin(); iter != _display.end(); ++iter) {
        if (iter->key == key) {
            return _calculateRenderInfo(*iter);
        }
    }
    g_assert_not_reached();
    return info;
}

void SPHatch::_updateView(View &view)
{
    RenderInfo info = _calculateRenderInfo(view);
    //The rendering of hatch overflow is implemented by repeated drawing
    //of hatch paths over one strip. Within each iteration paths are moved by pitch value.
    //The movement progresses from right to left. This gives the same result
    //as drawing whole strips in left-to-right order.


    view.arenaitem->setChildTransform(info.child_transform);
    view.arenaitem->setPatternToUserTransform(info.pattern_to_user_transform);
    view.arenaitem->setTileRect(info.tile_rect);
    view.arenaitem->setStyle(style);
    view.arenaitem->setOverflow(info.overflow_initial_transform, info.overflow_steps,
                                info.overflow_step_transform);
}

SPHatch::RenderInfo SPHatch::_calculateRenderInfo(View const &view) const
{
    RenderInfo info;

    Geom::OptInterval extents = _calculateStripExtents(view.bbox);
    if (extents) {
        double tile_x = x();
        double tile_y = y();
        double tile_width = pitch();
        double tile_height = extents->max() - extents->min();
        double tile_rotate = rotate();
        double tile_render_y = extents->min();

        if (view.bbox && (hatchUnits() == UNITS_OBJECTBOUNDINGBOX)) {
            tile_x *= view.bbox->width();
            tile_y *= view.bbox->height();
            tile_width *= view.bbox->width();
            tile_height *= view.bbox->height();
            tile_render_y *= view.bbox->height();
        }

        // Pattern size in hatch space
        Geom::Rect hatch_tile = Geom::Rect::from_xywh(0, tile_render_y, tile_width, tile_height);

        // Content to bbox
        Geom::Affine content2ps;
        if (view.bbox && (hatchContentUnits() == UNITS_OBJECTBOUNDINGBOX)) {
            content2ps = Geom::Affine(view.bbox->width(), 0.0, 0.0, view.bbox->height(), 0, 0);
        }

        // Tile (hatch space) to user.
        Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * Geom::Rotate::from_degrees(tile_rotate) * hatchTransform();

        info.child_transform = content2ps;
        info.pattern_to_user_transform = ps2user;
        info.tile_rect = hatch_tile;

        if (style->overflow.computed == SP_CSS_OVERFLOW_VISIBLE) {
            Geom::Interval bounds = this->bounds();
            gdouble pitch = this->pitch();
            gdouble overflow_right_strip = floor(bounds.max() / pitch) * pitch;
            info.overflow_steps = ceil((overflow_right_strip - bounds.min()) / pitch) + 1;
            info.overflow_step_transform = Geom::Translate(pitch, 0.0);
            info.overflow_initial_transform = Geom::Translate(-overflow_right_strip, 0.0);
        } else {
            info.overflow_steps = 1;
        }
    }

    return info;
}

//calculates strip extents in content space
Geom::OptInterval SPHatch::_calculateStripExtents(Geom::OptRect const &bbox) const
{
    if (!bbox || (bbox->area() == 0)) {
        return Geom::OptInterval();
    } else {
        double tile_x = x();
        double tile_y = y();
        double tile_rotate = rotate();

        Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * Geom::Rotate::from_degrees(tile_rotate) * hatchTransform();
        Geom::Affine user2ps = ps2user.inverse();

        Geom::Interval extents;
        for (int i = 0; i < 4; ++i) {
            Geom::Point corner = bbox->corner(i);
            Geom::Point corner_ps  =  corner * user2ps;
            if (i == 0 || corner_ps.y() < extents.min()) {
                extents.setMin(corner_ps.y());
            }
            if (i == 0 || corner_ps.y() > extents.max()) {
                extents.setMax(corner_ps.y());
            }
        }

        if (hatchUnits() == UNITS_OBJECTBOUNDINGBOX) {
            extents /= bbox->height();
        }

        return extents;
    }
}

cairo_pattern_t* SPHatch::pattern_new(cairo_t * /*base_ct*/, Geom::OptRect const &/*bbox*/, double /*opacity*/)
{
    //this code should not be used
    //it is however required by the fact that SPPaintServer::hatch_new is pure virtual
    return cairo_pattern_create_rgb(0.5, 0.5, 1.0);
}

void SPHatch::setBBox(unsigned int key, Geom::OptRect const &bbox)
{
    for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) {
        if (iter->key == key) {
            iter->bbox = bbox;
            break;
        }
    }
}

//

SPHatch::RenderInfo::RenderInfo()
    : child_transform(),
      pattern_to_user_transform(),
      tile_rect(),
      overflow_steps(0),
      overflow_step_transform(),
      overflow_initial_transform()
{
}

SPHatch::RenderInfo::~RenderInfo()
{
}

//

SPHatch::View::View(Inkscape::DrawingPattern *arenaitem, int key)
    : arenaitem(arenaitem),
      bbox(),
      key(key)
{
}

SPHatch::View::~View()
{
    // remember, do not delete arenaitem here
    arenaitem = NULL;
}

/*
 Local Variables:
 mode:c++
 c-file-style:"stroustrup"
 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
 indent-tabs-mode:nil
 fill-column:99
 End:
 */
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
