/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   copyright (C) 2006-2007                                               *
 *   Umbrello UML Modeller Authors <uml-devel@uml.sf.net>                  *
 ***************************************************************************/

// own header
#include "umlviewimageexportermodel.h"

// system includes
#include <math.h>

// include files for TQt
#include <tqstringlist.h>
#include <tqrect.h>
#include <tqimage.h>
#include <tqpicture.h>
#include <tqpainter.h>
#include <tqprinter.h>
#include <tqdir.h>
#include <tqregexp.h>

// kde include files
#include <kdebug.h>
#include <tdelocale.h>
#include <tdetempfile.h>
#include <tdeapplication.h>
#include <tdeio/netaccess.h>

// application specific includes
#include "uml.h"
#include "umldoc.h"
#include "umlview.h"
#include "umllistview.h"
#include "umllistviewitem.h"

static TQStringList supportedImageTypesList;
static TQStringList supportedMimeTypesList;

TQStringList UMLViewImageExporterModel::supportedImageTypes() {
    if (!supportedImageTypesList.size()) {
        // specific supported formats
        supportedImageTypesList << "eps";
        supportedImageTypesList << "svg";

        // QT supported formats
        TQStrList qImageFormats = TQImage::outputFormats();
        for (const char* format = qImageFormats.first(); format; format = qImageFormats.next()) {
            supportedImageTypesList << TQString(format).lower();
        }
    }

    return supportedImageTypesList;
}

TQStringList UMLViewImageExporterModel::supportedMimeTypes() {
    if (!supportedMimeTypesList.size()) {
        TQStringList imageTypes = UMLViewImageExporterModel::supportedImageTypes();
        for(TQStringList::Iterator it = imageTypes.begin(); it != imageTypes.end(); ++it ) {
            TQString mimeType = imageTypeToMimeType(*it);
            if (!mimeType.isNull())
                supportedMimeTypesList.append(mimeType);
        }
    }

    return supportedMimeTypesList;
}

TQString UMLViewImageExporterModel::imageTypeToMimeType(const TQString& imageType) {
    const TQString imgType = imageType.lower();
    if (TQString("bmp") == imgType) return "image/x-bmp";
    if (TQString("jpeg") == imgType) return "image/jpeg";
    if (TQString("pbm") == imgType) return "image/x-portable-bitmap";
    if (TQString("pgm") == imgType) return "image/x-portable-greymap";
    if (TQString("png") == imgType) return "image/png";
    if (TQString("ppm") == imgType) return "image/x-portable-pixmap";
    if (TQString("xbm") == imgType) return "image/x-xbm";
    if (TQString("xpm") == imgType) return "image/x-xpm";
    if (TQString("eps") == imgType) return "image/x-eps";
    if (TQString("svg") == imgType) return "image/svg+xml";
    return TQString();
}

TQString UMLViewImageExporterModel::mimeTypeToImageType(const TQString& mimeType) {
    if (TQString("image/x-bmp") == mimeType) return "bmp";
    if (TQString("image/jpeg") == mimeType) return "jpeg";
    if (TQString("image/x-portable-bitmap") == mimeType) return "pbm";
    if (TQString("image/x-portable-greymap") == mimeType) return "pgm";
    if (TQString("image/png") == mimeType) return "png";
    if (TQString("image/x-portable-pixmap") == mimeType) return "ppm";
    if (TQString("image/x-xbm") == mimeType) return "xbm";
    if (TQString("image/x-xpm") == mimeType) return "xpm";
    if (TQString("image/x-eps") == mimeType) return "eps";
    if (TQString("image/svg+xml") == mimeType) return "svg";
    return TQString();
}

TQStringList UMLViewImageExporterModel::exportAllViews(const TQString &imageType, const KURL &directory, bool useFolders) const {
    UMLApp *app = UMLApp::app();

    // contains all the error messages returned by exportView calls
    TQStringList errors;

    UMLViewList views = app->getDocument()->getViewIterator();
    for(UMLView *view = views.first(); view; view = views.next()) {
        KURL url = directory;
        url.addPath(getDiagramFileName(view, imageType, useFolders));

        TQString returnString = exportView(view, imageType, url);
        if (!returnString.isNull()) {
            errors.append(view->getName() + ": " + returnString);
        }
    }

    return errors;
}

