CViewMap.java

/*
 * @cond LICENSE
 * ######################################################################################
 * # LGPL License                                                                       #
 * #                                                                                    #
 * # This file is part of the LightJason AgentSpeak(L++)                                #
 * # Copyright (c) 2015-19, LightJason (info@lightjason.org)                            #
 * # This program is free software: you can redistribute it and/or modify               #
 * # it under the terms of the GNU Lesser General Public License as                     #
 * # published by the Free Software Foundation, either version 3 of the                 #
 * # License, or (at your option) any later version.                                    #
 * #                                                                                    #
 * # This program 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 Lesser General Public License for more details.                                #
 * #                                                                                    #
 * # You should have received a copy of the GNU Lesser General Public License           #
 * # along with this program. If not, see http://www.gnu.org/licenses/                  #
 * ######################################################################################
 * @endcond
 */

package org.lightjason.agentspeak.beliefbase.view;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.lightjason.agentspeak.agent.IAgent;
import org.lightjason.agentspeak.beliefbase.IBeliefbase;
import org.lightjason.agentspeak.common.CPath;
import org.lightjason.agentspeak.common.IPath;
import org.lightjason.agentspeak.language.CLiteral;
import org.lightjason.agentspeak.language.CRawTerm;
import org.lightjason.agentspeak.language.ILiteral;
import org.lightjason.agentspeak.language.ITerm;
import org.lightjason.agentspeak.language.instantiable.plan.trigger.ITrigger;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;


/**
 * view which can use a map of maps to represent
 * the hierarchical beliefbase structure
 *
 * @note given map should be thread-safe
 */
public final class CViewMap implements IView
{
    /**
     * view name
     */
    private final String m_name;
    /**
     * parent name
     */
    private final IView m_parent;
    /**
     * beliefbase
     */
    private final IBeliefbase m_beliefbase = new CWrapperBeliefbase();
    /**
     * root map
     */
    private final Map<String, Object> m_data;
    /**
     * path to key converting
     */
    private final Function<String, String> m_literaltokey;
    /**
     * key to literal converting
     */
    private final Function<String, String> m_keytoliteral;
    /**
     * clear consumer
     */
    private final Consumer<Map<String, Object>> m_clearconsumer;
    /**
     * add-view consumer
     */
    private final BiConsumer<String, Map<String, Object>> m_addviewconsumer;
    /**
     * add-literal consumer
     */
    private final BiConsumer<Pair<String, Stream<ITerm>>, Map<String, Object>> m_addliteralconsumer;
    /**
     * remove-view consumer
     */
    private final BiConsumer<String, Map<String, Object>> m_removeviewconsumer;
    /**
     * remove-literal consumer
     */
    private final BiConsumer<String, Map<String, Object>> m_removeliteralconsumer;

    /**
     * ctor
     *
     * @param p_name name of the view
     * @param p_map map reference
     */
    public CViewMap( @Nonnull final String p_name, @Nonnull final Map<String, Object> p_map )
    {
        this( p_name, p_map, null );
    }

    /**
     * ctor
     *
     * @param p_name view name
     * @param p_map map reference
     * @param p_parent parent view
     */
    public CViewMap( @Nonnull final String p_name, @Nonnull final Map<String, Object> p_map, @Nullable final IView p_parent )
    {
        this(
            p_name, p_map, p_parent,
            // add view
            ( i, j ) -> j.putIfAbsent( i, new ConcurrentHashMap<>() ),
            // add literal
            ( i, j ) -> i.getValue().limit( 1 ).filter( ITerm::raw ).forEach( n -> j.put( i.getKey(), n.raw() ) ),
            // remove view
            ( i, j ) ->
            {
                final Object l_data = j.get( i );
                if ( l_data instanceof Map<?, ?> )
                    j.remove( i, l_data );
            },
            // remove literal
            ( i, j ) ->
            {
                final Object l_data = j.get( i );
                if ( !( l_data instanceof Map<?, ?> ) )
                    j.remove( i, l_data );
            },
            Map::clear,
            i -> i.toLowerCase( Locale.ROOT ),
            i -> i.toLowerCase( Locale.ROOT )
        );
    }

