/****************************************************************************
** Copyright (C) 2001-2012 Klaralvdalens Datakonsult AB.  All rights reserved.
**
** This file is part of the KD Chart library.
**
** Licensees holding valid commercial KD Chart licenses may use this file in
** accordance with the KD Chart Commercial License Agreement provided with
** the Software.
**
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 and version 3 as published by the
** Free Software Foundation and appearing in the file LICENSE.GPL.txt included.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** Contact info@kdab.com if any conditions of this licensing are not
** clear to you.
**
**********************************************************************/

#ifndef CARTESIANCOORDINATETRANSFORMATION_H
#define CARTESIANCOORDINATETRANSFORMATION_H

#include <QList>
#include <QRectF>
#include <QPointF>

#include "KDChartZoomParameters.h"

#include <cmath>
#include <limits>

namespace KDChart {

    // FIXME: if this struct is used more often, we need to make it a class
    // with proper accessor methods:

    /**
      * \internal
      */
    struct CoordinateTransformation {
        QRectF diagramRect;
        // represents the distance of the diagram coordinate origin to the
        // origin of the coordinate plane space:
        QPointF originTranslation;
        // make a vector base for R2:
        double unitVectorX;
        double unitVectorY;
        // implement isometric scaling:
        double isoScaleX;
        double isoScaleY;

        CartesianCoordinatePlane::AxesCalcMode axesCalcModeY;
        CartesianCoordinatePlane::AxesCalcMode axesCalcModeX;

        ZoomParameters zoom;

        typedef QPair< qreal, qreal > qrealPair;

        inline qreal makeLogarithmic( qrealPair reference, qreal value ) const
        {
            qreal result = value;

            qreal relation;
            if( reference.second == -1.0 )
                relation = 1.0;
            else if( reference.second == 1.0 )
                relation = 1.0;
            else if( reference.second > 0.0 )
                relation = reference.second / log10( reference.second );
            else if( result < 0.0 )
                relation = reference.second / log10( -reference.second );
            else
                relation = 10.0;

            if( value == 0.0 )
                result = 0.0;//std::numeric_limits< qreal >::quiet_NaN();
            else if( value > 0.0 )
                result = log10( result ) * relation;
            else if( value < 0.0 )
                result = -log10( -result ) * relation;

            if( value == 0.0 )
                return result;

            result -= log10( qAbs( reference.first ) ) * relation;
            result *= ( reference.second - reference.first ) / relation / (log10(qAbs(reference.second))-log10(qAbs(reference.first)));
            result += reference.first;
           
            if( reference.first < 0.0 )
            {
                result += reference.first;
                result -= reference.second;
                result = reference.first - result + reference.second;

            }
           
            return result;
        }

        inline QPointF translate( const QPointF& diagramPoint ) const
        {
            // ### de-inline me
            QPointF result = originTranslation;
            QPointF tempPoint = diagramPoint;

            const QRectF& diagRect = diagramRect;
            
            if( axesCalcModeY == CartesianCoordinatePlane::Logarithmic )
            {
                tempPoint.setY( makeLogarithmic( qrealPair( diagRect.bottom(), diagRect.y() ), tempPoint.y() ) );
            }
            if( axesCalcModeX == CartesianCoordinatePlane::Logarithmic )
            {
                tempPoint.setX( makeLogarithmic( qrealPair( diagRect.x(), diagRect.right() ), tempPoint.x() ) );
            }

            tempPoint.rx() += diagRect.width() / (2.0 * zoom.xFactor);
            tempPoint.ry() += diagRect.height() / (2.0 * zoom.yFactor);

            tempPoint.rx() -= diagRect.width() * zoom.xCenter;
            tempPoint.ry() -= diagRect.height() * zoom.yCenter;

            // translate:      xNew = (xOld - diaX) * zoomX + diaX
            tempPoint.setX( ( tempPoint.x() - diagRect.x() ) * zoom.xFactor + diagRect.x() );
            tempPoint.setY( ( tempPoint.y() - diagRect.y() ) * zoom.yFactor + diagRect.y() );

            result.rx() += unitVectorX * tempPoint.x();
            result.ry() += unitVectorY * tempPoint.y();

            return result;
        }

        // convert screen points to value space points
        inline const QPointF translateBack( const QPointF& screenPoint ) const
        {
            qreal x, y;

            x = screenPoint.x();
            y = screenPoint.y();

            x -= originTranslation.x();
            y -= originTranslation.y();

            x /= zoom.xFactor * unitVectorX;
            y /= zoom.yFactor * unitVectorY;

            x -= diagramRect.x() / zoom.xFactor;
            y -= diagramRect.y() / zoom.yFactor;

            x += diagramRect.x();
            y += diagramRect.y();

            x += diagramRect.width() * zoom.xCenter;
            y += diagramRect.height() * zoom.yCenter;

            x -= diagramRect.width() / zoom.xFactor / 2.0;
            y -= diagramRect.height() / zoom.yFactor / 2.0;

            return QPointF(x, y);
        }

    };

    typedef QList<CoordinateTransformation> CoordinateTransformationList;

}

#endif

