// ==========================================================================
//                 SeqAn - The Library for Sequence Analysis
// ==========================================================================
// Copyright (c) 2006-2018, Knut Reinert, FU Berlin
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of Knut Reinert or the FU Berlin nor the names of
//       its contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL KNUT REINERT OR THE FU BERLIN BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
//
// ==========================================================================
// Author: Rene Rahn <rene.rahn@fu-berlin.de>
// ==========================================================================

#ifndef INCLUDE_SEQAN_JOURNALED_STRING_TREE_OBSERVABLE_H_
#define INCLUDE_SEQAN_JOURNALED_STRING_TREE_OBSERVABLE_H_

namespace seqan
{

// ============================================================================
// Forwards
// ============================================================================

// ============================================================================
// Tags, Classes, Enums
// ============================================================================

// Type alias used to hide std::tuple in SeqAn namespace.
template <typename... TObservers>
using ObserverList = std::tuple<TObservers...>;

// ============================================================================
// Metafunctions
// ============================================================================

// ----------------------------------------------------------------------------
// Metafunction LENGTH
// ----------------------------------------------------------------------------

template <typename... TObservers>
struct LENGTH<ObserverList<TObservers...> >
{
    static constexpr unsigned VALUE = sizeof...(TObservers);
};

// ----------------------------------------------------------------------------
// Metafunction Element
// ----------------------------------------------------------------------------

template <unsigned INDEX, typename TObject>
struct Element;

template <unsigned INDEX, typename TFirst, typename... TObservers>
struct Element<INDEX, ObserverList<TFirst, TObservers...> >
{
    static_assert(INDEX < LENGTH<ObserverList<TFirst, TObservers...> >::VALUE, "Trying to access element behind the end.");
    typedef typename Element<INDEX - 1, ObserverList<TObservers...> >::Type Type;
};

template <typename TFirst, typename... TObservers>
struct Element<0, ObserverList<TFirst, TObservers...> >
{
    typedef TFirst Type;
};

// ----------------------------------------------------------------------------
// Metafunction NotifyObserver
// ----------------------------------------------------------------------------

template <unsigned INDEX>
struct NotifyObserver
{
    template <typename... TObserver, typename TTag>
    inline void operator()(ObserverList<TObserver...> & subject, TTag const & /*tag*/)
    {
//        std::get<INDEX>(subject.observers)(TTag());
        notify(std::get<INDEX>(subject), TTag());
        NotifyObserver<INDEX - 1>{}(subject, TTag());
    }
};

template <>
struct NotifyObserver<0>
{

    template <typename... TObserver, typename TTag>
    inline void operator()(ObserverList<TObserver...> & subject, TTag const & /*tag*/)
    {
        notify(std::get<0>(subject), TTag());
    }
};

// ============================================================================
// Functions
// ============================================================================

// ----------------------------------------------------------------------------
// Function length()
// ----------------------------------------------------------------------------

template <typename... TObserverTypes>
inline constexpr unsigned
length(ObserverList<TObserverTypes...> const & /*subject*/)
{
    return LENGTH<ObserverList<TObserverTypes...> >::VALUE;
}

// ----------------------------------------------------------------------------
// Function setObserver()
// ----------------------------------------------------------------------------

template <unsigned INDEX, typename TObserver>
inline void
setObserver(ObserverList<> & /*subject*/,
            TObserver & /*observer*/)
{
    // no-op;
}

template <unsigned INDEX, typename... TObserverTypes, typename TObserver>
inline void
setObserver(ObserverList<TObserverTypes...> & subject,
            TObserver && observer)
{
    static_assert(INDEX < LENGTH<ObserverList<TObserverTypes...> >::VALUE, "Trying to access element behind the end.");
    std::get<INDEX>(subject) = std::forward<TObserver>(observer);
}

// ----------------------------------------------------------------------------
// Function notify()
// ----------------------------------------------------------------------------

template <typename TEventTag>
inline void
notify(ObserverList<> & /*subject*/,
       TEventTag const & /*event tag*/)
{
    // no-op;
}

template <typename... TObserverTypes, typename TEventTag>
inline void
notify(ObserverList<TObserverTypes...> & subject,
       TEventTag const & /*event tag*/)
{
    NotifyObserver<LENGTH<ObserverList<TObserverTypes...> >::VALUE - 1>()(subject, TEventTag());
}

// ----------------------------------------------------------------------------
// Function makeObserverList
// ----------------------------------------------------------------------------

template <typename... TObservers>
inline ObserverList<TObservers...>
makeObserverList(TObservers&&... observers)
{
    return std::forward_as_tuple(observers...);
}

}

#endif  // #ifndef INCLUDE_SEQAN_JOURNALED_STRING_TREE_OBSERVABLE_H_