/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <iostream>
#include <sstream>

#include <math.h>
#include "macro.h"
#include "arith.h"


long CNumber::sPrec = 0;  //-- cout precision, default set in serve_macro(...)
long CNumber::sDefaultPrec = 12;


void CNumber::Print(void)
{
    const char* s;
    ToString(s);
    std::cout << s;
}

int CNumber::Write(FILE* f)
{
    const char* s;
    ToString(s);
    fprintf(f, "%s", s);
    return ferror(f);
}

void CNumber::ToString(const char*& x)
{
    static char buf[127];
    std::ostringstream os;


    if (floor(number) == number)
        os.precision(sDefaultPrec);  //-- integer values as integers
    else
        os.precision(sPrec);  //-- changed by precision(p)

    os << number << std::ends;
    strcpy(buf, os.str().c_str());
    x = buf;
}

void CNumber::ToRequest(request*& x)
{
    static request* r = nullptr;
    if (r == nullptr)
        r = empty_request("NUMBER");

    const char* s;
    ToString(s);
    set_value(r, "VALUE", "%s", s);
    x = r;
}

class UnOp : public Function
{
    uniproc F_;

public:
    UnOp(const char* n, uniproc f) :
        Function(n, 1, tnumber) { F_ = f; }
    virtual Value Execute(int arity, Value* arg);
};

class BinOp : public Function
{
    binproc F_;

public:
    BinOp(const char* n, binproc f) :
        Function(n, 2, tnumber, tnumber) { F_ = f; }
    virtual Value Execute(int arity, Value* arg);
};

class MulOp : public Function
{
    using binproc = double (*)(double, double);
    binproc F_;

public:
    MulOp(const char* n, binproc f) :
        Function(n) { F_ = f; }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};


Value BinOp::Execute(int, Value* arg)
{
    double d1, d2;

    arg[0].GetValue(d1);
    arg[1].GetValue(d2);

    return Value(F_(d1, d2));
}

Value UnOp::Execute(int, Value* arg)
{
    double d;
    arg[0].GetValue(d);
    return Value(F_(d));
}

int MulOp::ValidArguments(int arity, Value* arg)
{
    if (arity < 1)
        return false;
    for (int i = 0; i < arity; i++)
        if (arg[i].GetType() != tnumber)
            return false;
    return true;
}

Value MulOp::Execute(int arity, Value* arg)
{
    double d;
    arg[0].GetValue(d);

    for (int i = 1; i < arity; i++) {
        double x;
        arg[i].GetValue(x);
        d = F_(d, x);
    }

    return Value(d);
}

//==================================================================
class SampleF : public Function
{
public:
    SampleF(const char* n) :
        Function(n, 1, tnumber) {}
    virtual Value Execute(int arity, Value* arg);
};

Value SampleF::Execute(int, Value* arg)
{
    double d;
    arg[0].GetValue(d);

    return Value(2 * d);
}

//==================================================================
class NumberRoundOff : public Function
{
public:
    NumberRoundOff(const char* n) :
        Function(n, 2, tnumber, tnumber)
    {
        info = "Rounds spurious decimals in a value.";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int NumberRoundOff::ValidArguments(int arity, Value* arg)
{
    if (arity != 2)
        return false;

    if ((arg[0].GetType() != tnumber) || (arg[1].GetType() != tnumber))
        return false;

    return true;
}

Value NumberRoundOff::Execute(int, Value* arg)
{
    double v, r;
    int d;
    arg[0].GetValue(v);
    arg[1].GetValue(d);

    double sgn = v < 0 ? -1.0 : 1.0;
    double e1 = pow(10.0, d);

    if (v < 0)
        v = -v;

#if 0
  double dd = (v + 0.5/e1) * e1;        //-- for debugging
  double df = floor(dd);
  r = df / e1;
#else
    r = floor((v + 0.5 / e1) * e1) / e1;
#endif

    return Value(sgn * r);
}

//==================================================================
class NumberPrecision : public Function
{
public:
    NumberPrecision(const char* n) :
        Function(n, 1, tnumber)
    {
        info = "Sets the printing precision for floating point values.";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int NumberPrecision::ValidArguments(int arity, Value* arg)
{
    if (arity == 0)
        return true;

    if (arity > 1)
        return false;

    if ((arg[0].GetType() != tnumber))
        return false;

    return true;
}

Value NumberPrecision::Execute(int arity, Value* arg)
{
    long oldPrec = CNumber::GetPrecision();
    long prec = CNumber::GetDefaultPrecision();  //-- precision not given
    if (arity == 1)
        arg[0].GetValue(prec);  //-- precision given

    CNumber::SetPrecision(prec);

    return Value(oldPrec);
}


/*******************************************************************************
 *
 * Function      : NumberIntBits
 *
 * Description   : Converts the given number into an integer and then returns
 *                 the value of a given bit or bits, starting from the least
 *                 significant. For example, intbits (4, 1) = 0,
 *                 intbits (4, 2) = 0, intbits (4, 3) = 1, intbits (4, 4) = 0,
 *                 ... The user can also specify the number of bits to extract,
 *                 for example intbits (3, 1, 2): this extracts the first
 *                 2 bits from the number 3, returning the result of 3.
 *                 As another example, intbits (12, 3, 2) = 3.
 *                 This function was originally written to help with ODB
 *                 access.
 *
 ******************************************************************************/

class NumberIntBits : public Function
{
public:
    NumberIntBits(const char* n) :
        Function(n)
    {
        info = "Returns value of a given range of bits in an integer";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int NumberIntBits::ValidArguments(int arity, Value* arg)
{
    if (arity != 2 && arity != 3)  // only accept 2- and 3-argument calls
        return false;

    if ((arg[0].GetType() != tnumber) || (arg[1].GetType() != tnumber))
        return false;  // arguments 1 and 2 must be numbers

    if ((arity == 3) && (arg[2].GetType() != tnumber))
        return false;  // argument 3, if supplied, must be a number

    return true;
}

Value NumberIntBits::Execute(int arity, Value* arg)
{
    int value, bitindex;
    int bitmask = 0;
    int bits;
    int i;
    int num_bits = 1;
    int max_bits_in_long;
    arg[0].GetValue(value);
    arg[1].GetValue(bitindex);


    // how many bits can we hold in a 'long'?

    max_bits_in_long = sizeof(long) * 8;


    // get the number of bits we want to retrieve

    if (arity == 3) {
        arg[2].GetValue(num_bits);
    }


    // sensible numbers of bits only, please

    if (num_bits < 1) {
        return Error("The number of bits must be between 1 and %d inclusive.", max_bits_in_long);
    }


    // we cannot handle bit indexes outside of a sensible range

    if (bitindex < 1 || (bitindex + num_bits - 1) > max_bits_in_long) {
        return Error("The bit indexes must be between 1 and %d inclusive.", max_bits_in_long);
    }


    // compute the bitmask we will use to isolate the desired bit(s)

    for (i = 0; i < num_bits; i++) {
        bitmask = bitmask | (int)pow(2.0, (bitindex - 1 + i));
    }


    // isolate the desired bits using the bitmask

    bits = value & bitmask;


    // shift the result down. For instance, if the 5th bit is set, we
    // just want the result to be 1 when checking for that bit. If
    // we are checking bits 10 and 11, then we want a result between
    // 0 and 3 inclusive. So what we want is the position-independent
    // value of the bit(s).

    return Value(bits >> (bitindex - 1));
}


//==================================================================

static void install(Context* c)
{
    c->AddGlobal(new Variable("e", Value(M_E)));
    c->AddGlobal(new Variable("pi", Value(M_PI)));

    int i;

    for (i = 0; BinOps[i].symb; i++)
        c->AddFunction(new BinOp(BinOps[i].symb, BinOps[i].proc));

    for (i = 0; MulOps[i].symb; i++)
        c->AddFunction(new MulOp(MulOps[i].symb, MulOps[i].proc));

    for (i = 0; UniOps[i].symb; i++)
        c->AddFunction(new UnOp(UniOps[i].symb, UniOps[i].proc));

    c->AddFunction(new NumberRoundOff("round"));
    c->AddFunction(new NumberPrecision("precision"));
    c->AddFunction(new NumberIntBits("intbits"));

    c->AddFunction(new SampleF("twice"));
}

static Linkage Link(install);
