/* -*- 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 "Tickmarks.hxx"
#include "Tickmarks_Equidistant.hxx"
#include "Tickmarks_Dates.hxx"
#include <ViewDefines.hxx>
#include "VAxisProperties.hxx"
#include <osl/diagnose.h>
#include <com/sun/star/chart2/AxisType.hpp>
#include <utility>
 
using namespace ::com::sun::star;
using ::basegfx::B2DVector;
 
namespace chart {
 
TickInfo::TickInfo( uno::Reference<chart2::XScaling> xInverse )
: fScaledTickValue( 0.0 )
, xInverseScaling(std::move( xInverse ))
, aTickScreenPosition(0.0,0.0)
, nFactorForLimitedTextWidth(1)
, bPaintIt( true )
{
}
 
double TickInfo::getUnscaledTickValue() const
{
    if( xInverseScaling.is() )
        return xInverseScaling->doScaling( fScaledTickValue );
    else
        return fScaledTickValue;
}
 
sal_Int32 TickInfo::getScreenDistanceBetweenTicks( const TickInfo& rOherTickInfo ) const
{
    //return the positive distance between the two first tickmarks in screen values
 
    B2DVector aDistance = rOherTickInfo.aTickScreenPosition - aTickScreenPosition;
    sal_Int32 nRet = static_cast<sal_Int32>(aDistance.getLength());
    if(nRet<0)
        nRet *= -1;
    return nRet;
}
 
PureTickIter::PureTickIter( TickInfoArrayType& rTickInfoVector )
            : m_rTickVector(rTickInfoVector)
            , m_aTickIter(m_rTickVector.begin())
{
}
PureTickIter::~PureTickIter()
{
}
TickInfo* PureTickIter::firstInfo()
{
    m_aTickIter = m_rTickVector.begin();
    if(m_aTickIter!=m_rTickVector.end())
        return &*m_aTickIter;
    return nullptr;
}
TickInfo* PureTickIter::nextInfo()
{
    if(m_aTickIter!=m_rTickVector.end())
    {
        ++m_aTickIter;
        if(m_aTickIter!=m_rTickVector.end())
            return &*m_aTickIter;
    }
    return nullptr;
}
 
TickFactory::TickFactory(
          ExplicitScaleData aScale, ExplicitIncrementData aIncrement )
            : m_rScale(std::move( aScale ))
            , m_rIncrement(std::move( aIncrement ))
{
    //@todo: make sure that the scale is valid for the scaling
 
    if( m_rScale.Scaling.is() )
    {
        m_xInverseScaling = m_rScale.Scaling->getInverseScaling();
        OSL_ENSURE( m_xInverseScaling.is(), "each Scaling needs to return an inverse Scaling" );
    }
 
    m_fScaledVisibleMin = m_rScale.Minimum;
    if( m_xInverseScaling.is() )
        m_fScaledVisibleMin = m_rScale.Scaling->doScaling(m_fScaledVisibleMin);
 
    m_fScaledVisibleMax = m_rScale.Maximum;
    if( m_xInverseScaling.is() )
        m_fScaledVisibleMax = m_rScale.Scaling->doScaling(m_fScaledVisibleMax);
}
 
TickFactory::~TickFactory()
{
}
 
bool TickFactory::isDateAxis() const
{
    return m_rScale.AxisType == chart2::AxisType::DATE;
}
 
void TickFactory::getAllTicks( TickInfoArraysType& rAllTickInfos ) const
{
    if( isDateAxis() )
        DateTickFactory( m_rScale, m_rIncrement ).getAllTicks( rAllTickInfos );
    else
        EquidistantTickFactory( m_rScale, m_rIncrement ).getAllTicks( rAllTickInfos );
}
 
void TickFactory::getAllTicksShifted( TickInfoArraysType& rAllTickInfos ) const
{
    if( isDateAxis() )
        DateTickFactory( m_rScale, m_rIncrement ).getAllTicksShifted( rAllTickInfos );
    else
        EquidistantTickFactory( m_rScale, m_rIncrement ).getAllTicksShifted( rAllTickInfos );
}
 
// ___TickFactory_2D___
TickFactory2D::TickFactory2D(
          const ExplicitScaleData& rScale, const ExplicitIncrementData& rIncrement
          //, double fStretch_SceneToScreen, double fOffset_SceneToScreen )
          , const B2DVector& rStartScreenPos, const B2DVector& rEndScreenPos
          , const B2DVector& rAxisLineToLabelLineShift )
          : TickFactory( rScale, rIncrement )
          , m_aAxisStartScreenPosition2D(rStartScreenPos)
          , m_aAxisEndScreenPosition2D(rEndScreenPos)
          , m_aAxisLineToLabelLineShift(rAxisLineToLabelLineShift)
          , m_fStretch_LogicToScreen(1.0)
          , m_fOffset_LogicToScreen(0.0)
{
    double fWidthY = m_fScaledVisibleMax - m_fScaledVisibleMin;
    if (m_rScale.Orientation == chart2::AxisOrientation_MATHEMATICAL)
    {
        m_fStretch_LogicToScreen = 1.0/fWidthY;
        m_fOffset_LogicToScreen = -m_fScaledVisibleMin;
    }
    else
    {
        B2DVector aSwap(m_aAxisStartScreenPosition2D);
        m_aAxisStartScreenPosition2D = m_aAxisEndScreenPosition2D;
        m_aAxisEndScreenPosition2D = aSwap;
 
        m_fStretch_LogicToScreen = -1.0/fWidthY;
        m_fOffset_LogicToScreen = -m_fScaledVisibleMax;
    }
}
 
TickFactory2D::~TickFactory2D()
{
}
 
bool TickFactory2D::isHorizontalAxis() const
{
    // check trivial cases:
    if ( m_aAxisStartScreenPosition2D.getY() == m_aAxisEndScreenPosition2D.getY() )
        return true;
    if ( m_aAxisStartScreenPosition2D.getX() == m_aAxisEndScreenPosition2D.getX() )
        return false;
 
    // for skew axes compare angle with horizontal vector
    double fInclination = std::abs(B2DVector(m_aAxisEndScreenPosition2D-m_aAxisStartScreenPosition2D).angle(B2DVector(1.0, 0.0)));
    return fInclination < M_PI_4 || fInclination > (M_PI-M_PI_4);
}
bool TickFactory2D::isVerticalAxis() const
{
    // check trivial cases:
    if ( m_aAxisStartScreenPosition2D.getX() == m_aAxisEndScreenPosition2D.getX() )
        return true;
    if ( m_aAxisStartScreenPosition2D.getY() == m_aAxisEndScreenPosition2D.getY() )
        return false;
 
    // for skew axes compare angle with vertical vector
    double fInclination = std::abs(B2DVector(m_aAxisEndScreenPosition2D-m_aAxisStartScreenPosition2D).angle(B2DVector(0.0, -1.0)));
    return fInclination < M_PI_4 || fInclination > (M_PI-M_PI_4);
}
//static
sal_Int32 TickFactory2D::getTickScreenDistance( TickIter& rIter )
{
    //return the positive distance between the two first tickmarks in screen values
    //if there are less than two tickmarks -1 is returned
 
    const TickInfo* pFirstTickInfo = rIter.firstInfo();
    const TickInfo* pSecondTickInfo = rIter.nextInfo();
    if(!pSecondTickInfo  || !pFirstTickInfo)
        return -1;
 
    return pFirstTickInfo->getScreenDistanceBetweenTicks( *pSecondTickInfo );
}
 
B2DVector TickFactory2D::getTickScreenPosition2D( double fScaledLogicTickValue ) const
{
    B2DVector aRet(m_aAxisStartScreenPosition2D);
    aRet += (m_aAxisEndScreenPosition2D-m_aAxisStartScreenPosition2D)
                *((fScaledLogicTickValue+m_fOffset_LogicToScreen)*m_fStretch_LogicToScreen);
    return aRet;
}
 
void TickFactory2D::addPointSequenceForTickLine( drawing::PointSequenceSequence& rPoints
                                , sal_Int32 nSequenceIndex
                                , double fScaledLogicTickValue, double fInnerDirectionSign
                                , const TickmarkProperties& rTickmarkProperties
                                , bool bPlaceAtLabels ) const
{
    if( fInnerDirectionSign==0.0 )
        fInnerDirectionSign = 1.0;
 
    B2DVector aTickScreenPosition = getTickScreenPosition2D(fScaledLogicTickValue);
    if( bPlaceAtLabels )
        aTickScreenPosition += m_aAxisLineToLabelLineShift;
 
    B2DVector aMainDirection = m_aAxisEndScreenPosition2D-m_aAxisStartScreenPosition2D;
    aMainDirection.normalize();
    B2DVector aOrthoDirection(-aMainDirection.getY(),aMainDirection.getX());
    aOrthoDirection *= fInnerDirectionSign;
    aOrthoDirection.normalize();
 
    B2DVector aStart = aTickScreenPosition + aOrthoDirection*rTickmarkProperties.RelativePos;
    B2DVector aEnd = aStart - aOrthoDirection*rTickmarkProperties.Length;
 
    rPoints.getArray()[nSequenceIndex]
        = { { static_cast<sal_Int32>(aStart.getX()), static_cast<sal_Int32>(aStart.getY()) },
            { static_cast<sal_Int32>(aEnd.getX()), static_cast<sal_Int32>(aEnd.getY()) } };
}
 
B2DVector TickFactory2D::getDistanceAxisTickToText( const AxisProperties& rAxisProperties, bool bIncludeFarAwayDistanceIfSo, bool bIncludeSpaceBetweenTickAndText ) const
{
    bool bFarAwayLabels = false;
    if( rAxisProperties.m_eLabelPos == css::chart::ChartAxisLabelPosition_OUTSIDE_START
        || rAxisProperties.m_eLabelPos == css::chart::ChartAxisLabelPosition_OUTSIDE_END )
        bFarAwayLabels = true;
 
    double fInnerDirectionSign = rAxisProperties.maLabelAlignment.mfInnerTickDirection;
    if( fInnerDirectionSign==0.0 )
        fInnerDirectionSign = 1.0;
 
    B2DVector aMainDirection = m_aAxisEndScreenPosition2D-m_aAxisStartScreenPosition2D;
    aMainDirection.normalize();
    B2DVector aOrthoDirection(-aMainDirection.getY(),aMainDirection.getX());
    aOrthoDirection *= fInnerDirectionSign;
    aOrthoDirection.normalize();
 
    B2DVector aStart(0,0), aEnd(0,0);
    if( bFarAwayLabels )
    {
        TickmarkProperties aProps( AxisProperties::getBiggestTickmarkProperties() );
        aStart = aOrthoDirection*aProps.RelativePos;
        aEnd = aStart - aOrthoDirection*aProps.Length;
    }
    else
    {
        for( sal_Int32 nN=rAxisProperties.m_aTickmarkPropertiesList.size();nN--;)
        {
            const TickmarkProperties& rProps = rAxisProperties.m_aTickmarkPropertiesList[nN];
            B2DVector aNewStart = aOrthoDirection*rProps.RelativePos;
            B2DVector aNewEnd = aNewStart - aOrthoDirection*rProps.Length;
            if(aNewStart.getLength()>aStart.getLength())
                aStart=aNewStart;
            if(aNewEnd.getLength()>aEnd.getLength())
                aEnd=aNewEnd;
        }
    }
 
    B2DVector aLabelDirection(aStart);
    if (rAxisProperties.maLabelAlignment.mfInnerTickDirection != rAxisProperties.maLabelAlignment.mfLabelDirection)
        aLabelDirection = aEnd;
 
    B2DVector aOrthoLabelDirection(aOrthoDirection);
    if (rAxisProperties.maLabelAlignment.mfInnerTickDirection != rAxisProperties.maLabelAlignment.mfLabelDirection)
        aOrthoLabelDirection*=-1.0;
    aOrthoLabelDirection.normalize();
    if( bIncludeSpaceBetweenTickAndText )
        aLabelDirection += aOrthoLabelDirection*AXIS2D_TICKLABELSPACING;
    if( bFarAwayLabels && bIncludeFarAwayDistanceIfSo )
        aLabelDirection += m_aAxisLineToLabelLineShift;
    return aLabelDirection;
}
 
void TickFactory2D::createPointSequenceForAxisMainLine( drawing::PointSequenceSequence& rPoints ) const
{
    rPoints.getArray()[0] = { { static_cast<sal_Int32>(m_aAxisStartScreenPosition2D.getX()),
                                static_cast<sal_Int32>(m_aAxisStartScreenPosition2D.getY()) },
                              { static_cast<sal_Int32>(m_aAxisEndScreenPosition2D.getX()),
                                static_cast<sal_Int32>(m_aAxisEndScreenPosition2D.getY()) } };
}
 
void TickFactory2D::updateScreenValues( TickInfoArraysType& rAllTickInfos ) const
{
    //get the transformed screen values for all tickmarks in rAllTickInfos
    for (auto & tickInfos : rAllTickInfos)
    {
        for (auto & tickInfo : tickInfos)
        {
            tickInfo.aTickScreenPosition =
                getTickScreenPosition2D(tickInfo.fScaledTickValue);
        }
    }
}
 
} //namespace chart
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'normalize' is required to be utilized.

V530 The return value of function 'normalize' is required to be utilized.

V530 The return value of function 'normalize' is required to be utilized.

V530 The return value of function 'normalize' is required to be utilized.

V530 The return value of function 'normalize' is required to be utilized.