/*
 * mpo_numstr.cpp
 *
 * Copyright (C) 2005 Matthew P. Ownby
 *
 * This file is part of MPOLIB, a multi-purpose library
 *
 * MPOLIB is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * MPOLIB is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

// MPO's NOTE:
//  I may wish to use MPOLIB in a proprietary product some day.  Therefore,
//   the only way I can accept other people's changes to my code is if they
//   give me full ownership of those changes.

#include "config.h"

#include "numstr.h"

#ifndef WIN32
#include <ctype.h> // for toupper
#endif

const char *DIGITS = "0123456789ABCDEF";

// NOTE : this function doesn't do full safety checking in the interest of
// simplicity and speed
int numstr::ToInt32(const char *str)
{
    const int BASE = 10; // for now we always assume base is 10 because I never
                         // seem to use anything else
    int result             = 0;
    bool found_first_digit = false; // whether we have found the first digit or
                                    // not
    int sign_mult = 1; // 1 if the number is positive, -1 if it's negative

    for (unsigned int i = 0; i < my_strlen(str); i++) {
        if (!found_first_digit) {
            if (is_digit(str[i], BASE)) {
                found_first_digit = true;
            }

            // if it a negative number?
            else if (str[i] == '-') {
                sign_mult = -1;
            }

            // else it's an unknown character, so we ignore it until we get to
            // the first digit
        }

        // note: we do not want this to be an "else if" because the above 'if'
        // needs to flow into this
        if (found_first_digit) {
            // make sure we aren't dealing with any non-integers
            if ((str[i] >= '0') && (str[i] <= '9')) {
                result *= BASE;
                result += str[i] - '0';
            }
            // else we've hit unknown characters so we're done
            else {
                break;
            }
        }
    }

    return result * sign_mult;
}

unsigned int numstr::ToUint32(const char *str, int base)
{
    unsigned int u = 0;
    ToUint(str, u, base);
    return u;
}

uint64_t numstr::ToUint64(const char *str, int base)
{
    uint64_t u = 0;
    ToUint(str, u, base);
    return u;
}

double numstr::ToDouble(const char *str)
{
    const double BASE        = 10.0; // this makes it easier for now ...
    const double BASE_DIVIDE = 0.1;  // because multiplaying by 0.1 is faster
                                     // than dividing by 10.0
    bool found_period = false; // whether we've encountered the period in the
                               // double yet
    bool found_first_digit = false;
    double result          = 0.0;
    double divide_by       = 1.0; // after we pass the decimal point, we need to
                            // divide subsequent numbers to put them in their
                            // proper sphere
    double sign_mult = 1.0; // final result is multiplied by this to set the
                            // sign

    for (unsigned int i = 0; i < my_strlen(str); i++) {
        if (!found_first_digit) {
            if (is_digit(str[i], (int)BASE)) {
                found_first_digit = true;
            }

            // if it a negative number?
            else if (str[i] == '-') {
                sign_mult = -1.0;
            }

            else if (str[i] == '.') {
                found_period = true;
            }

            // else it's an unknown character, so we ignore it until we get to
            // the first digit
        }

        // note: we do not want this to be an "else if" because the above 'if'
        // needs to flow into this
        if (found_first_digit) {
            // make sure we aren't dealing with any non-integers
            if ((str[i] >= '0') && (str[i] <= '9')) {
                // if we haven't encountered the decimal place yet
                if (found_period == false) {
                    result *= BASE;
                    result += str[i] - '0';
                }
                // if we are passed the decimal place
                else {
                    divide_by *= BASE_DIVIDE; // each new digit we find needs to
                                              // be divided by 10x the previous
                                              // divide_by value
                    result += ((str[i] - '0') * divide_by);
                }
            }
            // else if we've hit the decimal point
            else if (str[i] == '.') {
                found_period = true;
            }
            // else we've hit unknown characters so we're done
            else {
                break;
            }
        }
    }

    return result * sign_mult;
}

// DUMMY functions to force template instantiation
// (since I can't figure out how to make the compiler do this without! arrgh)

string numstr::ToStr(int num, int base, unsigned int min_digits)
{
    unsigned int dummy = 0; // see comment in .h for IToStr for explanation
    return IToStr(num, dummy, base, min_digits);
}

string numstr::ToStr(int64_t num, int base, unsigned int min_digits)
{
    uint64_t dummy = 0; // see comment in .h for IToStr for explanation
    return IToStr(num, dummy, base, min_digits);
}

string numstr::ToStr(unsigned char c, int base, unsigned int min_digits)
{
    return UToStr(c, base, min_digits);
}

string numstr::ToStr(unsigned int u, int base, unsigned int min_digits)
{
    return UToStr(u, base, min_digits);
}

string numstr::ToStr(uint64_t u, int base, unsigned int min_digits)
{
    return UToStr(u, base, min_digits);
}

string numstr::ToStr(double d, unsigned int min_digits_before,
                     unsigned int min_digits_after, unsigned int max_digits_after)
{
    string result     = "(overflow)";
    const double BASE = 10.0; // we will only support base 10 with doubles to
                              // simplify things ...
    unsigned int decimal_length = 0;

    // bounds check: make sure the double is within our limits (2^62 was the
    // highest I could get it to work without introducing overflow errors)
    if ((d <= 4611686018427387904.0) && (d >= -4611686018427387904.0)) {
        int64_t int64_portion = (int64_t)d; // strip off floating point part
        d                     = d - int64_portion; // isolate just the decimal portion

        result = ToStr(int64_portion, 10, min_digits_before); // use our other
                                                              // function to
                                                              // calculate the
                                                              // int portion

        result = result + "."; // add decimal place, it will always be displayed
                               // even if there is no fractional value to this
                               // number

        if (d < 0) d *= -1.0; // force d to be positive

        // NOTE : d will now always be positive
        do {
            d *= BASE; // move decimal point one notch to the right
            int int_portion = (int)d; // grab the number that is above the
                                      // decimal point
            result = result + DIGITS[int_portion];
            d      = d - int_portion;
            decimal_length++; // gotta keep track of this for 'min_digits_after'
                              // calculation
        } while ((d != 0.0) && (max_digits_after > decimal_length));

        while (decimal_length < min_digits_after) {
            result = result + "0"; // pad trailing zeroes
            decimal_length++;
        }
    }
    // else return default result to indicate error

    return result;
}

string numstr::ToUnitStr(uint64_t u)
{
    string result;
    double d;

    // if less than 1 k
    if (u < 1024) {
        result = ToStr(u) + " B";
    }

    // less than 1 meg
    else if (u < 1048576) {
        d      = u * 0.0009765625; // same as dividing by 1024
        result = ToStr(d, 0, 1, 2) + " KiB";
    }

    // less than 1 gig
    else if (u < 1073741824) {
        d      = u / (1048576.0); // convert to megs
        result = ToStr(d, 0, 1, 2) + " MiB";
    }

    // else leave it as gigs, we won't go any higher for now
    else {
        d      = u / (1073741824.0); // convert to gigs
        result = ToStr(d, 0, 1, 2) + " GiB";
    }

    return result;
}

unsigned int numstr::my_strlen(const char *s)
{
    unsigned int i = 0;
    while (s[i] != 0) i++;
    return i;
}

////////////////////////////////
// private funcs

inline bool numstr::is_digit(char ch, int base)
{
    if ((base == 10) && (ch >= '0') && (ch <= '9'))
        return (true);
    else if ((base == 16) && (((ch >= '0') && (ch <= '9')) ||
                              ((toupper(ch) >= 'A') && (toupper(ch) <= 'F'))))
        return (true);
    // else no other base is supported

    return false;
}
