// Generic Geometry Library
//
// Copyright Barend Gehrels 1995-2009, Geodan Holding B.V. Amsterdam, the Netherlands.
// Copyright Bruno Lalande 2008, 2009
// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef GGL_STRATEGIES_TRANSFORM_MAP_TRANSFORMER_HPP
#define GGL_STRATEGIES_TRANSFORM_MAP_TRANSFORMER_HPP


#include <ggl/strategies/transform/matrix_transformers.hpp>


namespace ggl
{

namespace strategy { namespace transform {

/*!
    \brief Transformation strategy to do map from one to another Cartesian system
    \ingroup transform
    \tparam P1 first point type
    \tparam P2 second point type
    \tparam Mirror if true map is mirrored upside-down (in most cases pixels
        are from top to bottom, while map is from bottom to top)
 */
template 
<
    typename P1, typename P2, 
    bool Mirror, bool SameScale = true,
    std::size_t Dimension1 = dimension<P1>::type::value,
    std::size_t Dimension2 = dimension<P2>::type::value
>
struct map_transformer 
    : ublas_transformer<P1, P2, Dimension1, Dimension2>
{
    typedef typename select_coordinate_type<P1, P2>::type T;
    typedef boost::numeric::ublas::matrix<T> M;

    template <typename B, typename D>
    explicit inline map_transformer(B const& box, D const& width, D const& height)
    {
        set_transformation(
                get<min_corner, 0>(box), get<min_corner, 1>(box),
                get<max_corner, 0>(box), get<max_corner, 1>(box),
                width, height);
    }

    template <typename W, typename D>
    explicit inline map_transformer(W const& wx1, W const& wy1, W const& wx2, W const& wy2,
                        D const& width, D const& height)
    {
        set_transformation(wx1, wy1, wx2, wy2, width, height);
    }


    private :
        void set_transformation_point(double wx, double wy, double px, double py, double scalex, double scaley)
        {

            // Translate to a coordinate system centered on world coordinates (-wx, -wy)
            M t1(3,3);
            t1(0,0) = 1;   t1(0,1) = 0;   t1(0,2) = -wx;
            t1(1,0) = 0;   t1(1,1) = 1;   t1(1,2) = -wy;
            t1(2,0) = 0;   t1(2,1) = 0;   t1(2,2) = 1;

            // Scale the map
            M s(3,3);
            s(0,0) = scalex;   s(0,1) = 0;   s(0,2) = 0;
            s(1,0) = 0;    s(1,1) = scaley;  s(1,2) = 0;
            s(2,0) = 0;    s(2,1) = 0;      s(2,2) = 1;

            // Translate to a coordinate system centered on the specified pixels (+px, +py)
            M t2(3, 3);
            t2(0,0) = 1;   t2(0,1) = 0;   t2(0,2) = px;
            t2(1,0) = 0;   t2(1,1) = 1;   t2(1,2) = py;
            t2(2,0) = 0;   t2(2,1) = 0;   t2(2,2) = 1;

            // Calculate combination matrix in two steps
            this->m_matrix = boost::numeric::ublas::prod(s, t1);
            this->m_matrix = boost::numeric::ublas::prod(t2, this->m_matrix);
        }


        template <typename W, typename D>
        void set_transformation(W const& wx1, W const& wy1, W const& wx2, W const& wy2,
                        D const& width, D const& height)
        {
            D px1 = 0;
            D py1 = 0;
            D px2 = width;
            D py2 = height;


            // Calculate appropriate scale, take min because whole box must fit
            // Scale is in PIXELS/MAPUNITS (meters)
            double sx = (px2 - px1) / (wx2 - wx1);
            double sy = (py2 - py1) / (wy2 - wy1);

            if (SameScale)
            {
                double scale = (std::min)(sx, sy);
                sx = scale;
                sy = scale;
            }

            // Calculate centerpoints
            double wmx = (wx1 + wx2) / 2.0;
            double wmy = (wy1 + wy2) / 2.0;
            double pmx = (px1 + px2) / 2.0;
            double pmy = (py1 + py2) / 2.0;

            set_transformation_point(wmx, wmy, pmx, pmy, sx, sy);

            if (Mirror)
            {
                // Mirror in y-direction
                M m(3,3);
                m(0,0) = 1;   m(0,1) = 0;   m(0,2) = 0;
                m(1,0) = 0;   m(1,1) = -1;  m(1,2) = 0;
                m(2,0) = 0;   m(2,1) = 0;   m(2,2) = 1;

                // Translate in y-direction such that it fits again
                M y(3, 3);
                y(0,0) = 1;   y(0,1) = 0;   y(0,2) = 0;
                y(1,0) = 0;   y(1,1) = 1;   y(1,2) = height;
                y(2,0) = 0;   y(2,1) = 0;   y(2,2) = 1;

                // Calculate combination matrix in two steps
                this->m_matrix = boost::numeric::ublas::prod(m, this->m_matrix);
                this->m_matrix = boost::numeric::ublas::prod(y, this->m_matrix);
            }
        }
};

}} // namespace strategy::transform



} // namespace ggl


#endif // GGL_STRATEGIES_TRANSFORM_MAP_TRANSFORMER_HPP
