//==============================================================
//
// SAMPLE SOURCE CODE - SUBJECT TO THE TERMS OF SAMPLE CODE LICENSE AGREEMENT,
// http://software.intel.com/en-us/articles/intel-sample-source-code-license-agreement/
//
// Copyright Intel Corporation
//
// THIS FILE IS PROVIDED "AS IS" WITH NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE, NON-INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS.
//
// =============================================================

#ifndef UTILITY_H_
#define UTILITY_H_

#if __TBB_MIC_OFFLOAD
#pragma offload_attribute (push,target(mic))
#include <exception>
#include <cstdio>
#pragma offload_attribute (pop)
#endif // __TBB_MIC_OFFLOAD

#include <string>
#include <cstring>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
#include <sstream>
#include <numeric>
#include <stdexcept>
#include <memory>
#include <cassert>
#include <iostream>
#include <cstdlib>
// TBB headers should not be used, as some examples may need to be built without TBB.

namespace utility{
    namespace internal{

#if ((__GNUC__*100+__GNUC_MINOR__>=404 && __GXX_EXPERIMENTAL_CXX0X__) || _MSC_VER >= 1600) && (!__INTEL_COMPILER || __INTEL_COMPILER >= 1200 )
    // std::unique_ptr is available, and compiler can use it
    #define smart_ptr std::unique_ptr
    using std::swap;
#else
    #if __INTEL_COMPILER && __GXX_EXPERIMENTAL_CXX0X__
    // std::unique_ptr is unavailable, so suppress std::auto_prt<> deprecation warning
    #pragma warning(disable: 1478)
    #endif
    #define smart_ptr std::auto_ptr
    // in some C++ libraries, std::swap does not work with std::auto_ptr
    template<typename T>
    void swap( std::auto_ptr<T>& ptr1, std::auto_ptr<T>& ptr2 ) {
        std::auto_ptr<T> tmp; tmp = ptr2; ptr2 = ptr1; ptr1 = tmp;
    }
#endif

        //TODO: add tcs
        template<class dest_type>
        dest_type& string_to(std::string const& s, dest_type& result){
            std::stringstream stream(s);
            stream>>result;
            if ((!stream)||(stream.fail())){
                throw std::invalid_argument("error converting string '"+std::string(s)+"'");
            }
            return result;
        }

        template<class dest_type>
        dest_type string_to(std::string const& s){
            dest_type result;
            return string_to(s,result);
        }

        template<typename>
        struct is_bool          { static bool value(){return false;}};
        template<>
        struct is_bool<bool>    { static bool value(){return true;}};

        class type_base {
            type_base& operator=(const type_base&);
            public:
            const std::string name;
            const std::string description;

            type_base (std::string a_name, std::string a_description) : name(a_name), description(a_description) {}
            virtual void parse_and_store (const std::string & s)=0;
            virtual std::string value() const =0;
            virtual smart_ptr<type_base> clone()const =0;
            virtual ~type_base(){}
        };
        template <typename type>
        class type_impl : public type_base {
        private:
            type_impl& operator=(const type_impl&);
            typedef bool(*validating_function_type)(const type&);
        private:
            type & target;
            validating_function_type validating_function;
        public:
            type_impl(std::string a_name, std::string a_description, type & a_target, validating_function_type a_validating_function = NULL)
                : type_base (a_name,a_description), target(a_target),validating_function(a_validating_function)
            {};
            void parse_and_store (const std::string & s){
                try{
                    const bool is_bool = internal::is_bool<type>::value();
                    if (is_bool && s.empty()){
                        //to avoid directly assigning true
                        //(as it will impose additional layer of indirection)
                        //so, simply pass it as string
                        internal::string_to("1",target);
                    }else {
                        internal::string_to(s,target);
                    }
                }catch(std::invalid_argument& e){
                    std::stringstream str;
                    str <<"'"<<s<<"' is incorrect input for argument '"<<name<<"'"
                        <<" ("<<e.what()<<")";
                    throw std::invalid_argument(str.str());
                }
                if (validating_function){
                    if (!((validating_function)(target))){
                        std::stringstream str;
                        str <<"'"<<target<<"' is invalid value for argument '"<<name<<"'";
                        throw std::invalid_argument(str.str());
                    }
                }
            }
            virtual std::string value()const{
                std::stringstream str;
                str<<target;
                return str.str();
            }
            virtual smart_ptr<type_base> clone() const {
                return smart_ptr<type_base>(new type_impl(*this));
            }
        };

