/*
 * 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;

import java.lang.reflect.Array;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;

/**
 * Utility methods for working with Iterables. See test for examples of how to use.
 */
public final class Iterables
{
    @SuppressWarnings( "raw" )
    private static final Iterable EMPTY = new Iterable()
    {
        Iterator iterator = new Iterator()
        {
            @Override
            public boolean hasNext()
            {
                return false;
            }

            @Override
            public Object next()
            {
                throw new NoSuchElementException();
            }

            @Override
            public void remove()
            {
            }
        };

        @Override
        public Iterator iterator()
        {
            return iterator;
        }
    };

    @SuppressWarnings( "unchecked" )
    public static <T> Iterable<T> empty()
    {
        return EMPTY;
    }

    public static <T> Iterable<T> constant( final T item )
    {
        return new Iterable<T>()
        {
            @Override
            public Iterator<T> iterator()
            {
                return new Iterator<T>()
                {
                    @Override
                    public boolean hasNext()
                    {
                        return true;
                    }

                    @Override
                    public T next()
                    {
                        return item;
                    }

                    @Override
                    public void remove()
                    {
                    }
                };
            }
        };
    }

    public static <T> Iterable<T> limit( final int limitItems, final Iterable<T> iterable )
    {
        return new Iterable<T>()
        {
            @Override
            public Iterator<T> iterator()
            {
                final Iterator<T> iterator = iterable.iterator();

                return new Iterator<T>()
                {
                    int count;

                    @Override
                    public boolean hasNext()
                    {
                        return count < limitItems && iterator.hasNext();
                    }

                    @Override
                    public T next()
                    {
                        count++;
                        return iterator.next();
                    }

                    @Override
                    public void remove()
                    {
                        iterator.remove();
                    }
                };
            }
        };
    }

    public static <T> Iterable<T> unique( final Iterable<T> iterable )
    {
        return new Iterable<T>()
        {
            @Override
            public Iterator<T> iterator()
            {
                final Iterator<T> iterator = iterable.iterator();

                return new Iterator<T>()
                {
                    private final Set<T> items = new HashSet<>();
                    private T nextItem;

                    @Override
                    public boolean hasNext()
                    {
                        while( iterator.hasNext() )
                        {
                            nextItem = iterator.next();
                            if( items.add( nextItem ) )
                            {
                                return true;
                            }
                        }

                        return false;
                    }

                    @Override
                    public T next()
                    {
                        if( nextItem == null && !hasNext() )
                        {
                            throw new NoSuchElementException();
                        }

                        return nextItem;
                    }

                    @Override
                    public void remove()
                    {
                    }
                };
            }
        };
    }

    public static <T, C extends Collection<T>> C addAll( C collection, Iterable<? extends T> iterable )
    {
        for( T item : iterable )
        {
            collection.add( item );
        }
        return collection;
    }

    public static long count( Iterable<?> iterable )
    {
        long c = 0;
        for( Object anIterable : iterable )
        {
            c++;
        }
        return c;
    }

    @SuppressWarnings( "unchecked" )
    public static <X> Iterable<X> filter( Specification<? /* super X*/> specification, Iterable<X> i )
    {
        return new FilterIterable<>( i, (Specification<? super X>) specification );
    }

    public static <X> X first( Iterable<X> i )
    {
        Iterator<X> iter = i.iterator();
        if( iter.hasNext() )
        {
            return iter.next();
        }
        else
        {
            return null;
        }
    }

    public static <X> X single( Iterable<X> i )
    {
        Iterator<X> iter = i.iterator();
        if( iter.hasNext() )
        {
            X result = iter.next();

            if( iter.hasNext() )
            {
                throw new IllegalArgumentException( "More than one element in iterable" );
            }

            return result;
        }
        else
        {
            throw new IllegalArgumentException( "No elements in iterable" );
        }
    }

    public static <X> Iterable<X> skip( final int skip, final Iterable<X> iterable )
    {
        return new Iterable<X>()
        {
            @Override
            public Iterator<X> iterator()
            {
                Iterator<X> iterator = iterable.iterator();

                for( int i = 0; i < skip; i++ )
                {
                    if( iterator.hasNext() )
                    {
                        iterator.next();
                    }
                    else
                    {
                        return Iterables.<X>empty().iterator();
                    }
                }

                return iterator;
            }
        };
    }

