// -*-c++-*-

// fixg2sxd - a utility to convert fig to sxd format

// Copyright (C) 2003-2022 Alexander Bürger, acfb@users.sourceforge.net

// 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.
//
// This program 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
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "xfigobjects.h"

#include "keep_range.h"
#include "misc.h"
#include "xmlwrite.h"

Poly::~Poly()
{
    delete x;
    delete y;
}

istream& Poly::read( istream& figfile )
{
    LineFillStyle lfstmp;
    lfstmp.read( figfile, sub_type, depth, true );
    figfile >> cap_style
            >> radius
            >> forward_arrow
            >> backward_arrow
            >> npoints;

    keep_range( sub_type,       "poly sub_type", 1, 5 );
    if( sub_type == 1 ) // cap_style is used only for polyline
        keep_range( cap_style,  "poly(polyline) cap_style", 0, 2 );
    if( sub_type == 4 ) // radius is used only for arc-boxes
        keep_range( radius,         "poly(arc-box) radius",  0 );
    keep_range( forward_arrow,  "poly forward_arrow",  0, 1 );
    keep_range( backward_arrow, "poly backward_arrow", 0, 1 );
    // limit n.o. points somewhat arbitrarily
    if( npoints<1 || npoints>65536 ) {
        ostringstream err;
        err << "Bad number of polygon points " << npoints
            << " (accepted range = [1,65536]).";
        throw err.str();
    }

    if( forward_arrow != 0 )
        lfstmp.read_arrow( figfile, true );
    if( backward_arrow != 0 )
        lfstmp.read_arrow( figfile, false );
    if( sub_type == 5 ) { // picture
        figfile >> flipped >> file;
    }
    x = new float[npoints];
    y = new float[npoints];
    for( int i=0; i<npoints; ++i )
        figfile >> x[i] >> y[i];
	
    lfs = linefillstyles.insert( lfstmp ).first;
    if( sub_type == 1 ) {
        lfs->stylename_line();
        if( lfs->area_fill != -1 )
            lfs->stylename_fill();
    } else {
        lfs->stylename();
    }

    return figfile;
}

void Poly::find_min_max( float& maxx, float& minx, float& maxy, float& miny, int begin, int end )
{
    maxx = minx = x[begin];
    maxy = miny = y[begin];
    for( int i=begin+1; i<end; ++i ) { // start from begin+1!
        if( x[i] > maxx ) maxx = x[i];
        if( y[i] > maxy ) maxy = y[i];
        if( x[i] < minx ) minx = x[i];
        if( y[i] < miny ) miny = y[i];
    }
}

ostream& Poly::write( ostream& out )
{
    if( sub_type == 5 ) // imported-picture bounding-box
	return write_image( out );
    else if( sub_type == 2 || sub_type == 4 ) // box or arc-box
	return write_rectangle( out );
    else
	return write_poly( out ); // polyline or polygon
}

ostream& Poly::write_image( ostream& out )
{
    if( npoints != 5 )
	fail( "image with != 5 points" );
    // points are clockwise around the image => determine rotation
    // from relative position of first two points
    float angle = 0;
    float maxx=0, maxy=0, minx=0, miny=0;
    find_min_max( maxx, minx, maxy, miny, 0, npoints );
    float width = maxx-minx, height= maxy-miny, posx=minx, posy=miny;
    if( x[0] == x[1] ) {
	swap( width, height );
	if( y[0] > y[1] ) {
	    angle = M_PI/2;
	    posy += height;
	} else {
	    angle = 3*M_PI/2;
	    posx += width;
	}
    } else if( x[0] > x[1] ) {
	posx += width;
	posy += height;
	angle = M_PI;
    }
    Node image("draw:image");
    image["draw:style-name"] << "gr_img";
    image["draw:z-index"] << depth2z(depth);
    image["draw:layer"] << "layout";
    if( angle == 0 ) {
	image["svg:x"] << tr(posx) << "cm";
	image["svg:y"] << tr(posy) << "cm";
    } else {
	image["draw:transform"]
	    << "rotate(" << angle << ")"
	    << "translate(" << tr(posx) << "cm " << tr(posy) << "cm)";
    }
    image["svg:width"]  << tr(width) << "cm";
    image["svg:height"] << tr(height) << "cm";
    image["xlink:href"] << file;
    image["xlink:type"] << "simple";
    image["xlink:show"] << "embed";
    image["xlink:actuate"] << "onLoad";
    return out << image;
}

