/****************************************************************************
**
** This file is part of the LibreCAD project, a 2D CAD program
**
** Copyright (C) 2018 A. Stebich (librecad@mail.lordofbikes.de)
** Copyright (C) 2010 R. van Twisk (librecad@rvt.dds.nl)
** Copyright (C) 2001-2003 RibbonSoft. All rights reserved.
**
**
** 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 gpl-2.0.txt included in the
** packaging of this file.
**
** 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
** This copyright notice MUST APPEAR in all copies of the script!
**
**********************************************************************/

#include<cmath>
#include<iostream>

#include "rs_arc.h"
#include "rs_constructionline.h"
#include "rs_debug.h"
#include "rs_dimangular.h"
#include "rs_information.h"
#include "rs_line.h"
#include "rs_math.h"
#include "rs_mtext.h"
#include "rs_solid.h"
#include "rs_units.h"

RS_DimAngularData::RS_DimAngularData():
    definitionPoint1( false),
    definitionPoint2( false),
    definitionPoint3( false),
    definitionPoint4( false)
{
}

RS_DimAngularData::RS_DimAngularData(const RS_DimAngularData &ed):
    definitionPoint1( ed.definitionPoint1),
    definitionPoint2( ed.definitionPoint2),
    definitionPoint3( ed.definitionPoint3),
    definitionPoint4( ed.definitionPoint4)
{
}

/**
 * Constructor with initialisation.
 *
 * @param definitionPoint Definition point of the angular dimension.
 * @param leader Leader length.
 */
RS_DimAngularData::RS_DimAngularData(const RS_Vector& _definitionPoint1,
                                     const RS_Vector& _definitionPoint2,
                                     const RS_Vector& _definitionPoint3,
                                     const RS_Vector& _definitionPoint4):
    definitionPoint1( _definitionPoint1),
    definitionPoint2( _definitionPoint2),
    definitionPoint3( _definitionPoint3),
    definitionPoint4( _definitionPoint4)
{
}

/**
 * Constructor with initialisation.
 *
 * @param dimscale  general scale (DIMSCALE)
 * @param dimexo  distance from entities (DIMEXO)
 * @param dimexe  extension line extension (DIMEXE)
 * @param dimtxt  text height (DIMTXT)
 * @param dimgap  text distance to line (DIMGAP)
 * @param arrowSize  arrow length
 */
LC_DimAngularVars::LC_DimAngularVars(const double _dimscale,
                                     const double _dimexo,
                                     const double _dimexe,
                                     const double _dimtxt,
                                     const double _dimgap,
                                     const double _arrowSize) :
    dimscale( _dimscale),
    dimexo( _dimexo * _dimscale),
    dimexe( _dimexe * _dimscale),
    dimtxt( _dimtxt * _dimscale),
    dimgap( _dimgap * _dimscale),
    arrowSize( _arrowSize * _dimscale)
{
}

LC_DimAngularVars::LC_DimAngularVars(const LC_DimAngularVars& av) :
    dimscale( av.dimscale),
    dimexo( av.dimexo),
    dimexe( av.dimexe),
    dimtxt( av.dimtxt),
    dimgap( av.dimgap),
    arrowSize( av.arrowSize)
{
}

/**
 * Constructor.
 *
 * @para parent Parent Entity Container.
 * @para d Common dimension geometrical data.
 * @para ed Extended geometrical data for angular dimension.
 */
RS_DimAngular::RS_DimAngular(RS_EntityContainer* parent,
                             const RS_DimensionData& d,
                             const RS_DimAngularData& ed) :
    RS_Dimension( parent, d),
    edata( ed)
{
    calcDimension();
    calculateBorders();
}

RS_Entity* RS_DimAngular::clone() const
{
    RS_DimAngular *d {new RS_DimAngular(*this)};

    d->setOwner( isOwner());
    d->initId();
    d->detach();

    return d;
}

/**
 * @return Automatically created label for the default
 * measurement of this dimension.
 */
