// SPDX-License-Identifier: GPL-2.0-or-later
/** \file
 * SVG <tref> implementation - All character data within the referenced
 * element, including character data enclosed within additional markup,
 * will be rendered.
 *
 * This file was created based on skeleton.cpp
 */
/*
 * Authors:
 *   Gail Banaszkiewicz <Gail.Banaszkiewicz@gmail.com>
 *   Jon A. Cruz <jon@joncruz.org>
 *   Abhishek Sharma
 *
 * Copyright (C) 2007 Gail Banaszkiewicz
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include "sp-tref.h"

#include <glibmm/i18n.h>

#include "bad-uri-exception.h"
#include "attributes.h"
#include "document.h"
#include "sp-factory.h"
#include "sp-text.h"
#include "style.h"
#include "text-editing.h"
#include "xml/href-attribute-helper.h"

//#define DEBUG_TREF
#ifdef DEBUG_TREF
# define debug(f, a...) { g_message("%s(%d) %s:", \
                                  __FILE__,__LINE__,__FUNCTION__); \
                          g_message(f, ## a); \
                          g_message("\n"); \
                        }
#else
# define debug(f, a...) /**/
#endif


static void build_string_from_root(Inkscape::XML::Node *root, Glib::ustring *retString);

/* TRef base class */
static void sp_tref_href_changed(SPObject *old_ref, SPObject *ref, SPTRef *tref);
static void sp_tref_delete_self(SPObject *deleted, SPTRef *self);

SPTRef::SPTRef()
    : SPItem()
    , href(nullptr)
    , uriOriginalRef(this)
    , stringChild(nullptr)
{
    _changed_connection = uriOriginalRef.changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_tref_href_changed), this));
}

SPTRef::~SPTRef()
{
}

void SPTRef::build(SPDocument *document, Inkscape::XML::Node *repr) {
    SPItem::build(document, repr);

    this->readAttr(SPAttr::XLINK_HREF);
    this->readAttr(SPAttr::X);
    this->readAttr(SPAttr::Y);
    this->readAttr(SPAttr::DX);
    this->readAttr(SPAttr::DY);
    this->readAttr(SPAttr::ROTATE);
}

void SPTRef::release() {
    //this->attributes.~TextTagAttributes();

    this->_delete_connection.disconnect();
    this->_changed_connection.disconnect();

    g_free(href);
    href = nullptr;

    uriOriginalRef.detach();

    SPItem::release();
}

void SPTRef::set(SPAttr key, const gchar* value) {
    debug("0x%p %s(%u): '%s'",this,
            sp_attribute_name(key),key,value ? value : "<no value>");

    if (this->attributes.readSingleAttribute(key, value, style, &viewport)) { // x, y, dx, dy, rotate
        this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
    } else if (key == SPAttr::XLINK_HREF) { // xlink:href
        if ( !value ) {
            // No value
            g_free(href);
            href = nullptr;
            uriOriginalRef.detach();
        } else if ((this->href && strcmp(value, this->href) != 0) || (!this->href)) {
            // Value has changed

            if ( this->href ) {
                g_free(this->href);
                this->href = nullptr;
            }

            href = g_strdup(value);

            try {
                uriOriginalRef.attach(Inkscape::URI(value));
                uriOriginalRef.updateObserver();
            } catch (Inkscape::BadURIException const &e) {
                g_warning("%s", e.what());
                uriOriginalRef.detach();
            }

            // No matter what happened, an update should be in order
            requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
        }
    } else { // default
        SPItem::set(key, value);
    }
}

void SPTRef::update(SPCtx *ctx, guint flags) {
    debug("0x%p",this);

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

    SPObject *child = this->stringChild;
    
    if (child) {
        if ( childflags || ( child->uflags & SP_OBJECT_MODIFIED_FLAG )) {
            child->updateDisplay(ctx, childflags);
        }
    }

    SPItem::update(ctx, flags);
}

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

    flags &= SP_OBJECT_MODIFIED_CASCADE;

    SPObject *child = this->stringChild;
    
    if (child) {
        sp_object_ref(child);
        
        if (flags || (child->mflags & SP_OBJECT_MODIFIED_FLAG)) {
            child->emitModified(flags);
        }
        
        sp_object_unref(child);
    }
}

Inkscape::XML::Node* SPTRef::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) {
    debug("0x%p",this);

    if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
        repr = xml_doc->createElement("svg:tref");
    }

    this->attributes.writeTo(repr);

    if (uriOriginalRef.getURI()) {
        auto uri = uriOriginalRef.getURI()->str();
        auto uri_string = uri.c_str();
        debug("uri_string=%s", uri_string);
        Inkscape::setHrefAttribute(*repr, uri_string);
    }

    SPItem::write(xml_doc, repr, flags);

    return repr;
}

