/*
 *  catch_generators.hpp
 *  Catch
 *
 *  Created by Phil on 27/01/2011.
 *  Copyright 2011 Two Blue Cubes Ltd. All rights reserved.
 *
 *  Distributed under the Boost Software License, Version 1.0. (See accompanying
 *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 *
 */

#ifndef TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED
#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED

#include "catch_hub.h"

#include <iterator>
#include <vector>
#include <string>
#include <stdlib.h>

namespace Catch
{

template<typename T>
struct IGenerator
{        
    virtual ~IGenerator
    ()
    {}
    
    virtual T getValue
        ( std::size_t index
        ) const = 0;
    
    virtual std::size_t size
        () const = 0;
};

template<typename T>
class BetweenGenerator : public IGenerator<T>
{
public:
    ///////////////////////////////////////////////////////////////////////////
    BetweenGenerator
    ( 
        T from, 
        T to 
    )
    :   m_from( from ), 
        m_to( to )
    {
    }
    
    ///////////////////////////////////////////////////////////////////////////
    virtual T getValue
    (
        std::size_t index
    )
    const
    {
        return m_from+index;
    }
    
    ///////////////////////////////////////////////////////////////////////////
    virtual std::size_t size
    ()
    const
    {
        return 1+m_to-m_from;
    }
    
private:
    
    T m_from;
    T m_to;
};

template<typename T>
class ValuesGenerator : public IGenerator<T>
{
public:
    ///////////////////////////////////////////////////////////////////////////
    ValuesGenerator
    ()
    {
    }
    
    ///////////////////////////////////////////////////////////////////////////
    void add
    (
        T value
    )
    {
        m_values.push_back( value );
    }
    
    ///////////////////////////////////////////////////////////////////////////
    virtual T getValue
    (
        std::size_t index
    )
    const
    {
        return m_values[index];
    }

    ///////////////////////////////////////////////////////////////////////////
    virtual std::size_t size
    ()
    const
    {
        return m_values.size();
    }
    
private:
    
    std::vector<T> m_values;
};

template<typename T>
class CompositeGenerator
{
public:
    ///////////////////////////////////////////////////////////////////////////
    CompositeGenerator()
    :   m_totalSize( 0 )
    {
    }
    
    ///////////////////////////////////////////////////////////////////////////
	// *** Move semantics, similar to auto_ptr ***
    CompositeGenerator( CompositeGenerator& other )
	:	m_fileInfo( other.m_fileInfo ),
		m_totalSize( 0 )
    {
		move( other );
    }
    
    ///////////////////////////////////////////////////////////////////////////
    CompositeGenerator& setFileInfo
    (
        const char* fileInfo
    )
    {
        m_fileInfo = fileInfo;
        return *this;
    }
    
    ///////////////////////////////////////////////////////////////////////////
    ~CompositeGenerator
    ()
    {
        deleteAll( m_composed );
    }
    
    ///////////////////////////////////////////////////////////////////////////
    operator T
    ()
    const
    {
        size_t overallIndex = Hub::getGeneratorIndex( m_fileInfo, m_totalSize );
        
        typename std::vector<const IGenerator<T>*>::const_iterator it = m_composed.begin();
        typename std::vector<const IGenerator<T>*>::const_iterator itEnd = m_composed.end();
        for( size_t index = 0; it != itEnd; ++it )
        {
            const IGenerator<T>* generator = *it;
            if( overallIndex >= index && overallIndex < index + generator->size() )
            {
                return generator->getValue( overallIndex-index );
            }
            index += generator->size();
        }
        CATCH_INTERNAL_ERROR( "Indexed past end of generated range" );
    }
    
    ///////////////////////////////////////////////////////////////////////////
    void add
    (
        const IGenerator<T>* generator
    )
    {
        m_totalSize += generator->size();
        m_composed.push_back( generator );
    }
    
    ///////////////////////////////////////////////////////////////////////////
    CompositeGenerator& then
    (
        CompositeGenerator& other
    )
    {
        move( other );
        return *this;
    }
    
    ///////////////////////////////////////////////////////////////////////////
    CompositeGenerator& then
    (
        T value
    )
    {
        ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
        valuesGen->add( value );
        add( valuesGen );
        return *this;
    }
    
private:
    
    ///////////////////////////////////////////////////////////////////////////
    void move
    (
        CompositeGenerator& other
    )
    {
        std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) );
        m_totalSize += other.m_totalSize;
        other.m_composed.clear();
    }
    
    std::vector<const IGenerator<T>*> m_composed;
    std::string m_fileInfo;
    size_t m_totalSize;
};

namespace Generators
{
    ///////////////////////////////////////////////////////////////////////////
    template<typename T>
    CompositeGenerator<T> between
    (
        T from, 
        T to
    )
    {
        CompositeGenerator<T> generators;
        generators.add( new BetweenGenerator<T>( from, to ) );
        return generators;
    }

    ///////////////////////////////////////////////////////////////////////////
    template<typename T>
    CompositeGenerator<T> values
    (
        T val1, 
        T val2
    )
    {
        CompositeGenerator<T> generators;
        ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
        valuesGen->add( val1 );
        valuesGen->add( val2 );
        generators.add( valuesGen );
        return generators;
    }

    ///////////////////////////////////////////////////////////////////////////
    template<typename T>
    CompositeGenerator<T> values
    (
        T val1, 
        T val2, 
        T val3
    )
    {
        CompositeGenerator<T> generators;
        ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
        valuesGen->add( val1 );
        valuesGen->add( val2 );
        valuesGen->add( val3 );
        generators.add( valuesGen );
        return generators;
    }

    ///////////////////////////////////////////////////////////////////////////
    template<typename T>
    CompositeGenerator<T> values
    (
        T val1, 
        T val2, 
        T val3, 
        T val4
    )
    {
        CompositeGenerator<T> generators;
        ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
        valuesGen->add( val1 );
        valuesGen->add( val2 );
        valuesGen->add( val3 );
        valuesGen->add( val4 );
        generators.add( valuesGen );
        return generators;
    }

} // end namespace Generators
    
using namespace Generators;
    
} // end namespace Catch

#define INTERNAL_CATCH_LINESTR2( line ) #line
#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line )

#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" )

#endif // TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED
