/***************************************************************************
 *   Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de>              *
 *   Copyright (c) 2014 Luke Parry <l.parry@warwick.ac.uk>                 *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#include "PreCompiled.h"

#ifndef _PreComp_
  #include <sstream>
  #include <QDomDocument>
  #include <QFile>
#endif

#include <QXmlQuery>
#include <QXmlResultItems>
#include "QDomNodeModel.h"

#include <Base/Exception.h>
#include <Base/Console.h>
#include <Base/Interpreter.h>
#include <Base/FileInfo.h>
#include <Base/PyObjectBase.h>
#include <Base/Quantity.h>
#include <Base/Tools.h>

#include <App/Application.h>

#include <boost/regex.hpp>
#include <boost/graph/graph_concepts.hpp>
#include <iostream>
#include <iterator>

#include <QDebug>

#include "DrawUtil.h"
#include "DrawPage.h"
#include "DrawSVGTemplate.h"

#include <Mod/TechDraw/App/DrawSVGTemplatePy.h>

using namespace TechDraw;
using namespace std;

PROPERTY_SOURCE(TechDraw::DrawSVGTemplate, TechDraw::DrawTemplate)

DrawSVGTemplate::DrawSVGTemplate()
{
    static const char *group = "Template";

    //TODO: Do we need PageResult anymore?  -wf Yes!
    // PageResult points to a temporary file in tmp/FreeCAD-AB-CD-EF-.../myTemplate.svg
    // which is really copy of original Template with EditableFields replaced
    // When restoring saved document, Template is redundant/incorrect/not present - PageResult is the correct info.  -wf-
    ADD_PROPERTY_TYPE(PageResult, (nullptr),  group, App::Prop_Output,    "Current SVG code for template");
    ADD_PROPERTY_TYPE(Template,   (""), group, App::Prop_Transient, "Template for the page");             //sb TemplateFileName???

    // Width and Height properties shouldn't be set by the user
    Height.setStatus(App::Property::ReadOnly,true);
    Width.setStatus(App::Property::ReadOnly,true);
    Orientation.setStatus(App::Property::ReadOnly,true);

    std::string svgFilter("Svg files (*.svg *.SVG);;All files (*)");
    Template.setFilter(svgFilter);
}

DrawSVGTemplate::~DrawSVGTemplate()
{
}

PyObject *DrawSVGTemplate::getPyObject(void)
{
    if (PythonObject.is(Py::_None())) {
        // ref counter is set to 1
        PythonObject = Py::Object(new DrawSVGTemplatePy(this),true);
    }
    return Py::new_reference_to(PythonObject);
}

unsigned int DrawSVGTemplate::getMemSize(void) const
{
    return 0;
}

short DrawSVGTemplate::mustExecute() const
{
    return TechDraw::DrawTemplate::mustExecute();
}

void DrawSVGTemplate::onChanged(const App::Property* prop)
{
    bool updatePage = false;

    if (prop == &PageResult) {
        if (isRestoring()) {

            //original template has been stored in fcstd file
            Template.setValue(PageResult.getValue());
        }
    } else if (prop == &Template) {            //fileName has changed
        if (!isRestoring()) {
            EditableTexts.setValues(getEditableTextsFromTemplate());
            updatePage = true;
        }
    } else if (prop == &EditableTexts) {
        if (!isRestoring()) {
            updatePage = true;
        }
    }

    if (updatePage) {
        execute();
    }

    TechDraw::DrawTemplate::onChanged(prop);
}

App::DocumentObjectExecReturn * DrawSVGTemplate::execute(void)
{
	std::string templateFilename = Template.getValue();
	if (templateFilename.empty())
		return App::DocumentObject::StdReturn;

	Base::FileInfo fi(templateFilename);
	if (!fi.isReadable()) {
		// non-empty template value, but can't read file
		// if there is a old absolute template file set use a redirect
		fi.setFile(App::Application::getResourceDir() + "Mod/Drawing/Templates/" + fi.fileName());
		// try the redirect
		if (!fi.isReadable()) {
			Base::Console().Log("DrawSVGTemplate::execute() not able to open %s!\n", Template.getValue());
			std::string error = std::string("Cannot open file ") + Template.getValue();
			return new App::DocumentObjectExecReturn(error);
		}
	}

	if (std::string(PageResult.getValue()).empty())                    //first time through?
		PageResult.setValue(fi.filePath().c_str());

	std::string templateFileSpec = fi.filePath();
	QString qSpec = Base::Tools::fromStdString(templateFileSpec);
	std::string documentImage;
	QString qDocImage;

	qDocImage = processTemplate(qSpec);

	if (!qDocImage.isEmpty()) {
		// make a temp file for FileIncluded Property
		string tempName = PageResult.getExchangeTempFile();
		ofstream outfinal(tempName.c_str());
		std::string result = Base::Tools::toStdString(qDocImage);
		outfinal << result;
		outfinal.close();
		PageResult.setValue(tempName.c_str());
	}
	else {
		Base::Console().Error("QSVGT::execute - failed to process Template\n");
	}

	return TechDraw::DrawTemplate::execute();
}

QString DrawSVGTemplate::processTemplate(QString fileSpec)
{
	QFile templateFile(fileSpec);
	if (!templateFile.open(QIODevice::ReadOnly)) {
		Base::Console().Log("DrawSVGTemplate::execute() can't read template %s!\n", Template.getValue());
		std::string error = std::string("Cannot read file ") + Template.getValue();
		return QString();
	}

	QDomDocument templateDocument;
	if (!templateDocument.setContent(&templateFile)) {
		Base::Console().Message("DrawSVGTemplate::execute() - failed to parse file: %s\n",
			Template.getValue());
		std::string error = std::string("Cannot parse file ") + Template.getValue();
		return QString();
	}

	QXmlQuery query(QXmlQuery::XQuery10);
	QDomNodeModel model(query.namePool(), templateDocument);
	query.setFocus(QXmlItem(model.fromDomNode(templateDocument.documentElement())));

	// XPath query to select all <tspan> nodes whose <text> parent
	// has "freecad:editable" attribute
	query.setQuery(QString::fromUtf8(
		"declare default element namespace \"" SVG_NS_URI "\"; "
		"declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; "
		"//text[@freecad:editable]/tspan"));

	QXmlResultItems queryResult;
	query.evaluateTo(&queryResult);

	std::map<std::string, std::string> substitutions = EditableTexts.getValues();
	while (!queryResult.next().isNull())
	{
		QDomElement tspan = model.toDomNode(queryResult.current().toNodeModelIndex()).toElement();

		// Replace the editable text spans with new nodes holding actual values
		QString editableName = tspan.parentNode().toElement().attribute(QString::fromUtf8("freecad:editable"));
		std::map<std::string, std::string>::iterator item =
			substitutions.find(std::string(editableName.toUtf8().constData()));
		if (item != substitutions.end()) {
			// Keep all spaces in the text node
			tspan.setAttribute(QString::fromUtf8("xml:space"), QString::fromUtf8("preserve"));

			// Remove all child nodes and append text node with editable replacement as the only descendant
			while (!tspan.lastChild().isNull()) {
				tspan.removeChild(tspan.lastChild());
			}
			tspan.appendChild(templateDocument.createTextNode(QString::fromUtf8(item->second.c_str())));
		}
	}

	// Calculate the dimensions of the page and store for retrieval
	// Obtain the size of the SVG document by reading the document attributes
	QDomElement docElement = templateDocument.documentElement();
	Base::Quantity quantity;

	// Obtain the width
	QString str = docElement.attribute(QString::fromLatin1("width"));
	quantity = Base::Quantity::parse(str);
	quantity.setUnit(Base::Unit::Length);

	Width.setValue(quantity.getValue());

	str = docElement.attribute(QString::fromLatin1("height"));
	quantity = Base::Quantity::parse(str);
	quantity.setUnit(Base::Unit::Length);

	Height.setValue(quantity.getValue());

	bool isLandscape = getWidth() / getHeight() >= 1.;

	Orientation.setValue(isLandscape ? 1 : 0);

	//all Qt holds on files should be released on exit #4085
	return templateDocument.toString();
}

double DrawSVGTemplate::getWidth() const
{
    return Width.getValue();
}

double DrawSVGTemplate::getHeight() const
{
    return Height.getValue();
}

std::map<std::string, std::string> DrawSVGTemplate::getEditableTextsFromTemplate()
{
    std::map<std::string, std::string> editables;

    std::string templateFilename = Template.getValue();
    if (templateFilename.empty()) {
        return editables;
    }

    Base::FileInfo tfi(templateFilename);
    if (!tfi.isReadable()) {
        // if there is a old absolute template file set use a redirect
        tfi.setFile(App::Application::getResourceDir() + "Mod/Drawing/Templates/" + tfi.fileName());
        // try the redirect
        if (!tfi.isReadable()) {
            Base::Console().Log("DrawSVGTemplate::getEditableTextsFromTemplate() not able to open %s!\n", Template.getValue());
            return editables;
        }
    }

    QFile templateFile(QString::fromUtf8(tfi.filePath().c_str()));
    if (!templateFile.open(QIODevice::ReadOnly)) {
        Base::Console().Log("DrawSVGTemplate::getEditableTextsFromTemplate() can't read template %s!\n", Template.getValue());
        return editables;
    }

    QDomDocument templateDocument;
    if (!templateDocument.setContent(&templateFile)) {
        Base::Console().Message("DrawSVGTemplate::getEditableTextsFromTemplate() - failed to parse file: %s\n",
                                Template.getValue());
        return editables;
    }

    QXmlQuery query(QXmlQuery::XQuery10);
    QDomNodeModel model(query.namePool(), templateDocument, true);
    query.setFocus(QXmlItem(model.fromDomNode(templateDocument.documentElement())));

    // XPath query to select all <tspan> nodes whose <text> parent
    // has "freecad:editable" attribute
    query.setQuery(QString::fromUtf8(
        "declare default element namespace \"" SVG_NS_URI "\"; "
        "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; "
        "//text[@freecad:editable]/tspan"));

    QXmlResultItems queryResult;
    query.evaluateTo(&queryResult);

    while (!queryResult.next().isNull()) {
        QDomElement tspan = model.toDomNode(queryResult.current().toNodeModelIndex()).toElement();

        QString editableName = tspan.parentNode().toElement().attribute(QString::fromUtf8("freecad:editable"));
        QString editableValue = tspan.firstChild().nodeValue();

        editables[std::string(editableName.toUtf8().constData())] =
            std::string(editableValue.toUtf8().constData());
    }

    return editables;
}


// Python Template feature ---------------------------------------------------------
namespace App {
/// @cond DOXERR
PROPERTY_SOURCE_TEMPLATE(TechDraw::DrawSVGTemplatePython, TechDraw::DrawSVGTemplate)
template<> const char* TechDraw::DrawSVGTemplatePython::getViewProviderName(void) const {
    return "TechDrawGui::ViewProviderPython";
}
/// @endcond

// explicit template instantiation
template class TechDrawExport FeaturePythonT<TechDraw::DrawSVGTemplate>;
}
