/*
 * Copyright (c) 2002-2006 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.
 *
 * Authors: Nathan Binkert
 */

#include <cassert>
#include <iomanip>
#include <iostream>
#include <sstream>

#include "base/cprintf.hh"

using namespace std;

namespace cp {

Print::Print(std::ostream &stream, const std::string &format)
    : stream(stream), format(format.c_str()), ptr(format.c_str()), cont(false)
{
    saved_flags = stream.flags();
    saved_fill = stream.fill();
    saved_precision = stream.precision();
}

Print::Print(std::ostream &stream, const char *format)
    : stream(stream), format(format), ptr(format), cont(false)
{
    saved_flags = stream.flags();
    saved_fill = stream.fill();
    saved_precision = stream.precision();
}

Print::~Print()
{
}

void
Print::process()
{
    fmt.clear();

    size_t len;

    while (*ptr) {
        switch (*ptr) {
          case '%':
            if (ptr[1] != '%')
                goto processing;

            stream.put('%');
            ptr += 2;
            break;

          case '\n':
            stream << endl;
            ++ptr;
            break;
          case '\r':
            ++ptr;
            if (*ptr != '\n')
                stream << endl;
            break;

          default:
            len = strcspn(ptr, "%\n\r\0");
            stream.write(ptr, len);
            ptr += len;
            break;
        }
    }

    return;

  processing:
    bool done = false;
    bool end_number = false;
    bool have_precision = false;
    int number = 0;

    stream.fill(' ');
    stream.flags((ios::fmtflags)0);

    while (!done) {
        ++ptr;
        if (*ptr >= '0' && *ptr <= '9') {
            if (end_number)
                continue;
        } else if (number > 0)
            end_number = true;

        switch (*ptr) {
          case 's':
            fmt.format = Format::string;
            done = true;
            break;

          case 'c':
            fmt.format = Format::character;
            done = true;
            break;

          case 'l':
            continue;

          case 'p':
            fmt.format = Format::integer;
            fmt.base = Format::hex;
            fmt.alternate_form = true;
            done = true;
            break;

          case 'X':
            fmt.uppercase = true;
          case 'x':
            fmt.base = Format::hex;
            fmt.format = Format::integer;
            done = true;
            break;

          case 'o':
            fmt.base = Format::oct;
            fmt.format = Format::integer;
            done = true;
            break;

          case 'd':
          case 'i':
          case 'u':
            fmt.format = Format::integer;
            done = true;
            break;

          case 'G':
            fmt.uppercase = true;
          case 'g':
            fmt.format = Format::floating;
            fmt.float_format = Format::best;
            done = true;
            break;

          case 'E':
            fmt.uppercase = true;
          case 'e':
            fmt.format = Format::floating;
            fmt.float_format = Format::scientific;
            done = true;
            break;

          case 'f':
            fmt.format = Format::floating;
            fmt.float_format = Format::fixed;
            done = true;
            break;

          case 'n':
            stream << "we don't do %n!!!\n";
            done = true;
            break;

          case '#':
            fmt.alternate_form = true;
            break;

          case '-':
            fmt.flush_left = true;
            break;

          case '+':
            fmt.print_sign = true;
            break;

          case ' ':
            fmt.blank_space = true;
            break;

          case '.':
            fmt.width = number;
            fmt.precision = 0;
            have_precision = true;
            number = 0;
            end_number = false;
            break;

          case '0':
            if (number == 0) {
                fmt.fill_zero = true;
                break;
            }
          case '1':
          case '2':
          case '3':
          case '4':
          case '5':
          case '6':
          case '7':
          case '8':
          case '9':
            number = number * 10 + (*ptr - '0');
            break;

          case '*':
            if (have_precision)
                fmt.get_precision = true;
            else
                fmt.get_width = true;
            break;

          case '%':
            assert(false && "we shouldn't get here");
            break;

          default:
            done = true;
            break;
        }

        if (end_number) {
            if (have_precision)
                fmt.precision = number;
            else
                fmt.width = number;

            end_number = false;
            number = 0;
        }
    }

    ++ptr;
}

void
Print::end_args()
{
    size_t len;

    while (*ptr) {
        switch (*ptr) {
          case '%':
            if (ptr[1] != '%')
                stream << "<extra arg>";

            stream.put('%');
            ptr += 2;
            break;

          case '\n':
            stream << endl;
            ++ptr;
            break;
          case '\r':
            ++ptr;
            if (*ptr != '\n')
                stream << endl;
            break;

          default:
            len = strcspn(ptr, "%\n\r\0");
            stream.write(ptr, len);
            ptr += len;
            break;
        }
    }

    stream.flags(saved_flags);
    stream.fill(saved_fill);
    stream.precision(saved_precision);
}

} // namespace cp