Geom::OptRect SPTRef::bbox(Geom::Affine const &transform, SPItem::BBoxType type) const {
    Geom::OptRect bbox;
    // find out the ancestor text which holds our layout
    SPObject const *parent_text = this;

    while ( parent_text && !is<SPText>(parent_text) ) {
        parent_text = parent_text->parent;
    }

    if (parent_text == nullptr) {
        return bbox;
    }

    // get the bbox of our portion of the layout
    return cast<SPText>(parent_text)->layout.bounds(transform,
        type == SPItem::VISUAL_BBOX,
        sp_text_get_length_upto(parent_text, this),
        sp_text_get_length_upto(this, nullptr) - 1);
}

const char* SPTRef::typeName() const {
    return "text-data";
}

const char* SPTRef::displayName() const {
    return _("Cloned Character Data");
}

gchar* SPTRef::description() const {
    SPObject const *referred = this->getObjectReferredTo();

    if (referred) {
	char *child_desc;

	if (is<SPItem>(referred)) {
	    child_desc = cast<SPItem>(referred)->detailedDescription();
	} else {
	    child_desc = g_strdup("");
	}

	char *ret = g_strdup_printf("%s%s",
	    (is<SPItem>(referred) ? _(" from ") : ""), child_desc);
	g_free(child_desc);

	return ret;
    }

    return g_strdup(_("[orphaned]"));
}


/* For the sigc::connection changes (i.e. when the object being referred to changes) */
static void
sp_tref_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPTRef *tref)
{
    if (tref)
    {
        // Save a pointer to the original object being referred to
        SPObject *refRoot = tref->getObjectReferredTo();

        tref->_delete_connection.disconnect();

        if (tref->stringChild) {
            tref->detach(tref->stringChild);
            tref->stringChild = nullptr;
        }

        // Ensure that we are referring to a legitimate object
        if (tref->href && refRoot && sp_tref_reference_allowed(tref, refRoot)) {

            // Update the text being referred to (will create a new string child)
            sp_tref_update_text(tref);

            // Restore the delete connection now that we're done messing with stuff
            tref->_delete_connection = refRoot->connectDelete(sigc::bind(sigc::ptr_fun(&sp_tref_delete_self), tref));
        }

    }
}


/**
 * Delete the tref object
 */
static void
sp_tref_delete_self(SPObject */*deleted*/, SPTRef *self)
{
    self->deleteObject();
}

/**
 * Return the object referred to via the URI reference
 */
SPObject * SPTRef::getObjectReferredTo()
{
    return uriOriginalRef.getObject();
}

/**
 * Return the object referred to via the URI reference
 */
SPObject const *SPTRef::getObjectReferredTo() const
{
    return uriOriginalRef.getObject();
}

/**
 * Returns true when the given tref is allowed to refer to a particular object
 */
bool
sp_tref_reference_allowed(SPTRef *tref, SPObject *possible_ref)
{
    bool allowed = false;

    if (tref && possible_ref) {
        if (tref != possible_ref) {
            bool ancestor = false;
            for (SPObject *obj = tref; obj; obj = obj->parent) {
                if (possible_ref == obj) {
                    ancestor = true;
                    break;
                }
            }
            allowed = !ancestor;
        }
    }

    return allowed;
}


/**
 * Returns true if a tref is fully contained in the confines of the given
 * iterators and layout (or if there is no tref).
 */
bool
sp_tref_fully_contained(SPObject *start_item, Glib::ustring::iterator &start,
                             SPObject *end_item, Glib::ustring::iterator &end)
{
    bool fully_contained = false;

    if (start_item && end_item) {

        // If neither the beginning or the end is a tref then we return true (whether there
        // is a tref in the innards or not, because if there is one then it must be totally
        // contained)
        if (!(is<SPString>(start_item) && is<SPTRef>(start_item->parent))
                && !(is<SPString>(end_item) && is<SPTRef>(end_item->parent))) {
            fully_contained = true;
        }

        // Both the beginning and end are trefs; but in this case, the string iterators
        // must be at the right places
        else if ((is<SPString>(start_item) && is<SPTRef>(start_item->parent))
                && (is<SPString>(end_item) && is<SPTRef>(end_item->parent))) {
            if (start == cast<SPString>(start_item)->string.begin()
                    && end == cast<SPString>(start_item)->string.end()) {
                fully_contained = true;
            }
        }

        // If the beginning is a string that is a child of a tref, the iterator has to be
        // at the beginning of the item
        else if ((is<SPString>(start_item) && is<SPTRef>(start_item->parent))
                    && !(is<SPString>(end_item) && is<SPTRef>(end_item->parent))) {
            if (start == cast<SPString>(start_item)->string.begin()) {
                fully_contained = true;
            }
        }

        // Same, but the for the end
        else if (!(is<SPString>(start_item) && is<SPTRef>(start_item->parent))
                    && (is<SPString>(end_item) && is<SPTRef>(end_item->parent))) {
            if (end == cast<SPString>(start_item)->string.end()) {
                fully_contained = true;
            }
        }
    }

    return fully_contained;
}


