/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <math.h>
#include <rtl/ustrbuf.hxx>

#include <svx/dialmgr.hxx>
#include <svx/strings.hrc>

#include <sdr/contact/viewcontactofsdrcircobj.hxx>
#include <sdr/properties/circleproperties.hxx>
#include <svx/svddrag.hxx>
#include <svx/svdmodel.hxx>
#include <svx/svdocirc.hxx>
#include <svx/svdopath.hxx>
#include <svx/svdtrans.hxx>
#include <svx/svdview.hxx>
#include <svx/sxciaitm.hxx>
#include <sxcikitm.hxx>
#include <svx/xfillit0.hxx>
#include <svx/xlineit0.hxx>
#include <svx/xlnedit.hxx>
#include <svx/xlnedwit.hxx>
#include <svx/xlnstit.hxx>
#include <svx/xlnstwit.hxx>
#include <svx/xlnwtit.hxx>
#include <vcl/canvastools.hxx>
#include <vcl/ptrstyle.hxx>

using namespace com::sun::star;

static Point GetAnglePnt(const tools::Rectangle& rR, Degree100 nAngle)
{
    Point aCenter(rR.Center());
    tools::Long nWdt=rR.Right()-rR.Left();
    tools::Long nHgt=rR.Bottom()-rR.Top();
    tools::Long nMaxRad=(std::max(nWdt,nHgt)+1) /2;
    double a = toRadians(nAngle);
    Point aRetval(basegfx::fround<tools::Long>(cos(a) * nMaxRad),
                  basegfx::fround<tools::Long>(-sin(a) * nMaxRad));
    if (nWdt==0) aRetval.setX(0 );
    if (nHgt==0) aRetval.setY(0 );
    if (nWdt!=nHgt) {
        if (nWdt>nHgt) {
            if (nWdt!=0) {
                // stop possible overruns for very large objects
                if (std::abs(nHgt)>32767 || std::abs(aRetval.Y())>32767) {
                    aRetval.setY(BigMulDiv(aRetval.Y(),nHgt,nWdt) );
                } else {
                    aRetval.setY(aRetval.Y()*nHgt/nWdt );
                }
            }
        } else {
            if (nHgt!=0) {
                // stop possible overruns for very large objects
                if (std::abs(nWdt)>32767 || std::abs(aRetval.X())>32767) {
                    aRetval.setX(BigMulDiv(aRetval.X(),nWdt,nHgt) );
                } else {
                    aRetval.setX(aRetval.X()*nWdt/nHgt );
                }
            }
        }
    }
    aRetval+=aCenter;
    return aRetval;
}


// BaseProperties section

std::unique_ptr<sdr::properties::BaseProperties> SdrCircObj::CreateObjectSpecificProperties()
{
    return std::make_unique<sdr::properties::CircleProperties>(*this);
}


// DrawContact section

std::unique_ptr<sdr::contact::ViewContact> SdrCircObj::CreateObjectSpecificViewContact()
{
    return std::make_unique<sdr::contact::ViewContactOfSdrCircObj>(*this);
}

SdrCircKind ToSdrCircKind(SdrObjKind eKind)
{
    switch (eKind)
    {
        case SdrObjKind::CircleOrEllipse: return SdrCircKind::Full;
        case SdrObjKind::CircleSection: return SdrCircKind::Section;
        case SdrObjKind::CircleArc: return SdrCircKind::Arc;
        case SdrObjKind::CircleCut: return SdrCircKind::Cut;
        default: assert(false);
    }
    return SdrCircKind::Full;
}

SdrCircObj::SdrCircObj(
    SdrModel& rSdrModel,
    SdrCircKind eNewKind)
:   SdrRectObj(rSdrModel)
{
    m_nStartAngle=0_deg100;
    m_nEndAngle=36000_deg100;
    meCircleKind=eNewKind;
    m_bClosedObj=eNewKind!=SdrCircKind::Arc;
}

SdrCircObj::SdrCircObj(SdrModel& rSdrModel, SdrCircObj const & rSource)
:   SdrRectObj(rSdrModel, rSource)
{
    meCircleKind = rSource.meCircleKind;
    m_nStartAngle = rSource.m_nStartAngle;
    m_nEndAngle = rSource.m_nEndAngle;
    m_bClosedObj = rSource.m_bClosedObj;
}

SdrCircObj::SdrCircObj(
    SdrModel& rSdrModel,
    SdrCircKind eNewKind,
    const tools::Rectangle& rRect)
:   SdrRectObj(rSdrModel, rRect)
{
    m_nStartAngle=0_deg100;
    m_nEndAngle=36000_deg100;
    meCircleKind=eNewKind;
    m_bClosedObj=eNewKind!=SdrCircKind::Arc;
}

SdrCircObj::SdrCircObj(
    SdrModel& rSdrModel,
    SdrCircKind eNewKind,
    const tools::Rectangle& rRect,
    Degree100 nNewStartAngle,
    Degree100 nNewEndAngle)
:   SdrRectObj(rSdrModel, rRect)
{
    Degree100 nAngleDif=nNewEndAngle-nNewStartAngle;
    m_nStartAngle=NormAngle36000(nNewStartAngle);
    m_nEndAngle=NormAngle36000(nNewEndAngle);
    if (nAngleDif==36000_deg100) m_nEndAngle+=nAngleDif; // full circle
    meCircleKind=eNewKind;
    m_bClosedObj=eNewKind!=SdrCircKind::Arc;
}

SdrCircObj::~SdrCircObj()
{
}

void SdrCircObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const
{
    bool bCanConv=!HasText() || ImpCanConvTextToCurve();
    rInfo.bEdgeRadiusAllowed    = false;
    rInfo.bCanConvToPath=bCanConv;
    rInfo.bCanConvToPoly=bCanConv;
    rInfo.bCanConvToContour = !IsFontwork() && (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary());
}

SdrObjKind SdrCircObj::GetObjIdentifier() const
{
    switch (meCircleKind)
    {
    case SdrCircKind::Full: return SdrObjKind::CircleOrEllipse;
    case SdrCircKind::Section: return SdrObjKind::CircleSection;
    case SdrCircKind::Cut: return SdrObjKind::CircleCut;
    case SdrCircKind::Arc: return SdrObjKind::CircleArc;
    default: assert(false);
    }
    return SdrObjKind::CircleOrEllipse;
}

bool SdrCircObj::PaintNeedsXPolyCirc() const
{
    // XPoly is necessary for all rotated ellipse objects, circle and
    // ellipse segments.
    // If not WIN, then (for now) also for circle/ellipse segments and circle/
    // ellipse arcs (for precision)
    bool bNeed = maGeo.m_nRotationAngle || maGeo.m_nShearAngle || meCircleKind == SdrCircKind::Cut;
    // If not WIN, then for everything except full circle (for now!)
    if (meCircleKind!=SdrCircKind::Full) bNeed = true;

    const SfxItemSet& rSet = GetObjectItemSet();
    if(!bNeed)
    {
        // XPoly is necessary for everything that isn't LineSolid or LineNone
        drawing::LineStyle eLine = rSet.Get(XATTR_LINESTYLE).GetValue();
        bNeed = eLine != drawing::LineStyle_NONE && eLine != drawing::LineStyle_SOLID;

        // XPoly is necessary for thick lines
        if(!bNeed && eLine != drawing::LineStyle_NONE)
            bNeed = rSet.Get(XATTR_LINEWIDTH).GetValue() != 0;

        // XPoly is necessary for circle arcs with line ends
        if(!bNeed && meCircleKind == SdrCircKind::Arc)
        {
            // start of the line is here if StartPolygon, StartWidth!=0
            bNeed=rSet.Get(XATTR_LINESTART).GetLineStartValue().count() != 0 &&
                  rSet.Get(XATTR_LINESTARTWIDTH).GetValue() != 0;

            if(!bNeed)
            {
                // end of the line is here if EndPolygon, EndWidth!=0
                bNeed = rSet.Get(XATTR_LINEEND).GetLineEndValue().count() != 0 &&
                        rSet.Get(XATTR_LINEENDWIDTH).GetValue() != 0;
            }
        }
    }

    // XPoly is necessary if Fill !=None and !=Solid
    if(!bNeed && meCircleKind != SdrCircKind::Arc)
    {
        drawing::FillStyle eFill=rSet.Get(XATTR_FILLSTYLE).GetValue();
        bNeed = eFill != drawing::FillStyle_NONE && eFill != drawing::FillStyle_SOLID;
    }

    if(!bNeed && meCircleKind != SdrCircKind::Full && m_nStartAngle == m_nEndAngle)
        bNeed = true; // otherwise we're drawing a full circle

    return bNeed;
}

basegfx::B2DPolygon SdrCircObj::ImpCalcXPolyCirc(const SdrCircKind eCircleKind, const tools::Rectangle& rRect1, Degree100 nStart, Degree100 nEnd) const
{
    const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRect1);
    basegfx::B2DPolygon aCircPolygon;

    if(SdrCircKind::Full == eCircleKind)
    {
        // create full circle. Do not use createPolygonFromEllipse; it's necessary
        // to get the start point to the bottom of the circle to keep compatible to
        // old geometry creation
        aCircPolygon = basegfx::utils::createPolygonFromUnitCircle(1);

        // needs own scaling and translation from unit circle to target size (same as
        // would be in createPolygonFromEllipse)
        const basegfx::B2DPoint aCenter(aRange.getCenter());
        const basegfx::B2DHomMatrix aMatrix(basegfx::utils::createScaleTranslateB2DHomMatrix(
            aRange.getWidth() / 2.0, aRange.getHeight() / 2.0,
            aCenter.getX(), aCenter.getY()));
        aCircPolygon.transform(aMatrix);
    }
    else
    {
        // mirror start, end for geometry creation since model coordinate system is mirrored in Y
        const double fStart(toRadians((36000_deg100 - nEnd) % 36000_deg100));
        const double fEnd(toRadians((36000_deg100 - nStart) % 36000_deg100));

        // create circle segment. This is not closed by default
        aCircPolygon = basegfx::utils::createPolygonFromEllipseSegment(
            aRange.getCenter(), aRange.getWidth() / 2.0, aRange.getHeight() / 2.0,
            fStart, fEnd);

        // check closing states
        const bool bCloseSegment(SdrCircKind::Arc != eCircleKind);
        const bool bCloseUsingCenter(SdrCircKind::Section == eCircleKind);

        if(bCloseSegment)
        {
            if(bCloseUsingCenter)
            {
                // add center point at start (for historical reasons)
                basegfx::B2DPolygon aSector;
                aSector.append(aRange.getCenter());
                aSector.append(aCircPolygon);
                aCircPolygon = std::move(aSector);
            }

            // close
            aCircPolygon.setClosed(true);
        }
    }

    // #i76950#
    if (maGeo.m_nShearAngle || maGeo.m_nRotationAngle)
    {
        // translate top left to (0,0)
        const basegfx::B2DPoint aTopLeft(aRange.getMinimum());
        basegfx::B2DHomMatrix aMatrix(basegfx::utils::createTranslateB2DHomMatrix(
            -aTopLeft.getX(), -aTopLeft.getY()));

        // shear, rotate and back to top left (if needed)
        aMatrix = basegfx::utils::createShearXRotateTranslateB2DHomMatrix(
            -maGeo.mfTanShearAngle,
            maGeo.m_nRotationAngle ? toRadians(36000_deg100 - maGeo.m_nRotationAngle) : 0.0,
            aTopLeft) * aMatrix;

        // apply transformation
        aCircPolygon.transform(aMatrix);
    }

    return aCircPolygon;
}