TQString UMLViewImageExporterModel::exportView(UMLView* view, const TQString &imageType, const KURL &url) const {
    // create the needed directories
    if (!prepareDirectory(url)) {
        return i18n("Can not create directory: %1").arg(url.directory());
    }

    // The fileName will be used when exporting the image. If the url isn't local,
    // the fileName is the name of a temporal local file to export the image to, and then
    // upload it to its destiny
    TQString fileName;
    // tmpFile needs to be unlinked before exiting the method!!!
    KTempFile tmpFile;
    if (url.isLocalFile()) {
        fileName = url.path();
    } else {
        fileName = tmpFile.name();
    }

    // check that the diagram isn't empty
    TQRect rect = view->getDiagramRect();
    if (rect.isEmpty()) {
        tmpFile.unlink();
        return i18n("Can not save an empty diagram");
    }

    // exporting the view to the file
    if (!exportViewTo(view, imageType, fileName)) {
        tmpFile.unlink();
        return i18n("A problem occured while saving diagram in %1").arg(fileName);
    }

    // if the file wasn't local, upload the temp file to the target
    if (!url.isLocalFile()) {
        if (!TDEIO::NetAccess::upload(tmpFile.name(), url, UMLApp::app())) {
            tmpFile.unlink();
            return i18n("There was a problem saving file: %1").arg(url.path());
        }
    } //!isLocalFile

    tmpFile.unlink();
    return TQString();
}

TQString UMLViewImageExporterModel::getDiagramFileName(UMLView *view, const TQString &imageType, bool useFolders /* = false */) const {
    TQString name = view->getName() + '.' + imageType.lower();

    if (!useFolders) {
        return name;
    }

    kapp->processEvents();
    UMLListView *listView = UMLApp::app()->getListView();
    UMLListViewItem* listViewItem = listView->findItem(view->getID());
    // skip the name of the first item because it's the View
    listViewItem = static_cast<UMLListViewItem*>(listViewItem->parent());

    // Relies on the tree structure of the UMLListView. There are a base "Views" folder
    // and five children, one for each view type (Logical, use case, components, deployment
    // and entity relationship)
    while (listView->rootView(listViewItem->getType()) == NULL) {
        name.insert(0, listViewItem->getText() + '/');
        listViewItem = static_cast<UMLListViewItem*>(listViewItem->parent());
        if (listViewItem == NULL)
            break;
    }
    return name;
}

bool UMLViewImageExporterModel::prepareDirectory(const KURL &url) const {
    // the KURL is copied to get protocol, user and so on and then the path is cleaned
    KURL directory = url;
    directory.setPath("");

    // creates the directory and any needed parent directories
    TQStringList dirs = TQStringList::split(TQDir::separator(), url.directory());
    for (TQStringList::ConstIterator it = dirs.begin() ; it != dirs.end(); ++it ) {
        directory.addPath(*it);

        if (!TDEIO::NetAccess::exists(directory, true, UMLApp::app())) {

            if (!TDEIO::NetAccess::mkdir(directory, UMLApp::app())) {
                return false;
            }
        }
    }

    return true;
}

bool UMLViewImageExporterModel::exportViewTo(UMLView* view, const TQString &imageType, const TQString &fileName) const {
    // remove 'blue squares' from exported picture.
    view->clearSelected();

    TQString imageMimeType = UMLViewImageExporterModel::imageTypeToMimeType(imageType);
    if (imageMimeType == "image/x-eps") {
        if (!exportViewToEps(view, fileName, true)) {
            return false;
        }
    } else if (imageMimeType == "image/svg+xml") {
        if (!exportViewToSvg(view, fileName)) {
            return false;
        }
    } else {
        if (!exportViewToPixmap(view, imageType, fileName)) {
            return false;
        }
    }

    return true;
}