        class argument{
        private:
            smart_ptr<type_base> p_type;
            bool matched_;
        public:
            argument(argument const& other)
                : p_type(other.p_type.get() ? (other.p_type->clone()).release() : NULL)
                 ,matched_(other.matched_)
            {}
            argument& operator=(argument a){
                this->swap(a);
                return *this;
            }
            void swap(argument& other){
                internal::swap(p_type, other.p_type);
                std::swap(matched_,other.matched_);
            }
            template<class type>
            argument(std::string a_name, std::string a_description, type& dest, bool(*a_validating_function)(const type&)= NULL)
                :p_type(new type_impl<type>(a_name,a_description,dest,a_validating_function))
                 ,matched_(false)
            {}
            std::string value()const{
                return p_type->value();
            }
            std::string name()const{
                return p_type->name;
            }
            std::string description() const{
                return p_type->description;
            }
            void parse_and_store(const std::string & s){
                p_type->parse_and_store(s);
                matched_=true;
            }
            bool is_matched() const{return matched_;}
        };
    } // namespace internal

    class cli_argument_pack{
        typedef std::map<std::string,internal::argument> args_map_type;
        typedef std::vector<std::string> args_display_order_type;
        typedef std::vector<std::string> positional_arg_names_type;
    private:
        args_map_type args_map;
        args_display_order_type args_display_order;
        positional_arg_names_type positional_arg_names;
        std::set<std::string> bool_args_names;
    private:
        void add_arg(internal::argument const& a){
            std::pair<args_map_type::iterator, bool> result = args_map.insert(std::make_pair(a.name(),a));
            if (!result.second){
                throw std::invalid_argument("argument with name: '"+a.name()+"' already registered");
            }
            args_display_order.push_back(a.name());
        }
    public:
        template<typename type>
        cli_argument_pack& arg(type& dest,std::string const& name, std::string const& description, bool(*validate)(const type &)= NULL){
            internal::argument a(name,description,dest,validate);
            add_arg(a);
            if (internal::is_bool<type>::value()){
                bool_args_names.insert(name);
            }
            return *this;
        }

        //Positional means that argument name can be omitted in actual CL
        //only key to match values for parameters with
        template<typename type>
        cli_argument_pack& positional_arg(type& dest,std::string const& name, std::string const& description, bool(*validate)(const type &)= NULL){
            internal::argument a(name,description,dest,validate);
            add_arg(a);
            if (internal::is_bool<type>::value()){
                bool_args_names.insert(name);
            }
            positional_arg_names.push_back(name);
            return *this;
        }

        void parse(std::size_t argc, char const* argv[]){
            {
                std::size_t current_positional_index=0;
                for (std::size_t j=1;j<argc;j++){
                    internal::argument* pa = NULL;
                    std::string argument_value;

                    const char * const begin=argv[j];
                    const char * const end=begin+std::strlen(argv[j]);

                    const char * const assign_sign = std::find(begin,end,'=');

                    struct throw_unknown_parameter{ static void _(std::string const& location){
                        throw std::invalid_argument(std::string("unknown parameter starting at:'")+location+"'");
                    }};
                    //first try to interpret it like parameter=value string
                    if (assign_sign!=end){
                        std::string name_found = std::string(begin,assign_sign);
                        args_map_type::iterator it = args_map.find(name_found );

                        if(it!=args_map.end()){
                            pa= &((*it).second);
                            argument_value = std::string(assign_sign+1,end);
                        }else {
                            throw_unknown_parameter::_(argv[j]);
                        }
                    }
                    //then see is it a named flag
                    else{
                        args_map_type::iterator it = args_map.find(argv[j] );
                        if(it!=args_map.end()){
                            pa= &((*it).second);
                            argument_value = "";
                        }
                        //then try it as positional argument without name specified
                        else if (current_positional_index < positional_arg_names.size()){
                            std::stringstream str(argv[j]);
                            args_map_type::iterator found_positional_arg = args_map.find(positional_arg_names.at(current_positional_index));
                            //TODO: probably use of smarter assert would help here
                            assert(found_positional_arg!=args_map.end()/*&&"positional_arg_names and args_map are out of sync"*/);
                            if (found_positional_arg==args_map.end()){
                                throw std::logic_error("positional_arg_names and args_map are out of sync");
                            }
                            pa= &((*found_positional_arg).second);
                            argument_value = argv[j];

                            current_positional_index++;
                        }else {
                            //TODO: add tc to check
                            throw_unknown_parameter::_(argv[j]);
                        }
                    }
                    assert(pa);
                    if (pa->is_matched()){
                        throw std::invalid_argument(std::string("several values specified for: '")+pa->name()+"' argument");
                    }
                    pa->parse_and_store(argument_value);
                }
            }
        }
        std::string usage_string(const std::string& binary_name)const{
            std::string command_line_params;
            std::string summary_description;

            for (args_display_order_type::const_iterator it = args_display_order.begin();it!=args_display_order.end();++it){
                const bool is_bool = (0!=bool_args_names.count((*it)));
                args_map_type::const_iterator argument_it = args_map.find(*it);
                //TODO: probably use of smarter assert would help here
                assert(argument_it!=args_map.end()/*&&"args_display_order and args_map are out of sync"*/);
                if (argument_it==args_map.end()){
                    throw std::logic_error("args_display_order and args_map are out of sync");
                }
                const internal::argument & a = (*argument_it).second;
                command_line_params +=" [" + a.name() + (is_bool ?"":"=value")+ "]";
                summary_description +=" " + a.name() + " - " + a.description() +" ("+a.value() +")" + "\n";
            }

            std::string positional_arg_cl;
            for (positional_arg_names_type::const_iterator it = positional_arg_names.begin();it!=positional_arg_names.end();++it){
                positional_arg_cl +=" ["+(*it);
            }
            for (std::size_t i=0;i<positional_arg_names.size();++i){
                positional_arg_cl+="]";
            }
            command_line_params+=positional_arg_cl;
            std::stringstream str;
            using std::endl;
            str << " Program usage is:" << endl
                 << " " << binary_name << command_line_params
                 << endl << endl
                 << " where:" << endl
                 << summary_description
            ;
            return str.str();
        }
    }; // class cli_argument_pack