    /**
     * ctor
     *
     * @param p_name view name
     * @param p_map map reference
     * @param p_parent parent view
     * @param p_addviewconsumer add-view consumer
     * @param p_addliteralconsumer add-literal consumer
     * @param p_removeviewconsumer remove-view consumer
     * @param p_removeliteralconsumer remove-view consumer
     * @param p_clearconsumer clear consumer
     * @param p_literaltokey converts a path ( functor item to a map key
     * @param p_keytoliteral converts a map key to literal path
     */
    public CViewMap( @Nonnull final String p_name, @Nonnull final Map<String, Object> p_map, @Nullable final IView p_parent,
                     @Nonnull final BiConsumer<String, Map<String, Object>> p_addviewconsumer,
                     @Nonnull final BiConsumer<Pair<String, Stream<ITerm>>, Map<String, Object>> p_addliteralconsumer,
                     @Nonnull final BiConsumer<String, Map<String, Object>> p_removeviewconsumer,
                     @Nonnull final BiConsumer<String, Map<String, Object>> p_removeliteralconsumer,
                     @Nonnull final Consumer<Map<String, Object>> p_clearconsumer,
                     @Nonnull final Function<String, String> p_literaltokey, @Nonnull final Function<String, String> p_keytoliteral
    )
    {
        m_name = p_name;
        m_parent = p_parent;
        m_data = p_map;
        m_literaltokey = p_literaltokey;
        m_keytoliteral = p_keytoliteral;
        m_clearconsumer = p_clearconsumer;
        m_addviewconsumer = p_addviewconsumer;
        m_addliteralconsumer = p_addliteralconsumer;
        m_removeviewconsumer = p_removeviewconsumer;
        m_removeliteralconsumer = p_removeliteralconsumer;
    }

    @Nonnull
    @Override
    public final Stream<IView> walk( @Nonnull final IPath p_path, @Nullable final IViewGenerator... p_generator )
    {
        return this.walkdown( p_path, p_generator );
    }

    @Nonnull
    @Override
    public IView generate( @Nonnull final IViewGenerator p_generator, @Nonnull final IPath... p_paths )
    {
        return this;
    }

    @Nonnull
    @Override
    public final Stream<IView> root()
    {
        return this.hasParent()
               ? Stream.concat( Stream.of( this ), Stream.of( this.parent() ).flatMap( IView::root ) )
               : Stream.empty();
    }

    @Nonnull
    @Override
    public final IBeliefbase beliefbase()
    {
        return m_beliefbase;
    }

    @Nonnull
    @Override
    public final IPath path()
    {
        return this.root().map( IView::name ).collect( CPath.collect() ).reverse();
    }

    @Nonnull
    @Override
    public final String name()
    {
        return m_name;
    }

    @Nullable
    @Override
    public final IView parent()
    {
        return m_parent;
    }

    @Override
    public final boolean hasParent()
    {
        return m_parent != null;
    }

    @Nonnull
    @Override
    public final Stream<ITrigger> trigger()
    {
        return m_beliefbase.trigger( this );
    }

    @Nonnull
    @Override
    @SuppressWarnings( "unchecked" )
    public final Stream<ILiteral> stream( @Nullable final IPath... p_path )
    {
        // build path relative to this view
        final IPath l_path = this.path();
        return ( ( Objects.isNull( p_path ) ) || ( p_path.length == 0 )
                 ? Stream.concat( m_beliefbase.streamLiteral(), m_beliefbase.streamView().flatMap( i -> i.stream() ) )
                 : Arrays.stream( p_path )
                         .flatMap( i -> this.leafview( this.walk( i.subpath( 0, -1 ) ) ).beliefbase().literal( i.suffix() ).stream() )
        ).map( i -> i.shallowcopy( l_path ) );
    }

    @Nonnull
    @Override
    public final Stream<ILiteral> stream( final boolean p_negated, @Nullable final IPath... p_path )
    {
        return p_negated ? Stream.empty() : this.stream( p_path );
    }

    @Nonnull
    @Override
    public final IView clear( @Nullable final IPath... p_path )
    {
        if ( ( Objects.isNull( p_path ) ) || ( p_path.length == 0 ) )
            m_clearconsumer.accept( m_data );
        else
            Arrays.stream( p_path ).flatMap( i -> this.walkdown( i ) ).forEach( i -> i.clear() );

        return this;
    }

    @Nonnull
    @Override
    public final IView add( @Nonnull final Stream<ILiteral> p_literal )
    {
        p_literal.forEach( m_beliefbase::add );
        return this;
    }

