/*
 * Copyright (c) 2015-2017 Carsten Sonne Larsen  <cs@innolan.dk>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 *
 */

#include "clib.h"
#include "math.h"
#include "dprint.h"
#include "complex.h"
#include "lib/real.h"
#include "lib/cplex.h"
#include "lib/ntext.h"
#include "lib/charbuf.h"

/******************************************************************************/

NumeralSystem::NumeralSystem()
{
    buf = new CharBuffer();
    buf->Empty();
}

NumeralSystem::~NumeralSystem()
{
    delete buf;
}

Number* NumeralSystem::Parse(const char *text)
{
    unsigned int length;
    char *end;
    return Parse(text, &length, &end);
}

/******************************************************************************/

PositionalNumeralSystem::PositionalNumeralSystem(
    unsigned int base,
    unsigned int digits,
    const char fractionpoint) :
    base(base * 1.0), digits(digits) {
    this->fractionpoint = fractionpoint;
}

PositionalNumeralSystem::~PositionalNumeralSystem()
{ }

const char* PositionalNumeralSystem::GetName()
{
    switch ((int)base) {
    case 2:
        return "binary";
        break;
    case 8:
        return "octal";
        break;
    case 10:
        return "decimal";
        break;
    case 16:
        return "hexadecimal";
        break;
    }

    const char *text = "base ";
    Number *n = new RealNumber(base);
    NumeralSystem *ns = new DecimalSystem(2);
    const char *numtext = ns->GetText(n);

    buf->EnsureSize(StrLen(text) + StrLen(numtext) + 1);
    buf->Empty();
    buf->Append(text);
    buf->Append(numtext);

    delete ns;
    delete n;
    return buf->GetString();
}

const char* PositionalNumeralSystem::GetPrefix()
{
    // TODO: Implement
    return "";
}

unsigned int PositionalNumeralSystem::GetDigits()
{
    return digits;
}

void PositionalNumeralSystem::SetDigits(unsigned int digits)
{
    this->digits = digits;
}

const char PositionalNumeralSystem::GetFractionPoint()
{
    return (const char)this->fractionpoint;
}

void PositionalNumeralSystem::SetFractionPoint(const char fractionpoint)
{
    this->fractionpoint = fractionpoint;
}

const char* PositionalNumeralSystem::GetText(Number *number)
{
    if (number->system == nsysreal) {
        return GetText(number->GetRealValue());
    }

    complex w = ((ComplexNumber*)number)->GetComplexValue();
    double a = creal(w);
    double b = cimag(w);

    if (a == 0.0 && b == 0.0) {
        buf->Empty();
        buf->Append('0');
        return buf->GetString();
    }

    CharBuffer *val = new CharBuffer(512);
    val->Empty();

    if (a != 0.0) {
        const char *real = GetText(a);
        val->Append(real);
    }

    const char *imag = GetText(b);
    if (a != 0.0 && b > 0.0) {
        val->Append('+');
    }

    if (b != 0.0) {
        val->Append(imag);
        val->Append('i');
    }

    buf->Copy(val);
    delete val;

    return buf->GetString();
}

const char* PositionalNumeralSystem::GetText(double number)
{
    if (!finite(number)) {
        return "Inf";
    } else if (isnan(number)) {
        return "NaN";
    } else if (number == 0.0) {
        return "0";
    }

    buf->Empty();

    double dnumber = number;
    if (dnumber < 0.0) {
        buf->Append('-');
        dnumber = -dnumber;
    }

    double expborder = log2p(base, dnumber);
    int exponent = 0;

    // Find exponent
    if (expborder > 6.0 || expborder < -5.0) {
        double dexp = trunc(log2p(base, dnumber));

        // Adjust to keep one digits before dot.
        if (dexp < 0.0) {
            dexp -= 1.0;
        }

        dnumber = dnumber * pow(base, -dexp);
        exponent = (int)dexp;
    }

    int digitout;
    int intdigits;

    // NOTICE: 1.5.3 Scaled trunc
    int intvalue = (int)(trunc(dnumber * base) / base);
    IntegerToBuffer(intvalue, digits, &intdigits);

    int fragdigits = digits - intdigits;
    if (fragdigits > 0) {
        buf->Append(fractionpoint);

        // TODO: Fix fraction error (try 1.5439)
        double fraction = fabs(round((dnumber - round(dnumber)) * pow(base, fragdigits)));

        // NOTICE: 1.5.3 Changed from trunc to round
        double actualdigits = round(log2p(base, fraction));
        int padding = fragdigits - (finite(actualdigits) == 1 ? (int)actualdigits : 0) - 1;

        // Pad zeros if needed
        while (padding-- > 0) {
            buf->Append('0');
        }

        // NOTICE: 1.5.3 Scaled trunc
        intvalue = (int)(trunc(fraction * base) / base);
        IntegerToBuffer(intvalue, digits - intdigits, &digitout);

        // Remove trailing zeros
        while (buf->RemoveTrailing('0'))
            ;

        buf->RemoveTrailing(fractionpoint);
    }

    // Add exponent
    if (exponent != 0) {
        buf->Append('e');
        buf->Append(exponent > 0 ? '+' : '-');
        IntegerToBuffer(abs(exponent), 3, &digitout);
    }

    // Make sure no rounding error is returned
    if (buf->Is("-0")) {
        buf->Empty();
        buf->Append('0');
    }

    return buf->GetString();
}

void PositionalNumeralSystem::IntegerToBuffer(double value, unsigned int digits, int *outdigits)
{
    static const char *alphaNumerics = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    unsigned int count = 0;
    char *chars = new char[128]; // TODO: Find correct size
    char *start = chars;

    do {
        count++;
        unsigned int intremainder = (unsigned int) trunc(fmod(value, base));
        *chars++ = alphaNumerics[intremainder];
        value /= base;
    } while (value >= 1.0);

    unsigned int n = count;
    unsigned int q = digits;
    chars--;

    while (n-- != 0 && q-- != 0) {
        buf->Append(*chars--);
    }

    n++;
    while (n-- != 0) {
        buf->Append('0');
    }

    *outdigits = count;
    delete [] start;
}

Number* PositionalNumeralSystem::Parse(const char *text, unsigned int *length, char **end)
{
    unsigned int intbase = (unsigned int)base;
    char maxNumeric = (intbase > 10 ? 10 : intbase) + '0' - 1;
    char maxAlpha = intbase > 10 ? intbase + 'A' - 11 : 0;

    unsigned int pos = 0;
    double integer = 0;
    double addition;

    while (*text != '\0' && ((*text >= '0' && *text <= maxNumeric) || (maxAlpha != 0 && *text >= 'A' && *text <= maxAlpha))) {
        addition = ((*text >= '0' && *text <= maxNumeric) ? (*text - '0') : (*text - 'A' + 10)) * 1.0;
        integer = integer * base + addition;
        text++;
        pos++;
    }

    // Digits not found
    if (pos == 0) {
        *length = 0;
        *end = (char*)text;
        return new RealNumber();
    }

    double fraction = 0.0;
    double divisor = 1.0;
    if (*text == fractionpoint && fractionpoint != '\0') {
        text++;
        pos++;

        while (*text != '\0' && ((*text >= '0' && *text <= maxNumeric) || (maxAlpha != '\0' && *text >= 'A' && *text <= maxAlpha))) {
            addition = ((*text >= '0' && *text <= maxNumeric) ? (*text - '0') : (*text - 'A' + 10));
            fraction = fraction * base + addition;
            divisor *= base;
            text++;
            pos++;
        }
    }

    double exp = 0;
    if (*text == 'e' || *text == 'E') {
        text++;
        pos++;

        double sign = *text == '+' ? 1.0 : *text == '-' ? -1.0 : 0.0;

        if (sign != 0.0) {
            text++;
            pos++;

            while (*text != '\0' && ((*text >= '0' && *text <= maxNumeric) || (maxAlpha != 0 && *text >= 'A' && *text <= maxAlpha))) {
                addition = ((*text >= '0' && *text <= maxNumeric) ? (*text - '0') : (*text - 'A' + 10)) * 1.0;
                exp = exp * base + addition;
                text++;
                pos++;
            }

            exp *= sign;
        } else {
            text--;
            pos--;
        }
    }

    *length = pos;
    *end = (char*)text;
    double dnumber = (integer + (fraction / divisor)) * pow(base, exp);

    return new RealNumber(dnumber);
}

/******************************************************************************/

DecimalSystem::DecimalSystem() :
    PositionalNumeralSystem(10, 5, '\0')
{ }

DecimalSystem::DecimalSystem(unsigned int digits) :
    PositionalNumeralSystem(10, digits, '\0')
{ }

DecimalSystem::DecimalSystem(
    unsigned int digits,
    const char fractionpoint) :
    PositionalNumeralSystem(10, digits, fractionpoint)
{ }

DecimalSystem::~DecimalSystem()
{ }

const char* DecimalSystem::GetText(Number* number)
{
    buf->Empty();

    if (number->system == nsysreal) {
        return GetRealText(number->GetRealValue());
    }

    complex w = ((ComplexNumber*)number)->GetComplexValue();
    double a = creal(w);
    double b = cimag(w);

    if (a == 0.0 && b == 0.0) {
        buf->Append('0');
        return buf->GetString();
    }

    if (a != 0.0) {
        GetRealText(a);
    }

    if (a != 0.0 && b > 0.0) {
        buf->Append('+');
    }

    if (b != 0.0) {
        GetRealText(b);
        buf->Append('i');
    }

    return buf->GetString();
}

const char* DecimalSystem::GetRealText(double value)
{
    double exponent = log10(fabs(value));
    int formatdigits = digits - (int)trunc(exponent) - 1;

    double rounddigits = digits - 1;
    if (exponent < 0.0) {
        rounddigits++;
        formatdigits++;
    }

    // Remove output of inaccuracy
    if (formatdigits > (int)digits) {
        formatdigits = digits;
    }

    double roundfactor = pow(10.0, rounddigits);
    double roundedvalue = round(value * roundfactor) / roundfactor;

    if (roundedvalue == 0.0) {
        return "0";
    }

    double ulimit = pow(10.0, (double)digits);
    double llimit = pow(10.0, -(double)digits);

    if (fabs(roundedvalue) > ulimit) {
        return "INF";
    } else if (fabs(roundedvalue) < llimit) {
        return "-INF";
    }

    double expborder = log10(fabs(roundedvalue));
    tPrintFloatFormat format = (expborder > 6.0 || expborder < -5.0) ?
                               PrintFloatFormat_Scientific :
                               PrintFloatFormat_Positional;

    static const int size = 32;
    char *out = new char[size];
    PrintFloat64(out, size, roundedvalue, format, formatdigits, fractionpoint);
    buf->EnsureGrowth(size);
    buf->Append(out);
    while (buf->RemoveTrailing('0'))
        ;
    buf->RemoveTrailing(fractionpoint);
    delete[] out;

    return buf->GetString();
}