ostream& Poly::write_rectangle( ostream& out )
{
    float maxx=0, maxy=0, minx=0, miny=0;
    find_min_max( maxx, minx, maxy, miny, 0, npoints );

    Node rect("draw:rect");
    rect["draw:style-name"] << lfs->stylename();
    rect["draw:z-index"] << depth2z(depth);
    if( sub_type == 4 ) { // rounded corners
	rect["draw:corner-radius"] << tr80(radius) << "cm";
    }
    rect["draw:layer"] << "layout";
    rect["svg:x"] << tr(minx) << "cm";
    rect["svg:y"] << tr(miny) << "cm";
    rect["svg:width"]  << tr(maxx-minx) << "cm";
    rect["svg:height"] << tr(maxy-miny) << "cm";
    return out << rect;
}

ostream& Poly::write_poly( ostream& out )
{
    // XXX: it looks like OOo has a bug and does not read attribute
    // values longer than 64kB, so for a lot of points, it will not
    // display anything; for lines, it is to split after some points
    // (more difficult to split before reaching 64kB), but I do not
    // know how to split an arbitrary, filled polygon
    

    // in case of a long, unfilled polygon, make a group with smaller
    // polygons
    const int limit = 500;
    const bool long_line = (sub_type==1 && npoints>limit && lfs->hasLine()
			    && !lfs->hasFill());
    const int step = long_line ? limit : npoints;
    if( long_line ) {
	static bool warn_split = false;
	if( !warn_split ) {
	    cerr << "Warning: very long polylines will be split into pieces of "
		 << limit << " points." << endl;
	    warn_split = true;
	}
	out << "<draw:g>" << endl;
    }

    for( int begin=0; begin<npoints-1; begin+=step-1 ) {
	const int end = std::min(begin + step, npoints);

	float maxx=0, maxy=0, minx=0, miny=0;
	find_min_max( maxx, minx, maxy, miny, begin, end );

	// store the polyline/polygon data temporarily
	Node poly("draw:polygon");
	poly["draw:z-index"] << depth2z(depth);
	poly["draw:layer"] << "layout";
	poly["svg:x"] << tr(minx) << "cm";
	poly["svg:y"] << tr(miny) << "cm";
	poly["svg:width"] << tr(maxx-minx) << "cm";
	poly["svg:height"] << tr(maxy-miny) << "cm";
	poly["svg:viewBox"] << "0 0 "<<tr_p(maxx-minx)<<' '<<tr_p(maxy-miny);
	ostringstream& p = poly["draw:points"];
	for( int i=begin; i<end; ++i )
	    p << tr_p(x[i]-minx) << ',' << tr_p(y[i]-miny) << ' ';

	if( p.str().size() >= 1<<16 ) {
	    static bool warn_long = false;
	    if( !warn_long ) {
		cerr << "Warning: some polylines/polygons might be too large"
		    " for OpenOffice.org." << endl;
		warn_long = true;
	    }
	}

	// now decide what to write
	if( sub_type == 1 ) { // polyline
	    if( lfs->hasFill() ) {
		// a polyline may be filled, but then it is missing the
		// last segment of the border
		poly["draw:style-name"] << lfs->stylename_fill();
		out << poly;
	    }
	    if( lfs->hasLine() ) {
		poly.SetName("draw:polyline");
		poly["draw:style-name"] << lfs->stylename_line();
		out << poly;
	    }
	} else {
	    if( lfs->hasLine() || lfs->hasFill() ) {
		poly["draw:style-name"] << lfs->stylename();
		out << poly;
	    }
	}
    }

    if( long_line )
	out << "</draw:g>" << endl;

    return out;
}