    @Nonnull
    @Override
    public final IView add( @Nonnull final ILiteral... p_literal )
    {
        return this.add( Arrays.stream( p_literal ) );
    }

    @Nonnull
    @Override
    @SuppressWarnings( "varargs" )
    public final IView add( @Nonnull final IView... p_view )
    {
        Arrays.stream( p_view ).forEach( m_beliefbase::remove );
        return this;
    }

    @Nonnull
    @Override
    public IView remove( @Nonnull final Stream<ILiteral> p_literal )
    {
        p_literal.forEach( m_beliefbase::remove );
        return this;
    }

    @Nonnull
    @Override
    @SuppressWarnings( "varargs" )
    public IView remove( @Nonnull final ILiteral... p_literal )
    {
        return this.remove( Arrays.stream( p_literal ) );
    }

    @Nonnull
    @Override
    @SuppressWarnings( "varargs" )
    public final IView remove( @Nonnull final IView... p_view )
    {
        Arrays.stream( p_view ).forEach( m_beliefbase::remove );
        return this;
    }

    @Override
    @SuppressWarnings( "unchecked" )
    public boolean containsLiteral( @Nonnull final IPath p_path )
    {
        return !p_path.empty()
               && ( p_path.size() == 1
                   ? m_beliefbase.containsLiteral( p_path.get( 0 ) )
                   : this.leafview( this.walk( p_path.subpath( 0, p_path.size() - 1 ) ) )
                         .containsLiteral( p_path.subpath( p_path.size() - 1, p_path.size() ) )
               );
    }

    @Override
    @SuppressWarnings( "unchecked" )
    public final boolean containsView( @Nonnull final IPath p_path )
    {
        return !p_path.empty()
               && ( p_path.size() == 1
                    ? m_beliefbase.containsView( p_path.get( 0 ) )
                    : this.leafview( this.walk( p_path.subpath( 0, p_path.size() - 1 ) ) )
                          .containsView( p_path.subpath( p_path.size() - 1, p_path.size() ) )
               );
    }

    @Override
    public final boolean empty()
    {
        return m_beliefbase.empty();
    }

    @Override
    public final int size()
    {
        return m_beliefbase.size();
    }

    @Nonnull
    @Override
    public final IAgent<?> update( @Nonnull final IAgent<?> p_agent )
    {
        return m_beliefbase.update( p_agent );
    }

    /**
     * returns the leaf of a view path
     *
     * @param p_stream stream of views
     * @return last / leaf view
     */
    @Nonnull
    private IView leafview( @Nonnull final Stream<IView> p_stream )
    {
        return p_stream
            .reduce( ( i, j ) -> j )
            .orElse( this );
    }

    @SuppressWarnings( "unchecked" )
    private Stream<IView> walkdown( final IPath p_path, @Nullable final IViewGenerator... p_generator )
    {
        if ( p_path.empty() )
            return Stream.of( this );

        final String l_key = m_literaltokey.apply( p_path.get( 0 ) );
        final Object l_data = m_data.get( l_key );
        return l_data instanceof Map<?, ?>
               ? Stream.concat(
                    Stream.of( this ),
                    new CViewMap(
                        l_key, (Map<String, Object>) l_data, this,
                        m_addviewconsumer, m_addliteralconsumer, m_removeviewconsumer, m_removeliteralconsumer, m_clearconsumer, m_literaltokey, m_keytoliteral
                    ).walk( p_path.subpath( 1 ), p_generator ) )
               : Stream.of( this );
    }


    /**
     * wrapper beliefbase
     */
    private final class CWrapperBeliefbase implements IBeliefbase
    {

        @Override
        public final boolean empty()
        {
            return m_data.isEmpty();
        }

        @Override
        @SuppressWarnings( "unchecked" )
        public final int size()
        {
            return (int) m_data.values()
                         .stream()
                         .filter( i -> !( i instanceof Map<?, ?> ) )
                         .count()
                    + m_data.entrySet()
                            .stream()
                            .filter( i -> i instanceof Map<?, ?> )
                            .mapToInt( i -> new CViewMap( i.getKey(), (Map<String, Object>) i.getValue() ).size() )
                            .sum();
        }

        @Nonnull
        @Override
        public final IAgent<?> update( @Nonnull final IAgent<?> p_agent )
        {
            return p_agent;
        }