void sp_tref_update_text(SPTRef *tref)
{
    if (tref) {
        // Get the character data that will be used with this tref
        Glib::ustring charData = "";
        build_string_from_root(tref->getObjectReferredTo()->getRepr(), &charData);

        if (tref->stringChild) {
            tref->detach(tref->stringChild);
            tref->stringChild = nullptr;
        }

        // Create the node and SPString to be the tref's child
        Inkscape::XML::Document *xml_doc = tref->document->getReprDoc();

        Inkscape::XML::Node *newStringRepr = xml_doc->createTextNode(charData.c_str());
        tref->stringChild = SPFactory::createObject(NodeTraits::get_type_string(*newStringRepr));

        // Add this SPString as a child of the tref
        tref->attach(tref->stringChild, tref->lastChild());
        sp_object_unref(tref->stringChild, nullptr);
        (tref->stringChild)->invoke_build(tref->document, newStringRepr, FALSE);

        Inkscape::GC::release(newStringRepr);
    }
}



/**
 * Using depth-first search, build up a string by concatenating all SPStrings
 * found in the tree starting at the root
 */
static void
build_string_from_root(Inkscape::XML::Node *root, Glib::ustring *retString)
{
    if (root && retString) {

        // Stop and concatenate when a SPString is found
        if (root->type() == Inkscape::XML::NodeType::TEXT_NODE) {
            *retString += (root->content());

            debug("%s", retString->c_str());

        // Otherwise, continue searching down the tree (with the assumption that no children nodes
        // of a SPString are actually legal)
        } else {
            Inkscape::XML::Node *childNode;
            for (childNode = root->firstChild(); childNode; childNode = childNode->next()) {
                build_string_from_root(childNode, retString);
            }
        }
    }
}

/**
 * This function will create a new tspan element with the same attributes as
 * the tref had and add the same text as a child.  The tref is replaced in the
 * tree with the new tspan.
 * The code is based partially on sp_use_unlink
 */
SPObject *
sp_tref_convert_to_tspan(SPObject *obj)
{
    SPObject * new_tspan = nullptr;

    ////////////////////
    // BASE CASE
    ////////////////////
    if (is<SPTRef>(obj)) {

        auto tref = cast<SPTRef>(obj);

        if (tref && tref->stringChild) {
            Inkscape::XML::Node *tref_repr = tref->getRepr();
            Inkscape::XML::Node *tref_parent = tref_repr->parent();

            SPDocument *document = tref->document;
            Inkscape::XML::Document *xml_doc = document->getReprDoc();

            Inkscape::XML::Node *new_tspan_repr = xml_doc->createElement("svg:tspan");

            // Add the new tspan element just after the current tref
            tref_parent->addChild(new_tspan_repr, tref_repr);
            Inkscape::GC::release(new_tspan_repr);

            new_tspan = document->getObjectByRepr(new_tspan_repr);

            // Create a new string child for the tspan
            Inkscape::XML::Node *new_string_repr = tref->stringChild->getRepr()->duplicate(xml_doc);
            new_tspan_repr->addChild(new_string_repr, nullptr);

            //SPObject * new_string_child = document->getObjectByRepr(new_string_repr);

            // Merge style from the tref
            new_tspan->style->merge( tref->style );
            new_tspan->style->cascade( new_tspan->parent->style );
            new_tspan->updateRepr();

            // Hold onto our SPObject and repr for now.
            sp_object_ref(tref);
            Inkscape::GC::anchor(tref_repr);

            // Remove ourselves, not propagating delete events to avoid a
            // chain-reaction with other elements that might reference us.
            tref->deleteObject(false);

            // Give the copy our old id and let go of our old repr.
            new_tspan_repr->setAttribute("id", tref_repr->attribute("id"));
            Inkscape::GC::release(tref_repr);

            // Establish the succession and let go of our object.
            tref->setSuccessor(new_tspan);
            sp_object_unref(tref);
        }
    }
    ////////////////////
    // RECURSIVE CASE
    ////////////////////
    else {
        std::vector<SPObject *> l;
        for (auto& child: obj->children) {
            sp_object_ref(&child, obj);
            l.push_back(&child);
        }
        for(auto child:l) {
            // Note that there may be more than one conversion happening here, so if it's not a
            // tref being passed into this function, the returned value can't be specifically known
            new_tspan = sp_tref_convert_to_tspan(child);

            sp_object_unref(child, obj);
        }
    }

    return new_tspan;
}


/*
  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 :