    public static <X> X last( Iterable<X> i )
    {
        Iterator<X> iter = i.iterator();
        X item = null;
        while( iter.hasNext() )
        {
            item = iter.next();
        }

        return item;
    }

    public static <X> Iterable<X> reverse( Iterable<X> iterable )
    {
        List<X> list = toList( iterable );
        Collections.reverse( list );
        return list;
    }

    public static <T> boolean matchesAny( Specification<? super T> specification, Iterable<T> iterable )
    {
        boolean result = false;

        for( T item : iterable )
        {
            if( ( (Specification<? super T>) specification ).satisfiedBy( item ) )
            {
                result = true;
                break;
            }
        }

        return result;
    }

    public static <T> boolean matchesAll( Specification<? super T> specification, Iterable<T> iterable )
    {
        boolean result = true;
        for( T item : iterable )
        {
            if( !specification.satisfiedBy( item ) )
            {
                result = false;
            }
        }

        return result;
    }

    public static <X> Iterable<X> flatten( Iterable<?>... multiIterator )
    {
        return new FlattenIterable<>( Iterables.<Iterable<X>>cast( Arrays.asList( multiIterator ) ) );
    }

    public static <X, I extends Iterable<? extends X>> Iterable<X> flattenIterables( Iterable<I> multiIterator )
    // public static <X> Iterable<X> flattenIterables( Iterable<Iterable<?>> multiIterator )
    {
        return new FlattenIterable<>( Iterables.<Iterable<X>>cast( multiIterator ) );
    }

    @SafeVarargs
    public static <T> Iterable<T> mix( final Iterable<T>... iterables )
    {
        return new Iterable<T>()
        {
            @Override
            public Iterator<T> iterator()
            {
                final Iterable<Iterator<T>> iterators = toList( map( new Function<Iterable<T>, Iterator<T>>()
                {
                    @Override
                    public Iterator<T> map( Iterable<T> iterable )
                    {
                        return iterable.iterator();
                    }
                }, Iterables.iterable( iterables ) ) );

                return new Iterator<T>()
                {
                    Iterator<Iterator<T>> iterator;

                    Iterator<T> iter;

                    @Override
                    public boolean hasNext()
                    {
                        for( Iterator<T> iterator : iterators )
                        {
                            if( iterator.hasNext() )
                            {
                                return true;
                            }
                        }

                        return false;
                    }

                    @Override
                    public T next()
                    {
                        if( iterator == null )
                        {
                            iterator = iterators.iterator();
                        }

                        while( iterator.hasNext() )
                        {
                            iter = iterator.next();

                            if( iter.hasNext() )
                            {
                                return iter.next();
                            }
                        }

                        iterator = null;

                        return next();
                    }

                    @Override
                    public void remove()
                    {
                        if( iter != null )
                        {
                            iter.remove();
                        }
                    }
                };
            }
        };
    }

    @SuppressWarnings( "unchecked" )
    public static <FROM, TO> Iterable<TO> map( Function<? /* super FROM */, TO> function, Iterable<FROM> from )
    {
        return new MapIterable<>( from, (Function<FROM, TO>) function );
    }

    public static <T> Iterable<T> iterable( Enumeration<T> enumeration )
    {
        List<T> list = new ArrayList<>();
        while( enumeration.hasMoreElements() )
        {
            T item = enumeration.nextElement();
            list.add( item );
        }

        return list;
    }

    @SafeVarargs
    public static <T> Iterable<T> iterable( T... items )
    {
        return Arrays.asList( items );
    }

    @SuppressWarnings( {"raw", "unchecked"} )
    public static <T> Iterable<T> cast( Iterable<?> iterable )
    {
        Iterable iter = iterable;
        return iter;
    }

    public static <FROM, TO> Function<FROM, TO> cast()
    {
        return new Function<FROM, TO>()
        {
            @Override
            @SuppressWarnings( "unchecked" )
            public TO map( FROM from )
            {
                return (TO) from;
            }
        };
    }

    public static <FROM, TO> TO fold( Function<? super FROM, TO> function, Iterable<? extends FROM> i )
    {
        return last( map( function, i ) );
    }

    public static <T> Iterable<T> prepend( final T item, final Iterable<T> iterable )
    {
        return new Iterable<T>()
        {
            @Override
            public Iterator<T> iterator()
            {
                return new Iterator<T>()
                {
                    T first = item;
                    Iterator<T> iterator;

                    @Override
                    public boolean hasNext()
                    {
                        if( first != null )
                        {
                            return true;
                        }
                        else
                        {
                            if( iterator == null )
                            {
                                iterator = iterable.iterator();
                            }
                        }

                        return iterator.hasNext();
                    }

                    @Override
                    public T next()
                    {
                        if( first != null )
                        {
                            try
                            {
                                return first;
                            }
                            finally
                            {
                                first = null;
                            }
                        }
                        else
                        {
                            return iterator.next();
                        }
                    }

                    @Override
                    public void remove()
                    {
                    }
                };
            }
        };
    }

    public static <T> Iterable<T> append( final T item, final Iterable<T> iterable )
    {
        return new Iterable<T>()
        {
            @Override
            public Iterator<T> iterator()
            {
                final Iterator<T> iterator = iterable.iterator();

                return new Iterator<T>()
                {
                    T last = item;

                    @Override
                    public boolean hasNext()
                    {
                        if( iterator.hasNext() )
                        {
                            return true;
                        }
                        else
                        {
                            return last != null;
                        }
                    }

                    @Override
                    public T next()
                    {
                        if( iterator.hasNext() )
                        {
                            return iterator.next();
                        }
                        else
                        {
                            try
                            {
                                return last;
                            }
                            finally
                            {
                                last = null;
                            }
                        }
                    }

                    @Override
                    public void remove()
                    {
                    }
                };
            }
        };
    }

    @SafeVarargs
    public static <T> Iterable<T> debug( String format,
                                         final Iterable<T> iterable,
                                         final Function<T, String>... functions
    )
    {
        final MessageFormat msgFormat = new MessageFormat( format );

        return map( new Function<T, T>()
        {
            @Override
            public T map( T t )
            {
                if( functions.length != 0 )
                {
                    String[] mapped = new String[ functions.length ];
                    for( int i = 0; i < functions.length; i++ )
                    {
                        Function<T, String> function = functions[i];
                        mapped[i] = function.map( t );
                    }
                }
                return t;
            }
        }, iterable );
    }

    public static <T> Iterable<T> cache( Iterable<T> iterable )
    {
        return new CacheIterable<>( iterable );
    }

    public static <T> String toString( Iterable<T> iterable )
    {
        return toString( iterable, new Function<T, String>()
        {
            @Override
            public String map( T t )
            {
                return t == null ? "[null]" : t.toString();
            }
        }, "," );
    }

    public static <T> String toString( Iterable<T> iterable, Function<T, String> toStringFunction, String separator )
    {
        StringBuilder builder = new StringBuilder();
        boolean first = true;
        for( T item : iterable )
        {
            if( !first )
            {
                builder.append( separator );
            }
            builder.append( toStringFunction.map( item ) );
            first = false;
        }
        return builder.toString();
    }

    public static <T> List<T> toList( Iterable<T> iterable )
    {
        return addAll( new ArrayList<T>(), iterable );
    }

    public static Object[] toArray( Iterable<Object> iterable )
    {
        return toArray( Object.class, iterable );
    }

    @SuppressWarnings( "unchecked" )
    public static <T> T[] toArray( Class<T> componentType, Iterable<T> iterable )
    {
        if( iterable == null )
        {
            return null;
        }
        List<T> list = toList( iterable );
        return list.toArray( (T[]) Array.newInstance( componentType, list.size() ) );
    }

    @SuppressWarnings( {"raw", "unchecked"} )
    public static <X extends Comparable> Iterable<X> sort( Iterable<X> iterable )
    {
        List<X> list = toList( iterable );
        Collections.sort( list );
        return list;
    }

    public static <X> Iterable<X> sort( Comparator<? super X> comparator, Iterable<X> iterable )
    {
        List<X> list = toList( iterable );
        Collections.sort( list, comparator );
        return list;
    }

    private static class MapIterable<FROM, TO>
        implements Iterable<TO>
    {
        private final Iterable<FROM> from;
        private final Function<? super FROM, TO> function;

        private MapIterable( Iterable<FROM> from, Function<? super FROM, TO> function )
        {
            this.from = from;
            this.function = function;
        }

        @Override
        public Iterator<TO> iterator()
        {
            return new MapIterator<>( from.iterator(), function );
        }

        static class MapIterator<FROM, TO>
            implements Iterator<TO>
        {
            private final Iterator<FROM> fromIterator;
            private final Function<? super FROM, TO> function;

            private MapIterator( Iterator<FROM> fromIterator, Function<? super FROM, TO> function )
            {
                this.fromIterator = fromIterator;
                this.function = function;
            }

            @Override
            public boolean hasNext()
            {
                return fromIterator.hasNext();
            }

            @Override
            public TO next()
            {
                FROM from = fromIterator.next();
                return function.map( from );
            }

            @Override
            public void remove()
            {
                fromIterator.remove();
            }
        }

    }

