CView.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.lightjason.agentspeak.agent.IAgent;
import org.lightjason.agentspeak.beliefbase.IBeliefbase;
import org.lightjason.agentspeak.common.CCommon;
import org.lightjason.agentspeak.common.CPath;
import org.lightjason.agentspeak.common.IPath;
import org.lightjason.agentspeak.error.CIllegalArgumentException;
import org.lightjason.agentspeak.language.ILiteral;
import org.lightjason.agentspeak.language.instantiable.plan.trigger.ITrigger;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Stream;


/**
 * view of a beliefbase
 *
 * @tparam T agent type
 */
public final class CView implements IView
{
    /**
     * view name
     */
    private final String m_name;
    /**
     * reference to the beliefbase context
     */
    private final IBeliefbase m_beliefbase;
    /**
     * parent name
     */
    private final IView m_parent;



    /**
     * ctor
     *
     * @param p_name view name
     * @param p_beliefbase reference to the beliefbase context
     */
    public CView( @Nonnull final String p_name, @Nonnull final IBeliefbase p_beliefbase )
    {
        this( p_name, p_beliefbase, null );
    }

    /**
     * ctor
     *
     * @param p_name view name
     * @param p_beliefbase reference to the beliefbase context
     * @param p_parent reference to the parent view
     */
    @SuppressWarnings( "unchecked" )
    public CView( @Nonnull final String p_name, @Nonnull final IBeliefbase p_beliefbase, final IView p_parent )
    {
        if ( p_name.isEmpty() )
            throw new CIllegalArgumentException( CCommon.languagestring( this, "empty" ) );

        m_name = p_name;
        m_beliefbase = p_beliefbase;
        m_parent = p_parent;
    }



    // --- agent operations ------------------------------------------------------------------------------------------------------------------------------------

    @Nonnull
    @Override
    public final Stream<ITrigger> trigger()
    {
        // remove the root element (position 0), because the root element
        // is not used on the agent (asl) side
        final IPath l_path = this.path().remove( 0 );
        return m_beliefbase.trigger( this ).map( i -> i.shallowcopy( l_path ) );
    }

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

    // ---------------------------------------------------------------------------------------------------------------------------------------------------------



    // --- operations ------------------------------------------------------------------------------------------------------------------------------------------

    @Nonnull
    @Override
    public final IView add( @Nonnull final Stream<ILiteral> p_literal )
    {
        p_literal.parallel()
                 .forEach( i -> this.leafview( this.walk( i.functorpath() ) ).beliefbase().add( i.shallowcopysuffix() ) );
        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 )
              .parallel()
              .forEach( i ->
              {
                  this.root()
                      .filter( j -> i.beliefbase().equals( this.beliefbase() ) )
                      .findAny()
                      .ifPresent( j ->
                      {
                          throw new CIllegalArgumentException( CCommon.languagestring( this, "equal", i.path(), j.path() ) );
                      } );
                  m_beliefbase.add( i );
              } );
        return this;
    }

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

    @Nonnull
    @Override
    public final IView remove( @Nonnull final Stream<ILiteral> p_literal )
    {
        p_literal.parallel()
            .forEach( i -> this.leafview( this.walk( i.functorpath() ) ).beliefbase().remove( i.shallowcopysuffix() ) );
        return this;
    }

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

    @Nonnull
    @Override
    public final IView clear( @Nullable final IPath... p_path )
    {
        if ( ( Objects.isNull( p_path ) ) || ( p_path.length == 0 ) )
            m_beliefbase.clear();
        else
            Arrays.stream( p_path ).parallel()
                  .forEach( i -> this.leafview( this.walk( i ) ).clear() );

        return this;
    }

    @Override
    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 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() ) )
               );
    }

    // ---------------------------------------------------------------------------------------------------------------------------------------------------------




    // --- streaming access ------------------------------------------------------------------------------------------------------------------------------------

    @Nonnull
    @Override
    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 )
    {
        // 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().filter( i -> i.negated() == p_negated ),
                   m_beliefbase.streamView().flatMap( i -> i.stream( p_negated ) )
               )
               : Arrays.stream( p_path )
                       .flatMap( i -> this.leafview( this.walk( i.subpath( 0, -1 ) ) ).beliefbase().literal( i.suffix() ).stream() )
                       .filter( j -> j.negated() == p_negated )
        ).map( i -> i.shallowcopy( l_path ) );
    }

    @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 final IView generate( @Nonnull final IViewGenerator p_generator, @Nonnull final IPath... p_paths )
    {
        Arrays.stream( p_paths )
            .parallel()
            .forEach( i -> this.walk( i, p_generator ) );
        return this;
    }

    /**
     * inner walking structure of views
     *
     * @param p_path path
     * @param p_generator generator (first argument is used, other elements will be ignored)
     * @return view stream
     */
    @SuppressWarnings( "varargs" )
    private Stream<IView> walkdown( @Nonnull final IPath p_path, @Nullable final IViewGenerator... p_generator )
    {
        if ( p_path.empty() )
            return Stream.of( this );

        final IView l_view;
        final String l_root = p_path.get( 0 );

        synchronized ( this )
        {
            // add is run here for avoid overwriting view with a new object reference
            l_view = m_beliefbase.viewOrDefault(
                        l_root,

                        Objects.isNull( p_generator ) || p_generator.length == 0
                        ? null
                        : p_generator[0].apply( l_root, this )
                     );

            if ( Objects.isNull( l_view ) )
                return Stream.empty();
            m_beliefbase.add( l_view );
        }

        return Stream.concat(
            Stream.of( this ),
            l_view.walk( p_path.subpath( 1 ), p_generator )
        );
    }

    /**
     * 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( IView.EMPTY );
    }

    // ---------------------------------------------------------------------------------------------------------------------------------------------------------



    // --- basic access ----------------------------------------------------------------------------------------------------------------------------------------

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

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

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

    @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 String name()
    {
        return m_name;
    }

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

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

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

    @Override
    public final int hashCode()
    {
        return m_name.hashCode() ^ m_beliefbase.hashCode();
    }

    @Override
    public final boolean equals( final Object p_object )
    {
        return ( p_object instanceof IView ) && ( this.hashCode() == p_object.hashCode() );
    }

    @Override
    public final String toString()
    {
        return MessageFormat.format( "{0} ({1}): [{2}]", this.name(), super.toString(), m_beliefbase );
    }

    // ---------------------------------------------------------------------------------------------------------------------------------------------------------

}