#include "scalar.h"
#include "state.h"

using namespace Frost;

const s_str Scalar::TYPE = "Scalar";

s_double factorial(s_double d)
{
    if (fabs(d.Get() - 1.0) < 0.1)
        return 1.0;
    else
        return round(d.Get())*factorial(d - 1.0);
}

s_str Scalar::PrintContent() const
{
    if (name.IsEmpty())
        return s_str::Convert(v);
    else
        return name + " = " + v;
}

s_str Scalar::GetType() const
{
    return TYPE;
}

s_refptr<Symbol> Scalar::Clone() const
{
    return s_refptr<Symbol>(new Scalar(*this));
}

s_bool Scalar::IsNumber(const s_str& s)
{
    s_str t = s;
    t.Trim(' ');
    if (t.StartsWith("+") || t.StartsWith("-"))
        t.EraseFromStart(1);

    s_bool bCommaFound;
    s_str::iterator iter = t.Begin();
    while (iter != t.End())
    {
        if (*iter == '.')
        {
            if (!bCommaFound) bCommaFound = true;
            else return false;
        }
        else if (!s_str::IsNumber(*iter))
            break;

        ++iter;
    }

    if (iter == t.End())
        return true;

    if (*iter != 'e')
        return false;

    ++iter;
    if (iter == t.End())
        return false;

    if (*iter == '+' || *iter == '-')
    {
        ++iter;
        if (iter == t.End())
            return false;
    }

    while (iter != t.End())
    {
        if (!s_str::IsNumber(*iter))
            return false;

        ++iter;
    }

    return true;
}

s_bool Scalar::MatchesConstructor(const s_str& s)
{
    return IsNumber(s);
}

s_refptr<Symbol> Scalar::CreateFrom(State& mState, const s_str& n, const s_str& s, s_str& e)
{
    return s_refptr<Symbol>(new Scalar(n, s_double(s)));
}

void Scalar::Register(State& mState)
{
    mState.RegisterSymbolType<Scalar>();
    mState.RegisterConstructor<Scalar>(&Scalar::MatchesConstructor, &Scalar::CreateFrom);

    mState.RegisterLeftUnaryOperator<Scalar>("!", [] (State& s, s_refptr<Symbol> a, s_str& e) -> s_refptr<Symbol> {
        s_refptr<Scalar> sa = s_refptr<Scalar>::StaticCast(a);
        return s_refptr<Symbol>(new Scalar("", factorial(sa->v)));
    });
    mState.RegisterBinaryOperator<Scalar,Scalar>("*", [] (State& s, s_refptr<Symbol> a, s_refptr<Symbol> b, s_str& e) -> s_refptr<Symbol> {
        s_refptr<Scalar> sa = s_refptr<Scalar>::StaticCast(a), sb = s_refptr<Scalar>::StaticCast(b);
        return s_refptr<Symbol>(new Scalar("", sa->v*sb->v));
    });
    mState.RegisterBinaryOperator<Scalar,Scalar>("/", [] (State& s, s_refptr<Symbol> a, s_refptr<Symbol> b, s_str& e) -> s_refptr<Symbol> {
        s_refptr<Scalar> sa = s_refptr<Scalar>::StaticCast(a), sb = s_refptr<Scalar>::StaticCast(b);
        return s_refptr<Symbol>(new Scalar("", sa->v/sb->v));
    });
    mState.RegisterBinaryOperator<Scalar,Scalar>("+", [] (State& s, s_refptr<Symbol> a, s_refptr<Symbol> b, s_str& e) -> s_refptr<Symbol> {
        s_refptr<Scalar> sa = s_refptr<Scalar>::StaticCast(a), sb = s_refptr<Scalar>::StaticCast(b);
        return s_refptr<Symbol>(new Scalar("", sa->v + sb->v));
    });
    mState.RegisterBinaryOperator<Scalar,Scalar>("-", [] (State& s, s_refptr<Symbol> a, s_refptr<Symbol> b, s_str& e) -> s_refptr<Symbol> {
        s_refptr<Scalar> sa = s_refptr<Scalar>::StaticCast(a), sb = s_refptr<Scalar>::StaticCast(b);
        return s_refptr<Symbol>(new Scalar("", sa->v - sb->v));
    });
    mState.RegisterRightUnaryOperator<Scalar>("+", [] (State& s, s_refptr<Symbol> a, s_str& e) -> s_refptr<Symbol> {
        return a->Clone();
    });
    mState.RegisterRightUnaryOperator<Scalar>("-", [] (State& s, s_refptr<Symbol> a, s_str& e) -> s_refptr<Symbol> {
        s_refptr<Scalar> sa = s_refptr<Scalar>::StaticCast(a);
        return s_refptr<Symbol>(new Scalar("", -sa->v));
    });

    mState.RegisterFunction("rand", [] (State& s, const s_ctnr<s_refptr<Symbol>>& args, s_ctnr<s_refptr<Symbol>>& res, s_str& e) -> s_bool {
        res = {s_refptr<Symbol>(new Scalar("", s_double::Random()))};
        return true;
    });
    mState.RegisterFunction<Scalar,Scalar>("rand", [] (State& s, const s_ctnr<s_refptr<Symbol>>& args, s_ctnr<s_refptr<Symbol>>& res, s_str& e) -> s_bool {
        s_refptr<Scalar> sa = s_refptr<Scalar>::StaticCast(args[0]), sb = s_refptr<Scalar>::StaticCast(args[1]);
        res = {s_refptr<Symbol>(new Scalar("", s_double::Random(sa->v, sb->v)))};
        return true;
    });
    mState.RegisterFunction<Scalar,Scalar>("min", [] (State& s, const s_ctnr<s_refptr<Symbol>>& args, s_ctnr<s_refptr<Symbol>>& res, s_str& e) -> s_bool {
        s_refptr<Scalar> sa = s_refptr<Scalar>::StaticCast(args[0]), sb = s_refptr<Scalar>::StaticCast(args[1]);
        res = {s_refptr<Symbol>(new Scalar("", s_double::Min(sa->v, sb->v)))};
        return true;
    });
    mState.RegisterFunction<Scalar,Scalar>("max", [] (State& s, const s_ctnr<s_refptr<Symbol>>& args, s_ctnr<s_refptr<Symbol>>& res, s_str& e) -> s_bool {
        s_refptr<Scalar> sa = s_refptr<Scalar>::StaticCast(args[0]), sb = s_refptr<Scalar>::StaticCast(args[1]);
        res = {s_refptr<Symbol>(new Scalar("", s_double::Max(sa->v, sb->v)))};
        return true;
    });
    mState.RegisterFunction<Scalar>("round", [] (State& s, const s_ctnr<s_refptr<Symbol>>& args, s_ctnr<s_refptr<Symbol>>& res, s_str& e) -> s_bool {
        s_refptr<Scalar> sa = s_refptr<Scalar>::StaticCast(args[0]), sb = s_refptr<Scalar>::StaticCast(args[1]);
        res = {s_refptr<Symbol>(new Scalar("", s_double::Round(sa->v)))};
        return true;
    });
    mState.RegisterFunction<Scalar>("round_up", [] (State& s, const s_ctnr<s_refptr<Symbol>>& args, s_ctnr<s_refptr<Symbol>>& res, s_str& e) -> s_bool {
        s_refptr<Scalar> sa = s_refptr<Scalar>::StaticCast(args[0]), sb = s_refptr<Scalar>::StaticCast(args[1]);
        res = {s_refptr<Symbol>(new Scalar("", s_double::RoundUp(sa->v)))};
        return true;
    });
    mState.RegisterFunction<Scalar>("round_down", [] (State& s, const s_ctnr<s_refptr<Symbol>>& args, s_ctnr<s_refptr<Symbol>>& res, s_str& e) -> s_bool {
        s_refptr<Scalar> sa = s_refptr<Scalar>::StaticCast(args[0]), sb = s_refptr<Scalar>::StaticCast(args[1]);
        res = {s_refptr<Symbol>(new Scalar("", s_double::RoundDown(sa->v)))};
        return true;
    });
}