        @Nonnull
        @Override
        public final Stream<ITrigger> trigger( @Nonnull final IView p_view )
        {
            return Stream.empty();
        }

        @Nonnull
        @Override
        @SuppressWarnings( "unchecked" )
        public final Stream<ILiteral> streamLiteral()
        {
            return m_data.entrySet()
                         .stream()
                         .filter( i -> !( i.getValue() instanceof Map<?, ?> ) )
                         .map( i -> CLiteral.from( m_keytoliteral.apply( i.getKey() ), this.toterm( i.getValue() ) ) );
        }

        @Nonnull
        @Override
        @SuppressWarnings( "unchecked" )
        public final Stream<IView> streamView()
        {
            return m_data.entrySet()
                         .stream()
                         .filter( i -> i.getValue() instanceof Map<?, ?> )
                         .map( i -> new CViewMap( m_keytoliteral.apply( i.getKey() ), (Map<String, Object>) i.getValue() ) );
        }

        @Nonnull
        @Override
        public final IBeliefbase clear()
        {
            m_data.clear();
            return this;
        }

        @Nonnull
        @Override
        public final ILiteral add( @Nonnull final ILiteral p_literal )
        {
            m_addliteralconsumer.accept( new ImmutablePair<>( m_literaltokey.apply( p_literal.functor() ), p_literal.orderedvalues() ), m_data );
            return p_literal;
        }

        @Nonnull
        @Override
        public final IView add( @Nonnull final IView p_view )
        {
            m_addviewconsumer.accept( m_keytoliteral.apply( p_view.name() ), m_data );
            return p_view;
        }

        @Nonnull
        @Override
        public final ILiteral remove( @Nonnull final ILiteral p_literal )
        {
            m_removeliteralconsumer.accept( m_literaltokey.apply( p_literal.functor() ), m_data );
            return p_literal;
        }

        @Nonnull
        @Override
        public final IView remove( @Nonnull final IView p_view )
        {
            m_removeviewconsumer.accept( m_literaltokey.apply( p_view.name() ), m_data );
            return p_view;
        }

        @Override
        public final boolean containsLiteral( @Nonnull final String p_key )
        {
            final String l_key = m_literaltokey.apply( p_key );
            return m_data.containsKey( l_key ) && ( !( m_data.get( l_key ) instanceof Map<?, ?> ) );
        }

        @Override
        public boolean containsView( @Nonnull final String p_key )
        {
            final String l_key = m_literaltokey.apply( p_key );
            return m_data.containsKey( l_key ) &&  ( m_data.get( l_key ) instanceof Map<?, ?> );
        }

        @Nullable
        @Override
        public final IView view( @Nonnull final String p_key )
        {
            return CViewMap.this;
        }

        @Nonnull
        @Override
        public final Collection<ILiteral> literal( @Nonnull final String p_key )
        {
            final String l_key = m_literaltokey.apply( p_key );
            if ( !m_data.containsKey( l_key ) )
                return Collections.emptySet();

            final Object l_data = m_data.get( l_key );
            if ( m_data.get( l_key ) instanceof Map<?, ?> )
                return Collections.emptySet();

            return Stream.of( CLiteral.from( p_key, this.toterm( l_data ) ) ).collect( Collectors.toSet() );
        }

        @Nullable
        @Override
        public final IView viewOrDefault( @Nonnull final String p_key, @Nullable final IView p_default )
        {
            return null;
        }

        @Nonnull
        @Override
        public final IView create( @Nonnull final String p_name )
        {
            return CViewMap.this;
        }

        @Nonnull
        @Override
        public final IView create( @Nonnull final String p_name, @Nullable final IView p_parent )
        {
            return CViewMap.this;
        }

        @SuppressWarnings( "unchecked" )
        private Stream<ITerm> toterm( final Object p_value )
        {
            if ( p_value instanceof Collection<?> )
                return ( (Collection<Object>) p_value ).stream().flatMap( this::toterm );

            if ( p_value instanceof Map<?, ?> )
                return ( (Map<String, Object>) p_value ).entrySet().stream()
                                                        .map( i -> CLiteral.from( m_keytoliteral.apply( i.getKey() ), this.toterm( i.getValue() ) ) );

            if ( p_value instanceof Integer )
                return Stream.of( CRawTerm.from( ( (Number) p_value ).longValue() ) );

            return Stream.of( CRawTerm.from( p_value ) );
        }
    }
}