QString RS_DimAngular::getMeasuredLabel()
{
    int dimaunit {getGraphicVariableInt( QStringLiteral( "$DIMAUNIT"), 0)};
    int dimadec {getGraphicVariableInt( QStringLiteral( "$DIMADEC"), 0)};
    int dimazin {getGraphicVariableInt( QStringLiteral( "$DIMAZIN"), 0)};
    RS2::AngleFormat format {RS_Units::numberToAngleFormat( dimaunit)};
    QString strLabel( RS_Units::formatAngle( dimAngle, format, dimadec));

    if (RS2::DegreesMinutesSeconds != format
        && RS2::Surveyors != format) {
        strLabel = stripZerosAngle( strLabel, dimazin);
    }

    //verify if units are decimal and comma separator
    if (RS2::DegreesMinutesSeconds != dimaunit) {
        if (',' == getGraphicVariableInt( QStringLiteral( "$DIMDSEP"), 0)) {
            strLabel.replace( QChar('.'), QChar(','));
        }
    }

    return strLabel;
}

/**
 * @return Center of the measured dimension.
 */
RS_Vector RS_DimAngular::getCenter() const
{
    return dimCenter;
}

/**
 * @brief Add an extension line if necessary
 *
 * @param dimLine  dimension definition line including extension offset
 * @param dimPoint  point where the arc meets the definition line
 * @param dirStart  unit vector defining the lines starting point direction
 * @param dirEnd  unit vector defining the lines ending point direction
 * @param av  DXF variables with offset and extension line length
 * @param pen  pen to draw the extension line
 */
void RS_DimAngular::extensionLine(const RS_ConstructionLine& dimLine,
                                  const RS_Vector& dimPoint,
                                  const RS_Vector& dirStart,
                                  const RS_Vector& dirEnd,
                                  const LC_DimAngularVars& av,
                                  const RS_Pen& pen)
{
    double diffLine {RS_Vector::posInLine( dimLine.getStartpoint(), dimLine.getEndpoint(), dimPoint)};
    double diffCenter {RS_Vector::posInLine( dimLine.getStartpoint(), dimCenter, dimPoint)};

    if( 0.0 <= diffLine && 1.0 >= diffLine) {
        // dimension ends on entity, nothing to extend
        return;
    }

    if( 0.0 > diffLine && 0.0 > diffCenter) {
        RS_Line* line {new RS_Line( this,
                                    dimLine.getStartpoint(),
                                    dimPoint - dirStart * av.exe())};

        line->setPen( pen);
        line->setLayer( nullptr);
        addEntity( line);
    }
    else if( 1.0 < diffLine && 0.0 < diffCenter) {
        RS_Line* line {new RS_Line( this,
                                    dimLine.getEndpoint(),
                                    dimPoint - dirEnd * av.exe())};

        line->setPen( pen);
        line->setLayer( nullptr);
        addEntity( line);
    }
    else if( 0.0 > diffLine && 1.0 < diffCenter) {
        RS_Line* line {new RS_Line( this,
                                    dimCenter - dirStart * av.exo(),
                                    dimPoint + dirEnd * av.exe())};

        line->setPen( pen);
        line->setLayer( nullptr);
        addEntity( line);
    }
}

/**
 * @brief Add an arrow to the dimension arc
 *
 * @param point  arc endpoint, the arrow tip
 * @param angle  the angle from center to the arc endpoint
 * @param direction  this holds the sign for the arrow endpoint direction
 * @param outsideArrow  when the arc becomes too small, arrows are placed outside
 * @param av  DXF variables with offset and extension line length
 * @param pen  pen to draw the extension line
 */
