/****************************************************************************
** $Id: rs_polyline.cpp 1959 2005-03-08 14:09:02Z js $
**
** Copyright (C) 2001-2003 RibbonSoft. All rights reserved.
**
** This file is part of the qcadlib Library project.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding valid qcadlib Professional Edition licenses may use 
** this file in accordance with the qcadlib Commercial License
** Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.ribbonsoft.com for further details.
**
** Contact info@ribbonsoft.com if any conditions of this licensing are
** not clear to you.
**
**********************************************************************/


#include "rs_polyline.h"

#include "rs_debug.h"
#include "rs_line.h"
#include "rs_arc.h"
#include "rs_graphicview.h"


/**
 * Constructor.
 */
RS_Polyline::RS_Polyline(RS_EntityContainer* parent)
        :RS_EntityContainer(parent) {

    closingEntity = NULL;
    nextBulge = 0.0;
}


/**
 * Constructor.
 * @param d Polyline data
 */
RS_Polyline::RS_Polyline(RS_EntityContainer* parent,
                         const RS_PolylineData& d)
        :RS_EntityContainer(parent), data(d) {

    closingEntity = NULL;
    nextBulge = 0.0;
    calculateBorders();
}


/**
 * Destructor 
 */
RS_Polyline::~RS_Polyline() {}


/**
 * Removes the last vertex of this polyline.
 */
void RS_Polyline::removeLastVertex() {
	RS_Entity* last = lastEntity();
	if (last!=NULL) {
		removeEntity(last);
		last = lastEntity();
		if (last!=NULL) {
			if (last->isAtomic()) {
				data.endpoint = ((RS_AtomicEntity*)last)->getEndpoint();
			}
			else {
				RS_DEBUG->print(RS_Debug::D_WARNING, 
					"RS_Polyline::removeLastVertex: "
				 	"polyline contains non-atomic entity");
			}
		}
	}
}


/**
 * Adds a vertex from the endpoint of the last segment or
 * from the startpoint of the first segment to 'v' or
 * sets the startpoint to the point 'v'.
 *
 * The very first vertex added with this method is the startpoint.
 * 
 * @param v vertex coordinate to be added
 * @param bulge The bulge of the arc or 0 for a line segment (see DXF documentation)
 * @param prepend true: prepend at start instead of append at end
 *
 * @return Pointer to the entity that was addded or NULL if this
 *         was the first vertex added.
 */
RS_Entity* RS_Polyline::addVertex(const RS_Vector& v, double bulge, bool prepend) {

    RS_Entity* entity=NULL;
    //static double nextBulge = 0.0;

    // very first vertex:
    if (!data.startpoint.valid) {
        data.startpoint = data.endpoint = v;
        nextBulge = bulge;
    }

    // consequent vertices:
    else {
        // add entity to the polyline:
        entity = createVertex(v, nextBulge, prepend);
        if (entity!=NULL) {
			if (prepend==false) {
            	RS_EntityContainer::addEntity(entity);
				data.endpoint = v;
			}
			else {
            	RS_EntityContainer::insertEntity(0, entity);
				data.startpoint = v;
			}
        }
        nextBulge = bulge;
    	endPolyline();
    }
    //data.endpoint = v;

    return entity;
}



/**
 * Creates a vertex from the endpoint of the last element or 
 * sets the startpoint to the point 'v'.
 *
 * The very first vertex added is the starting point.
 * 
 * @param v vertex coordinate
 * @param bulge The bulge of the arc (see DXF documentation)
 * @param prepend true: Prepend instead of append at end
 *
 * @return Pointer to the entity that was created or NULL if this
 *         was the first vertex added.
 */
