/*
 *  Created by Phil on 02/11/2010.
 *  Copyright 2010 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_COMMANDLINE_HPP_INCLUDED
#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED

#include "catch_config.hpp"

namespace Catch {

    class Command {
    public:
        Command(){}
        
        explicit Command( const std::string& name ) : m_name( name ) {}
                
        Command& operator += ( const std::string& arg ) {
            m_args.push_back( arg );
            return *this;
        }
        Command& operator += ( const Command& other ) {
            std::copy( other.m_args.begin(), other.m_args.end(), std::back_inserter( m_args ) );
            if( m_name.empty() )
                m_name = other.m_name;
            return *this;
        }
        Command operator + ( const Command& other ) {
            Command newCommand( *this );
            newCommand += other;
            return newCommand;
        }
        
        operator SafeBool::type() const {
            return SafeBool::makeSafe( !m_name.empty() );
        }
        
        std::string name() const { return m_name; }
        std::string operator[]( std::size_t i ) const { return m_args[i]; }
        std::size_t argsCount() const { return m_args.size(); }

        CATCH_ATTRIBUTE_NORETURN
        void raiseError( const std::string& message ) const {
            std::ostringstream oss;
            oss << "Error while parsing " << m_name << ". " << message << ".";
            if( m_args.size() > 0 )
                oss << " Arguments where:";
            for( std::size_t i = 0; i < m_args.size(); ++i )
                oss << " " << m_args[i];
            throw std::domain_error( oss.str() );
        }
        
    private:
        
        std::string m_name;
        std::vector<std::string> m_args;
    };
    
    class CommandParser {
    public:
        CommandParser( int argc, char const * const * argv ) : m_argc( static_cast<std::size_t>( argc ) ), m_argv( argv ) {}

        Command find( const std::string& arg1,  const std::string& arg2, const std::string& arg3 ) const {
            return find( arg1 ) + find( arg2 ) + find( arg3 );
        }

        Command find( const std::string& shortArg, const std::string& longArg ) const {
            return find( shortArg ) + find( longArg );
        }
        Command find( const std::string& arg ) const {
            for( std::size_t i = 0; i < m_argc; ++i  )
                if( m_argv[i] == arg )
                    return getArgs( i );
            return Command();
        }
        
    private:
        Command getArgs( std::size_t from ) const {
            Command command( m_argv[from] );
            for( std::size_t i = from+1; i < m_argc && m_argv[i][0] != '-'; ++i  )
                command += m_argv[i];
            return command;
        }
        
        std::size_t m_argc;
        char const * const * m_argv;
    };
   
    inline void parseIntoConfig( const CommandParser& parser, ConfigData& config ) {
        
        if( Command cmd = parser.find( "-l", "--list" ) ) {
            if( cmd.argsCount() > 2 )
                cmd.raiseError( "Expected upto 2 arguments" );

            List::What listSpec = List::All;
            if( cmd.argsCount() >= 1 ) {
                if( cmd[0] == "tests" )
                    config.listSpec = List::Tests;
                else if( cmd[0] == "reporters" )
                    config.listSpec = List::Reports;
                else
                    cmd.raiseError( "Expected [tests] or [reporters]" );
            }
            if( cmd.argsCount() >= 2 ) {
                if( cmd[1] == "xml" )
                    config.listSpec = static_cast<List::What>( listSpec | List::AsXml );
                else if( cmd[1] == "text" )
                    config.listSpec = static_cast<List::What>( listSpec | List::AsText );
                else
                    cmd.raiseError( "Expected [xml] or [text]" );
            }
        }
                            
        if( Command cmd = parser.find( "-t", "--test" ) ) {
            if( cmd.argsCount() == 0 )
                cmd.raiseError( "Expected at least one argument" );
            for( std::size_t i = 0; i < cmd.argsCount(); ++i )
                config.testSpecs.push_back( cmd[i] );
        }
        
        if( Command cmd = parser.find( "-r", "--reporter" ) ) {
            if( cmd.argsCount() != 1 )
                cmd.raiseError( "Expected one argument" );
            config.reporter = cmd[0];
        }
        
        if( Command cmd = parser.find( "-o", "--out" ) ) {
            if( cmd.argsCount() == 0 )
                cmd.raiseError( "Expected filename" );
            if( cmd[0][0] == '%' )
                config.stream = cmd[0].substr( 1 );
            else
                config.outputFilename = cmd[0];
        }

        if( Command cmd = parser.find( "-s", "--success" ) ) {
            if( cmd.argsCount() != 0 )
                cmd.raiseError( "Does not accept arguments" );
            config.includeWhichResults = Include::SuccessfulResults;
        }
        
        if( Command cmd = parser.find( "-b", "--break" ) ) {
            if( cmd.argsCount() != 0 )
                cmd.raiseError( "Does not accept arguments" );
            config.shouldDebugBreak = true;
        }

        if( Command cmd = parser.find( "-n", "--name" ) ) {
            if( cmd.argsCount() != 1 )
                cmd.raiseError( "Expected a name" );
            config.name = cmd[0];
        }

        if( Command cmd = parser.find( "-a", "--abort" ) ) {
            if( cmd.argsCount() > 1 )
                cmd.raiseError( "Only accepts 0-1 arguments" );
            int threshold = 1;
            if( cmd.argsCount() == 1 ) {
                std::stringstream ss;
                ss << cmd[0];
                ss >> threshold;
                if( ss.fail() || threshold <= 0 )
                    cmd.raiseError( "threshold must be a number greater than zero" );
            }
            config.cutoff = threshold;
        }

        if( Command cmd = parser.find( "-nt", "--nothrow" ) ) {
            if( cmd.argsCount() != 0 )
                cmd.raiseError( "Does not accept arguments" );
            config.allowThrows = false;
        }

    }
        
} // end namespace Catch

#endif // TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED