//
//  operators.h
//
// Streamulus Copyright (c) 2012 Irit Katriel. All rights reserved.
//
// This file is part of Streamulus.
//
// Streamulus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Streamulus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Streamulus.  If not, see <http://www.gnu.org/licenses/>.
//

#pragma once

#include <boost/type_traits.hpp>
#include <boost/proto/proto.hpp>

namespace streamulus
{
    template<typename TAG>
    struct functor_of;

    template<>
    struct functor_of<boost::proto::tag::unary_plus>
    {
        template<class Sig> struct result;

        template<class This,typename A>
        struct result<This(A)>
        {
            typedef A type;
        };

        template<typename A>
        typename result<functor_of<boost::proto::tag::unary_plus>(A)>::type
        operator()(const A& value) const
        {
            return +value;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::negate>
    {
        template<class Sig> struct result;

        template<class This,typename A>
        struct result<This(A)>
        {
            typedef A type;
        };

        template<typename A>
        typename result<functor_of<boost::proto::tag::negate>(A)>::type
        operator()(const A& value) const
        {
            return -value;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::dereference>
    {
        template<class Sig> struct result;

        template<class This,typename A>
        struct result<This(A)>
        {
            typedef typename boost::remove_pointer<A>::type type;
        };

        template<typename A>
        typename result<functor_of<boost::proto::tag::dereference> (A)>::type
        operator()(const A& value) const
        {
            return *value;
        }
    };


    template<>
    struct functor_of<boost::proto::tag::complement>
    {
        template<class Sig> struct result;

        template<class This,typename A>
        struct result<This(A)>
        {
            typedef A type;
        };

        template<typename A>
        typename result<functor_of<boost::proto::tag::complement>(A)>::type
        operator()(const A& value) const
        {
            return ~value;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::address_of>
    {
        template<class Sig> struct result;

        template<class This,typename A>
        struct result<This(A)>
        {
            typedef typename boost::add_pointer<A>::type type;
        };

        template<typename A>
        typename result<functor_of<boost::proto::tag::address_of>(A)>::type
        operator()(A& value) const
        {
            return &value;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::logical_not>
    {
        template<class Sig> struct result;

        template<class This,typename A>
        struct result<This(A)>
        {
            typedef bool type;
        };

        template<typename A>
        typename result<functor_of<boost::proto::tag::logical_not>(A)>::type
        operator()(const A& value) const
        {
            return !value;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::pre_inc>
    {
        template<class Sig> struct result;

        template<class This,typename A>
        struct result<This(A)>
        {
            typedef A type;
        };

        template<typename A>
        typename result<functor_of<boost::proto::tag::pre_inc>(A)>::type
        operator()(A& value) const
        {
            typename boost::remove_const<A>::type res(value);
            return ++res;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::pre_dec>
    {
        template<class Sig> struct result;

        template<class This,typename A>
        struct result<This(A)>
        {
            typedef A type;
        };

        template<typename A>
        typename result<functor_of<boost::proto::tag::pre_dec>(A)>::type
        operator()(A& value) const
        {
            typename boost::remove_const<A>::type res(value);
            return --res;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::post_inc>
    {
        template<class Sig> struct result;

        template<class This,typename A>
        struct result<This(A)>
        {
            typedef A type;
        };

        template<typename A>
        typename result<functor_of<boost::proto::tag::post_inc>(A)>::type
        operator()(A& value) const
        {
            typename boost::remove_const<A>::type res(value);
            return res++;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::post_dec>
    {
        template<class Sig> struct result;

        template<class This,typename A>
        struct result<This(A)>
        {
            typedef A type;
        };

        template<typename A>
        typename result<functor_of<boost::proto::tag::post_dec>(A)>::type
        operator()(A& value) const
        {
            typename boost::remove_const<A>::type res(value);
            return res--;
        }
    };


    template<>
    struct functor_of<boost::proto::tag::shift_left>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef A type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::shift_left>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs<<rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::shift_right>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef A type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::shift_right>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs>>rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::multiplies>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef typename boost::common_type<A,B>::type type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::multiplies>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs*rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::divides>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef typename boost::common_type<A,B>::type type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::divides>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs/rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::modulus>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef typename boost::common_type<A,B>::type type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::modulus>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs%rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::plus>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef typename boost::common_type<A,B>::type type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::plus>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs+rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::minus>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef typename boost::common_type<A,B>::type type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::minus>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs-rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::less>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef bool type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::less>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs<rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::greater>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef bool type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::greater>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs>rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::less_equal>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef bool type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::less_equal>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs<=rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::greater_equal>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef bool type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::greater_equal>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs>=rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::equal_to>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef bool type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::equal_to>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs==rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::not_equal_to>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef bool type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::not_equal_to>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs!=rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::logical_or>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef bool type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::logical_or>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs||rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::logical_and>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef bool type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::logical_and>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs&&rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::bitwise_or>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef typename boost::common_type<A,B>::type type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::bitwise_or>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs|rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::bitwise_and>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef typename boost::common_type<A,B>::type type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::bitwise_and>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs&rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::bitwise_xor>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef typename boost::common_type<A,B>::type type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::bitwise_xor>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs^rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::comma>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef B type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::comma>(A,B)>::type
        operator()(const A& lhs, const B& rhs) const
        {
            return lhs,rhs;
        }
    };


    template<>
    struct functor_of<boost::proto::tag::assign>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef A type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::assign>(A,B)>::type
        operator()(A& lhs, const B& rhs) const
        {
            typename boost::remove_const<A>::type dummy(lhs);
            return dummy=rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::shift_left_assign>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef A type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::shift_left_assign>(A,B)>::type
        operator()(A& lhs, const B& rhs) const
        {
            typename boost::remove_const<A>::type dummy(lhs);
            return dummy <<= rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::shift_right_assign>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef A type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::shift_right_assign>(A,B)>::type
        operator()(A& lhs, const B& rhs) const
        {
            typename boost::remove_const<A>::type dummy(lhs);
            return dummy >>= rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::multiplies_assign>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef A type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::multiplies_assign>(A,B)>::type
        operator()(A& lhs, const B& rhs) const
        {
            typename boost::remove_const<A>::type dummy(lhs);
            return dummy*=rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::divides_assign>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef A type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::divides_assign>(A,B)>::type
        operator()(A& lhs, const B& rhs) const
        {
            typename boost::remove_const<A>::type dummy(lhs);
            return dummy/=rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::modulus_assign>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef A type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::modulus_assign>(A,B)>::type
        operator()(A& lhs, const B& rhs) const
        {
            typename boost::remove_const<A>::type dummy(lhs);
            return dummy%=rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::plus_assign>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef A type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::plus_assign>(A,B)>::type
        operator()(A& lhs, const B& rhs) const
        {
            typename boost::remove_const<A>::type dummy(lhs);
            return dummy+=rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::minus_assign>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef A type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::minus_assign>(A,B)>::type
        operator()(A& lhs, const B& rhs) const
        {
            typename boost::remove_const<A>::type dummy(lhs);
            return dummy-=rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::bitwise_and_assign>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef A type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::bitwise_and_assign>(A,B)>::type
        operator()(A& lhs, const B& rhs) const
        {
            typename boost::remove_const<A>::type dummy(lhs);
            return dummy&=rhs;
        }
    };

    template<>
    struct functor_of<boost::proto::tag::bitwise_or_assign>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef A type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::bitwise_or_assign>(A,B)>::type
        operator()(A& lhs, const B& rhs) const
        {
            typename boost::remove_const<A>::type dummy(lhs);
            return dummy|=rhs;
        }
    };


    template<>
    struct functor_of<boost::proto::tag::bitwise_xor_assign>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            typedef A type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::bitwise_xor_assign>(A,B)>::type
        operator()(A& lhs, const B& rhs) const
        {
            typename boost::remove_const<A>::type dummy(lhs);
            return dummy^=rhs;
        }
    };


    template<>
    struct functor_of<boost::proto::tag::subscript>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B>
        struct result<This(A,B)>
        {
            // TODO: this will work for arrays but not in general. fix or remove.
            typedef typename boost::remove_pointer<A>::type type;
        };

        template<typename A, typename B>
        typename result<functor_of<boost::proto::tag::subscript>(A,B)>::type
        operator()(const A& array, const B& index) const
        {
            return array[index];
        }
    };


    template<>
    struct functor_of<boost::proto::tag::if_else_>
    {
        template<class Sig> struct result;

        template<class This,typename A, typename B, typename C>
        struct result<This(A,B,C)>
        {
            typedef typename boost::common_type<A,B>::type type;
        };

        template<typename A, typename B, typename C>
        typename result<functor_of<boost::proto::tag::if_else_>(A,B,C)>::type
        operator()(const A& condition, const B& yes, const C& no)
        {
            return condition ? yes : no;
        }
    };

} // ns streamulus