void SdrCircObj::RecalcXPoly()
{
    basegfx::B2DPolygon aPolyCirc(ImpCalcXPolyCirc(meCircleKind, getRectangle(), m_nStartAngle, m_nEndAngle));
    mpXPoly = XPolygon(aPolyCirc);
}

OUString SdrCircObj::TakeObjNameSingul() const
{
    TranslateId pID=STR_ObjNameSingulCIRC;
    if (getRectangle().GetWidth() == getRectangle().GetHeight() && maGeo.m_nShearAngle == 0_deg100)
    {
        switch (meCircleKind) {
            case SdrCircKind::Full: pID=STR_ObjNameSingulCIRC; break;
            case SdrCircKind::Section: pID=STR_ObjNameSingulSECT; break;
            case SdrCircKind::Arc: pID=STR_ObjNameSingulCARC; break;
            case SdrCircKind::Cut: pID=STR_ObjNameSingulCCUT; break;
            default: break;
        }
    } else {
        switch (meCircleKind) {
            case SdrCircKind::Full: pID=STR_ObjNameSingulCIRCE; break;
            case SdrCircKind::Section: pID=STR_ObjNameSingulSECTE; break;
            case SdrCircKind::Arc: pID=STR_ObjNameSingulCARCE; break;
            case SdrCircKind::Cut: pID=STR_ObjNameSingulCCUTE; break;
            default: break;
        }
    }
    OUString sName(SvxResId(pID));

    OUString aName(GetName());
    if (!aName.isEmpty())
        sName += " '" + aName + "'";
    return sName;
}

OUString SdrCircObj::TakeObjNamePlural() const
{
    TranslateId pID=STR_ObjNamePluralCIRC;
    if (getRectangle().GetWidth() == getRectangle().GetHeight() && maGeo.m_nShearAngle == 0_deg100)
    {
        switch (meCircleKind) {
            case SdrCircKind::Full: pID=STR_ObjNamePluralCIRC; break;
            case SdrCircKind::Section: pID=STR_ObjNamePluralSECT; break;
            case SdrCircKind::Arc: pID=STR_ObjNamePluralCARC; break;
            case SdrCircKind::Cut: pID=STR_ObjNamePluralCCUT; break;
            default: break;
        }
    } else {
        switch (meCircleKind) {
            case SdrCircKind::Full: pID=STR_ObjNamePluralCIRCE; break;
            case SdrCircKind::Section: pID=STR_ObjNamePluralSECTE; break;
            case SdrCircKind::Arc: pID=STR_ObjNamePluralCARCE; break;
            case SdrCircKind::Cut: pID=STR_ObjNamePluralCCUTE; break;
            default: break;
        }
    }
    return SvxResId(pID);
}

rtl::Reference<SdrObject> SdrCircObj::CloneSdrObject(SdrModel& rTargetModel) const
{
    return new SdrCircObj(rTargetModel, *this);
}

basegfx::B2DPolyPolygon SdrCircObj::TakeXorPoly() const
{
    const basegfx::B2DPolygon aCircPolygon(ImpCalcXPolyCirc(meCircleKind, getRectangle(), m_nStartAngle, m_nEndAngle));
    return basegfx::B2DPolyPolygon(aCircPolygon);
}

namespace {

struct ImpCircUser : public SdrDragStatUserData
{
    tools::Rectangle                   aR;
    Point                       aCenter;
    Point                       aP1;
    tools::Long                        nHgt;
    tools::Long                        nWdt;
    Degree100                          nStart;
    Degree100                          nEnd;

public:
    ImpCircUser()
    :   nHgt(0),
        nWdt(0),
        nStart(0),
        nEnd(0)
    {}
    void SetCreateParams(SdrDragStat const & rStat);
};

}

sal_uInt32 SdrCircObj::GetHdlCount() const
{
    if(SdrCircKind::Full != meCircleKind)
    {
        return 10;
    }
    else
    {
        return 8;
    }
}

