// -*-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 "vector2.h"
#include "xmlwrite.h"

#include <cmath>

// TODO: interpolated splines bad, x-splines missing
// TODO: control points are not exacty as in xfig

Spline::~Spline()
{
    delete p;
    delete shapefactors;
}

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

    keep_range( sub_type,       "spline sub_type", 0, 5 );
    if( (sub_type&1)==0 ) // cap_style is used only for open splines
        keep_range( cap_style,  "spline(open) cap_style", 0, 2 );
    keep_range( forward_arrow,  "spline forward_arrow",  0, 1 );
    keep_range( backward_arrow, "spline backward_arrow", 0, 1 );
    if( npoints<2 || npoints>65536 ) {
        ostringstream err;
        err << "Bad number of spline points " << npoints
            << " (accepted range = [2,65536]).";
        throw err.str();
    }

    if( forward_arrow != 0 )
        lfstmp.read_arrow( figfile, true );
    if( backward_arrow != 0 )
        lfstmp.read_arrow( figfile, false );
    lfs = linefillstyles.insert( lfstmp ).first;
    lfs->stylename_line();
    lfs->stylename_fill();
    lfs->stylename();

    // allocate more for easier loop for closed splines
    p = new Vector2f[npoints+2];
    for( int i=0; i<npoints; ++i ) {
        int x, y;
        figfile >> x >> y;
        p[i] = Vector2f(x,y);
    }
    shapefactors = new float[npoints+2];
    for( int i=0; i<npoints; ++i )
        figfile >> shapefactors[i];
    return figfile;
}

