// SPDX-License-Identifier: GPL-2.0-or-later
/** @file
 * @brief Metafile input - common routines
 *//*
 * Authors:
 *   David Mathog
 *
 * Copyright (C) 2013 Authors
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <cstring>
#include <fstream>
#include <glib.h>
#include <glibmm/miscutils.h>

#include "display/curve.h"
#include "extension/internal/metafile-inout.h" // picks up PNG
#include "extension/print.h"
#include "path-prefix.h"
#include "document.h"
#include "util/units.h"
#include "ui/shape-editor.h"
#include "document-undo.h"
#include "inkscape.h"
#include "preferences.h"

#include "object/sp-root.h"
#include "object/sp-namedview.h"
#include "svg/stringstream.h"

namespace Inkscape {
namespace Extension {
namespace Internal {

Metafile::~Metafile()
{
    return;
}

/** Construct a PNG in memory from an RGB from the EMF file

from:
http://www.lemoda.net/c/write-png/

which was based on:
http://stackoverflow.com/questions/1821806/how-to-encode-png-to-buffer-using-libpng

gcc -Wall -o testpng testpng.c -lpng

Originally here, but moved up

#include <png.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
*/


/*  Given "bitmap", this returns the pixel of bitmap at the point
    ("x", "y"). */

pixel_t * Metafile::pixel_at (bitmap_t * bitmap, int x, int y)
{
    return bitmap->pixels + bitmap->width * y + x;
}


/*  Write "bitmap" to a PNG file specified by "path"; returns 0 on
    success, non-zero on error. */

void
Metafile::my_png_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
{
    PMEMPNG p=(PMEMPNG)png_get_io_ptr(png_ptr);

    size_t nsize = p->size + length;

    /* allocate or grow buffer */
    if(p->buffer){ p->buffer = (char *) realloc(p->buffer, nsize); }
    else{          p->buffer = (char *) malloc(nsize);             }

    if(!p->buffer){ png_error(png_ptr, "Write Error"); }

    /* copy new bytes to end of buffer */
    memcpy(p->buffer + p->size, data, length);
    p->size += length;
}

void Metafile::toPNG(PMEMPNG accum, int width, int height, const char *px){
    bitmap_t bmStore;
    bitmap_t *bitmap = &bmStore;
    accum->buffer=nullptr;  // PNG constructed in memory will end up here, caller must free().
    accum->size=0;
    bitmap->pixels=(pixel_t *)px;
    bitmap->width  = width;
    bitmap->height = height;

    png_structp png_ptr = nullptr;
    png_infop info_ptr = nullptr;
    size_t x, y;
    png_byte ** row_pointers = nullptr;
    /*  The following number is set by trial and error only. I cannot
        see where it it is documented in the libpng manual.
    */
    int pixel_size = 3;
    int depth = 8;

    png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
    if (png_ptr == nullptr){
        accum->buffer=nullptr;
        return;
    }

    info_ptr = png_create_info_struct (png_ptr);
    if (info_ptr == nullptr){
        png_destroy_write_struct (&png_ptr, &info_ptr);
        accum->buffer=nullptr;
        return;
    }

    /* Set up error handling. */

    if (setjmp (png_jmpbuf (png_ptr))) {
        png_destroy_write_struct (&png_ptr, &info_ptr);
        accum->buffer=nullptr;
        return;
    }

    /* Set image attributes. */

    png_set_IHDR (
        png_ptr,
        info_ptr,
        bitmap->width,
        bitmap->height,
        depth,
        PNG_COLOR_TYPE_RGB,
        PNG_INTERLACE_NONE,
        PNG_COMPRESSION_TYPE_DEFAULT,
        PNG_FILTER_TYPE_DEFAULT
    );

    /* Initialize rows of PNG. */

    row_pointers = (png_byte **) png_malloc (png_ptr, bitmap->height * sizeof (png_byte *));
    for (y = 0; y < bitmap->height; ++y) {
        png_byte *row =
            (png_byte *) png_malloc (png_ptr, sizeof (uint8_t) * bitmap->width * pixel_size);
        row_pointers[bitmap->height - y - 1] = row;  // Row order in EMF is reversed.
        for (x = 0; x < bitmap->width; ++x) {
            pixel_t * pixel = pixel_at (bitmap, x, y);
            *row++ = pixel->red;   // R & B channels were set correctly by DIB_to_RGB
            *row++ = pixel->green;
            *row++ = pixel->blue;
        }
    }

    /* Write the image data to memory */

    png_set_rows (png_ptr, info_ptr, row_pointers);

    png_set_write_fn(png_ptr, accum, my_png_write_data, nullptr);

    png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr);

    for (y = 0; y < bitmap->height; y++) {
        png_free (png_ptr, row_pointers[y]);
    }
    png_free (png_ptr, row_pointers);
    png_destroy_write_struct(&png_ptr, &info_ptr);

}