RS_Entity* RS_Polyline::createVertex(const RS_Vector& v, double bulge, bool prepend) {

    RS_Entity* entity=NULL;

    RS_DEBUG->print("RS_Polyline::createVertex: %f/%f to %f/%f bulge: %f",
                    data.endpoint.x, data.endpoint.y, v.x, v.y, bulge);

    // create line for the polyline:
    if (fabs(bulge)<RS_TOLERANCE) {
		if (prepend==false) {
        	entity = new RS_Line(this, RS_LineData(data.endpoint, v));
		}
		else {
        	entity = new RS_Line(this, RS_LineData(v, data.startpoint));
		}
        entity->setSelected(isSelected());
        entity->setPen(RS_Pen(RS2::FlagInvalid));
        entity->setLayer(NULL);
        //RS_EntityContainer::addEntity(entity);
        //data.endpoint = v;
    }

    // create arc for the polyline:
    else {
        bool reversed = (bulge<0.0);
        double alpha = atan(bulge)*4.0;

        double radius;
        RS_Vector center;
        RS_Vector middle;
        double dist;
        double angle;

		if (prepend==false) {
	        middle = (data.endpoint+v)/2.0;
    	    dist = data.endpoint.distanceTo(v)/2.0;
        	angle = data.endpoint.angleTo(v);
		}
		else {
	        middle = (data.startpoint+v)/2.0;
    	    dist = data.startpoint.distanceTo(v)/2.0;
        	angle = v.angleTo(data.startpoint);
		}

        // alpha can't be 0.0 at this point
        radius = fabs(dist / sin(alpha/2.0));

        double wu = fabs(RS_Math::pow(radius, 2.0) - RS_Math::pow(dist, 2.0));
        double h = sqrt(wu);

        if (bulge>0.0) {
            angle+=M_PI/2.0;
        } else {
            angle-=M_PI/2.0;
        }

        if (fabs(alpha)>M_PI) {
            h*=-1.0;
        }

        center.setPolar(h, angle);
        center+=middle;

		double a1;
		double a2;

		if (prepend==false) {
			a1 = center.angleTo(data.endpoint);
			a2 = center.angleTo(v);
		}
		else {
			a1 = center.angleTo(v);
			a2 = center.angleTo(data.startpoint);
		}

        RS_ArcData d(center, radius,
                     a1, a2,
                     reversed);

        entity = new RS_Arc(this, d);
        entity->setSelected(isSelected());
        entity->setPen(RS_Pen(RS2::FlagInvalid));
        entity->setLayer(NULL);
    }

    return entity;
}


/**
 * Ends polyline and adds the last entity if the polyline is closed
 */
void RS_Polyline::endPolyline() {
	RS_DEBUG->print("RS_Polyline::endPolyline");

    if (isClosed()) {
		RS_DEBUG->print("RS_Polyline::endPolyline: adding closing entity");
		
        // remove old closing entity:
        if (closingEntity!=NULL) {
            removeEntity(closingEntity);
        }

        // add closing entity to the polyline:
        closingEntity = createVertex(data.startpoint, nextBulge);
        if (closingEntity!=NULL) {
            RS_EntityContainer::addEntity(closingEntity);
            //data.endpoint = data.startpoint;
        }
    }
}
	
	

/**
 * @return The bulge of the closing entity.
 */
double RS_Polyline::getClosingBulge() {
    if (isClosed()) {
		RS_Entity* e = lastEntity();
		if (e!=NULL && e->rtti()==RS2::EntityArc) {
			return ((RS_Arc*)e)->getBulge();
		}
	}

	return 0.0;
}


/**
 * Sets the polylines start and endpoint to match the first and last vertex.
 */
void RS_Polyline::updateEndpoints() {
	RS_Entity* e1 = firstEntity();
	if (e1!=NULL && e1->isAtomic()) {
		RS_Vector v = ((RS_AtomicEntity*)e1)->getStartpoint();
		setStartpoint(v);
	}
	
	RS_Entity* e2 = lastEntity();
	if (isClosed()) {
		e2 = prevEntity();
	}
	if (e2!=NULL && e2->isAtomic()) {
		RS_Vector v = ((RS_AtomicEntity*)e2)->getEndpoint();
		setEndpoint(v);
	}
}



/**
 * Reimplementation of the addEntity method for a normal container.
 * This reimplementation deletes the given entity!
 *
 * To add entities use addVertex() or addSegment() instead.
 */
void RS_Polyline::addEntity(RS_Entity* entity) {
    RS_DEBUG->print(RS_Debug::D_WARNING, "RS_Polyline::addEntity:"
                    " should never be called");

    if (entity==NULL) {
        return;
    }
    delete entity;
}


