// Boost.TypeErasure library
//
// Copyright 2012 Steven Watanabe
//
// 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)
//
// $Id$

//[printf
/*`
    (For the source of this example see
    [@boost:/libs/type_erasure/example/printf.cpp printf.cpp])

    This example uses the library to implement a type safe printf.

    [note This example uses C++11 features.  You'll need a
    recent compiler for it to work.]
 */

#include <boost/type_erasure/builtin.hpp>
#include <boost/type_erasure/operators.hpp>
#include <boost/type_erasure/any_cast.hpp>
#include <boost/type_erasure/any.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/io/ios_state.hpp>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <vector>
#include <string>

namespace mpl = boost::mpl;
using namespace boost::type_erasure;
using namespace boost::io;

// We capture the arguments by reference and require nothing
// except that each one must provide a stream insertion operator.
typedef any<
    mpl::vector<
        typeid_<>,
        ostreamable<>
    >,
    const _self&
> any_printable;
typedef std::vector<any_printable> print_storage;

// Forward declaration of the implementation function
void print_impl(std::ostream& os, const char * format, const print_storage& args);

// print
//
// Writes values to a stream like the classic C printf function.  The
// arguments are formatted based on specifiers in the format string,
// which match the pattern:
//
// '%' [ argument-number '$' ] flags * [ width ] [ '.' precision ] [ type-code ] format-specifier
//
// Other characters in the format string are written to the stream unchanged.
// In addition the sequence, "%%" can be used to print a literal '%' character.
// Each component is explained in detail below
//
// argument-number:
//   The value must be between 1 and sizeof... T.  It indicates the
//   index of the argument to be formatted.  If no index is specified
//   the arguments will be processed sequentially.  If an index is
//   specified for one argument, then it must be specified for every argument.
//
// flags:
//   Consists of zero or more of the following:
//   '-': Left justify the argument
//   '+': Print a plus sign for positive integers
//   '0': Use leading 0's to pad instead of filling with spaces.
//   ' ': If the value doesn't begin with a sign, prepend a space
//   '#': Print 0x or 0 for hexadecimal and octal numbers.
//
// width:
//   Indicates the minimum width to print.  This can be either
//   an integer or a '*'.  an asterisk means to read the next
//   argument (which must have type int) as the width.
//
// precision:
//   For numeric arguments, indicates the number of digits to print.  For
//   strings (%s) the precision indicates the maximum number of characters
//   to print.  Longer strings will be truncated.  As with width
//   this can be either an integer or a '*'.  an asterisk means
//   to read the next argument (which must have type int) as
//   the width.  If both the width and the precision are specified
//   as '*', the width is read first.
//
// type-code:
//   This is ignored, but provided for compatibility with C printf.
//
// format-specifier:
//   Must be one of the following characters:
//   d, i, u: The argument is formatted as a decimal integer
//   o:       The argument is formatted as an octal integer
//   x, X:    The argument is formatted as a hexadecimal integer
//   p:       The argument is formatted as a pointer
//   f:       The argument is formatted as a fixed point decimal
//   e, E:    The argument is formatted in exponential notation
//   g, G:    The argument is formatted as either fixed point or using
//            scientific notation depending on its magnitude
//   c:       The argument is formatted as a character
//   s:       The argument is formatted as a string
//
template<class... T>
void print(std::ostream& os, const char * format, const T&... t)
{
    // capture the arguments
    print_storage args = { any_printable(t)... };
    // and forward to the real implementation
    print_impl(os, format, args);
}

// This overload of print with no explicit stream writes to std::cout.
template<class... T>
void print(const char * format, const T&... t)
{
    print(std::cout, format, t...);
}

// The implementation from here on can be separately compiled.

// utility function to parse an integer
int parse_int(const char *& format) {
    int result = 0;
    while(char ch = *format) {
        switch(ch) {
        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
            result = result * 10 + (ch - '0');
            break;
        default: return result;
        }
        ++format;
    }
    return result;
}