/*  If the viewBox is missing, set one 
*/
void Metafile::setViewBoxIfMissing(SPDocument *doc) {

    if (doc && !doc->getRoot()->viewBox_set) {
        bool saved = Inkscape::DocumentUndo::getUndoSensitive(doc);
        Inkscape::DocumentUndo::setUndoSensitive(doc, false);
        
        doc->ensureUpToDate();
        
        // Set document unit
        Inkscape::XML::Node *repr = sp_document_namedview(doc, nullptr)->getRepr();
        Inkscape::SVGOStringStream os;
        Inkscape::Util::Unit const* doc_unit = doc->getWidth().unit;
        os << doc_unit->abbr;
        repr->setAttribute("inkscape:document-units", os.str().c_str());

        // Set viewBox
        doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc_unit), doc->getHeight().value(doc_unit)));
        doc->ensureUpToDate();

        // Scale and translate objects
        double scale = Inkscape::Util::Quantity::convert(1, "px", doc_unit);
        Inkscape::UI::ShapeEditor::blockSetItem(true);
        double dh;
        if(SP_ACTIVE_DOCUMENT){ // for file menu open or import, or paste from clipboard
            dh = SP_ACTIVE_DOCUMENT->getHeight().value("px");
        }
        else { // for open via --file on command line
            dh = doc->getHeight().value("px");
        }

        // These should not affect input, but they do, so set them to a neutral state
        Inkscape::Preferences *prefs = Inkscape::Preferences::get();
        bool transform_stroke      = prefs->getBool("/options/transform/stroke", true);
        bool transform_rectcorners = prefs->getBool("/options/transform/rectcorners", true);
        bool transform_pattern     = prefs->getBool("/options/transform/pattern", true);
        bool transform_gradient    = prefs->getBool("/options/transform/gradient", true);
        prefs->setBool("/options/transform/stroke", true);
        prefs->setBool("/options/transform/rectcorners", true);
        prefs->setBool("/options/transform/pattern", true);
        prefs->setBool("/options/transform/gradient", true);
        
        doc->getRoot()->scaleChildItemsRec(Geom::Scale(scale), Geom::Point(0, dh), true);
        Inkscape::UI::ShapeEditor::blockSetItem(false);

        // restore options
        prefs->setBool("/options/transform/stroke",      transform_stroke);
        prefs->setBool("/options/transform/rectcorners", transform_rectcorners);
        prefs->setBool("/options/transform/pattern",     transform_pattern);
        prefs->setBool("/options/transform/gradient",    transform_gradient);

        Inkscape::DocumentUndo::setUndoSensitive(doc, saved);
    }
}

/**
    \fn Convert EMF/WMF region combining ops to livarot region combining ops
    \return combination operators in livarot enumeration, or -1 on no match
    \param  ops      (int) combination operator (Inkscape)
*/
int Metafile::combine_ops_to_livarot(const int op)
{
    int ret = -1; 
    switch(op) {
        case U_RGN_AND:
           ret = bool_op_inters;
           break;
        case U_RGN_OR:
           ret = bool_op_union;
           break;
        case U_RGN_XOR:
           ret = bool_op_symdiff;
           break;
        case U_RGN_DIFF:
           ret = bool_op_diff;
           break;
    }
    return(ret);
}



/* convert an EMF RGB(A) color to 0RGB
inverse of gethexcolor() in emf-print.cpp
*/
uint32_t Metafile::sethexcolor(U_COLORREF color){

    uint32_t out;
    out = (U_RGBAGetR(color) << 16) +
          (U_RGBAGetG(color) << 8 ) +
          (U_RGBAGetB(color)      );
    return(out);
}

/* Return the base64 encoded png which is shown for all bad images.
Currently a random 3x4 blotch.
Caller must free.
*/
gchar *Metafile::bad_image_png(){
    gchar *gstring = g_strdup("iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAA3NCSVQICAjb4U/gAAAALElEQVQImQXBQQ2AMAAAsUJQMSWI2H8qME1yMshojwrvGB8XcHKvR1XtOTc/8HENumHCsOMAAAAASUVORK5CYII=");
    return(gstring);
}



} // namespace Internal
} // namespace Extension
} // namespace Inkscape

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