    private static class FilterIterable<T>
        implements Iterable<T>
    {
        private final Iterable<T> iterable;

        private final Specification<? super T> specification;

        private FilterIterable( Iterable<T> iterable, Specification<? super T> specification )
        {
            this.iterable = iterable;
            this.specification = specification;
        }

        @Override
        public Iterator<T> iterator()
        {
            return new FilterIterator<>( iterable.iterator(), specification );
        }

        private static class FilterIterator<T>
            implements Iterator<T>
        {
            private final Iterator<T> iterator;

            private final Specification<? super T> specification;

            private T currentValue;
            boolean finished = false;
            boolean nextConsumed = true;

            private FilterIterator( Iterator<T> iterator, Specification<? super T> specification )
            {
                this.specification = specification;
                this.iterator = iterator;
            }

            public boolean moveToNextValid()
            {
                boolean found = false;
                while( !found && iterator.hasNext() )
                {
                    T currentValue = iterator.next();
                    boolean satisfies = specification.satisfiedBy( currentValue );

                    if( satisfies )
                    {
                        found = true;
                        this.currentValue = currentValue;
                        nextConsumed = false;
                    }
                }
                if( !found )
                {
                    finished = true;
                }
                return found;
            }

            @Override
            public T next()
            {
                if( !nextConsumed )
                {
                    nextConsumed = true;
                    return currentValue;
                }
                else
                {
                    if( !finished )
                    {
                        if( moveToNextValid() )
                        {
                            nextConsumed = true;
                            return currentValue;
                        }
                    }
                }
                return null;
            }

            @Override
            public boolean hasNext()
            {
                return !finished
                       && ( !nextConsumed || moveToNextValid() );
            }

            @Override
            public void remove()
            {
            }
        }

    }

    private static class FlattenIterable<T, I extends Iterable<? extends T>>
        implements Iterable<T>
    {
        private final Iterable<I> iterable;

        private FlattenIterable( Iterable<I> iterable )
        {
            this.iterable = iterable;
        }

        @Override
        public Iterator<T> iterator()
        {
            return new FlattenIterator<>( iterable.iterator() );
        }

        static class FlattenIterator<T, I extends Iterable<? extends T>>
            implements Iterator<T>
        {
            private final Iterator<I> iterator;
            private Iterator<? extends T> currentIterator;

            private FlattenIterator( Iterator<I> iterator )
            {
                this.iterator = iterator;
                currentIterator = null;
            }

            @Override
            public boolean hasNext()
            {
                if( currentIterator == null )
                {
                    if( iterator.hasNext() )
                    {
                        I next = iterator.next();
                        currentIterator = next.iterator();
                    }
                    else
                    {
                        return false;
                    }
                }

                while( !currentIterator.hasNext()
                       && iterator.hasNext() )
                {
                    currentIterator = iterator.next().iterator();
                }

                return currentIterator.hasNext();
            }

            @Override
            public T next()
            {
                return currentIterator.next();
            }

            @Override
            public void remove()
            {
                if( currentIterator == null )
                {
                    throw new IllegalStateException();
                }

                currentIterator.remove();
            }
        }

    }

    private static class CacheIterable<T>
        implements Iterable<T>
    {
        private final Iterable<T> iterable;
        private Iterable<T> cache;

        private CacheIterable( Iterable<T> iterable )
        {
            this.iterable = iterable;
        }

        @Override
        public Iterator<T> iterator()
        {
            if( cache != null )
            {
                return cache.iterator();
            }

            final Iterator<T> source = iterable.iterator();

            return new Iterator<T>()
            {
                List<T> iteratorCache = new ArrayList<>();

                @Override
                public boolean hasNext()
                {
                    boolean hasNext = source.hasNext();
                    if( !hasNext )
                    {
                        cache = iteratorCache;
                    }
                    return hasNext;
                }

                @Override
                public T next()
                {
                    T next = source.next();
                    iteratorCache.add( next );
                    return next;
                }

                @Override
                public void remove()
                {

                }
            };
        }
    }

    private Iterables()
    {
    }

}
