/*
 Copyright (C) 2022 Fredrik Öhrström (gpl-3.0-or-later)

 This program 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 3 of the License, or
 (at your option) any later version.

 This program 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, see <http://www.gnu.org/licenses/>.
*/

#include"formula.h"
#include"formula_implementation.h"
#include"meters.h"
#include"units.h"

#include<cmath>
#include<string.h>
#include<limits>

NumericFormula::~NumericFormula() { }
NumericFormulaConstant::~NumericFormulaConstant() { }
NumericFormulaMeterField::~NumericFormulaMeterField() { }
NumericFormulaDVEntryField::~NumericFormulaDVEntryField() { }
NumericFormulaPair::~NumericFormulaPair() { }
NumericFormulaAddition::~NumericFormulaAddition() { }
NumericFormulaSubtraction::~NumericFormulaSubtraction() { }
NumericFormulaMultiplication::~NumericFormulaMultiplication() { }
NumericFormulaDivision::~NumericFormulaDivision() { }
NumericFormulaExponentiation::~NumericFormulaExponentiation() { }
NumericFormulaSquareRoot::~NumericFormulaSquareRoot() { }

double NumericFormulaConstant::calculate(SIUnit to)
{
    double r {};
    siunit().convertTo(constant_, to, &r);
    return r;
}

double NumericFormulaMeterField::calculate(SIUnit to_si_unit)
{
    if (formula()->meter() == NULL) return std::numeric_limits<double>::quiet_NaN();

    FieldInfo *fi = formula()->meter()->findFieldInfo(vname_, quantity_);

    Unit field_unit = fi->displayUnit();
    double val = formula()->meter()->getNumericValue(fi, field_unit);

    const SIUnit& field_si_unit = toSIUnit(field_unit);

    double r {};
    field_si_unit.convertTo(val, to_si_unit, &r);
    return r;
}

double NumericFormulaDVEntryField::calculate(SIUnit to_si_unit)
{
    if (formula()->dventry() == NULL) return std::numeric_limits<double>::quiet_NaN();

    double val = formula()->dventry()->getCounter(counter_);

    const SIUnit& counter_si_unit = toSIUnit(Unit::COUNTER);

    double r {};
    counter_si_unit.convertTo(val, to_si_unit, &r);
    return r;
}

double NumericFormulaAddition::calculate(SIUnit to_siunit)
{
    double l = left_->calculate(left_->siunit());
    double r = right_->calculate(right_->siunit());

    double v {};
    SIUnit v_siunit(Unit::COUNTER);
    left_->siunit().mathOpTo(MathOp::ADD, l, r, right_->siunit(), &v_siunit, &v);

    double result {};
    v_siunit.convertTo(v, to_siunit, &result);

    if (isDebugEnabled())
    {
        debug("(formula) ADD %g (%s) %g (%s) --> %g %s --> %g %s\n",
              l, left_->siunit().info().c_str(),
              r, right_->siunit().info().c_str(),
              v, v_siunit.info().c_str(),
              result, siunit().info().c_str());
    }

    return result;
}

double NumericFormulaSubtraction::calculate(SIUnit to_siunit)
{
    double l = left_->calculate(left_->siunit());
    double r = right_->calculate(right_->siunit());

    double v {};
    SIUnit v_siunit(Unit::COUNTER);
    left_->siunit().mathOpTo(MathOp::SUB, l, r, right_->siunit(), &v_siunit, &v);

    double result {};
    v_siunit.convertTo(v, to_siunit, &result);

    if (isDebugEnabled())
    {
        debug("(formula) SUB %g (%s) %g (%s) --> %g %s --> %g %s\n",
              l, left_->siunit().info().c_str(),
              r, right_->siunit().info().c_str(),
              v, v_siunit.info().c_str(),
              result, siunit().info().c_str());
    }

    return result;
}

double NumericFormulaMultiplication::calculate(SIUnit to_siunit)
{
    double l = left_->calculate(left_->siunit());
    double r = right_->calculate(right_->siunit());
    double m = l*r;
    double v {};
    siunit().convertTo(m, to_siunit, &v);

    if (isDebugEnabled())
    {
        debug("(formula) MUL %g (%s) %g (%s) --> %g --> %g %s\n",
              l, left_->siunit().info().c_str(),
              r, right_->siunit().info().c_str(),
              m,
              v, to_siunit.info().c_str());
    }
    return v;
}

