/* -*- 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 <drawinglayer/geometry/viewinformation2d.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/utils/canvastools.hxx>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/drawing/XDrawPage.hpp>
#include <com/sun/star/geometry/AffineMatrix2D.hpp>
#include <com/sun/star/geometry/RealRectangle2D.hpp>
#include <rtl/instance.hxx>
#include <com/sun/star/uno/Sequence.hxx>


using namespace com::sun::star;


namespace drawinglayer
{
    namespace geometry
    {
        class ImpViewInformation2D
        {
        private:
            // ViewInformation2D implementation can change refcount, so we have only
            // two memory regions for pairs of ViewInformation2D/ImpViewInformation2D
            friend class ::drawinglayer::geometry::ViewInformation2D;

        protected:
            // the object transformation
            basegfx::B2DHomMatrix                       maObjectTransformation;

            // the view transformation
            basegfx::B2DHomMatrix                       maViewTransformation;

            // the ObjectToView and it's inverse, both on demand from ObjectTransformation
            // and ViewTransformation
            basegfx::B2DHomMatrix                       maObjectToViewTransformation;
            basegfx::B2DHomMatrix                       maInverseObjectToViewTransformation;

            // the visible range and the on-demand one in ViewCoordinates
            basegfx::B2DRange                           maViewport;
            basegfx::B2DRange                           maDiscreteViewport;

            // the DrawPage which is target of visualisation. This is needed e.g. for
            // the view-dependent decomposition of PageNumber TextFields.
            // This parameter is buffered here, but mainly resides in mxExtendedInformation,
            // so it will be interpreted, but held there. It will also not be added
            // to mxExtendedInformation in impFillViewInformationFromContent (it's there already)
            uno::Reference< drawing::XDrawPage >        mxVisualizedPage;

            // the point in time
            double                                      mfViewTime;

            bool                                        mbReducedDisplayQuality : 1;

            // the complete PropertyValue representation (if already created)
            uno::Sequence< beans::PropertyValue >       mxViewInformation;

            // the extra PropertyValues; not represented by ViewTransformation,
            // Viewport, VisualizedPage or ViewTime
            uno::Sequence< beans::PropertyValue >       mxExtendedInformation;

            // the local UNO API strings
            static OUString getNamePropertyObjectTransformation()
            {
                return OUString("ObjectTransformation");
            }

            static OUString getNamePropertyViewTransformation()
            {
                return OUString("ViewTransformation");
            }

            static OUString getNamePropertyViewport()
            {
                return OUString("Viewport");
            }

            static OUString getNamePropertyTime()
            {
                return OUString("Time");
            }

            static OUString getNamePropertyVisualizedPage()
            {
                return OUString("VisualizedPage");
            }

            static OUString getNamePropertyReducedDisplayQuality()
            {
                return OUString("ReducedDisplayQuality");
            }

            void impInterpretPropertyValues(const uno::Sequence< beans::PropertyValue >& rViewParameters)
            {
                if(rViewParameters.hasElements())
                {
                    const sal_Int32 nCount(rViewParameters.getLength());
                    sal_Int32 nExtendedInsert(0);

                    // prepare extended information for filtering. Maximum size is nCount
                    mxExtendedInformation.realloc(nCount);

                    for(sal_Int32 a(0); a < nCount; a++)
                    {
                        const beans::PropertyValue& rProp = rViewParameters[a];

                        if(rProp.Name == getNamePropertyReducedDisplayQuality())
                        {
                            // extra information; add to filtered information
                            mxExtendedInformation[nExtendedInsert++] = rProp;

                            // for performance reasons, also cache content locally
                            bool bSalBool(false);
                            rProp.Value >>= bSalBool;
                            mbReducedDisplayQuality = bSalBool;
                        }
                        else if(rProp.Name == getNamePropertyObjectTransformation())
                        {
                            css::geometry::AffineMatrix2D aAffineMatrix2D;
                            rProp.Value >>= aAffineMatrix2D;
                            basegfx::unotools::homMatrixFromAffineMatrix(maObjectTransformation, aAffineMatrix2D);
                        }
                        else if(rProp.Name == getNamePropertyViewTransformation())
                        {
                            css::geometry::AffineMatrix2D aAffineMatrix2D;
                            rProp.Value >>= aAffineMatrix2D;
                            basegfx::unotools::homMatrixFromAffineMatrix(maViewTransformation, aAffineMatrix2D);
                        }
                        else if(rProp.Name == getNamePropertyViewport())
                        {
                            css::geometry::RealRectangle2D aViewport;
                            rProp.Value >>= aViewport;
                            maViewport = basegfx::unotools::b2DRectangleFromRealRectangle2D(aViewport);
                        }
                        else if(rProp.Name == getNamePropertyTime())
                        {
                            rProp.Value >>= mfViewTime;
                        }
                        else if(rProp.Name == getNamePropertyVisualizedPage())
                        {
                            rProp.Value >>= mxVisualizedPage;
                        }
                        else
                        {
                            // extra information; add to filtered information
                            mxExtendedInformation[nExtendedInsert++] = rProp;
                        }
                    }

                    // extra information size is now known; realloc to final size
                    mxExtendedInformation.realloc(nExtendedInsert);
                }
            }

            void impFillViewInformationFromContent()
            {
                const bool bObjectTransformationUsed(!maObjectTransformation.isIdentity());
                const bool bViewTransformationUsed(!maViewTransformation.isIdentity());
                const bool bViewportUsed(!maViewport.isEmpty());
                const bool bTimeUsed(0.0 < mfViewTime);
                const bool bVisualizedPageUsed(mxVisualizedPage.is());
                const bool bReducedDisplayQualityUsed(mbReducedDisplayQuality);
                const bool bExtraInformation(mxExtendedInformation.hasElements());
                sal_uInt32 nIndex(0);
                const sal_uInt32 nCount(
                    (bObjectTransformationUsed ? 1 : 0) +
                    (bViewTransformationUsed ? 1 : 0) +
                    (bViewportUsed ? 1 : 0) +
                    (bTimeUsed ? 1 : 0) +
                    (bVisualizedPageUsed ? 1 : 0) +
                    (bReducedDisplayQualityUsed ? 1 : 0) +
                    (bExtraInformation ? mxExtendedInformation.getLength() : 0));

                mxViewInformation.realloc(nCount);

                if(bObjectTransformationUsed)
                {
                    css::geometry::AffineMatrix2D aAffineMatrix2D;
                    basegfx::unotools::affineMatrixFromHomMatrix(aAffineMatrix2D, maObjectTransformation);
                    mxViewInformation[nIndex].Name = getNamePropertyObjectTransformation();
                    mxViewInformation[nIndex].Value <<= aAffineMatrix2D;
                    nIndex++;
                }

                if(bViewTransformationUsed)
                {
                    css::geometry::AffineMatrix2D aAffineMatrix2D;
                    basegfx::unotools::affineMatrixFromHomMatrix(aAffineMatrix2D, maViewTransformation);
                    mxViewInformation[nIndex].Name = getNamePropertyViewTransformation();
                    mxViewInformation[nIndex].Value <<= aAffineMatrix2D;
                    nIndex++;
                }

                if(bViewportUsed)
                {
                    const css::geometry::RealRectangle2D aViewport(basegfx::unotools::rectangle2DFromB2DRectangle(maViewport));
                    mxViewInformation[nIndex].Name = getNamePropertyViewport();
                    mxViewInformation[nIndex].Value <<= aViewport;
                    nIndex++;
                }

                if(bTimeUsed)
                {
                    mxViewInformation[nIndex].Name = getNamePropertyTime();
                    mxViewInformation[nIndex].Value <<= mfViewTime;
                    nIndex++;
                }

                if(bVisualizedPageUsed)
                {
                    mxViewInformation[nIndex].Name = getNamePropertyVisualizedPage();
                    mxViewInformation[nIndex].Value <<= mxVisualizedPage;
                    nIndex++;
                }

                if(bExtraInformation)
                {
                    const sal_Int32 nExtra(mxExtendedInformation.getLength());

                    for(sal_Int32 a(0); a < nExtra; a++)
                    {
                        mxViewInformation[nIndex++] = mxExtendedInformation[a];
                    }
                }
            }

        public:
            ImpViewInformation2D(
                const basegfx::B2DHomMatrix& rObjectTransformation,
                const basegfx::B2DHomMatrix& rViewTransformation,
                const basegfx::B2DRange& rViewport,
                const uno::Reference< drawing::XDrawPage >& rxDrawPage,
                double fViewTime,
                const uno::Sequence< beans::PropertyValue >& rExtendedParameters)
            :   maObjectTransformation(rObjectTransformation),
                maViewTransformation(rViewTransformation),
                maObjectToViewTransformation(),
                maInverseObjectToViewTransformation(),
                maViewport(rViewport),
                maDiscreteViewport(),
                mxVisualizedPage(rxDrawPage),
                mfViewTime(fViewTime),
                mbReducedDisplayQuality(false),
                mxViewInformation(),
                mxExtendedInformation()
            {
                impInterpretPropertyValues(rExtendedParameters);
            }

            explicit ImpViewInformation2D(const uno::Sequence< beans::PropertyValue >& rViewParameters)
            :   maObjectTransformation(),
                maViewTransformation(),
                maObjectToViewTransformation(),
                maInverseObjectToViewTransformation(),
                maViewport(),
                maDiscreteViewport(),
                mxVisualizedPage(),
                mfViewTime(),
                mbReducedDisplayQuality(false),
                mxViewInformation(rViewParameters),
                mxExtendedInformation()
            {
                impInterpretPropertyValues(rViewParameters);
            }

            ImpViewInformation2D()
            :   maObjectTransformation(),
                maViewTransformation(),
                maObjectToViewTransformation(),
                maInverseObjectToViewTransformation(),
                maViewport(),
                maDiscreteViewport(),
                mxVisualizedPage(),
                mfViewTime(),
                mbReducedDisplayQuality(false),
                mxViewInformation(),
                mxExtendedInformation()
            {
            }

            const basegfx::B2DHomMatrix& getObjectTransformation() const
            {
                return maObjectTransformation;
            }

            const basegfx::B2DHomMatrix& getViewTransformation() const
            {
                return maViewTransformation;
            }

            const basegfx::B2DRange& getViewport() const
            {
                return maViewport;
            }

            const basegfx::B2DRange& getDiscreteViewport() const
            {
                if(maDiscreteViewport.isEmpty() && !maViewport.isEmpty())
                {
                    basegfx::B2DRange aDiscreteViewport(maViewport);
                    aDiscreteViewport.transform(getViewTransformation());
                    const_cast< ImpViewInformation2D* >(this)->maDiscreteViewport = aDiscreteViewport;
                }

                return maDiscreteViewport;
            }

            const basegfx::B2DHomMatrix& getObjectToViewTransformation() const
            {
                if(maObjectToViewTransformation.isIdentity() &&
                    (!maObjectTransformation.isIdentity() || !maViewTransformation.isIdentity()))
                {
                    basegfx::B2DHomMatrix aObjectToView(maViewTransformation * maObjectTransformation);
                    const_cast< ImpViewInformation2D* >(this)->maObjectToViewTransformation = aObjectToView;
                }

                return maObjectToViewTransformation;
            }

            const basegfx::B2DHomMatrix& getInverseObjectToViewTransformation() const
            {
                if(maInverseObjectToViewTransformation.isIdentity() &&
                    (!maObjectTransformation.isIdentity() || !maViewTransformation.isIdentity()))
                {
                    basegfx::B2DHomMatrix aInverseObjectToView(maViewTransformation * maObjectTransformation);
                    aInverseObjectToView.invert();
                    const_cast< ImpViewInformation2D* >(this)->maInverseObjectToViewTransformation = aInverseObjectToView;
                }

                return maInverseObjectToViewTransformation;
            }

            double getViewTime() const
            {
                return mfViewTime;
            }

            const uno::Reference< drawing::XDrawPage >& getVisualizedPage() const
            {
                return mxVisualizedPage;
            }

            bool getReducedDisplayQuality() const
            {
                return mbReducedDisplayQuality;
            }

            const uno::Sequence< beans::PropertyValue >& getViewInformationSequence() const
            {
                if(!mxViewInformation.hasElements())
                {
                    const_cast< ImpViewInformation2D* >(this)->impFillViewInformationFromContent();
                }

                return mxViewInformation;
            }

            const uno::Sequence< beans::PropertyValue >& getExtendedInformationSequence() const
            {
                return mxExtendedInformation;
            }

            bool operator==(const ImpViewInformation2D& rCandidate) const
            {
                return (maObjectTransformation == rCandidate.maObjectTransformation
                    && maViewTransformation == rCandidate.maViewTransformation
                    && maViewport == rCandidate.maViewport
                    && mxVisualizedPage == rCandidate.mxVisualizedPage
                    && mfViewTime == rCandidate.mfViewTime
                    && mxExtendedInformation == rCandidate.mxExtendedInformation);
            }
        };
    } // end of anonymous namespace
} // end of namespace drawinglayer


namespace drawinglayer
{
    namespace geometry
    {
        namespace
        {
            struct theGlobalDefault :
                public rtl::Static< ViewInformation2D::ImplType, theGlobalDefault > {};
        }

        ViewInformation2D::ViewInformation2D(
            const basegfx::B2DHomMatrix& rObjectTransformation,
            const basegfx::B2DHomMatrix& rViewTransformation,
            const basegfx::B2DRange& rViewport,
            const uno::Reference< drawing::XDrawPage >& rxDrawPage,
            double fViewTime,
            const uno::Sequence< beans::PropertyValue >& rExtendedParameters)
        :   mpViewInformation2D(ImpViewInformation2D(
                rObjectTransformation,
                rViewTransformation,
                rViewport,
                rxDrawPage,
                fViewTime,
                rExtendedParameters))
        {
        }

        ViewInformation2D::ViewInformation2D(const uno::Sequence< beans::PropertyValue >& rViewParameters)
        :   mpViewInformation2D(ImpViewInformation2D(rViewParameters))
        {
        }

        ViewInformation2D::ViewInformation2D()
        :   mpViewInformation2D(theGlobalDefault::get())
        {
        }

        ViewInformation2D::ViewInformation2D(const ViewInformation2D&) = default;

        ViewInformation2D::ViewInformation2D(ViewInformation2D&&) = default;

        ViewInformation2D::~ViewInformation2D() = default;

        ViewInformation2D& ViewInformation2D::operator=(const ViewInformation2D&) = default;

        ViewInformation2D& ViewInformation2D::operator=(ViewInformation2D&&) = default;

        bool ViewInformation2D::operator==(const ViewInformation2D& rCandidate) const
        {
            return rCandidate.mpViewInformation2D == mpViewInformation2D;
        }

        const basegfx::B2DHomMatrix& ViewInformation2D::getObjectTransformation() const
        {
            return mpViewInformation2D->getObjectTransformation();
        }

        const basegfx::B2DHomMatrix& ViewInformation2D::getViewTransformation() const
        {
            return mpViewInformation2D->getViewTransformation();
        }

        const basegfx::B2DRange& ViewInformation2D::getViewport() const
        {
            return mpViewInformation2D->getViewport();
        }

        double ViewInformation2D::getViewTime() const
        {
            return mpViewInformation2D->getViewTime();
        }

        const uno::Reference< drawing::XDrawPage >& ViewInformation2D::getVisualizedPage() const
        {
            return mpViewInformation2D->getVisualizedPage();
        }

        const basegfx::B2DHomMatrix& ViewInformation2D::getObjectToViewTransformation() const
        {
            return mpViewInformation2D->getObjectToViewTransformation();
        }

        const basegfx::B2DHomMatrix& ViewInformation2D::getInverseObjectToViewTransformation() const
        {
            return mpViewInformation2D->getInverseObjectToViewTransformation();
        }

        const basegfx::B2DRange& ViewInformation2D::getDiscreteViewport() const
        {
            return mpViewInformation2D->getDiscreteViewport();
        }

        bool ViewInformation2D::getReducedDisplayQuality() const
        {
            return mpViewInformation2D->getReducedDisplayQuality();
        }

        const uno::Sequence< beans::PropertyValue >& ViewInformation2D::getViewInformationSequence() const
        {
            return mpViewInformation2D->getViewInformationSequence();
        }

        const uno::Sequence< beans::PropertyValue >& ViewInformation2D::getExtendedInformationSequence() const
        {
            return mpViewInformation2D->getExtendedInformationSequence();
        }
    } // end of namespace geometry
} // end of namespace drawinglayer

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