void SdrCircObj::AddToHdlList(SdrHdlList& rHdlList) const
{
    for (sal_uInt32 nHdlNum=(SdrCircKind::Full==meCircleKind)?2:0; nHdlNum<=9; ++nHdlNum)
    {
        Point aPnt;
        SdrHdlKind eLocalKind(SdrHdlKind::Move);
        sal_uInt32 nPNum(0);
        tools::Rectangle aRectangle = getRectangle();
        switch (nHdlNum)
        {
            case 0:
                aPnt = GetAnglePnt(aRectangle, m_nStartAngle);
                eLocalKind = SdrHdlKind::Circle;
                nPNum = 1;
                break;
            case 1:
                aPnt = GetAnglePnt(aRectangle, m_nEndAngle);
                eLocalKind = SdrHdlKind::Circle;
                nPNum = 2;
                break;
            case 2:
                aPnt = aRectangle.TopLeft();
                eLocalKind = SdrHdlKind::UpperLeft;
                break;
            case 3:
                aPnt = aRectangle.TopCenter();
                eLocalKind = SdrHdlKind::Upper;
                break;
            case 4:
                aPnt = aRectangle.TopRight();
                eLocalKind = SdrHdlKind::UpperRight;
                break;
            case 5:
                aPnt = aRectangle.LeftCenter();
                eLocalKind = SdrHdlKind::Left;
                break;
            case 6:
                aPnt = aRectangle.RightCenter();
                eLocalKind = SdrHdlKind::Right;
                break;
            case 7:
                aPnt = aRectangle.BottomLeft();
                eLocalKind = SdrHdlKind::LowerLeft;
                break;
            case 8:
                aPnt = aRectangle.BottomCenter();
                eLocalKind = SdrHdlKind::Lower;
                break;
            case 9:
                aPnt = aRectangle.BottomRight();
                eLocalKind = SdrHdlKind::LowerRight;
                break;
        }

        if (maGeo.m_nShearAngle)
        {
            ShearPoint(aPnt, aRectangle.TopLeft(), maGeo.mfTanShearAngle);
        }

        if (maGeo.m_nRotationAngle)
        {
            RotatePoint(aPnt, aRectangle.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
        }

        std::unique_ptr<SdrHdl> pH(new SdrHdl(aPnt,eLocalKind));
        pH->SetPointNum(nPNum);
        pH->SetObj(const_cast<SdrCircObj*>(this));
        pH->SetRotationAngle(maGeo.m_nRotationAngle);
        rHdlList.AddHdl(std::move(pH));
    }
}


bool SdrCircObj::hasSpecialDrag() const
{
    return true;
}

bool SdrCircObj::beginSpecialDrag(SdrDragStat& rDrag) const
{
    const bool bAngle(rDrag.GetHdl() && SdrHdlKind::Circle == rDrag.GetHdl()->GetKind());

    if(bAngle)
    {
        if(1 == rDrag.GetHdl()->GetPointNum() || 2 == rDrag.GetHdl()->GetPointNum())
        {
            rDrag.SetNoSnap();
        }

        return true;
    }

    return SdrTextObj::beginSpecialDrag(rDrag);
}

bool SdrCircObj::applySpecialDrag(SdrDragStat& rDrag)
{
    const bool bAngle(rDrag.GetHdl() && SdrHdlKind::Circle == rDrag.GetHdl()->GetKind());

    if(bAngle)
    {
        Point aPt(rDrag.GetNow());

        if (maGeo.m_nRotationAngle)
            RotatePoint(aPt, getRectangle().TopLeft(), -maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);

        if (maGeo.m_nShearAngle)
            ShearPoint(aPt, getRectangle().TopLeft(), -maGeo.mfTanShearAngle);

        aPt -= getRectangle().Center();

        tools::Long nWdt = getRectangle().Right() - getRectangle().Left();
        tools::Long nHgt = getRectangle().Bottom() - getRectangle().Top();

        if(nWdt>=nHgt)
        {
            aPt.setY(BigMulDiv(aPt.Y(),nWdt,nHgt) );
        }
        else
        {
            aPt.setX(BigMulDiv(aPt.X(),nHgt,nWdt) );
        }

        Degree100 nAngle=NormAngle36000(GetAngle(aPt));

        if (rDrag.GetView() && rDrag.GetView()->IsAngleSnapEnabled())
        {
            Degree100 nSA=rDrag.GetView()->GetSnapAngle();

            if (nSA)
            {
                nAngle+=nSA/2_deg100;
                nAngle/=nSA;
                nAngle*=nSA;
                nAngle=NormAngle36000(nAngle);
            }
        }

        if(1 == rDrag.GetHdl()->GetPointNum())
        {
            m_nStartAngle = nAngle;
        }
        else if(2 == rDrag.GetHdl()->GetPointNum())
        {
            m_nEndAngle = nAngle;
        }

        SetBoundAndSnapRectsDirty();
        SetXPolyDirty();
        ImpSetCircInfoToAttr();
        SetChanged();

        return true;
    }
    else
    {
        return SdrTextObj::applySpecialDrag(rDrag);
    }
}

OUString SdrCircObj::getSpecialDragComment(const SdrDragStat& rDrag) const
{
    const bool bCreateComment(rDrag.GetView() && this == rDrag.GetView()->GetCreateObj());

    if(bCreateComment)
    {
        OUStringBuffer aBuf(ImpGetDescriptionStr(STR_ViewCreateObj));
        const sal_uInt32 nPointCount(rDrag.GetPointCount());

        if(SdrCircKind::Full != meCircleKind && nPointCount > 2)
        {
            const ImpCircUser* pU = static_cast<const ImpCircUser*>(rDrag.GetUser());
            Degree100 nAngle;

            aBuf.append(" (");

            if(3 == nPointCount)
            {
                nAngle = pU->nStart;
            }
            else
            {
                nAngle = pU->nEnd;
            }

            aBuf.append(SdrModel::GetAngleString(nAngle));
            aBuf.append(')');
        }

        return aBuf.makeStringAndClear();
    }
    else
    {
        const bool bAngle(rDrag.GetHdl() && SdrHdlKind::Circle == rDrag.GetHdl()->GetKind());

        if(bAngle)
        {
            const Degree100 nAngle(1 == rDrag.GetHdl()->GetPointNum() ? m_nStartAngle : m_nEndAngle);

            return ImpGetDescriptionStr(STR_DragCircAngle) +
                " (" +
                SdrModel::GetAngleString(nAngle) +
                ")";
        }
        else
        {
            return SdrTextObj::getSpecialDragComment(rDrag);
        }
    }
}


void ImpCircUser::SetCreateParams(SdrDragStat const & rStat)
{
    rStat.TakeCreateRect(aR);
    aR.Normalize();
    aCenter=aR.Center();
    nWdt=aR.Right()-aR.Left();
    nHgt=aR.Bottom()-aR.Top();
    nStart=0_deg100;
    nEnd=36000_deg100;
    if (rStat.GetPointCount()>2) {
        Point aP(rStat.GetPoint(2)-aCenter);
        if (nWdt==0) aP.setX(0 );
        if (nHgt==0) aP.setY(0 );
        if (nWdt>=nHgt) {
            if (nHgt!=0) aP.setY(aP.Y()*nWdt/nHgt );
        } else {
            if (nWdt!=0) aP.setX(aP.X()*nHgt/nWdt );
        }
        nStart=NormAngle36000(GetAngle(aP));
        if (rStat.GetView()!=nullptr && rStat.GetView()->IsAngleSnapEnabled()) {
            Degree100 nSA=rStat.GetView()->GetSnapAngle();
            if (nSA) { // angle snapping
                nStart+=nSA/2_deg100;
                nStart/=nSA;
                nStart*=nSA;
                nStart=NormAngle36000(nStart);
            }
        }
        aP1 = GetAnglePnt(aR,nStart);
        nEnd=nStart;
    } else aP1=aCenter;
    if (rStat.GetPointCount()<=3)
        return;

    Point aP(rStat.GetPoint(3)-aCenter);
    if (nWdt>=nHgt) {
        aP.setY(BigMulDiv(aP.Y(),nWdt,nHgt) );
    } else {
        aP.setX(BigMulDiv(aP.X(),nHgt,nWdt) );
    }
    nEnd=NormAngle36000(GetAngle(aP));
    if (rStat.GetView()!=nullptr && rStat.GetView()->IsAngleSnapEnabled()) {
        Degree100 nSA=rStat.GetView()->GetSnapAngle();
        if (nSA) { // angle snapping
            nEnd+=nSA/2_deg100;
            nEnd/=nSA;
            nEnd*=nSA;
            nEnd=NormAngle36000(nEnd);
        }
    }
}

void SdrCircObj::ImpSetCreateParams(SdrDragStat& rStat)
{
    ImpCircUser* pU=static_cast<ImpCircUser*>(rStat.GetUser());
    if (pU==nullptr) {
        pU=new ImpCircUser;
        rStat.SetUser(std::unique_ptr<ImpCircUser>(pU));
    }
    pU->SetCreateParams(rStat);
}

bool SdrCircObj::BegCreate(SdrDragStat& rStat)
{
    rStat.SetOrtho4Possible();
    tools::Rectangle aRect1(rStat.GetStart(), rStat.GetNow());
    aRect1.Normalize();
    rStat.SetActionRect(aRect1);
    setRectangle(aRect1);
    ImpSetCreateParams(rStat);
    return true;
}

bool SdrCircObj::MovCreate(SdrDragStat& rStat)
{
    ImpSetCreateParams(rStat);
    ImpCircUser* pU=static_cast<ImpCircUser*>(rStat.GetUser());
    rStat.SetActionRect(pU->aR);
    setRectangle(pU->aR); // for ObjName
    ImpJustifyRect(maRectangle);
    m_nStartAngle=pU->nStart;
    m_nEndAngle=pU->nEnd;
    SetBoundRectDirty();
    m_bSnapRectDirty=true;
    SetXPolyDirty();

    // #i103058# push current angle settings to ItemSet to
    // allow FullDrag visualisation
    if(rStat.GetPointCount() >= 4)
    {
        ImpSetCircInfoToAttr();
    }

    return true;
}

bool SdrCircObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
{
    ImpSetCreateParams(rStat);
    ImpCircUser* pU=static_cast<ImpCircUser*>(rStat.GetUser());
    bool bRet = false;
    if (eCmd==SdrCreateCmd::ForceEnd && rStat.GetPointCount()<4) meCircleKind=SdrCircKind::Full;
    if (meCircleKind==SdrCircKind::Full) {
        bRet=rStat.GetPointCount()>=2;
        if (bRet) {
            tools::Rectangle aRectangle(pU->aR);
            ImpJustifyRect(aRectangle);
            setRectangle(aRectangle);
        }
    } else {
        rStat.SetNoSnap(rStat.GetPointCount()>=2);
        rStat.SetOrtho4Possible(rStat.GetPointCount()<2);
        bRet=rStat.GetPointCount()>=4;
        if (bRet) {
            tools::Rectangle aRectangle(pU->aR);
            ImpJustifyRect(aRectangle);
            setRectangle(aRectangle);
            m_nStartAngle=pU->nStart;
            m_nEndAngle=pU->nEnd;
        }
    }
    m_bClosedObj=meCircleKind!=SdrCircKind::Arc;
    SetBoundAndSnapRectsDirty();
    SetXPolyDirty();
    ImpSetCircInfoToAttr();
    if (bRet)
        rStat.SetUser(nullptr);
    return bRet;
}

void SdrCircObj::BrkCreate(SdrDragStat& rStat)
{
    rStat.SetUser(nullptr);
}

bool SdrCircObj::BckCreate(SdrDragStat& rStat)
{
    rStat.SetNoSnap(rStat.GetPointCount()>=3);
    rStat.SetOrtho4Possible(rStat.GetPointCount()<3);
    return meCircleKind!=SdrCircKind::Full;
}

basegfx::B2DPolyPolygon SdrCircObj::TakeCreatePoly(const SdrDragStat& rDrag) const
{
    const ImpCircUser* pU = static_cast<const ImpCircUser*>(rDrag.GetUser());

    if(rDrag.GetPointCount() < 4)
    {
        // force to OBJ_CIRC to get full visualisation
        basegfx::B2DPolyPolygon aRetval(ImpCalcXPolyCirc(SdrCircKind::Full, pU->aR, pU->nStart, pU->nEnd));

        if(3 == rDrag.GetPointCount())
        {
            // add edge to first point on ellipse
            basegfx::B2DPolygon aNew;

            aNew.append(basegfx::B2DPoint(pU->aCenter.X(), pU->aCenter.Y()));
            aNew.append(basegfx::B2DPoint(pU->aP1.X(), pU->aP1.Y()));
            aRetval.append(aNew);
        }

        return aRetval;
    }
    else
    {
        return basegfx::B2DPolyPolygon(ImpCalcXPolyCirc(meCircleKind, pU->aR, pU->nStart, pU->nEnd));
    }
}

PointerStyle SdrCircObj::GetCreatePointer() const
{
    switch (meCircleKind) {
        case SdrCircKind::Full: return PointerStyle::DrawEllipse;
        case SdrCircKind::Section: return PointerStyle::DrawPie;
        case SdrCircKind::Arc: return PointerStyle::DrawArc;
        case SdrCircKind::Cut: return PointerStyle::DrawCircleCut;
        default: break;
    } // switch
    return PointerStyle::Cross;
}

void SdrCircObj::NbcMove(const Size& aSize)
{
    moveRectangle(aSize.Width(), aSize.Height());
    moveOutRectangle(aSize.Width(), aSize.Height());
    maSnapRect.Move(aSize);
    SetXPolyDirty();
    SetBoundAndSnapRectsDirty(true);
}

void SdrCircObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact)
{
    Degree100 nAngle0 = maGeo.m_nRotationAngle;
    bool bNoShearRota = (maGeo.m_nRotationAngle == 0_deg100 && maGeo.m_nShearAngle == 0_deg100);
    SdrTextObj::NbcResize(rRef,xFact,yFact);
    bNoShearRota |= (maGeo.m_nRotationAngle == 0_deg100 && maGeo.m_nShearAngle == 0_deg100);
    if (meCircleKind!=SdrCircKind::Full) {
        bool bXMirr=(xFact.GetNumerator()<0) != (xFact.GetDenominator()<0);
        bool bYMirr=(yFact.GetNumerator()<0) != (yFact.GetDenominator()<0);
        if (bXMirr || bYMirr) {
            // At bXMirr!=bYMirr we should actually swap both line ends.
            // That, however, is pretty bad (because of forced "hard" formatting).
            // Alternatively, we could implement a bMirrored flag (maybe even
            // a more general one, e. g. for mirrored text, ...).
            Degree100 nS0=m_nStartAngle;
            Degree100 nE0=m_nEndAngle;
            if (bNoShearRota) {
                // the RectObj already mirrors at VMirror because of a 180deg rotation
                if (! (bXMirr && bYMirr)) {
                    Degree100 nTmp=nS0;
                    nS0=18000_deg100-nE0;
                    nE0=18000_deg100-nTmp;
                }
            } else { // mirror contorted ellipses
                if (bXMirr!=bYMirr) {
                    nS0+=nAngle0;
                    nE0+=nAngle0;
                    if (bXMirr) {
                        Degree100 nTmp=nS0;
                        nS0=18000_deg100-nE0;
                        nE0=18000_deg100-nTmp;
                    }
                    if (bYMirr) {
                        Degree100 nTmp=nS0;
                        nS0=-nE0;
                        nE0=-nTmp;
                    }
                    nS0 -= maGeo.m_nRotationAngle;
                    nE0 -= maGeo.m_nRotationAngle;
                }
            }
            Degree100 nAngleDif=nE0-nS0;
            m_nStartAngle=NormAngle36000(nS0);
            m_nEndAngle  =NormAngle36000(nE0);
            if (nAngleDif==36000_deg100) m_nEndAngle+=nAngleDif; // full circle
        }
    }
    SetXPolyDirty();
    ImpSetCircInfoToAttr();
}