    namespace internal {
        template<typename T>
        bool is_power_of_2( T val ) {
            size_t intval = size_t(val);
            return (intval&(intval-1)) == size_t(0);
        }
        int step_function_plus(int previous, double step){
            return static_cast<int>(previous+step);
        }
        int step_function_multiply(int previous, double multiply){
            return static_cast<int>(previous*multiply);
        }
        // "Power-of-2 ladder": nsteps is the desired number of steps between any subsequent powers of 2.
        // The actual step is the quotient of the nearest smaller power of 2 divided by that number (but at least 1).
        // E.g., '1:32:#4' means 1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32
        int step_function_power2_ladder(int previous, double nsteps){
            int steps = int(nsteps);
            assert( is_power_of_2(steps) );  // must be a power of 2
            // The actual step is 1 until the value is twice as big as nsteps
            if( previous < 2*steps )
                return previous+1;
            // calculate the previous power of 2
            int prev_power2 = previous/2;                 // start with half the given value
            int rshift = 1;                               // and with the shift of 1;
            while( int shifted = prev_power2>>rshift ) {  // shift the value right; while the result is non-zero,
                prev_power2 |= shifted;                   //   add the bits set in 'shifted';
                rshift <<= 1;                             //   double the shift, as twice as many top bits are set;
            }                                             // repeat.
            ++prev_power2; // all low bits set; now it's just one less than the desired power of 2
            assert( is_power_of_2(prev_power2) );
            assert( (prev_power2<=previous)&&(2*prev_power2>previous) );
            // The actual step value is the previous power of 2 divided by steps
            return previous + (prev_power2/steps);
        }
        typedef int (* step_function_ptr_type)(int,double);

        struct step_function_descriptor  {
            char mnemonic;
            step_function_ptr_type function;
        public:
            step_function_descriptor(char a_mnemonic, step_function_ptr_type a_function) : mnemonic(a_mnemonic), function(a_function) {}
        private:
            void operator=(step_function_descriptor  const&);
        };
        step_function_descriptor step_function_descriptors[] = {
                step_function_descriptor('*',step_function_multiply),
                step_function_descriptor('+',step_function_plus),
                step_function_descriptor('#',step_function_power2_ladder)
        };

        template<typename T, size_t N>
        inline size_t array_length(const T(&)[N])
        {
           return N;
        }

        struct thread_range_step {
            step_function_ptr_type step_function;
            double step_function_argument;

            thread_range_step ( step_function_ptr_type step_function_, double step_function_argument_)
                :step_function(step_function_),step_function_argument(step_function_argument_)
            {
                if (!step_function_)
                    throw std::invalid_argument("step_function for thread range step should not be NULL");
            }
            int operator()(int previous)const {
                assert(0<=previous); // test 0<=first and loop discipline
                const int ret = step_function(previous,step_function_argument);
                assert(previous<ret);
                return ret;
            }
            friend std::istream& operator>>(std::istream& input_stream, thread_range_step& step){
                char function_char;
                double function_argument;
                input_stream >> function_char >> function_argument;
                size_t i = 0;
                while ((i<array_length(step_function_descriptors)) && (step_function_descriptors[i].mnemonic!=function_char)) ++i;
                if (i >= array_length(step_function_descriptors)){
                    throw std::invalid_argument("unknown step function mnemonic: "+std::string(1,function_char));
                } else if ((function_char=='#') && !is_power_of_2(function_argument)) {
                    throw std::invalid_argument("the argument of # should be a power of 2");
                }
                step.step_function = step_function_descriptors[i].function;
                step.step_function_argument = function_argument;
                return input_stream;
            }
        };
    } // namespace internal