void RS_DimAngular::arrow(const RS_Vector& point,
                          const double angle,
                          const double direction,
                          const bool outsideArrows,
                          const LC_DimAngularVars& av,
                          const RS_Pen& pen)
{
    if (RS_TOLERANCE_ANGLE >= av.arrow()) {
        // arrow size is 0, no need to add an arrow
        return;
    }

    double arrowAngle {0.0};

    if (outsideArrows) {
        // for outside arrows use tangent angle on endpoints
        // because for small radius the arrows looked inclined
        arrowAngle = angle + std::copysign( M_PI_2, direction);
    }
    else {
        // compute the angle from center to the endpoint of the arrow on the arc
        double endAngle {0.0};
        if (RS_TOLERANCE_ANGLE < dimRadius) {
            endAngle = av.arrow() / dimRadius;
        }

        // compute the endpoint of the arrow on the arc
        RS_Vector arrowEnd;
        arrowEnd.setPolar( dimRadius, angle + std::copysign( endAngle, direction));
        arrowEnd += dimCenter;
        arrowAngle = arrowEnd.angleTo( point);
    }

    RS_SolidData sd;
    RS_Solid* arrow;

    arrow = new RS_Solid( this, sd);
    arrow->shapeArrow( point, arrowAngle, av.arrow());
    arrow->setPen( pen);
    arrow->setLayer( nullptr);
    addEntity( arrow);

}

/**
 * Updates the sub entities of this dimension. Called when the
 * dimension or the position, alignment, .. changes.
 *
 * @param autoText Automatically reposition the text label
 */
void RS_DimAngular::updateDim(bool autoText /*= false*/)
{
    Q_UNUSED( autoText)
    RS_DEBUG->print("RS_DimAngular::update");

    clear();

    if (isUndone()) {
        return;
    }

    if ( ! dimCenter.valid) {
        return;
    }

    LC_DimAngularVars   av( getGeneralScale(),
                            getExtensionLineOffset(),
                            getExtensionLineExtension(),
                            getTextHeight(),
                            getDimensionLineGap(),
                            getArrowSize());

    // create new lines with offsets for extension lines
    RS_ConstructionLine line1( nullptr,
                               RS_ConstructionLineData( dimLine1.getStartpoint() - dimDir1s * av.exo(),
                                                        dimLine1.getEndpoint() - dimDir1e * av.exo()));
    RS_ConstructionLine line2( nullptr,
                               RS_ConstructionLineData( dimLine2.getStartpoint() - dimDir2s * av.exo(),
                                                        dimLine2.getEndpoint() - dimDir2e * av.exo()));

    RS_Vector p1 {dimCenter + dimDir1e * dimRadius};
    RS_Vector p2 {dimCenter + dimDir2e * dimRadius};
    RS_Pen pen( getExtensionLineColor(), getExtensionLineWidth(), RS2::LineByBlock);

    extensionLine( line1, p1, dimDir1s, dimDir1e, av, pen);
    extensionLine( line2, p2, dimDir2s, dimDir2e, av, pen);

    // Create dimension line (arc)
    RS_Arc* arc {new RS_Arc( this, RS_ArcData( dimCenter, dimRadius, dimAngleL1, dimAngleL2, false))};
    pen.setWidth( getDimensionLineWidth());
    pen.setColor( getDimensionLineColor());
    arc->setPen( pen);
    arc->setLayer( nullptr);
    addEntity( arc);

    // do we have to put the arrows outside of the arc?
    bool outsideArrows {arc->getLength() < 3.0 * av.arrow()};

    arrow( p1, dimAngleL1, +1.0, outsideArrows, av, pen);
    arrow( p2, dimAngleL2, -1.0, outsideArrows, av, pen);

    // text label
    RS_MTextData textData;
    RS_Vector textPos {arc->getMiddlePoint()};

    RS_Vector distV;
    double textAngle {0.0};
    double angle1 {textPos.angleTo( dimCenter) - M_PI_2};

    // rotate text so it's readable from the bottom or right (ISO)
    // quadrant 1 & 4
    if (angle1 > M_PI_2 * 3.0 + 0.001
        || angle1 < M_PI_2 + 0.001) {
        distV.setPolar( av.gap(), angle1 + M_PI_2);
        textAngle = angle1;
    }
    // quadrant 2 & 3
    else {
        distV.setPolar( av.gap(), angle1 - M_PI_2);
        textAngle = angle1 + M_PI;
    }

    // move text away from dimension line:
    textPos += distV;

    textData = RS_MTextData( textPos,
                             av.txt(), 30.0,
                             RS_MTextData::VABottom,
                             RS_MTextData::HACenter,
                             RS_MTextData::LeftToRight,
                             RS_MTextData::Exact,
                             1.0,
                             getLabel(),
                             getTextStyle(),
                             textAngle);

    RS_MText* text {new RS_MText( this, textData)};

    // move text to the side:
    text->setPen( RS_Pen( getTextColor(), RS2::WidthByBlock, RS2::SolidLine));
    text->setLayer( nullptr);
    addEntity( text);

    calculateBorders();
}