void SdrCircObj::NbcShear(const Point& rRef, Degree100 nAngle, double tn, bool bVShear)
{
    SdrTextObj::NbcShear(rRef,nAngle,tn,bVShear);
    SetXPolyDirty();
    ImpSetCircInfoToAttr();
}

void SdrCircObj::NbcMirror(const Point& rRef1, const Point& rRef2)
{
    bool bFreeMirr=meCircleKind!=SdrCircKind::Full;
    Point aTmpPt1;
    Point aTmpPt2;
    if (bFreeMirr) { // some preparations for using an arbitrary axis of reflection
        Point aCenter(getRectangle().Center());
        tools::Long nWdt = getRectangle().GetWidth() - 1;
        tools::Long nHgt = getRectangle().GetHeight() - 1;
        tools::Long nMaxRad=(std::max(nWdt,nHgt)+1) /2;
        // starting point
        double a = toRadians(m_nStartAngle);
        aTmpPt1 = Point(basegfx::fround<tools::Long>(cos(a) * nMaxRad),
                        basegfx::fround<tools::Long>(-sin(a) * nMaxRad));
        if (nWdt==0) aTmpPt1.setX(0 );
        if (nHgt==0) aTmpPt1.setY(0 );
        aTmpPt1+=aCenter;
        // finishing point
        a = toRadians(m_nEndAngle);
        aTmpPt2 = Point(basegfx::fround<tools::Long>(cos(a) * nMaxRad),
                        basegfx::fround<tools::Long>(-sin(a) * nMaxRad));
        if (nWdt==0) aTmpPt2.setX(0 );
        if (nHgt==0) aTmpPt2.setY(0 );
        aTmpPt2+=aCenter;
        if (maGeo.m_nRotationAngle)
        {
            RotatePoint(aTmpPt1, getRectangle().TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
            RotatePoint(aTmpPt2, getRectangle().TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
        }
        if (maGeo.m_nShearAngle)
        {
            ShearPoint(aTmpPt1, getRectangle().TopLeft(), maGeo.mfTanShearAngle);
            ShearPoint(aTmpPt2, getRectangle().TopLeft(), maGeo.mfTanShearAngle);
        }
    }
    SdrTextObj::NbcMirror(rRef1,rRef2);
    if (meCircleKind!=SdrCircKind::Full) { // adapt starting and finishing angle
        MirrorPoint(aTmpPt1,rRef1,rRef2);
        MirrorPoint(aTmpPt2,rRef1,rRef2);
        // unrotate:
        if (maGeo.m_nRotationAngle)
        {
            RotatePoint(aTmpPt1, getRectangle().TopLeft(), -maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); // -sin for reversion
            RotatePoint(aTmpPt2, getRectangle().TopLeft(), -maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle); // -sin for reversion
        }
        // unshear:
        if (maGeo.m_nShearAngle)
        {
            ShearPoint(aTmpPt1, getRectangle().TopLeft(), -maGeo.mfTanShearAngle); // -tan for reversion
            ShearPoint(aTmpPt2, getRectangle().TopLeft(), -maGeo.mfTanShearAngle); // -tan for reversion
        }
        Point aCenter(getRectangle().Center());
        aTmpPt1-=aCenter;
        aTmpPt2-=aCenter;
        // because it's mirrored, the angles are swapped, too
        m_nStartAngle=GetAngle(aTmpPt2);
        m_nEndAngle  =GetAngle(aTmpPt1);
        Degree100 nAngleDif=m_nEndAngle-m_nStartAngle;
        m_nStartAngle=NormAngle36000(m_nStartAngle);
        m_nEndAngle  =NormAngle36000(m_nEndAngle);
        if (nAngleDif==36000_deg100) m_nEndAngle+=nAngleDif; // full circle
    }
    SetXPolyDirty();
    ImpSetCircInfoToAttr();
}

std::unique_ptr<SdrObjGeoData> SdrCircObj::NewGeoData() const
{
    return std::make_unique<SdrCircObjGeoData>();
}

void SdrCircObj::SaveGeoData(SdrObjGeoData& rGeo) const
{
    SdrRectObj::SaveGeoData(rGeo);
    SdrCircObjGeoData& rCGeo=static_cast<SdrCircObjGeoData&>(rGeo);
    rCGeo.nStartAngle=m_nStartAngle;
    rCGeo.nEndAngle  =m_nEndAngle;
}

void SdrCircObj::RestoreGeoData(const SdrObjGeoData& rGeo)
{
    SdrRectObj::RestoreGeoData(rGeo);
    const SdrCircObjGeoData& rCGeo=static_cast<const SdrCircObjGeoData&>(rGeo);
    m_nStartAngle=rCGeo.nStartAngle;
    m_nEndAngle  =rCGeo.nEndAngle;
    SetXPolyDirty();
    ImpSetCircInfoToAttr();
}

static void Union(tools::Rectangle& rR, const Point& rP)
{
    if (rP.X()<rR.Left  ()) rR.SetLeft(rP.X() );
    if (rP.X()>rR.Right ()) rR.SetRight(rP.X() );
    if (rP.Y()<rR.Top   ()) rR.SetTop(rP.Y() );
    if (rP.Y()>rR.Bottom()) rR.SetBottom(rP.Y() );
}

void SdrCircObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const
{
    rRect = getRectangle();
    if (meCircleKind!=SdrCircKind::Full) {
        const Point aPntStart(GetAnglePnt(getRectangle(), m_nStartAngle));
        const Point aPntEnd(GetAnglePnt(getRectangle(), m_nEndAngle));
        Degree100 a=m_nStartAngle;
        Degree100 e=m_nEndAngle;
        rRect.SetLeft(getRectangle().Right() );
        rRect.SetRight(getRectangle().Left() );
        rRect.SetTop(getRectangle().Bottom() );
        rRect.SetBottom(getRectangle().Top() );
        Union(rRect,aPntStart);
        Union(rRect,aPntEnd);
        if ((a<=18000_deg100 && e>=18000_deg100) || (a>e && (a<=18000_deg100 || e>=18000_deg100))) {
            Union(rRect, getRectangle().LeftCenter());
        }
        if ((a<=27000_deg100 && e>=27000_deg100) || (a>e && (a<=27000_deg100 || e>=27000_deg100))) {
            Union(rRect, getRectangle().BottomCenter());
        }
        if (a>e) {
            Union(rRect, getRectangle().RightCenter());
        }
        if ((a<=9000_deg100 && e>=9000_deg100) || (a>e && (a<=9000_deg100 || e>=9000_deg100))) {
            Union(rRect, getRectangle().TopCenter());
        }
        if (meCircleKind==SdrCircKind::Section) {
            Union(rRect, getRectangle().Center());
        }
        if (maGeo.m_nRotationAngle)
        {
            Point aDst(rRect.TopLeft());
            aDst -= getRectangle().TopLeft();
            Point aDst0(aDst);
            RotatePoint(aDst,Point(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
            aDst-=aDst0;
            rRect.Move(aDst.X(),aDst.Y());
        }
    }
    if (maGeo.m_nShearAngle==0_deg100)
        return;

    tools::Long nDst = basegfx::fround<tools::Long>((rRect.Bottom() - rRect.Top()) * maGeo.mfTanShearAngle);
    if (maGeo.m_nShearAngle > 0_deg100)
    {
        Point aRef(rRect.TopLeft());
        rRect.AdjustLeft( -nDst );
        Point aTmpPt(rRect.TopLeft());
        RotatePoint(aTmpPt, aRef, maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
        aTmpPt-=rRect.TopLeft();
        rRect.Move(aTmpPt.X(),aTmpPt.Y());
    } else {
        rRect.AdjustRight( -nDst );
    }
}

void SdrCircObj::RecalcSnapRect()
{
    if (PaintNeedsXPolyCirc()) {
        maSnapRect=GetXPoly().GetBoundRect();
    } else {
        TakeUnrotatedSnapRect(maSnapRect);
    }
}

void SdrCircObj::NbcSetSnapRect(const tools::Rectangle& rRect)
{
    if (maGeo.m_nRotationAngle || maGeo.m_nShearAngle || meCircleKind != SdrCircKind::Full)
    {
        tools::Rectangle aSR0(GetSnapRect());
        tools::Long nWdt0=aSR0.Right()-aSR0.Left();
        tools::Long nHgt0=aSR0.Bottom()-aSR0.Top();
        tools::Long nWdt1=rRect.Right()-rRect.Left();
        tools::Long nHgt1=rRect.Bottom()-rRect.Top();
        NbcResize(maSnapRect.TopLeft(),Fraction(nWdt1,nWdt0),Fraction(nHgt1,nHgt0));
        NbcMove(Size(rRect.Left()-aSR0.Left(),rRect.Top()-aSR0.Top()));
    } else {
        setRectangle(rRect);
        ImpJustifyRect(maRectangle);
    }
    SetBoundAndSnapRectsDirty();
    SetXPolyDirty();
    ImpSetCircInfoToAttr();
}

sal_uInt32 SdrCircObj::GetSnapPointCount() const
{
    if (meCircleKind==SdrCircKind::Full) {
        return 1;
    } else {
        return 3;
    }
}

Point SdrCircObj::GetSnapPoint(sal_uInt32 i) const
{
    switch (i)
    {
        case 1 : return GetAnglePnt(getRectangle(), m_nStartAngle);
        case 2 : return GetAnglePnt(getRectangle(), m_nEndAngle);
        default: return getRectangle().Center();
    }
}

void SdrCircObj::Notify(SfxBroadcaster& rBC, const SfxHint& rHint)
{
    SetXPolyDirty();
    SdrRectObj::Notify(rBC,rHint);
    ImpSetAttrToCircInfo();
}


void SdrCircObj::ImpSetAttrToCircInfo()
{
    const SfxItemSet& rSet = GetObjectItemSet();
    SdrCircKind eNewKind = rSet.Get(SDRATTR_CIRCKIND).GetValue();

    Degree100 nNewStart = rSet.Get(SDRATTR_CIRCSTARTANGLE).GetValue();
    Degree100 nNewEnd = rSet.Get(SDRATTR_CIRCENDANGLE).GetValue();

    bool bKindChg = meCircleKind != eNewKind;
    bool bAngleChg = nNewStart != m_nStartAngle || nNewEnd != m_nEndAngle;

    if(bKindChg || bAngleChg)
    {
        meCircleKind = eNewKind;
        m_nStartAngle = nNewStart;
        m_nEndAngle = nNewEnd;

        if(bKindChg || (meCircleKind != SdrCircKind::Full && bAngleChg))
        {
            SetXPolyDirty();
            SetBoundAndSnapRectsDirty();
        }
    }
}

void SdrCircObj::ImpSetCircInfoToAttr()
{
    const SfxItemSet& rSet = GetObjectItemSet();

    SdrCircKind eOldKindA = rSet.Get(SDRATTR_CIRCKIND).GetValue();
    Degree100 nOldStartAngle = rSet.Get(SDRATTR_CIRCSTARTANGLE).GetValue();
    Degree100 nOldEndAngle = rSet.Get(SDRATTR_CIRCENDANGLE).GetValue();

    if(meCircleKind == eOldKindA && m_nStartAngle == nOldStartAngle && m_nEndAngle == nOldEndAngle)
        return;

    // since SetItem() implicitly calls ImpSetAttrToCircInfo()
    // setting the item directly is necessary here.
    if(meCircleKind != eOldKindA)
    {
        GetProperties().SetObjectItemDirect(SdrCircKindItem(meCircleKind));
    }

    if(m_nStartAngle != nOldStartAngle)
    {
        GetProperties().SetObjectItemDirect(makeSdrCircStartAngleItem(m_nStartAngle));
    }

    if(m_nEndAngle != nOldEndAngle)
    {
        GetProperties().SetObjectItemDirect(makeSdrCircEndAngleItem(m_nEndAngle));
    }

    SetXPolyDirty();
    ImpSetAttrToCircInfo();
}

rtl::Reference<SdrObject> SdrCircObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const
{
    const bool bFill(meCircleKind != SdrCircKind::Arc);
    const basegfx::B2DPolygon aCircPolygon(ImpCalcXPolyCirc(meCircleKind, getRectangle(), m_nStartAngle, m_nEndAngle));
    rtl::Reference<SdrObject> pRet = ImpConvertMakeObj(basegfx::B2DPolyPolygon(aCircPolygon), bFill, bBezier);

    if(bAddText)
    {
        pRet = ImpConvertAddText(std::move(pRet), bBezier);
    }

    return pRet;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
