//
//  strop_func.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 <sstream>

#include <boost/optional.hpp>

#include "strop.h"

namespace streamulus
{
    template<class F, typename Sig>
    class FuncBase : public Strop<Sig>
    {
    public:
        typedef F function_type;

        FuncBase(const F& f)
            : mFunction(f)
            , mInputExists(false)
        {
            std::stringstream ss;
            ss << "Func_" << "F";
            Strop<Sig>::SetDisplayName(ss.str());
        }

        inline const F& GetFunction() const
        {
            return mFunction;
        }

    protected:
        F mFunction;
        bool mInputExists;
    };


    template<class F,
    typename R =typename F::template result<F(void)>::type>
    class Func0 : public FuncBase<F, R(void)>
    {
    public:

        typedef FuncBase<F, R(void)> BaseType;

        Func0(const F& f)
        : BaseType(f)
        {
        }

        virtual void Work()
        {
            R value = BaseType::mFunction();
            StropStreamProducer<R>::Output(value);
        }
    };


    template<class F,
    typename A1,
    typename R =typename F::template result<F(A1)>::type>
    class Func1 : public FuncBase<F, R(A1)>
    {
    public:

        typedef FuncBase<F, R(A1)> BaseType;

        Func1(const F& f)
            : BaseType(f)
        {
        }

        virtual void Work()
        {
            Stream<A1>* const input1(Strop<R(A1)>::template Input<A1,0>());

            BaseType::mInputExists |= input1->IsValid();

            if (BaseType::mInputExists)
            {
                while (input1->HasMore())
                {
                    const A1& a1(input1->Current());
                    boost::optional<R> res = BaseType::mFunction(a1);
                    if (res)
                        StropStreamProducer<R>::Output(*res);
                }
            }
        }
    };

    template<class F,
    typename A1,
    typename A2,
    typename R =typename F::template result<F(A1,A2)>::type>
    class Func2 : public FuncBase<F, R(A1,A2)>
    {
    public:

        typedef FuncBase<F, R(A1,A2)> BaseType;

        Func2(const F& f)
            : BaseType(f)
        {
        }

        virtual void Work()
        {
            Stream<A1>* const input1(Strop<R(A1,A2)>::template Input<A1,0>());
            Stream<A2>* const input2(Strop<R(A1,A2)>::template Input<A2,1>());

            BaseType::mInputExists |= (input1->IsValid() && input2->IsValid());

            if (BaseType::mInputExists)
            {
                while (input1->HasMore() ||
                       input2->HasMore() )
                {
                    const A1& a1(input1->Current());
                    const A2& a2(input2->Current());

                    boost::optional<R> res = BaseType::mFunction(a1,a2);
                    if (res)
                        StropStreamProducer<R>::Output(*res);
                }
            }
        }
    };

    template<class F,
    typename A1,
    typename A2,
    typename A3,
    typename R =typename F::template result<F(A1,A2,A3)>::type>
    class Func3 : public FuncBase<F, R(A1,A2,A3)>
    {
    public:

        typedef FuncBase<F, R(A1,A2,A3)> BaseType;

        Func3(const F& f)
            : BaseType(f)
        {
        }

        virtual void Work()
        {
            Stream<A1>* const input1(Strop<R(A1,A2,A3)>::template Input<A1,0>());
            Stream<A2>* const input2(Strop<R(A1,A2,A3)>::template Input<A2,1>());
            Stream<A3>* const input3(Strop<R(A1,A2,A3)>::template Input<A3,2>());

            BaseType::mInputExists |= (input1->IsValid() && input2->IsValid() && input3->IsValid());

            if (BaseType::mInputExists)
            {
                while (input1->HasMore() ||
                       input2->HasMore() ||
                       input3->HasMore() )
                {
                    const A1& a1(input1->Current());
                    const A2& a2(input2->Current());
                    const A3& a3(input3->Current());

                    boost::optional<R> res = BaseType::mFunction(a1,a2,a3);
                    if (res)
                        StropStreamProducer<R>::Output(*res);
                }
            }
        }
    };

    template<class F,
    typename A1,
    typename A2,
    typename A3,
    typename A4,
    typename R =typename F::template result<F(A1,A2,A3,A4)>::type>
    class Func4 : public FuncBase<F, R(A1,A2,A3,A4)>
    {
    public:

        typedef FuncBase<F, R(A1,A2,A3,A4)> BaseType;

        Func4(const F& f)
            : BaseType(f)
        {
        }

        virtual void Work()
        {
            Stream<A1>* const input1(Strop<R(A1,A2,A3,A4)>::template Input<A1,0>());
            Stream<A2>* const input2(Strop<R(A1,A2,A3,A4)>::template Input<A2,1>());
            Stream<A3>* const input3(Strop<R(A1,A2,A3,A4)>::template Input<A3,2>());
            Stream<A4>* const input4(Strop<R(A1,A2,A3,A4)>::template Input<A4,3>());

            BaseType::mInputExists |=
                (input1->IsValid() && input2->IsValid() && input3->IsValid() && input4->IsValid());

            if (BaseType::mInputExists)
            {
                while (input1->HasMore() ||
                       input2->HasMore() ||
                       input3->HasMore() ||
                       input4->HasMore() )
                {
                    const A1& a1(input1->Current());
                    const A2& a2(input2->Current());
                    const A3& a3(input3->Current());
                    const A4& a4(input4->Current());

                    boost::optional<R> res = BaseType::mFunction(a1,a2,a3,a4);
                    if (res)
                        StropStreamProducer<R>::Output(*res);
                }
            }
        }
    };

    template<class F,
    typename A1,
    typename A2,
    typename A3,
    typename A4,
    typename A5,
    typename R =typename F::template result<F(A1,A2,A3,A4,A5)>::type>
    class Func5 : public FuncBase<F, R(A1,A2,A3,A4,A5)>
    {
    public:

        typedef FuncBase<F, R(A1,A2,A3,A4,A5)> BaseType;

        Func5(const F& f)
            : BaseType(f)
        {
        }

        virtual void Work()
        {
            Stream<A1>* const input1(Strop<R(A1,A2,A3,A4,A5)>::template Input<A1,0>());
            Stream<A2>* const input2(Strop<R(A1,A2,A3,A4,A5)>::template Input<A2,1>());
            Stream<A3>* const input3(Strop<R(A1,A2,A3,A4,A5)>::template Input<A3,2>());
            Stream<A4>* const input4(Strop<R(A1,A2,A3,A4,A5)>::template Input<A4,3>());
            Stream<A5>* const input5(Strop<R(A1,A2,A3,A4,A5)>::template Input<A5,4>());

            BaseType::mInputExists |=
                (input1->IsValid() && input2->IsValid() && input3->IsValid() && input4->IsValid() && input5->IsValid());

            if (BaseType::mInputExists)
            {
                while (input1->HasMore() ||
                       input2->HasMore() ||
                       input3->HasMore() ||
                       input4->HasMore() ||
                       input5->HasMore() )
                {
                    const A1& a1(input1->Current());
                    const A2& a2(input2->Current());
                    const A3& a3(input3->Current());
                    const A4& a4(input4->Current());
                    const A5& a5(input5->Current());

                    boost::optional<R> res = BaseType::mFunction(a1,a2,a3,a4,a5);
                    if (res)
                        StropStreamProducer<R>::Output(*res);
                }
            }
        }
    };


} // ns streamulus