double NumericFormulaDivision::calculate(SIUnit to_siunit)
{
    double l = left_->calculate(left_->siunit());
    double r = right_->calculate(right_->siunit());
    double d = l/r;
    double v {};
    siunit().convertTo(d, to_siunit, &v);

    if (isDebugEnabled())
    {
        debug("(formula) DIV %g (%s) %g (%s) --> %g --> %g %s\n",
              l, left_->siunit().info().c_str(),
              r, right_->siunit().info().c_str(),
              d,
              v,
              to_siunit.info().c_str());
    }

    return v;
}

double NumericFormulaExponentiation::calculate(SIUnit to_siunit)
{
    double l = left_->calculate(to_siunit);
    double r = right_->calculate(to_siunit);
    double p = pow(l,r);
    double v {};
    siunit().convertTo(p, to_siunit, &v);

    debug("(formula) %g <-- %g <-- pow %g ^ %g\n", v, p, l, r);
    return v;
}

double NumericFormulaSquareRoot::calculate(SIUnit to_siunit)
{
    double i = inner_->calculate(inner_->siunit());
    double s = sqrt(i);
    double v {};
    siunit().convertTo(s, to_siunit, &v);

    if (isDebugEnabled())
    {
        debug("(formula) SQRT %g (%s) --> %g --> %g %s\n",
              i, inner_->siunit().info().c_str(),
              s,
              v,
              to_siunit.info().c_str());
    }
    return v;
}

const char *toString(TokenType tt)
{
    switch (tt) {
    case TokenType::SPACE: return "SPACE";
    case TokenType::NUMBER: return "NUMBER";
    case TokenType::DATETIME: return "DATETIME";
    case TokenType::TIME: return "TIME";
    case TokenType::LPAR: return "LPAR";
    case TokenType::RPAR: return "RPAR";
    case TokenType::PLUS: return "PLUS";
    case TokenType::MINUS: return "MINUS";
    case TokenType::TIMES: return "TIMES";
    case TokenType::DIV: return "DIV";
    case TokenType::EXP: return "EXP";
    case TokenType::SQRT: return "SQRT";
    case TokenType::UNIT: return "UNIT";
    case TokenType::FIELD: return "FIELD";
    }
    return "?";
}

string Token::str(const string &s)
{
    string v = s.substr(start, len);
    return tostrprintf("%s(%s)", toString(type), v.c_str());
}

double Token::val(const string &s)
{
    string v = s.substr(start, len);
    if (type == TokenType::NUMBER)
    {
        return atof(v.c_str());
    }
    else if (type == TokenType::DATETIME)
    {
        struct tm time {};
        char *ok = strptime(v.c_str(), "'%Y-%m-%d %H:%M:%S'", &time);
        if (!ok) ok = strptime(v.c_str(), "'%Y-%m-%dT%H:%M:%SZ'", &time);
        time_t epoch = mktime(&time);
        double result = (double)epoch;
        return result;
    }
    else if (type == TokenType::TIME)
    {
        int h = 0;
        int m = 0;
        int s = 0;
        sscanf(v.c_str(), "'%02d:%02d:%02d'", &h, &m, &s);
        double result = h*3600+m*60+s;
        return result;
    }

    assert(0);
    return 0;
}

string Token::vals(const string &s)
{
    return s.substr(start, len);
}

Unit Token::unit(const string &s)
{
    string v = s.substr(start, len);
    return toUnit(v);
}

void FormulaImplementation::clear()
{
    valid_ = true;
    op_stack_.clear();
    tokens_.clear();
    formula_ = "";
    dventry_ = NULL;
    meter_ = NULL;
}

bool is_letter(char c)
{
    return c >= 'a' && c <= 'z';
}

bool is_letter_or_underscore(char c)
{
    return c == '_' || (c >= 'a' && c <= 'z');
}

bool is_letter_digit_or_underscore(char c)
{
    return c == '_' || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
}

size_t FormulaImplementation::findSpace(size_t i)
{
    size_t len = 0;
    while(isspace(formula_[i]))
    {
        i++;
        len++;
    }
    return len;
}

size_t FormulaImplementation::findNumber(size_t i)
{
    size_t len = 0;
    size_t start = i;
    int num_dots = 0;
    while (true)
    {
        if (i >= formula_.length()) break;
        char c = formula_[i];
        if ((c < '0' || c > '9') && c != '.') break;
        if (c == '.')
        {
            if (start == i) return 0; // Numbers do not start with a dot.
            num_dots++;
        }
        i++;
        len++;
    }

    if (num_dots > 1) return 0; // More than one decimal dot is an error.

    return len;
}

