/*
 * Copyright (c) 2003-2005 The Regents of The University of Michigan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef __CPRINTF_FORMATS_HH__
#define __CPRINTF_FORMATS_HH__

struct Format
{
    bool alternate_form;
    bool flush_left;
    bool print_sign;
    bool blank_space;
    bool fill_zero;
    bool uppercase;
    enum { dec, hex, oct } base;
    enum { none, string, integer, character, floating } format;
    enum { best, fixed, scientific } float_format;
    int precision;
    int width;

    Format() { clear(); }

    void clear()
    {
        alternate_form = false;
        flush_left = false;
        print_sign = false;
        blank_space = false;
        fill_zero = false;
        uppercase = false;
        base = dec;
        format = none;
        precision = -1;
        width = 0;
    }
};

template <typename T>
inline void
_format_char(std::ostream &out, const T &data, Format &fmt)
{
    using namespace std;

    out << data;
}

template <typename T>
inline void
_format_integer(std::ostream &out, const T &data, Format &fmt)
{
    using namespace std;

    switch (fmt.base) {
      case Format::hex:
        out.setf(ios::hex, ios::basefield);
        break;

      case Format::oct:
        out.setf(ios::oct, ios::basefield);
        break;

      case Format::dec:
        out.setf(ios::dec, ios::basefield);
        break;
    }

    if (fmt.alternate_form) {
        if (!fmt.fill_zero)
            out.setf(ios::showbase);
        else {
            switch (fmt.base) {
              case Format::hex:
                out << "0x";
                fmt.width -= 2;
                break;
              case Format::oct:
                out << "0";
                fmt.width -= 1;
                break;
              case Format::dec:
                break;
            }
        }
    }

    if (fmt.fill_zero)
        out.fill('0');

    if (fmt.width > 0)
        out.width(fmt.width);

    if (fmt.flush_left && !fmt.fill_zero)
        out.setf(ios::left);

    if (fmt.print_sign)
        out.setf(ios::showpos);

    if (fmt.uppercase)
        out.setf(ios::uppercase);

    out << data;
}

template <typename T>
inline void
_format_float(std::ostream &out, const T &data, Format &fmt)
{
    using namespace std;

    switch (fmt.float_format) {
      case Format::scientific:
        if (fmt.precision != -1) {
            if (fmt.width > 0)
                out.width(fmt.width);

            if (fmt.precision == 0)
                fmt.precision = 1;
            else
                out.setf(ios::scientific);

            out.precision(fmt.precision);
        } else
            if (fmt.width > 0)
                out.width(fmt.width);

        if (fmt.uppercase)
            out.setf(ios::uppercase);
        break;

      case Format::fixed:
        if (fmt.precision != -1) {
            if (fmt.width > 0)
                out.width(fmt.width);

            out.setf(ios::fixed);
            out.precision(fmt.precision);
        } else
            if (fmt.width > 0)
                out.width(fmt.width);

        break;

      default:
        if (fmt.precision != -1)
            out.precision(fmt.precision);

        if (fmt.width > 0)
            out.width(fmt.width);

        break;
    }

    out << data;
}

template <typename T>
inline void
_format_string(std::ostream &out, const T &data, Format &fmt)
{
    using namespace std;

#if defined(__GNUC__) && (__GNUC__ < 3) || 1
    if (fmt.width > 0) {
        std::stringstream foo;
        foo << data;
        int flen = foo.str().size();

        if (fmt.width > flen) {
            char *spaces = new char[fmt.width - flen + 1];
            memset(spaces, ' ', fmt.width - flen);
            spaces[fmt.width - flen] = 0;

            if (fmt.flush_left)
                out << foo.str() << spaces;
            else
                out << spaces << foo.str();

            delete [] spaces;
        } else
            out << data;
    } else
        out << data;
#else
    if (fmt.width > 0)
        out.width(fmt.width);
    if (fmt.flush_left)
        out.setf(ios::left);

    out << data;
#endif
}

/////////////////////////////////////////////////////////////////////////////
//
//  The code below controls the actual usage of formats for various types
//

//
// character formats
//
template <typename T>
inline void
format_char(std::ostream &out, const T &data, Format &fmt)
{ out << "<bad arg type for char format>"; }

inline void
format_char(std::ostream &out, char data, Format &fmt)
{ _format_char(out, data, fmt); }

inline void
format_char(std::ostream &out, unsigned char data, Format &fmt)
{ _format_char(out, data, fmt); }

inline void
format_char(std::ostream &out, signed char data, Format &fmt)
{ _format_char(out, data, fmt); }

inline void
format_char(std::ostream &out, short data, Format &fmt)
{ _format_char(out, (char)data, fmt); }

inline void
format_char(std::ostream &out, unsigned short data, Format &fmt)
{ _format_char(out, (char)data, fmt); }

inline void
format_char(std::ostream &out, int data, Format &fmt)
{ _format_char(out, (char)data, fmt); }

inline void
format_char(std::ostream &out, unsigned int data, Format &fmt)
{ _format_char(out, (char)data, fmt); }

inline void
format_char(std::ostream &out, long data, Format &fmt)
{ _format_char(out, (char)data, fmt); }

inline void
format_char(std::ostream &out, unsigned long data, Format &fmt)
{ _format_char(out, (char)data, fmt); }

inline void
format_char(std::ostream &out, long long data, Format &fmt)
{ _format_char(out, (char)data, fmt); }

inline void
format_char(std::ostream &out, unsigned long long data, Format &fmt)
{ _format_char(out, (char)data, fmt); }

//
// integer formats
//
template <typename T>
inline void
format_integer(std::ostream &out, const T &data, Format &fmt)
{ _format_integer(out, data, fmt); }
inline void
format_integer(std::ostream &out, char data, Format &fmt)
{ _format_integer(out, data, fmt); }
inline void
format_integer(std::ostream &out, unsigned char data, Format &fmt)
{ _format_integer(out, data, fmt); }
inline void
format_integer(std::ostream &out, signed char data, Format &fmt)
{ _format_integer(out, data, fmt); }
#if 0
inline void
format_integer(std::ostream &out, short data, Format &fmt)
{ _format_integer(out, data, fmt); }
inline void
format_integer(std::ostream &out, unsigned short data, Format &fmt)
{ _format_integer(out, data, fmt); }
inline void
format_integer(std::ostream &out, int data, Format &fmt)
{ _format_integer(out, data, fmt); }
inline void
format_integer(std::ostream &out, unsigned int data, Format &fmt)
{ _format_integer(out, data, fmt); }
inline void
format_integer(std::ostream &out, long data, Format &fmt)
{ _format_integer(out, data, fmt); }
inline void
format_integer(std::ostream &out, unsigned long data, Format &fmt)
{ _format_integer(out, data, fmt); }
inline void
format_integer(std::ostream &out, long long data, Format &fmt)
{ _format_integer(out, data, fmt); }
inline void
format_integer(std::ostream &out, unsigned long long data, Format &fmt)
{ _format_integer(out, data, fmt); }
#endif

//
// floating point formats
//
template <typename T>
inline void
format_float(std::ostream &out, const T &data, Format &fmt)
{ out << "<bad arg type for float format>"; }

inline void
format_float(std::ostream &out, float data, Format &fmt)
{ _format_float(out, data, fmt); }

inline void
format_float(std::ostream &out, double data, Format &fmt)
{ _format_float(out, data, fmt); }

//
// string formats
//
template <typename T>
inline void
format_string(std::ostream &out, const T &data, Format &fmt)
{ _format_string(out, data, fmt); }

inline void
format_string(std::ostream &out, const std::stringstream &data, Format &fmt)
{ _format_string(out, data.str(), fmt); }

#endif // __CPRINTF_FORMATS_HH__