// printf implementation
void print_impl(std::ostream& os, const char * format, const print_storage& args) {
    int idx = 0;
    ios_flags_saver savef_outer(os, std::ios_base::dec);
    bool has_positional = false;
    bool has_indexed = false;
    while(char ch = *format++) {
        if (ch == '%') {
            if (*format == '%') { os << '%'; continue; }

            ios_flags_saver savef(os);
            ios_precision_saver savep(os);
            ios_fill_saver savefill(os);
            
            int precision = 0;
            bool pad_space = false;
            bool pad_zero = false;

            // parse argument index
            if (*format != '0') {
                int i = parse_int(format);
                if (i != 0) {
                    if(*format == '$') {
                        idx = i - 1;
                        has_indexed = true;
                        ++format;
                    } else {
                        os << std::setw(i);
                        has_positional = true;
                        goto parse_precision;
                    }
                } else {
                    has_positional = true;
                }
            } else {
                has_positional = true;
            }

            // Parse format modifiers
            while((ch = *format)) {
                switch(ch) {
                case '-': os << std::left; break;
                case '+': os << std::showpos; break;
                case '0': pad_zero = true; break;
                case ' ': pad_space = true; break;
                case '#': os << std::showpoint << std::showbase; break;
                default: goto parse_width;
                }
                ++format;
            }

        parse_width:
            int width;
            if (*format == '*') {
                ++format;
                width = any_cast<int>(args.at(idx++));
            } else {
                width = parse_int(format);
            }
            os << std::setw(width);

        parse_precision:
            if (*format == '.') {
                ++format;
                if (*format == '*') {
                    ++format;
                    precision = any_cast<int>(args.at(idx++));
                } else {
                    precision = parse_int(format);
                }
                os << std::setprecision(precision);
            }

            // parse (and ignore) the type modifier
            switch(*format) {
            case 'h': ++format; if(*format == 'h') ++format; break;
            case 'l': ++format; if(*format == 'l') ++format; break;
            case 'j':
            case 'L':
            case 'q':
            case 't':
            case 'z':
                ++format; break;
            }

            std::size_t truncate = 0;

            // parse the format code
            switch(*format++) {
            case 'd': case 'i': case 'u': os << std::dec; break;
            case 'o': os << std::oct; break;
            case 'p': case 'x': os << std::hex; break;
            case 'X': os << std::uppercase << std::hex; break;
            case 'f': os << std::fixed; break;
            case 'e': os << std::scientific; break;
            case 'E': os << std::uppercase << std::scientific; break;
            case 'g': break;
            case 'G': os << std::uppercase; break;
            case 'c': case 'C': break;
            case 's': case 'S': truncate = precision; os << std::setprecision(6); break;
            default: assert(!"Bad format string");
            }

            if (pad_zero && !(os.flags() & std::ios_base::left)) {
                os << std::setfill('0') << std::internal;
                pad_space = false;
            }

            if (truncate != 0 || pad_space) {
                // These can't be handled by std::setw.  Write to a stringstream and
                // pad/truncate manually.
                std::ostringstream oss;
                oss.copyfmt(os);
                oss << args.at(idx++);
                std::string data = oss.str();

                if (pad_space) {
                    if (data.empty() || (data[0] != '+' && data[0] != '-' && data[0] != ' ')) {
                        os << ' ';
                    }
                }
                if (truncate != 0 && data.size() > truncate) {
                    data.resize(truncate);
                }
                os << data;
            } else {
                os << args.at(idx++);
            }

            // we can't have both positional and indexed arguments in
            // the format string.
            assert(has_positional ^ has_indexed);

        } else {
            std::cout << ch;
        }
    }
}

int main() {
    print("int: %d\n", 10);
    print("int: %0#8X\n", 0xA56E);
    print("double: %g\n", 3.14159265358979323846);
    print("double: %f\n", 3.14159265358979323846);
    print("double: %+20.9e\n", 3.14159265358979323846);
    print("double: %0+20.9g\n", 3.14159265358979323846);
    print("double: %*.*g\n", 20, 5, 3.14159265358979323846);
    print("string: %.10s\n", "Hello World!");
    print("double: %2$*.*g int: %1$d\n", 10, 20, 5, 3.14159265358979323846);
}

//]