/**
 * Adds a segment to the polyline.
 */
/*void RS_Polyline::addSegment(RS_Entity* entity) {
	RS_EntityContainer::addEntity(entity);
	// TODO: reorder and check polyline
}*/



RS_VectorSolutions RS_Polyline::getRefPoints() {
    RS_VectorSolutions ret(count()+1);

    int i=0;
    ret.set(0, data.startpoint);
	i++;
    
	for (RS_Entity* e=firstEntity(RS2::ResolveNone);
            e!=NULL;
            e = nextEntity(RS2::ResolveNone), i++) {
		if (e->isAtomic()) {
        	ret.set(i, ((RS_AtomicEntity*)e)->getEndpoint());
		}
    }
    
	ret.set(count(), data.endpoint);

    return ret;
}

RS_Vector RS_Polyline::getNearestRef(const RS_Vector& coord,
                                   double* dist) {

    return RS_Entity::getNearestRef(coord, dist);
}

RS_Vector RS_Polyline::getNearestSelectedRef(const RS_Vector& coord,
        double* dist) {

    return RS_Entity::getNearestSelectedRef(coord, dist);
}



/*
void RS_Polyline::reorder() {
	// current point:
	RS_Vector cp;

	bool done = false;
	do {
		
	} while(!done);
}
*/



void RS_Polyline::move(RS_Vector offset) {
    RS_EntityContainer::move(offset);
    data.startpoint.move(offset);
    data.endpoint.move(offset);
}



void RS_Polyline::rotate(RS_Vector center, double angle) {
    RS_EntityContainer::rotate(center, angle);
    data.startpoint.rotate(center, angle);
    data.endpoint.rotate(center, angle);
}



void RS_Polyline::scale(RS_Vector center, RS_Vector factor) {
    RS_EntityContainer::scale(center, factor);
    data.startpoint.scale(center, factor);
    data.endpoint.scale(center, factor);
}



void RS_Polyline::mirror(RS_Vector axisPoint1, RS_Vector axisPoint2) {
    RS_EntityContainer::mirror(axisPoint1, axisPoint2);
    data.startpoint.mirror(axisPoint1, axisPoint2);
    data.endpoint.mirror(axisPoint1, axisPoint2);
}



void RS_Polyline::moveRef(const RS_Vector& ref, const RS_Vector& offset) {
	RS_EntityContainer::moveRef(ref, offset);
    if (ref.distanceTo(data.startpoint)<1.0e-4) {
       data.startpoint.move(offset);
    }
    if (ref.distanceTo(data.endpoint)<1.0e-4) {
       data.endpoint.move(offset);
    }
    //update();
}



void RS_Polyline::stretch(RS_Vector firstCorner,
                                 RS_Vector secondCorner,
                                 RS_Vector offset) {

    if (data.startpoint.isInWindow(firstCorner, secondCorner)) {
        data.startpoint.move(offset);
    } 
    if (data.endpoint.isInWindow(firstCorner, secondCorner)) {
        data.endpoint.move(offset);
    } 
	
	RS_EntityContainer::stretch(firstCorner, secondCorner, offset);
}


/**
 * Slightly optimized drawing for polylines.
 */
void RS_Polyline::draw(RS_Painter* painter, RS_GraphicView* view, 
	double /*patternOffset*/) {

    if (painter==NULL || view==NULL) {
        return;
    }
	
	// draw first entity and set correct pen:
    RS_Entity* e = firstEntity(RS2::ResolveNone);
    view->drawEntity(e);

	// draw subsequent entities with same pen:
    for (RS_Entity* e=nextEntity(RS2::ResolveNone);
            e!=NULL;
            e = nextEntity(RS2::ResolveNone)) {

        view->drawEntityPlain(e);
    }
}



/**
 * Dumps the point's data to stdout.
 */
std::ostream& operator << (std::ostream& os, const RS_Polyline& l) {
    os << " Polyline: " << l.getData() << " {\n";

    os << (RS_EntityContainer&)l;

    os << "\n}\n";

    return os;
}

