#ifndef EMPATHY_SEMA_UTRIE_UNI_INL
#define EMPATHY_SEMA_UTRIE_UNI_INL

namespace empathy::sema
{
    template< typename U > typename UTrie< U >::template UniGen< U >
    UTrie< U >::UnifyRepetition( VecGenerator vgen, const Vector& solutionVec, const RepetitionNode& rptNode, UnificationContext& c )
    {
        if( vgen.finished() )
            co_yield { solutionVec, rptNode.m_next, c };
        else
        {
            const auto& vt = vgen();

            for( auto&& rt : Enumerate( rptNode.m_repetition ) )
            {
                auto localC = c;
                for( auto&& [s,c] : sema::Unify( rt, vt, localC ) )
                    co_yield UnifyRepetition( vgen, Vector::MakeAppend( solutionVec, move( s ) ), rptNode, c );
            }
        }
    }

    template< typename U > template< typename T > Generator< tuple< Vector, const T&, UnificationContext, VecGenerator > >
    UTrie< U >::Unify( VecGenerator vgen, const Vector& solutionVec, const branch_t& branch, UnificationContext& c )
    {
        if( branch.index() == 0 )
        {
            const auto& vt = vgen();

            for( auto&& [t, payload] : Enumerate( get< 0 >( branch )->m_trie ) )
            {
                auto localC = c;
                for( auto&& [s, c] : sema::Unify( t, vt, localC ) )
                    co_yield Unify< T >( vgen, Vector::MakeAppend( solutionVec, move( s ) ), payload, c );
            }
        }
        else
            co_yield { solutionVec, any_cast< T >( get< 1 >( branch ) ), c, vgen };
    }

    template< typename U >
    typename UTrie< U >::template UniGen< U > UTrie< U >::unify( const Vector& rhs, UnificationContext& c ) const
    {
        auto exprLen = rhs.length();

        c.incComplexity( 2 );

        auto it = m_fixedLengthBranches.find( exprLen.minLength() );
        if( it != m_fixedLengthBranches.end() )
        {
            VecGenerator vgen( rhs );
            Vector sol;

            for( auto&& [s, content, c, g] : Unify< U >( vgen, sol, it->second, c ) )
                co_yield { s, content, c };
        }

        using rpt_node = ptr< RepetitionNode >;

        it = m_variableLengthBranches.begin();
        auto end = m_variableLengthBranches.upper_bound( exprLen.minLength() );
        while( it != end )
        {
            VecGenerator vgen( rhs );
            Vector sol;

            for( auto&& [s, pRptNode, c, g] : Unify< rpt_node >( vgen, sol, it->second, c ) )
                co_yield UnifyRepetition( g, sol, *pRptNode, c );

            ++it;
        }
    }

    template< typename U > template< typename T > typename UTrie< U >::template UniGen< T >
    UTrie< U >::HalfUnify( const Vector& solutionVec, const branch_t& branch, UnificationContext& c )
    {
        if( branch.index() == 0 )
        {
            for( auto&& [t, payload] : Enumerate( get< 0 >( branch )->m_trie ) )
            {
                auto localC = c;
                for( auto&& [s, c] : sema::HalfUnify( t, localC ) )
                    co_yield HalfUnify< T >( Vector::MakeAppend( solutionVec, move( s ) ), payload, c );
            }
        }
        else
            co_yield { solutionVec, any_cast< T >( get< 1 >( branch ) ), c };
    }

    template< typename U >
    typename UTrie< U >::template UniGen< U > UTrie< U >::halfUnify( UnificationContext& c ) const
    {
        c.incComplexity();

        for( auto&& [len, branch] : m_fixedLengthBranches )
        {
            Vector sol;

            for( auto&& [s, content, c] : HalfUnify< U >( sol, branch, c ) )
                co_yield { s, content, c };
        }

        using rpt_node = ptr< TrieContainerRepetitionNode< U > >;

        for( auto&& [minLen, branch] : m_variableLengthBranches )
        {
            Vector sol;

            for( auto&& [s, pRptNode, c] : HalfUnify< rpt_node >( sol, branch, c ) )
            {
                for( auto&& [rt, payload] : Enumerate( pRptNode->m_repetition ) )
                {
                    for( auto&& [s, c] : sema::HalfUnify( rt, c ) )
                    {
                        auto localSol = sol;
                        localSol.setRepetitionTerm( move( s ) );
                        co_yield { localSol, payload, c };
                    }
                }
            }
        }
    }
}

#endif
