/*
 * Copyright (c) 2010, Rickard Öberg. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.qi4j.functional;

/**
 * Common generic specification expressions
 */
public class Specifications
{
    public static <T> Specification<T> TRUE()
    {
        return new Specification<T>()
        {
            @Override
            public boolean satisfiedBy( T instance )
            {
                return true;
            }
        };
    }

    public static <T> Specification<T> not( final Specification<T> specification )
    {
        return new Specification<T>()
        {
            @Override
            public boolean satisfiedBy( T instance )
            {
                return !specification.satisfiedBy( instance );
            }
        };
    }

    @SafeVarargs
    public static <T> AndSpecification<T> and( final Specification<T>... specifications )
    {
        return and( Iterables.iterable( specifications ) );
    }

    public static <T> AndSpecification<T> and( final Iterable<Specification<T>> specifications )
    {
        return new AndSpecification<>( specifications );
    }

    @SafeVarargs
    public static <T> OrSpecification<T> or( final Specification<T>... specifications )
    {
        return or( Iterables.iterable( specifications ) );
    }

    public static <T> OrSpecification<T> or( final Iterable<Specification<T>> specifications )
    {
        return new OrSpecification<>( specifications );
    }

    @SafeVarargs
    public static <T> Specification<T> in( final T... allowed )
    {
        return in( Iterables.iterable( allowed ) );
    }

    public static <T> Specification<T> in( final Iterable<T> allowed )
    {
        return new Specification<T>()
        {
            @Override
            public boolean satisfiedBy( T item )
            {
                for( T allow : allowed )
                {
                    if( allow.equals( item ) )
                    {
                        return true;
                    }
                }
                return false;
            }
        };
    }

    public static <T> Specification<T> notNull()
    {
        return new Specification<T>()
        {
            @Override
            public boolean satisfiedBy( T item )
            {
                return item != null;
            }
        };
    }

    public static <FROM, TO> Specification<FROM> translate( final Function<FROM, TO> function,
                                                            final Specification<? super TO> specification
    )
    {
        return new Specification<FROM>()
        {
            @Override
            public boolean satisfiedBy( FROM item )
            {
                return specification.satisfiedBy( function.map( item ) );
            }
        };
    }

    /**
     * AND Specification.
     */
    public static class AndSpecification<T>
        implements Specification<T>
    {
        private final Iterable<Specification<T>> specifications;

        private AndSpecification( Iterable<Specification<T>> specifications )
        {
            this.specifications = specifications;
        }

        @Override
        public boolean satisfiedBy( T instance )
        {
            for( Specification<T> specification : specifications )
            {
                if( !specification.satisfiedBy( instance ) )
                {
                    return false;
                }
            }

            return true;
        }

        @SafeVarargs
        public final AndSpecification<T> and( Specification<T>... specifications )
        {
            Iterable<Specification<T>> iterable = Iterables.iterable( specifications );
            Iterable<Specification<T>> flatten = Iterables.flatten( this.specifications, iterable );
            return Specifications.and( flatten );
        }

        @SafeVarargs
        public final OrSpecification<T> or( Specification<T>... specifications )
        {
            return Specifications.or( Iterables.prepend( this, Iterables.iterable( specifications ) ) );
        }
    }

    /**
     * OR Specification.
     */
    public static class OrSpecification<T>
        implements Specification<T>
    {
        private final Iterable<Specification<T>> specifications;

        private OrSpecification( Iterable<Specification<T>> specifications )
        {
            this.specifications = specifications;
        }

        @Override
        public boolean satisfiedBy( T instance )
        {
            for( Specification<T> specification : specifications )
            {
                if( specification.satisfiedBy( instance ) )
                {
                    return true;
                }
            }

            return false;
        }

        @SafeVarargs
        public final AndSpecification<T> and( Specification<T>... specifications )
        {
            return Specifications.and( Iterables.prepend( this, Iterables.iterable( specifications ) ) );
        }

        @SafeVarargs
        public final OrSpecification<T> or( Specification<T>... specifications )
        {
            Iterable<Specification<T>> iterable = Iterables.iterable( specifications );
            Iterable<Specification<T>> flatten = Iterables.flatten( this.specifications, iterable );
            return Specifications.or( flatten );
        }
    }

    private Specifications()
    {
    }

}