ostream& Spline::write( ostream& out )
{
    Vector2f moveto;  // will store the initial moveto
    Vector2f lineto1; // will store the initial lineto for sub_type == 0
    Vector2f lineto2; // will store the final lineto for sub_type == 0
    vector<Vector2f> curve; // will store sequences of controlpoint 1,
                            // 2 and "target point"
    moveto = p[0];
    Vector2f p0 = moveto; // first point of curveto
    if( sub_type == 0 || sub_type == 1 ) {
        // open/closed approximated spline
        // using middle-points was taken from fig2dev/genps
        Vector2f mid = (p[0]+p[1])/2;
        p0 = mid;
        if( sub_type == 0 ) {
            lineto1 = mid;
        } else {
            p[npoints] = p[0];
            p[npoints+1] = p[1];
            moveto = mid;
        }
        const int stop = (sub_type==0) ? npoints-1 : npoints+1;
        for( int i=1; i<stop; ++i ) {
            const Vector2f lmid = mid;
            mid = (p[i]+p[i+1])/2;
            // div==0.5 is the best approximation I found, but it's not perfect
            const float div = 0.5;
            curve.push_back( lmid + (p[i]-lmid)*div ); // first control point
            curve.push_back( mid + (p[i]- mid)*div );  // second control point
            curve.push_back( mid );
        }
        if( sub_type == 0 )
            lineto2 = p[stop];
    } else if( sub_type == 2 || sub_type == 3 ) {
        // open/closed interpolated spline
        Vector2f con2 = p[0];
        const float div = 0.5;
        if( sub_type == 3 ) {
            p[npoints] = p[0];
            p[npoints+1] = p[1];
            Vector2f a =  p[npoints-1] - p[0];
            Vector2f b =  p[1] - p[0];
            Vector2f h = (b.norm() - a.norm()).norm();
            con2 = p[0] - (div*(h*a))*h;
        }
        const int stop = (sub_type==3) ? npoints+1 : npoints-1;
        for( int i=1; i<stop; ++i ) {
            Vector2f a =  p[i-1] - p[i];
            Vector2f b =  p[i+1] - p[i];
            Vector2f h = (b.norm() - a.norm()).norm();
            curve.push_back( con2 );                 // first control point
            curve.push_back( p[i] - (div*(h*b))*h ); // second control point
            curve.push_back( p[i] );
            con2 = p[i] - (div*(h*a))*h;
        }
        if( sub_type == 2 ) {
            curve.push_back( con2 );
            curve.push_back( p[npoints-1] );
            curve.push_back( p[npoints-1] );
        }
    } else {
        static bool warn_xspline = false;
        if( !warn_xspline ) {
            warn_xspline = true;
            cerr << "X-Splines are replaced by splines and need to be edited "
                "by hand for now. Sorry." << endl;
        }
        for( int i=1; i<npoints; ++i ) {
            curve.push_back( p[i-1] );
            curve.push_back( p[i] );
            curve.push_back( p[i] );
        }
    }
    // calculate the extrema of the curve
    float maxx = moveto.X(), maxy = moveto.Y(), minx=maxx, miny=maxy;
    for( unsigned int i=0; i<curve.size(); i+=3 ) {
        Vector2f p1 = curve[i  ];
        Vector2f p2 = curve[i+1];
        Vector2f p3 = curve[i+2];
        Vector2f c = (p1-p0)*3;
        Vector2f b = (p2-p1)*3-c;
        Vector2f a = p3-p0-b-c;

        float txD = b.X()*b.X()-3*a.X()*c.X();
        float txp=0, txm=1;
        if( a.X() != 0 ) {
            if( txD >= 0 ) {
            txp = (-b.X()+sqrt(txD))/(3*a.X());
            txm = (-b.X()-sqrt(txD))/(3*a.X());
            }
        } else if ( b.X()!=0 ) {
            txp = txm = -0.5*c.X()/b.X();
        }
        txp = max( min( txp, 1.0f ), 0.0f );
        txm = max( min( txm, 1.0f ), 0.0f );
        float xp = ((a.X()*txp + b.X() )*txp + c.X())*txp + p0.X();
        float xm = ((a.X()*txm + b.X() )*txm + c.X())*txm + p0.X();
        maxx = max( maxx, max( xp, xm ) );
        minx = min( minx, min( xp, xm ) );

        float tyD = b.Y()*b.Y()-3*a.Y()*c.Y();
        float typ=0, tym=1;
        if( a.Y() != 0 ) {
            if( tyD >= 0 ) {
                typ = (-b.Y()+sqrt(tyD))/(3*a.Y());
                tym = (-b.Y()-sqrt(tyD))/(3*a.Y());
            }
        } else if ( b.Y()!=0 ) {
            typ = tym = -0.5*c.Y()/b.Y();
        }
        typ = max( min( typ, 1.0f ), 0.0f );
        tym = max( min( tym, 1.0f ), 0.0f );
        float yp = ((a.Y()*typ + b.Y() )*typ + c.Y())*typ + p0.Y();
        float ym = ((a.Y()*tym + b.Y() )*tym + c.Y())*tym + p0.Y();
        maxy = max( maxy, max( yp, ym ) );
        miny = min( miny, min( yp, ym ) );
        p0 = p3;
    }
    if( sub_type == 0 ) {
        maxx = max( maxx, lineto1.X() );
        maxx = max( maxx, lineto2.X() );
        maxy = max( maxy, lineto1.Y() );
        maxy = max( maxy, lineto2.Y() );
        minx = min( minx, lineto1.X() );
        minx = min( minx, lineto2.X() );
        miny = min( miny, lineto1.Y() );
        miny = min( miny, lineto2.Y() );
    }
    const Vector2f m( minx, miny );
#if 0
    Node viewBox("draw:rect");
    viewBox["draw:style-name"] << lfs->stylename_line();
    viewBox["draw:z-index"] << depth2z(depth);
    viewBox["draw:layer"] << "layout";
    viewBox["svg:x"] << tr(minx) << "cm";
    viewBox["svg:y"] << tr(miny) << "cm";
    viewBox["svg:width"]  << tr(maxx-minx) << "cm";
    viewBox["svg:height"] << tr(maxy-miny) << "cm";
    out << viewBox;
#endif
    // build xml
    Node path("draw:path");
    path["draw:style-name"] << lfs->stylename();
    path["draw:z-index"] << depth2z(depth);
    path["draw:layer"] << "layout";
    path["svg:x"] << tr(minx) << "cm";
    path["svg:y"] << tr(miny) << "cm";
    path["svg:width"]  << tr(maxx-minx) << "cm";
    path["svg:height"] << tr(maxy-miny) << "cm";
    path["svg:viewBox"] << "0 0 " << tr_p(maxx-minx)<<' '<<tr_p(maxy-miny);
    // => construct path from stored points
    ostringstream& d=path["svg:d"];
    d << "M " << tr_p(moveto-m);
    if( sub_type == 0 )
        d << "L " << tr_p(lineto1-m);
    for( unsigned int i=0; i<curve.size(); i+=3 ) {
        d << "C " << tr_p(curve[i]-m) << ' ' << tr_p(curve[i+1]-m)
          << ' ' << tr_p(curve[i+2]-m) << ' ';
    }
    if( sub_type == 0 )
        d << "L " << tr_p(lineto2-m);

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

    // fill it, if necessary
    if( sub_type % 2 == 0 ) {
        const string line_d = d.str();
        if( lfs->hasFill() ) {
            d << " Z";
            path["draw:style-name"] << lfs->stylename_fill();
            out << path;
        }
        if( lfs->hasLine() ) {
            path["draw:style-name"] << lfs->stylename_line();
            path["svg:d"] << line_d;
            out << path;
        }
    } else {
        d << " Z";
        if( lfs->hasLine() || lfs->hasFill() )
            out << path;
    }
    return out;
}