size_t FormulaImplementation::findDateTime(size_t i)
{
    // A datetime is converted into a unix timestamp.
    // Patterns: '2022-12-30T09:32:45Z'
    //           '2222-22-22 11:11:00'
    //           '2222-22-22 11:11'
    //           '2222-22-22'

    struct tm time {};
    const char *start = &formula_[i];
    const char *end;

    memset(&time, 0, sizeof(time));
    if (i+21 < formula_.length())
    {
        end = strptime(start, "'%Y-%m-%dT%H:%M:%SZ'", &time);
        if (distance(start, end) == 22) return 22;
    }

    if (i+20 < formula_.length())
    {
        end = strptime(start, "'%Y-%m-%d %H:%M:%S'", &time);
        if (distance(start, end) == 21) return 21;
    }

    if (i+17 < formula_.length())
    {
        end = strptime(start, "'%Y-%m-%d %H:%M'", &time);
        if (distance(start, end) == 18) return 18;
    }

    if (i+11 < formula_.length())
    {
        end = strptime(start, "'%Y-%m-%d'", &time);
        if (distance(start, end) == 12) return 12;
    }

    return 0;
}

size_t FormulaImplementation::findTime(size_t i)
{
    // A time is converted into seconds 10:11:12 is 10*3600+11*60+12 seconds.
    // Patterns: '11:22:15'
    //           '11:22'

    if (i+9 < formula_.length() &&
        '\'' == formula_[i+0] &&
        isdigit(formula_[i+1]) &&
        isdigit(formula_[i+2]) &&
        ':' == formula_[i+3] &&
        isdigit(formula_[i+4]) &&
        isdigit(formula_[i+5]) &&
        ':' == formula_[i+6] &&
        isdigit(formula_[i+7]) &&
        isdigit(formula_[i+8]) &&
        '\'' == formula_[i+9])
    {
        return 10;
    }

    if (i+6 < formula_.length() &&
        '\'' == formula_[i+0] &&
        isdigit(formula_[i+1]) &&
        isdigit(formula_[i+2]) &&
        ':' == formula_[i+3] &&
        isdigit(formula_[i+4]) &&
        isdigit(formula_[i+5]) &&
        '\'' == formula_[i+6])
    {
        return 7;
    }

    return 0;
}


size_t FormulaImplementation::findPlus(size_t i)
{
    if (i >= formula_.length()) return 0;

    char c = formula_[i];
    if (c == '+') return 1;

    return 0;
}

size_t FormulaImplementation::findMinus(size_t i)
{
    if (i >= formula_.length()) return 0;

    char c = formula_[i];
    if (c == '-') return 1;

    return 0;
}

size_t FormulaImplementation::findTimes(size_t i)
{
    if (i >= formula_.length()) return 0;

    char c = formula_[i];
    if (c == '*') return 1;

    return 0;
}

size_t FormulaImplementation::findDiv(size_t i)
{
    if (i >= formula_.length()) return 0;

    char c = formula_[i];
    if (c == '/') return 1;

    return 0;
}

size_t FormulaImplementation::findExp(size_t i)
{
    return 0;
    /*
    if (i >= formula_.length()) return 0;

    char c = formula_[i];
    if (c == '^') return 1;

    return 0;
    */
}

size_t FormulaImplementation::findSqrt(size_t i)
{
    if (i+4 >= formula_.length()) return 0;

    if (!strncmp(&formula_[i], "sqrt", 4) && !is_letter(formula_[i+4]))
    {
        return 4;
    }

    return 0;
}

size_t FormulaImplementation::findLPar(size_t i)
{
    if (i >= formula_.length()) return 0;

    char c = formula_[i];
    if (c == '(') return 1;

    return 0;
}

size_t FormulaImplementation::findRPar(size_t i)
{
    if (i >= formula_.length()) return 0;

    char c = formula_[i];
    if (c == ')') return 1;

    return 0;
}