    struct thread_number_range{
        int (*auto_number_of_threads)();
        int first; // 0<=first (0 can be used as a special value)
        int last;  // first<=last

        internal::thread_range_step step;

        thread_number_range( int (*auto_number_of_threads_)(),int low_=1, int high_=-1
                , internal::thread_range_step step_ =  internal::thread_range_step(internal::step_function_power2_ladder,4)
        )
            : auto_number_of_threads(auto_number_of_threads_), first(low_), last((high_>-1) ? high_ : auto_number_of_threads_())
              ,step(step_)
        {
            if (first<0) {
                throw std::invalid_argument("negative value not allowed");
            }
            if (first>last) {
                throw std::invalid_argument("decreasing sequence not allowed");
            }
        }
        friend std::istream& operator>>(std::istream& i, thread_number_range& range){
            try{
                std::string s;
                i>>s;
                struct string_to_number_of_threads{
                    int auto_value;
                    string_to_number_of_threads(int auto_value_):auto_value(auto_value_){}
                    int operator()(const std::string & value)const{
                        return (value=="auto")? auto_value : internal::string_to<int>(value);
                    }
                };
                string_to_number_of_threads string_to_number_of_threads(range.auto_number_of_threads());
                int low, high;
                std::size_t colon = s.find(':');
                if ( colon == std::string::npos ){
                    low = high = string_to_number_of_threads(s);
                } else {
                    //it is a range
                    std::size_t second_colon = s.find(':',colon+1);

                    low  = string_to_number_of_threads(std::string(s, 0, colon)); //not copying the colon
                    high = string_to_number_of_threads(std::string(s, colon+1, second_colon - (colon+1))); //not copying the colons
                    if (second_colon != std::string::npos){
                        internal::string_to(std::string(s,second_colon + 1),range.step);
                    }
                }
                range = thread_number_range(range.auto_number_of_threads,low,high,range.step);
            }catch(std::invalid_argument&){
                i.setstate(std::ios::failbit);
                throw;
            }
            return i;
        }
        friend std::ostream& operator<<(std::ostream& o, thread_number_range const& range){
            using namespace internal;
            size_t i = 0;
            for (; i < array_length(step_function_descriptors) && step_function_descriptors[i].function != range.step.step_function; ++i ) {}
            if (i >= array_length(step_function_descriptors)){
                throw std::invalid_argument("unknown step function for thread range");
            }
            o<<range.first<<":"<<range.last<<":"<<step_function_descriptors[i].mnemonic<<range.step.step_function_argument;
            return o;
        }
    }; // struct thread_number_range
    //TODO: fix unused warning here
    //TODO: update the thread range description in the .html files
    static const char* thread_number_range_desc="number of threads to use; a range of the form low[:high[:(+|*|#)step]],"
                                                "\n\twhere low and optional high are non-negative integers or 'auto' for the default choice,"
                                                "\n\tand optional step expression specifies how thread numbers are chosen within the range."
                                                "\n\tSee examples/common/index.html for detailed description."
   ;

    inline void report_elapsed_time(double seconds){
        std::cout << "elapsed time : "<<seconds<<" seconds \n";
    }

    inline void parse_cli_arguments(int argc, const char* argv[], utility::cli_argument_pack cli_pack){
        bool show_help = false;
        cli_pack.arg(show_help,"-h","show this message");

        bool invalid_input=false;
        try {
            cli_pack.parse(argc,argv);
        }catch(std::exception& e){
            std::cerr
                    <<"error occurred while parsing command line."<<std::endl
                    <<"error text: "<<e.what()<<std::endl
                    <<std::flush;
            invalid_input =true;
        }
        if (show_help || invalid_input){
            std::cout<<cli_pack.usage_string(argv[0])<<std::flush;
            std::exit(0);
        }

    }
    inline void parse_cli_arguments(int argc, char* argv[], utility::cli_argument_pack cli_pack){
         parse_cli_arguments(argc, const_cast<const char**>(argv), cli_pack);
    }
}

#endif /* UTILITY_H_ */