void RS_DimAngular::update()
{
    calcDimension();
    RS_Dimension::update();
}

void RS_DimAngular::move(const RS_Vector& offset)
{
    RS_Dimension::move( offset);

    edata.definitionPoint1.move( offset);
    edata.definitionPoint2.move( offset);
    edata.definitionPoint3.move( offset);
    edata.definitionPoint4.move( offset);
    update();
}

void RS_DimAngular::rotate(const RS_Vector& center, const double& angle)
{
    rotate( center, RS_Vector( angle));
}

void RS_DimAngular::rotate(const RS_Vector& center, const RS_Vector& angleVector)
{
    RS_Dimension::rotate( center, angleVector);

    edata.definitionPoint1.rotate( center, angleVector);
    edata.definitionPoint2.rotate( center, angleVector);
    edata.definitionPoint3.rotate( center, angleVector);
    edata.definitionPoint4.rotate( center, angleVector);
    update();
}

void RS_DimAngular::scale(const RS_Vector& center, const RS_Vector& factor)
{
    RS_Dimension::scale( center, factor);

    edata.definitionPoint1.scale( center, factor);
    edata.definitionPoint2.scale( center, factor);
    edata.definitionPoint3.scale( center, factor);
    edata.definitionPoint4.scale( center, factor);
    update();
}

void RS_DimAngular::mirror(const RS_Vector& axisPoint1, const RS_Vector& axisPoint2)
{
    RS_Dimension::mirror( axisPoint1, axisPoint2);

    edata.definitionPoint1.mirror( axisPoint1, axisPoint2);
    edata.definitionPoint2.mirror( axisPoint1, axisPoint2);
    edata.definitionPoint3.mirror( axisPoint1, axisPoint2);
    edata.definitionPoint4.mirror( axisPoint1, axisPoint2);
    update();
}

/**
 * @brief Compute all static values for dimension.
 *
 * From DXF reference the lines are P2-P1 and P-P3.
 * The dimension is drawn from line1 (P2-P1) to line2 (P-P3) in CCW direction.
 */
void RS_DimAngular::calcDimension(void)
{
    // get unit vectors for definition points
    dimDir1s = RS_Vector::polar( 1.0, RS_Math::correctAngle( edata.definitionPoint2.angleTo( edata.definitionPoint1)));
    dimDir1e = RS_Vector::polar( 1.0, RS_Math::correctAngle( edata.definitionPoint1.angleTo( edata.definitionPoint2)));
    dimDir2s = RS_Vector::polar( 1.0, RS_Math::correctAngle( data.definitionPoint.angleTo( edata.definitionPoint3)));
    dimDir2e = RS_Vector::polar( 1.0, RS_Math::correctAngle( edata.definitionPoint3.angleTo( data.definitionPoint)));

    // create the two dimension definition lines
    dimLine1 = RS_ConstructionLine( nullptr,
                                    RS_ConstructionLineData( edata.definitionPoint2,
                                                             edata.definitionPoint1));
    dimLine2 = RS_ConstructionLine( nullptr,
                                    RS_ConstructionLineData( data.definitionPoint,
                                                             edata.definitionPoint3));

    RS_VectorSolutions vs {RS_Information::getIntersection( &dimLine1, &dimLine2, false)};
    dimCenter = vs.get(0);
    dimRadius = dimCenter.distanceTo( edata.definitionPoint4);
    dimDirRad = RS_Vector::polar( 1.0, RS_Math::correctAngle( dimCenter.angleTo( edata.definitionPoint4)));

    fixDimension();

    dimAngleL1 = dimLine1.getDirection2();
    dimAngleL2 = dimLine2.getDirection2();

    dimAngle = RS_Math::correctAngle( dimLine2.getDirection1() - dimLine1.getDirection1());
}

/**
 * @brief check the dimension and fix non conform values from foreign CAD systems
 *
 * check if the radius definition point is on the arc,
 * from line1 to line2 in counter clockwise direction
 * LibreCAD takes care on correct orientation and line order in RS_ActionDimAngular
 * but angular dimensions, created in other CAD software, may fail and must be fixed here
 */
void RS_DimAngular::fixDimension(void)
{
    if( ! RS_Math::isAngleBetween( dimDirRad.angle(), dimDir2s.angle(), dimDir1s.angle(), false)) {
        double distance0 {data.definitionPoint.distanceTo( dimCenter)};
        double distance1 {edata.definitionPoint1.distanceTo( dimCenter)};
        double distance2 {edata.definitionPoint2.distanceTo( dimCenter)};
        double distance3 {edata.definitionPoint3.distanceTo( dimCenter)};
        double  angle0 {0.0};
        double  angle1 {0.0};
        double  angle2 {0.0};
        double  angle3 {0.0};
        if( RS_TOLERANCE >= distance0) {
            angle3 = (edata.definitionPoint3 - dimCenter).angle();
            angle0 = angle3;
        }
        else if( RS_TOLERANCE >= distance3) {
            angle0 = (data.definitionPoint - dimCenter).angle();
            angle3 = angle0;
        }
        else {
            angle0 = (data.definitionPoint - dimCenter).angle();
            angle3 = (edata.definitionPoint3 - dimCenter).angle();
        }

        if( RS_TOLERANCE >= distance1) {
            angle2 = (edata.definitionPoint2- dimCenter).angle();
            angle1 = angle2;
        }
        else if( RS_TOLERANCE >= distance2) {
            angle1 = (edata.definitionPoint1 - dimCenter).angle();
            angle2 = angle1;
        }
        else {
            angle1 = (edata.definitionPoint1 - dimCenter).angle();
            angle2 = (edata.definitionPoint2 - dimCenter).angle();
        }

        if( angle2 == angle1
            && distance2 < distance1
            && angle0 == angle3
            && distance0 < distance3) {
            // revert both lines
            dimLine1 = RS_ConstructionLine( nullptr,
                                            RS_ConstructionLineData( dimLine1.getEndpoint(),
                                                                     dimLine1.getStartpoint()));
            dimLine2 = RS_ConstructionLine( nullptr,
                                            RS_ConstructionLineData( dimLine2.getEndpoint(),
                                                                     dimLine2.getStartpoint()));

            // and their unit vectors
            RS_Vector swapDir {dimDir1s};
            dimDir1s = dimDir1e;
            dimDir1e = swapDir;

            swapDir = dimDir2s;
            dimDir2s = dimDir2e;
            dimDir2e = swapDir;
        }

        // check again, as the previous revert may have made this condition false
        if( ! RS_Math::isAngleBetween( dimDirRad.angle(), dimDir2s.angle(), dimDir1s.angle(), false)) {
            // swap the lines
            RS_ConstructionLine swapLine {dimLine1};
            dimLine1 = dimLine2;
            dimLine2 = swapLine;

            // and their unit vectors
            RS_Vector swapDir {dimDir1s};
            dimDir1s = dimDir2s;
            dimDir2s = swapDir;

            swapDir = dimDir1e;
            dimDir1e = dimDir2e;
            dimDir2e = swapDir;
        }
    }
}

/**
 * Dumps the point's data to stdout.
 */
std::ostream& operator << (std::ostream& os, const RS_DimAngular& d)
{
    os << " DimAngular: "
       << d.getData() << std::endl
       << d.getEData() << std::endl;

    return os;
}

std::ostream& operator << (std::ostream& os, const RS_DimAngularData& dd)
{
    os << "(" << dd.definitionPoint1
       << "," << dd.definitionPoint2
       << "," << dd.definitionPoint3
       << "," << dd.definitionPoint3
       << ")";

    return os;
}