size_t FormulaImplementation::findUnit(size_t i)
{
    if (i >= formula_.length()) return 0;

    size_t len = formula_.length();
    char c = formula_[i];

    // All units start with a lower case a-z, followed by more letters and _ underscores.
    if (!is_letter(c)) return 0;

    size_t longest_unit = 0;

#define X(cname,lcname,hrname,quantity,explanation) \
    if ( (i+sizeof(#lcname)-1 <= len) &&                                \
         !is_letter_digit_or_underscore(formula_[i+sizeof(#lcname)-1]) &&     \
         !strncmp(#lcname, formula_.c_str()+i, sizeof(#lcname)-1) &&    \
         longest_unit < (sizeof(#lcname)-1)) { longest_unit = (sizeof(#lcname)-1); }
LIST_OF_UNITS
#undef X

    return longest_unit;
}

size_t FormulaImplementation::findField(size_t i)
{
    size_t len = 0;
    size_t start = i;

    // All field names are lower case a-z.
    if (!is_letter(formula_[start])) return 0;

    while (true)
    {
        if (i >= formula_.length()) break;
        char c = formula_[i];
        // After first letter field names can contain more letters digits and underscores.
        if (!is_letter_digit_or_underscore(c)) break;
        i++;
        len++;
    }

    return len;
}

bool FormulaImplementation::tokenize()
{
    size_t i = 0;

    while (i<formula_.length())
    {
        size_t len;

        len = findSpace(i);
        if (len > 0) { i+=len; continue; } // No token added for whitespace.

        len = findDateTime(i);
        if (len > 0) { tokens_.push_back(Token(TokenType::DATETIME, i, len)); i+=len; continue; }

        len = findTime(i);
        if (len > 0) { tokens_.push_back(Token(TokenType::TIME, i, len)); i+=len; continue; }

        len = findNumber(i);
        if (len > 0) { tokens_.push_back(Token(TokenType::NUMBER, i, len)); i+=len; continue; }

        len = findLPar(i);
        if (len > 0) { tokens_.push_back(Token(TokenType::LPAR, i, len)); i+=len; continue; }

        len = findRPar(i);
        if (len > 0) { tokens_.push_back(Token(TokenType::RPAR, i, len)); i+=len; continue; }

        len = findPlus(i);
        if (len > 0) { tokens_.push_back(Token(TokenType::PLUS, i, len)); i+=len; continue; }

        len = findMinus(i);
        if (len > 0) { tokens_.push_back(Token(TokenType::MINUS, i, len)); i+=len; continue; }

        len = findTimes(i);
        if (len > 0) { tokens_.push_back(Token(TokenType::TIMES, i, len)); i+=len; continue; }

        len = findDiv(i);
        if (len > 0) { tokens_.push_back(Token(TokenType::DIV, i, len)); i+=len; continue; }

        len = findExp(i);
        if (len > 0) { tokens_.push_back(Token(TokenType::EXP, i, len)); i+=len; continue; }

        len = findSqrt(i);
        if (len > 0) { tokens_.push_back(Token(TokenType::SQRT, i, len)); i+=len; continue; }

        len = findUnit(i);
        if (len > 0) { tokens_.push_back(Token(TokenType::UNIT, i, len)); i+=len; continue; }

        len = findField(i);
        if (len > 0) { tokens_.push_back(Token(TokenType::FIELD, i, len)); i+=len; continue; }

        break;
    }

    // Interrupted early, thus there was an error tokenizing.
    if (i < formula_.length())
    {
        Token tok(TokenType::SPACE, i, 0);
        string s = string("Unknown token!\n")+tok.withMarker(formula_);
        errors_.push_back(s);
        valid_ = false;
        return false;
    }

    return true;
}

size_t FormulaImplementation::parseOps(size_t i)
{
    Token *tok = LA(i);
    Token *next= LA(i+1);
    if (tok == NULL) return i;

    if (tok->type == TokenType::FIELD)
    {
        handleField(tok);
        return i+1;
    }

    if (tok->type == TokenType::DATETIME)
    {
        handleUnixTimestamp(tok);
        return i+1;
    }

    if (tok->type == TokenType::TIME)
    {
        handleSeconds(tok);
        return i+1;
    }

    if (tok->type == TokenType::PLUS)
    {
        size_t next = parseOps(i+1);
        if (!valid_) return next;
        handleAddition(tok);
        return next;
    }

    if (tok->type == TokenType::MINUS)
    {
        size_t next = parseOps(i+1);
        if (!valid_) return next;
        handleSubtraction(tok);
        return next;
    }

    if (tok->type == TokenType::TIMES)
    {
        size_t next = parseOps(i+1);
        if (!valid_) return next;
        handleMultiplication(tok);
        return next;
    }

    if (tok->type == TokenType::DIV)
    {
        size_t next = parseOps(i+1);
        if (!valid_) return next;
        handleDivision(tok);
        return next;
    }

    if (tok->type == TokenType::EXP)
    {
        size_t next = parseOps(i+1);
        if (!valid_) return next;
        handleExponentiation(tok);
        return next;
    }

    if (tok->type == TokenType::SQRT)
    {
        size_t next = parseOps(i+1);
        if (!valid_) return next;
        handleSquareRoot(tok);
        return next;
    }

    if (tok->type == TokenType::LPAR)
    {
        return parsePar(i);
    }

    if (tok->type == TokenType::RPAR)
    {
        return i;
    }

    if (tok->type == TokenType::NUMBER)
    {
        if (next == NULL || next->type != TokenType::UNIT)
        {
            errors_.push_back(tostrprintf("Constant number %s lacks a valid unit!\n%s",
                                          tok->vals(formula_).c_str(),
                                          tok->withMarker(formula_).c_str()));
            valid_ = false;
            return i;
        }
        handleConstant(tok, next);
        return i+2;
    }

    return i;
}

size_t FormulaImplementation::parsePar(size_t i)
{
    Token *tok = LA(i);
    assert(tok->type == TokenType::LPAR);

    i++;
    for (;;)
    {
        tok = LA(i);
        if (tok == NULL) break;
        if (tok->type == TokenType::RPAR) break;
        size_t next = parseOps(i);
        if (!valid_ || next == i) break;
        i = next;
    }

    if (valid_ && tok == NULL)
    {
        errors_.push_back("Missing closing parenthesis at end of formula!\n");
        valid_ = false;
        return i;
    }

    if (valid_ && tok->type != TokenType::RPAR)
    {
        errors_.push_back("Expected closing parenthesis!\n"+tok->withMarker(formula_));
        valid_ = false;
        return i;
    }
    return i+1;
}

void FormulaImplementation::handleConstant(Token *number, Token *unit)
{
    double c = number->val(formula_);
    Unit u = unit->unit(formula_);

    if (u == Unit::Unknown)
    {
        errors_.push_back(tostrprintf("Unknown unit \"%s\"!\n%s",
                                      unit->vals(formula_).c_str(),
                                      unit->withMarker(formula_).c_str()));
        valid_ = false;
        return;
    }

    doConstant(u, c);
}

void FormulaImplementation::handleUnixTimestamp(Token *number)
{
    double c = number->val(formula_);

    doConstant(Unit::UnixTimestamp, c);
}

void FormulaImplementation::handleSeconds(Token *number)
{
    double c = number->val(formula_);
    Unit u = Unit::Second;

    doConstant(u, c);
}

void FormulaImplementation::handleAddition(Token *tok)
{
    SIUnit right_siunit = topOp()->siunit();
    SIUnit left_siunit = top2Op()->siunit();
    SIUnit to_siunit(Unit::COUNTER);

    bool ok = left_siunit.mathOpTo(MathOp::ADD, 0, 0, right_siunit, &to_siunit, NULL);

    if (!ok)
    {
        string lsis = left_siunit.str();
        string rsis = right_siunit.str();
        errors_.push_back(tostrprintf("Cannot add %s to %s!\n%s",
                                      left_siunit.info().c_str(),
                                      right_siunit.info().c_str(),
                                      tok->withMarker(formula_).c_str()));
        valid_ = false;
        return;
    }

    doAddition(to_siunit);
}

void FormulaImplementation::handleSubtraction(Token *tok)
{
    SIUnit right_siunit = topOp()->siunit();
    SIUnit left_siunit = top2Op()->siunit();

    SIUnit v_siunit(Unit::COUNTER);

    bool ok = left_siunit.mathOpTo(MathOp::SUB, 0, 0, right_siunit, &v_siunit, NULL);

    if (!ok)
    {
        string lsis = left_siunit.str();
        string rsis = right_siunit.str();
        errors_.push_back(tostrprintf("Cannot subtract %s from %s!\n%s",
                                      left_siunit.info().c_str(),
                                      right_siunit.info().c_str(),
                                      tok->withMarker(formula_).c_str()));
        valid_ = false;
        return;
    }

    doSubtraction(v_siunit);
}

void FormulaImplementation::handleMultiplication(Token *tok)
{
    // Any two units can be multiplied! You might not like the answer thought....
    doMultiplication();
}

void FormulaImplementation::handleDivision(Token *tok)
{
    // Any two units can be divided! You might not like the answer thought....
    doDivision();
}

void FormulaImplementation::handleExponentiation(Token *tok)
{
    // You can only exponentiate to a number.
    doExponentiation();
}

void FormulaImplementation::handleSquareRoot(Token *tok)
{
    doSquareRoot();
}

void FormulaImplementation::handleField(Token *field)
{
    string field_name = field->vals(formula_); // Full field: total_m3
    string vname;                              // Without unit: total
    Unit named_unit;                      // The extracted unit: m3
    bool ok = extractUnit(field_name, &vname, &named_unit);

    if (!ok)
    {
        errors_.push_back("Cannot extra a valid unit from field name \""+field_name+"\"\n"+field->withMarker(formula_));
        return;
    }

    DVEntryCounterType dve = toDVEntryCounterType(field_name);

    if (dve != DVEntryCounterType::UNKNOWN)
    {
        debug("(formula) handle dventry field %s into %s %s\n", field_name.c_str(), vname.c_str(),
              unitToStringLowerCase(named_unit).c_str());

        doDVEntryField(named_unit, dve);
    }
    else
    {
        debug("(formula) handle meter field %s into %s %s\n", field_name.c_str(), vname.c_str(),
              unitToStringLowerCase(named_unit).c_str());

        Quantity q = toQuantity(named_unit);
        FieldInfo *f = meter_->findFieldInfo(vname, q);

        if (f == NULL)
        {
            errors_.push_back("No such field found \""+field_name+"\"\n"+field->withMarker(formula_));
            valid_ = false;
            return;
        }

        doMeterField(named_unit, f);
    }
}

bool FormulaImplementation::go()
{
    size_t i = 0;

    for (;;)
    {
        size_t next = parseOps(i);
        if (!valid_ || next == i) break;
        i = next;
    }

    return i == tokens_.size();
}

Token *FormulaImplementation::LA(size_t i)
{
    if (i < 0 || i >= tokens_.size()) return NULL;
    return &tokens_[i];
}

bool FormulaImplementation::parse(Meter *m, const string &f)
{
    bool ok;

    clear();

    meter_ = m;
    formula_ = f;

    debug("(formula) parsing \"%s\"\n", formula_.c_str());

    ok = tokenize();
    if (!ok) return false;

    if (isDebugEnabled())
    {
        debug("(formula) tokens: ");
        for (Token &t : tokens_)
        {
            debug("%s ", t.str(formula_).c_str());
        }
        debug("\n");
    }

    ok = go();
    if (!ok) return false;

    if (isDebugEnabled())
    {
        debug("(formula) %s\n", tree().c_str());
    }
    return valid_;
}

bool FormulaImplementation::valid()
{
    return valid_ == true && op_stack_.size() == 1;
}

string FormulaImplementation::errors()
{
    string s;
    for (string& e : errors_) s += e;
    return s;
}

double FormulaImplementation::calculate(Unit to, DVEntry *dve, Meter *m)
{
    if (dve != NULL) dventry_ = dve;
    if (m != NULL) meter_ = m;

    if (!valid_)
    {
        string t = tree();
        warning("Warning! Formula is not valid! Returning nan!\n%s\n", t.c_str());
        return std::nan("");
    }

    if (op_stack_.size() != 1)
    {
        string t = tree();
        warning("Warning! Formula is not valid! Multiple ops on stack! Returning nan!\n%s\n", t.c_str());
        return std::nan("");
    }

    return topOp()->calculate(toSIUnit(to));
}

void FormulaImplementation::doConstant(Unit u, double c)
{
    pushOp(new NumericFormulaConstant(this, u, c));
}

void FormulaImplementation::doAddition(const SIUnit &to_siunit)
{
    assert(op_stack_.size() >= 2);

    unique_ptr<NumericFormula> right_node = popOp();
    unique_ptr<NumericFormula> left_node = popOp();

    pushOp(new NumericFormulaAddition(this, to_siunit, left_node, right_node));
}

void FormulaImplementation::doSubtraction(const SIUnit &to_siunit)
{
    assert(op_stack_.size() >= 2);

    unique_ptr<NumericFormula> right_node = popOp();
    unique_ptr<NumericFormula> left_node = popOp();

    pushOp(new NumericFormulaSubtraction(this, to_siunit, left_node, right_node));
}

void FormulaImplementation::doMultiplication()
{
    assert(op_stack_.size() >= 2);

    SIUnit right_siunit = topOp()->siunit();

    unique_ptr<NumericFormula> right_node = popOp();

    SIUnit left_siunit = topOp()->siunit();

    unique_ptr<NumericFormula> left_node = popOp();

    SIUnit mul_siunit = left_siunit.mul(right_siunit);

    string lsis = left_siunit.info();
    string rsis = right_siunit.info();
    string msis = mul_siunit.info();

    pushOp(new NumericFormulaMultiplication(this, mul_siunit, left_node, right_node));
}

void FormulaImplementation::doDivision()
{
    assert(op_stack_.size() >= 2);

    SIUnit right_siunit = topOp()->siunit();

    unique_ptr<NumericFormula> right_node = popOp();

    SIUnit left_siunit = topOp()->siunit();

    unique_ptr<NumericFormula> left_node = popOp();

    SIUnit div_siunit = left_siunit.div(right_siunit);

    string lsis = left_siunit.info();
    string rsis = right_siunit.info();
    string dsis = div_siunit.info();

    debug("(formula) unit %s DIV %s ==> %s\n", lsis.c_str(), rsis.c_str(), dsis.c_str());

    pushOp(new NumericFormulaDivision(this, div_siunit, left_node, right_node));
}

void FormulaImplementation::doExponentiation()
{
    assert(op_stack_.size() >= 2);

//    SIUnit right_siunit = topOp()->siunit();

    unique_ptr<NumericFormula> right_node = popOp();

    SIUnit left_siunit = topOp()->siunit();

    unique_ptr<NumericFormula> left_node = popOp();

    pushOp(new NumericFormulaDivision(this, left_siunit, left_node, right_node));

//    assert(canConvert(left_siunit, right_siunit));
}

void FormulaImplementation::doSquareRoot()
{
    assert(op_stack_.size() >= 1);

    SIUnit inner_siunit = topOp()->siunit();

    SIUnit siunit = inner_siunit.sqrt();

    unique_ptr<NumericFormula> inner_node = popOp();

    pushOp(new NumericFormulaSquareRoot(this, siunit, inner_node));
}


void FormulaImplementation::doMeterField(Unit u, FieldInfo *fi)
{
    SIUnit from_si_unit = toSIUnit(fi->displayUnit());
    SIUnit to_si_unit = toSIUnit(u);
    assert(from_si_unit.convertTo(0, to_si_unit, NULL));

    pushOp(new NumericFormulaMeterField(this, u, fi->vname(), fi->xuantity()));
}

void FormulaImplementation::doDVEntryField(Unit u, DVEntryCounterType ct)
{
    pushOp(new NumericFormulaDVEntryField(this, Unit::COUNTER, ct));
}

Formula *newFormula()
{
    return new FormulaImplementation();
}

Formula::~Formula()
{
}

FormulaImplementation::~FormulaImplementation()
{
}

string FormulaImplementation::str()
{
    return topOp()->str();
}

string FormulaImplementation::tree()
{
    string s;

    for (auto &op : op_stack_)
    {
        if (s.length() > 0) s += " | ";
        s += op->tree();
        if (s.back() == ' ') s.pop_back();
    }
    return s;
}

void FormulaImplementation::setMeter(Meter *m)
{
    meter_ = m;
}

void FormulaImplementation::setDVEntry(DVEntry *dve)
{
    dventry_ = dve;
}

string NumericFormulaConstant::str()
{
    return tostrprintf("%.17g %s", constant_, siunit());
}

string NumericFormulaConstant::tree()
{
    Quantity q = siunit().quantity();
    Unit nearest = siunit().asUnit(q);
    string sis = siunit().str();

    return tostrprintf("<CONST %.17g %s[%s]%s> ", constant_, unitToStringLowerCase(nearest).c_str(), sis.c_str(), toString(q));
}

string NumericFormulaPair::str()
{
    string left = left_->tree();
    string right = right_->tree();
    return left+" "+op_+" "+right;
}

string NumericFormulaPair::tree()
{
    string left = left_->tree();
    string right = right_->tree();
    return "<"+name_+" "+left+right+"> ";
}

string NumericFormulaSquareRoot::str()
{
    string inner = inner_->str();
    return "sqrt("+inner+")";
}

string NumericFormulaSquareRoot::tree()
{
    string inner = inner_->tree();
    return "<SQRT "+inner+"> ";
}

string NumericFormulaMeterField::str()
{
    if (formula()->meter() == NULL) return "<?"+vname_+"?>";
    FieldInfo *fi = formula()->meter()->findFieldInfo(vname_, quantity_);

    return fi->vname()+"_"+unitToStringLowerCase(fi->displayUnit());
}

string NumericFormulaMeterField::tree()
{
    if (formula()->meter() == NULL) return "<?"+vname_+"?>";
    FieldInfo *fi = formula()->meter()->findFieldInfo(vname_, quantity_);
    return "<FIELD "+fi->vname()+"_"+unitToStringLowerCase(fi->displayUnit())+"> ";
}

string NumericFormulaDVEntryField::str()
{
    return toString(counter_);
}

string NumericFormulaDVEntryField::tree()
{
    return string("<DVENTRY ")+toString(counter_)+"> ";
}

void FormulaImplementation::pushOp(NumericFormula *nf)
{
    op_stack_.push_back(unique_ptr<NumericFormula>(nf));
}

unique_ptr<NumericFormula> FormulaImplementation::popOp()
{
    assert(op_stack_.size() > 0);
    unique_ptr<NumericFormula> nf = std::move(op_stack_.back());
    op_stack_.pop_back();
    return nf;
}

NumericFormula * FormulaImplementation::topOp()
{
    assert(op_stack_.size() > 0);
    return op_stack_.back().get();
}

NumericFormula * FormulaImplementation::top2Op()
{
    assert(op_stack_.size() > 1);
    return op_stack_[op_stack_.size()-2].get();
}

SIUnit &FormulaImplementation::siUnit()
{
    return topOp()->siunit();
}

string Token::withMarker(const string& formula)
{
    string indent;
    size_t n = start;
    while (n > 0)
    {
        indent += " ";
        n--;
    }
    return formula+"\n"+indent+"^~~~~\n";
}

StringInterpolator *newStringInterpolator()
{
    return new StringInterpolatorImplementation();
}

StringInterpolator::~StringInterpolator()
{
}

StringInterpolatorImplementation::~StringInterpolatorImplementation()
{
}

bool StringInterpolatorImplementation::parse(Meter *m, const std::string &f)
{
    strings_.clear();
    formulas_.clear();

    size_t prev_string_start = 0;
    size_t next_start_brace = f.find('{', prev_string_start);

    while (next_start_brace != string::npos)
    {
        // Push the string up to the brace.
        string part = f.substr(prev_string_start, next_start_brace - prev_string_start);
        strings_.push_back(part);

        // Find the end of the formula.
        size_t next_end_brace = f.find('}', next_start_brace);
        if (next_end_brace == string::npos) return false; // Oups, missing closing }

        string formula = f.substr(next_start_brace+1, next_end_brace - next_start_brace - 1);

        formulas_.push_back(unique_ptr<Formula>(newFormula()));
        bool ok = formulas_.back()->parse(m, formula);
        if (!ok) return false;

        prev_string_start = next_end_brace+1;

        next_start_brace = f.find('{', prev_string_start);
    }

    // Add any remaining string segment after the last formula.
    if (prev_string_start < f.length())
    {
        string part = f.substr(prev_string_start);
        strings_.push_back(part);
    }

    return true;
}

string StringInterpolatorImplementation::apply(Meter *m, DVEntry *dve)
{
    string result;
    size_t s = 0;
    size_t f = 0;

    for (;;)
    {
        if (s >= strings_.size() && f >= formulas_.size()) break;

        if (s < strings_.size())
        {
            result += strings_[s];
            s++;
        }

        if (f < formulas_.size())
        {
            Formula *ff = formulas_[f].get();
            if (dve != NULL)
            {
                Unit u = ff->siUnit().asUnit(Quantity::PointInTime);
                if (u == Unit::UnixTimestamp)
                {
                    double pt = ff->calculate(Unit::UnixTimestamp, dve, m);
                    // Print as DateLT
                    result += strdate(pt);
                }
                else
                {
                    result += tostrprintf("%g", ff->calculate(Unit::COUNTER, dve, m));
                }
            }
            else
            {
                result += "{NULL}";
            }
            f++;
        }
    }

    return result;
}