bool UMLViewImageExporterModel::exportViewToEps(UMLView* view, const TQString &fileName, bool isEPS) const {
    bool exportSuccessful = true;

    // print the image to a normal postscript file,
    // do not clip so that everything ends up in the file
    // regardless of "paper size"

    // because we want to work with postscript
    // user-coordinates, set to the resolution
    // of the printer (which should be 72dpi here)
    TQPrinter *printer;

    if (isEPS == false) {
        printer = new TQPrinter(TQPrinter::PrinterResolution);
    } else {
        printer = new TQPrinter(TQPrinter::ScreenResolution);
    }
    printer->setOutputToFile(true);
    printer->setOutputFileName(fileName);
    printer->setColorMode(TQPrinter::Color);

    // do not call printer.setup(); because we want no user
    // interaction here
    TQPainter *painter = new TQPainter(printer);

    // make sure the widget sizes will be according to the
    // actually used printer font, important for getDiagramRect()
    // and the actual painting
    view->forceUpdateWidgetFontMetrics(painter);

    TQRect rect = view->getDiagramRect();
    painter->translate(-rect.x(),-rect.y());
    view->getDiagram(rect,*painter);

    int resolution = printer->resolution();

    // delete painter and printer before we try to open and fix the file
    delete painter;
    delete printer;
    if (isEPS) {
        // modify bounding box from screen to eps resolution.
        rect.setWidth( int(ceil(rect.width() * 72.0/resolution)) );
        rect.setHeight( int(ceil(rect.height() * 72.0/resolution)) );
        exportSuccessful = fixEPS(fileName,rect);
    }
    // next painting will most probably be to a different device (i.e. the screen)
    view->forceUpdateWidgetFontMetrics(0);

    return exportSuccessful;
}

bool UMLViewImageExporterModel::fixEPS(const TQString &fileName, const TQRect& rect) const {
    // now open the file and make a correct eps out of it
    TQFile epsfile(fileName);
    if (! epsfile.open(IO_ReadOnly)) {
        return false;
    }
    // read
    TQTextStream ts(&epsfile);
    TQString fileContent = ts.read();
    epsfile.close();

    // read information
    TQRegExp rx("%%BoundingBox:\\s*(-?[\\d\\.:]+)\\s*(-?[\\d\\.:]+)\\s*(-?[\\d\\.:]+)\\s*(-?[\\d\\.:]+)");
    const int pos = rx.search(fileContent);
    if (pos < 0) {
        kError() << "UMLViewImageExporterModel::fixEPS(" << fileName
                  << "): cannot find %%BoundingBox" << endl;
        return false;
    }

    // write new content to file
    if (! epsfile.open(IO_WriteOnly | IO_Truncate)) {
        kError() << "UMLViewImageExporterModel::fixEPS(" << fileName
                  << "): cannot open file for writing" << endl;
        return false;
    }

    // be careful when rounding (ceil/floor) the BB, these roundings
    // were mainly obtained experimentally...
    const double epsleft = rx.cap(1).toFloat();
    const double epstop = rx.cap(4).toFloat();
    const int left = int(floor(epsleft));
    const int right = int(ceil(epsleft)) + rect.width();
    const int top = int(ceil(epstop)) + 1;
    const int bottom = int(floor(epstop)) - rect.height() + 1;

    // modify content
    fileContent.replace(pos,rx.cap(0).length(),
                        TQString("%%BoundingBox: %1 %2 %3 %4").arg(left).arg(bottom).arg(right).arg(top));

    ts << fileContent;
    epsfile.close();

    return true;
}

bool UMLViewImageExporterModel::exportViewToSvg(UMLView* view, const TQString &fileName) const {
    bool exportSuccesful;

    TQPicture* diagram = new TQPicture();

    // do not call printer.setup(); because we want no user
    // interaction here
    TQPainter* painter = new TQPainter();
    painter->begin( diagram );

    // make sure the widget sizes will be according to the
    // actually used printer font, important for getDiagramRect()
    // and the actual painting
    view->forceUpdateWidgetFontMetrics(painter);

    TQRect rect = view->getDiagramRect();
    painter->translate(-rect.x(),-rect.y());
    view->getDiagram(rect,*painter);
    painter->end();
    exportSuccesful = diagram->save(fileName, TQString("SVG").ascii());

    // delete painter and printer before we try to open and fix the file
    delete painter;
    delete diagram;
    // next painting will most probably be to a different device (i.e. the screen)
    view->forceUpdateWidgetFontMetrics(0);

    return exportSuccesful;
}

bool UMLViewImageExporterModel::exportViewToPixmap(UMLView* view, const TQString &imageType, const TQString &fileName) const {
    TQRect rect = view->getDiagramRect();
    TQPixmap diagram(rect.width(), rect.height());
    view->getDiagram(rect, diagram);
    return diagram.save(fileName, imageType.upper().ascii());
}
