/***************************** 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 <new>
#include <cassert>
#include <cstdio>
#include <cctype>
#include <cmath>
#include <deque>
#include <fstream>
#include <iostream>
#include <map>
#include <regex>
#include <set>
#include <stack>
#include <string>
#include <vector>

#include "script.h"
#include "code.h"
#include "opcodes.h"
#include "compile.h"
#include "MvDate.h"
#include "MvDebugPrintControl.h"
#include "MvPath.hpp"
#include "MvException.h"
#include "MvMiscellaneous.h"
#include "MvVersionInfo.h"
#include "Path.h"

//#define MACRO2PY_INTERNAL_DEBUG

//=======================================================
// Compiler (lex/yacc) interface used for the conversion
//=======================================================

extern "C" {

int parse_macro_convert(void);
void zzcerror(char* msg)
{
    extern int zzclineno;
    //    Script::Compiled->CompileError(msg, zzlineno + 1);
    throw MvException("Line " + std::to_string(zzclineno + 1) + " " +
                      ((msg != nullptr)?std::string(msg):""));
}
}

class ConvertCompiler : public Compiler
{
public:
    using Compiler::Compiler;
    int compile(const char* filename) override;
    void parseIt() override;
};


int ConvertCompiler::compile(const char* filename)
{
    extern FILE* zzcin;
    extern int zzclineno;

    return compileCore(filename, &zzclineno, &zzcin);
}

void ConvertCompiler::parseIt()
{
    parse_macro_convert();
}

//-----------------------------------------
// Represents items on the convert stack
//-----------------------------------------

class ConvertStackItem;
using ConvertStackItemPtr = std::shared_ptr<ConvertStackItem>;

class ConvertNodeStackItem;
class ConvertConcatStackItem;

class ConvertStackItem
{
public:
    virtual ~ConvertStackItem() = default;
    virtual std::string str() const = 0;
    virtual Date date() const {return Date();}
    virtual bool time(int &/*h*/, int &/*m*/, int &/*z*/) const {return false;}
    virtual int type() const {return -1;}

    static ConvertStackItemPtr makeNode(OpPush *node);
    static ConvertStackItemPtr makeString(const std::string&);
    static ConvertStackItemPtr makeConcat(ConvertStackItemPtr p1,  ConvertStackItemPtr p2);

    virtual ConvertConcatStackItem* concatItem() {return nullptr;}

protected:
    ConvertStackItem() = default;
    bool isQuoted(const std::string& s) const;
};

class ConvertNodeStackItem : public ConvertStackItem
{
    friend class ConvertStackItem;

public:
    std::string str() const override;
    Date date() const override;
    bool time(int &h, int &m, int &z) const override;
    int type() const override {return (node_ != nullptr)?node_->getType():-1;}

protected:
    ConvertNodeStackItem(OpPush *node) : node_(node) {}

private:
    OpPush *node_{nullptr};
};

class ConvertStringStackItem : public ConvertStackItem
{
    friend class ConvertStackItem;

public:
    std::string str() const override {return str_;}

protected:
    ConvertStringStackItem(const std::string& s) : str_(s) {}

private:
    std::string str_;
};

class ConvertConcatStackItem : public ConvertStackItem
{
    friend class ConvertStackItem;
public:
    std::string str() const override;
    ConvertConcatStackItem* concatItem() override {return this;}
    void prepend(ConvertStackItemPtr o);
    void append(ConvertStackItemPtr o);
    static const std::string& concatMethod() {return concatMethod_;}

protected:
    ConvertConcatStackItem(ConvertStackItemPtr p1, ConvertStackItemPtr p2);

private:
    bool stringVars_{false};
    std::deque<std::string> items_;
    static std::string concatMethod_;
};

std::string ConvertConcatStackItem::concatMethod_ = "mv.compat.concat";

ConvertStackItemPtr ConvertStackItem::makeNode(OpPush *node)
{
    return ConvertStackItemPtr(new ConvertNodeStackItem(node));
}

ConvertStackItemPtr ConvertStackItem::makeString(const std::string& s)
{
    return ConvertStackItemPtr(new ConvertStringStackItem(s));
}

ConvertStackItemPtr ConvertStackItem::makeConcat(ConvertStackItemPtr p1,  ConvertStackItemPtr p2)
{
    return ConvertStackItemPtr(new ConvertConcatStackItem(p1, p2));
}


bool ConvertStackItem::isQuoted(const std::string& s) const
{
    return (s.size() >=2 &&
           (s[0] == '"' || s[0] == '\'') &&
           (s[s.size()-1] == '"' || s[s.size()-1] == '\''));
}

std::string ConvertNodeStackItem::str() const
{
    if (node_ == nullptr) {
        return {};
    }

    std::string s;
    if (node_->getType() == PUSH_NIL) {
        s = "None";
    } else {
        s = std::string(node_->Name());
        if (node_->getType() == PUSH_STRING) {
            s = "\"" +  s + "\"";
        } else if (node_->getType() == PUSH_NEGATIVE) {
            s = "-" + s;
        } else if (node_->getType() == PUSH_DATE) {
            Date d(node_->Name());
            s = "datetime.date(" + std::to_string(d.Year()) + ", " +
                    std::to_string(d.Month()) + ", " +
                    std::to_string(d.Day()) + ")";
        } else if (node_->getType() == PUSH_TIME) {
            int h = 0, m = 0, z = 0;
            sscanf(node_->Name(), "%d:%d:%d", &h, &m, &z);
            s = "datetime.time(" + std::to_string(h) + ", " +
                    std::to_string(m) + ", " +
                    std::to_string(z) + ")";
        }
    }
    return s;
}

Date ConvertNodeStackItem::date() const
{
    if (node_ == nullptr) {
        return Date();
    } else if (node_->getType() == PUSH_DATE) {
        Date d(node_->Name());
        return d;

    }
    return Date();
}

bool ConvertNodeStackItem::time(int &h, int &m, int &z) const
{
    if (node_ == nullptr) {
        return false;
    } else if (node_->getType() == PUSH_TIME) {
        h = 0;
        m = 0;
        z = 0;
        sscanf(node_->Name(), "%d:%d:%d", &h, &m, &z);
        return true;
    }
    return false;
}

ConvertConcatStackItem::ConvertConcatStackItem(ConvertStackItemPtr o1, ConvertStackItemPtr o2)
{
    auto s1 = o1->str();
    auto s2 = o2->str();
    stringVars_ = (isQuoted(s1) || isQuoted(s2));

    if (stringVars_) {
        if (s1 != "None") {
            if (isQuoted(s1) || s1.find("mv.string(") == 0) {
                items_.emplace_back(s1);
            } else {
                items_.emplace_back("str(" + s1 + ")");
            }
        }
        if (s2 != "None") {
            if (isQuoted(s2) || s1.find("mv.string(") == 0) {
                items_.emplace_back(s2);
            } else {
                items_.emplace_back("str(" + s2 + ")");
            }
        }
    } else {
        items_.emplace_back(s1);
        items_.emplace_back(s2);
    }
}


void ConvertConcatStackItem::prepend(ConvertStackItemPtr o)
{
    auto c = o->concatItem();
    if (c != nullptr) {
        items_.insert(items_.begin(), c->items_.begin(), c->items_.end() );
        if (c->stringVars_) {
            stringVars_ = true;
        }
    } else {
        auto s = o->str();
        if  (isQuoted(s)) {
            stringVars_ = true;
        } else if (stringVars_) {
            if (s == "None") {
                return;
            }
            s = "str(" + s + ")";
        }
        items_.emplace_front(s);
    }
}

void ConvertConcatStackItem::append(ConvertStackItemPtr o)
{
    auto c = o->concatItem();
    if (c != nullptr) {
        items_.insert(items_.end(), c->items_.begin(), c->items_.end() );
        if (c->stringVars_) {
            stringVars_ = true;
        }
    } else {
        auto s = o->str();
        if (isQuoted(s)) {
             stringVars_ = true;
        } else if (stringVars_) {
            if (s == "None") {
                return;
            }
            s = "str(" + s + ")";
        }
        items_.emplace_back(s);
    }
}

std::string ConvertConcatStackItem::str() const
{
    std::string s;
    if (stringVars_) {
        for(std::size_t i=0; i < items_.size(); i++) {
            s += items_[i];
            if (i+1 < items_.size()) {
                s += " + ";
            }
        }
    } else {
        s = concatMethod_ + "(";
        for(std::size_t i=0; i < items_.size(); i++) {
            s += items_[i];
            if (i+1 < items_.size()) {
                s += ", ";
            }
        }
        s += ")";
    }

    return s;
}

class VarsCollector
{
public:
    void setActive(bool b) {active_= b;}
    void add(const std::string& v) {if (active_) vars_.insert(v);}
    bool contains(const std::string& v) const {return vars_.find(v) != vars_.end();}
    const std::set<std::string>& vars() const {return vars_;}
    void merge(const VarsCollector& other) {
        for (const auto &v: other.vars()) {
            vars_.insert(v);
        }
    }
private:
    bool active_{false};
    std::set<std::string> vars_;
};



//=======================================================
// Converter
//=======================================================

namespace metview {
namespace macro {

struct CaseInfo
{
    CaseInfo(const std::string& param) : param_(param) {}
    std::string param_;
    int choiceCnt_{0};
};

struct WhenInfo
{
    int selectCnt_{0};
};

class FunctionConverter;

class Converter :   public NodeVisitor
{
public:
    Converter() = default;

    enum NumberType {IntNumberType, FloatNumberType, NotNumberType};
    void setRootContext(Context* c) {rootContext_ = c;}
    void convert(Context*);

    void visit(Node*) override;
    void visit(OpPop*) override;
    void visit(OpPush*) override;
    void visit(OpStore*) override;
    void visit(OpParam*) override;
    void visit(OpCall*) override;
    void visit(OpReturn*) override;
    void visit(UserFunction*) override;
    void visit(ExternFunction*) override;
    void visit(OpConvert*) override;
    void visit(OpConvertFor*) override;
    void visit(OpConvertLoop*) override;
    void visit(OpComment*) override;
    void visit(OpConvertFnStart*) override;

    void dumpResult(const char* inFile, const char* outFile, bool needHeader, bool needWarnings, bool needLicense);

protected:
    std::string rtdUrl(const std::string& path) const;
    std::string addPlotWarn() const;
    std::string addNewpageWarn() const;
    std::string addDateWarn() const;
    std::string addConcatWarn() const;
    std::string addInlineWarn() const;
    std::string addIndex4Warn() const;
    std::string addReadWarn() const;
    std::string addForWarn() const;
    void addLocalForWarn();
    void addLocalWarn(const std::vector<std::string>& r);

    std::string popAsStr();
    ConvertStackItemPtr popAsItem();
//    std::string concatCode() const;
//    std::string index4Code() const;
    std::string licenseText() const;
    void addRow(const std::string& s);
    void addRowFront(const std::string& s);
    void addEmptyRow();
    void addStackContents();
    void changeTab(int delta);
    std::string makeListFromStack(int num);

    void dequote(std::string& s) const;
    bool isQuoted(const std::string& ) const;
    void addQuote(std::string& s) const;
    void addDelta(std::string &s, int delta) const;
    void addDeltaIfInt(std::string &s, int delta) const;

    Function* findfunction(const std::string& name, Context* context) const;
    void toLower(std::string& v) const {
        std::transform(v.begin(), v.end(), v.begin(), ::tolower);
    }
    bool isInteger(const std::string & s) const {
        return std::regex_match(s, std::regex(R"(^(-|\+)?[0-9]+$)"));
    }
    bool isNegativeInt(std::string& s) const;
    bool isNegative(std::string& s) const;
    bool isFloat(const std::string& s) const
    {
        return std::regex_match(s, std::regex(R"([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)"));
    }
    NumberType guessNumberType(std::string& s) const;

    void convertAdd2();
    void convertEq2();
    void convertNotEq2();
    void convertBeginIf();
    void convertBeginElseIf();
    void convertBeginElse();
    void convertEndIf();
    void convertBeginRepeat();
    void convertEndRepeat();
    void convertBeginWhile();
    void convertEndWhile();
    void convertBeginCase();
    void convertEndCase();
    void convertBeginChoice();
    void convertEndChoice();
    void convertBeginOtherwise();
    void convertEndOtherwise();
    void convertBeginFor(const std::string& id);
    void convertEndFor();
    void convertBeginLoop(const std::string& id);
    void convertEndLoop();
    void convertBeginWhen();
    void convertEndWhen();
    void convertBeginSelection();
    void convertEndSelection();
    void convertInclude();

    std::string convertBracketAssign(const std::string& par, int arity);
    void convertBracket(int arity);
    void convertConcat(int arity);
    void convertDefinition(int arity);
    void convertList(int arity);
    void convertVector(int arity);
    void convertVectorFunction(int arity);
    void convertParenthesis(int arity);
    void convertStringFunction(int arity);
    void convertLength(int arity);
    void convertNowFunction(int arity);
    void convertCallFunction(const std::string& name, int arity, bool user);
    void convertCallRequestFunction(const std::string& name, int arity);
    void convertGlobals();

    void addFunctionResult(FunctionConverter*);

    static Context* rootContext_;
    Context* context_{nullptr};
    std::deque<std::string> res_;
    std::deque<std::string> resMain_;
    std::stack<ConvertStackItemPtr> stack_;
    std::stack<CaseInfo> caseStack_;
    std::stack<std::string> paramStack_;
    std::stack<WhenInfo> whenStack_;
    std::deque<std::string> includes_;

    int tab_{0};
    static constexpr int tabCharNum_{4};
    bool useNumpy_{false};
    static std::set<std::string> basicOperators_;
    static std::map<std::string, std::string> builtinToPythonFunctions_;
    static std::vector<std::string> dateFunctions_;
    static std::vector<std::string> uiFunctions_;
    static bool hasGlobals_;
    static bool hasPlot_;
    static bool hasNewpage_;
    static bool hasInline_;
    static bool hasIndex4_;
    static bool hasFor_;
    static std::string fnStartId_;
    VarsCollector globalsCollector_;
    VarsCollector localVarCollector_;
    std::string index4Method_{"mv.compat.index4"};
};

bool Converter::hasGlobals_ = false;
bool Converter::hasPlot_ = false;
bool Converter::hasNewpage_ = false;
bool Converter::hasInline_ = false;
bool Converter::hasIndex4_ = false;
bool Converter::hasFor_ = false;
std::string Converter::fnStartId_ = "_:::MVC_FUNC_START:::_";
Context* Converter::rootContext_ = nullptr;

std::set<std::string> Converter::basicOperators_ = {"+", "-", "*", "/", "**", "<", ">", "==",
                                               "!=", ">=", "<=", "not", "and", "or", "in"};

std::map<std::string, std::string> Converter::builtinToPythonFunctions_ = {{"print", "print"},
                                                                           {"count", "len"}};

std::vector<std::string> Converter::dateFunctions_ = {"date", "time", "year", "month", "day",
                                         "hour", "minute", "second", "now", "dow",
                                         "addmonths", "julday", "juldate",
                                         "yymmdd", "yyyymmdd"};

std::vector<std::string> Converter::uiFunctions_ = {"any", "colour", "menu", "option_menu", "icon",
                                                    "slider", "on_off", "checkbox", "ui_integer",
                                                    "ui_float", "toggle"};

class FunctionConverter : public Converter
{
public:
    FunctionConverter(UserFunction* fn, int tab, const VarsCollector& globals);
    const std::string& functionName() const {return functionName_;}

private:
    std::string functionName_;
};

void Converter::convert(Context* c)
{
    assert(context_ == nullptr);
    context_ = c;
    assert(context_ != nullptr);

    std::deque<std::string> resGlobals, resUserFn;

    // generate globals code
    convertGlobals();
    resGlobals = res_;
    res_.clear();

    // generate the main code
    auto s = context_->FirstStep();
    while(s) {
        s->accept(this);
        // this prints to the stdout
        s->Print();
        s = s->Next();
    }

    resMain_ = res_;
    res_.clear();

    // generate user function code. This has to come after the main code
    // since we want to add the function comments (they are in the main part) to
    // the function code.
    Function* f = context_->FirstFunction();
    while(f) {
        f->accept(this);
        f = (Function*)f->Next();
    }

    resUserFn = res_;
    res_.clear();

    res_ = resGlobals;
    res_.insert(res_.end(), resUserFn.begin(), resUserFn.end());

    //remove function start indicator tokens and duplicate empty lines
    int emptyCnt = 0;
    for (auto v: resMain_) {
        if (v.find(fnStartId_) != std::string::npos || v.empty()) {
            emptyCnt++;
        } else {
            res_.emplace_back(v);
            emptyCnt=0;
        }

        if (emptyCnt == 1) {
            res_.emplace_back("");
        }
    }
    resMain_.clear();
}

void Converter::dumpResult(const char* inFile, const char* outFile, bool needHeader,
                           bool needWarnings, bool needLicense)
{
    if (outFile == nullptr) {
        throw MvException("No ouput file defined");
    }

    tab_ = 0;
    std::deque<std::string> front;

    if (needLicense) {
        front.push_front(licenseText());
        front.push_front("");
    }

    if (needHeader) {
        front.push_front("# This Python code was automatically generated from the following Metview Macro:");
        front.push_front("# " + std::string(inFile));
        front.push_front("# at: " +  MvDate().current());
        MvVersionInfo mvInfo;
        front.push_front("# using version: " + mvInfo.nameAndVersion());
        front.push_front("");
    }

    bool hasDateTime = false;
    for (auto &s: res_) {
        if (s.find("datetime.") != std::string::npos) {
            hasDateTime = true;
            break;
        }
    }

    bool hasMvDate = false;
    for (const auto &s: res_) {
        for (const auto &v: dateFunctions_) {
            if (s.find("mv." + v + "(") != std::string::npos) {
                hasMvDate = true;
                break;
            }
        }
    }

    bool hasConcat = false;
    for (const auto &s: res_) {
        if (s.find(ConvertConcatStackItem::concatMethod()) != std::string::npos) {
            hasConcat = true;
            break;
        }
    }


    bool addWarn = hasPlot_ || hasNewpage_ || hasDateTime || hasMvDate || hasConcat || hasInline_ ||
            hasIndex4_ || hasFor_;
    if (needWarnings && addWarn) {
        front.push_front("# Warnings: ");
        if (hasPlot_) {
            front.push_front(addPlotWarn());
        }
        if (hasNewpage_) {
            front.push_front(addNewpageWarn());
        }
        if (hasDateTime || hasMvDate) {
            front.push_front(addDateWarn());
        }
        if (hasConcat) {
            front.push_front(addConcatWarn());
        }
        if (hasInline_) {
            front.push_front(addInlineWarn());
        }
        if (hasIndex4_) {
            front.push_front(addIndex4Warn());
        }
        if (hasFor_) {
            front.push_front(addForWarn());
        }
    }

    front.push_front("");

    bool importedOther = false;
    // import datetime
    if (hasDateTime) {
        front.push_front("import datetime");
        importedOther = true;
    }

    // import numpy
    if (useNumpy_ || hasConcat || hasIndex4_) {
        front.push_front("import numpy as np");
        importedOther = true;
    }

    if (importedOther) {
        front.push_front("");
    }

    front.push_front("import metview as mv");
    front.push_front("");

    for (auto v: includes_) {
        front.push_front("");
        std::string s = "from " + v + " import *";
        front.push_front(s);
    }

    if (!includes_.empty()) {
        front.push_front("");
    }

    for (auto const &s: front) {
        addRowFront(s);
    }

//    for (auto &s: res_) {
//        std::cout << s << std::endl;
//    }

#ifdef MACRO2PY_INTERNAL_DEBUG
    std::cout << "stack contents=" << std::endl;
    while(!stack_.empty()) {
        std::cout << " -> " << stack_.top() << std::endl;
        stack_.popAsStr();
    }
#endif

    std::ofstream out;
    out.open(outFile);
    if (!out.is_open()) {
        throw MvException("Cannot open output file= " + std::string(outFile));
    }

    for (auto &s: res_) {
        out << s << std::endl;
    }
}

std::string Converter::rtdUrl(const std::string& path) const
{
    static std::string base = "https://metview.readthedocs.io/en/latest/metview/macro/";
    return base + path;
}

std::string Converter::addPlotWarn() const
{
    return
    "# - plot():\n"
    "#   The generated code calls the plot() function, which might require\n"
    "#   manual adjustment.\n"
    "#   See the following page for details: \n"
    "#      " + rtdUrl("convert/convert_date.html");
}

std::string Converter::addNewpageWarn() const
{
    return
    "# - newpage():\n"
    "#   The generated code calls the newpage() function, which might require\n"
    "#   manual adjustment.\n"
    "#   See the following page for details: \n"
    "#     " + rtdUrl("convert/convert_newpage.html");
}

std::string Converter::addDateWarn() const
{
    return
    "# - date and time:\n"
    "#   The generated code contains date/time objects, which might require\n"
    "#   manual adjustment.\n"
    "#   See the following page for details: \n"
    "#     " + rtdUrl("convert/convert_date.html");
}

std::string Converter::addConcatWarn() const
{
    return
    "# - concatenation:\n"
    "#   The generated code contains concatenations, which might require\n"
    "#   manual adjustment. Check for the " + ConvertConcatStackItem::concatMethod() + "() calls\n"
    "#   See the following page for details: \n"
    "#     " + rtdUrl("convert/convert_concat.html");
}

std::string Converter::addInlineWarn() const
{
    return
    "# - inline:\n"
    "#   The generated code contains inline C or Fortran code, which cannot be\n"
    "#   properly used in Python.\n"
    "#   See the following page for details: \n"
    "#     " + rtdUrl("convert/convert_inline.html");
}

std::string Converter::addIndex4Warn() const
{
    return
    "# - vector slicing:\n"
    "#   The Macro code contained vector slicing based on 4 arguments. The generated Python\n"
    "#   code uses the " +  index4Method_ + "() method to resolve it. It might require\""
    "#   manual adjustment.\n"
    "#   See the following page for details: \n"
    "#     " + rtdUrl("convert/convert_vector_index4.html");
}

std::string Converter::addForWarn() const
{
    return
    "# - for(): \n"
    "#   The generated code has for loop(s) that might contain float values and \n"
    "#   require manual adjustment. \n"
    "#   See the following page for details: \n"
    "#     " + rtdUrl("convert/convert_for.html");
}

void Converter::addLocalForWarn()
{
    std::vector<std::string> r = {
        "# Warning: for()",
        "# The following for loop might contain float values and need manual adjustment.",
        "# See the following page for details:",
        "#   " + rtdUrl("convert/convert_for.html")
    };
    addLocalWarn(r);
}

void Converter::addLocalWarn(const std::vector<std::string>& r)
{
    for (auto s: r) {
        addRow(s);
    }
}

std::string Converter::licenseText() const
{
    static std::string lTxt;
    if (lTxt.empty()) {
        std::ifstream in(metview::etcDirFile("licence_for_macros.txt"), std::ios_base::in);
        std::string line;
        while(std::getline(in, line)) {
            lTxt += "# " + line + "\n";
        }
        lTxt = metview::replace(lTxt, "[YEAR]", MvDate().current("%Y"));
    }
    return lTxt;
}

void Converter::addRow(const std::string& s)
{
    //    std::cout << "TAB=" << tab_ << " ROW=" << s << std::endl;
    if (res_.empty() || !s.empty() || !res_.back().empty()) {
        res_.emplace_back(std::string(tab_*tabCharNum_, ' ') + s);
    }
}

void Converter::addRowFront(const std::string& s)
{
    // std::cout << "TAB=" << tab_ << " ROW=" << s << std::endl;
    res_.emplace_front(std::string(tab_*tabCharNum_, ' ') + s);
}

void Converter::addEmptyRow()
{
    addRow("");
}

void Converter::addStackContents()
{
//    while(!stack_.empty()) {
//        addRow(popAsStr());
//    }
}

void Converter::changeTab(int delta)
{
    tab_ += delta;
    assert(tab_ >= 0);
    if (tab_ < 0) {
        tab_ = 0;
    }
}

std::string Converter::makeListFromStack(int num)
{
    std::deque<std::string> p;
    for (int i=0; i < num; i++) {
        p.emplace_front(popAsStr());
    }
    std::string s;
    for (std::size_t i=0; i < p.size(); i++) {
        s += p[i];
        if (i < p.size()-1) {
            s += ", ";
        }
    }
    return s;
}

void Converter::dequote(std::string& s) const
{
    if (s.size() >=2) {
        if (s[0] == '"' || s[0] == '\'') {
            if (s[s.size()-1] == '"' || s[s.size()-1] == '\'') {
                const int pos1=1;
                const int len=s.size()-pos1-1;
                s = s.substr(pos1, len);
            }
        }
    }
}

bool Converter::isQuoted(const std::string& s) const
{
    return (s.size() >=2 &&
           (s[0] == '"' || s[0] == '\'') &&
           (s[s.size()-1] == '"' || s[s.size()-1] == '\''));
}

void Converter::addQuote(std::string& s) const
{
    if (s.size() >=2) {
        if (s[0] != '"') {
            s = '\"' + s;
        }
        if (s[s.size()-1] != '"') {
            s = s + '\"';
        }
    }
}

void Converter::addDelta(std::string& s, int delta) const
{
    if (delta != 0) {
        if (isInteger(s)) {
            int n = std::stoi(s);
            n += delta;
            s = std::to_string(n);
        } else if (!s.empty() && s.back() != '\"'){
            if (delta > 0) {
                s += " + " + std::to_string(delta);
            } else {
                s += " - " + std::to_string(std::abs(delta));
            }
        }
    }
}

bool Converter::isNegativeInt(std::string& s) const
{
    if (isInteger(s)) {
        int n = std::stoi(s);
        return n < 0;
    }
    return false;
}

bool Converter::isNegative(std::string& s) const
{
    return s.length() >0 && s[0] == '-';
}

Converter::NumberType Converter::guessNumberType(std::string& s) const
{
    NumberType t = NotNumberType;
    if (isInteger(s)) {
        t = IntNumberType;
    } else if (isFloat(s)) {
        t = FloatNumberType;
    }
    return t;
}

void Converter::addDeltaIfInt(std::string& s, int delta) const
{
    if (delta != 0 && isInteger(s)) {
        int n = std::stoi(s);
        n += delta;
        s = std::to_string(n);
    }
}

Function* Converter::findfunction(const std::string& name, Context* context) const
{
    assert(context!= nullptr);
    Function* f = context->FirstFunction();
    Function* firstFound =  nullptr;
    while(f) {
        if (f->Name() != nullptr &&
            strcmp(f->Name(), name.c_str()) == 0) {
            if (f->isRequestFunction()) {
                return f;
            } else if (firstFound == nullptr) {
                firstFound = f;
            }
        }
        f = (Function*)f->Next();
    }

    return firstFound;
}


std::string Converter::popAsStr()
{
    ConvertStackItemPtr item = popAsItem();
    if (item == nullptr) {
        throw MvException("top level item in stack is nullptr");
    }
    return item->str();
}


ConvertStackItemPtr Converter::popAsItem()
{
    if (!stack_.empty()) {
        ConvertStackItemPtr v = stack_.top();
        stack_.pop();
        return v;
    }
    return {};
}


void Converter::visit(Node* /*node*/)
{
    //node->Print();
}

void Converter::visit(OpPop* /*node*/)
{
    addRow(popAsStr());
}

void Converter::visit(OpPush* node)
{
    stack_.emplace(ConvertStackItem::makeNode(node));
}

// handles assignment
void Converter::visit(OpStore* node)
{
    std::string name = std::string(node->Name());
    if (node->getCount() == 0) {
        std::string s = name + " = " +  popAsStr();
        addRow(s);
    } else if (node->getCount() == 1) {
        std::string v = popAsStr();
        std::string key = popAsStr();
        if (!isQuoted(key)) {
             addDelta(key, -1);
        }
        std::string s = std::string(node->Name()) + "[" + key + "] = " + v;
        addRow(s);
    } else if (node->getCount() >= 2) {
        std::string v = popAsStr();
        auto s = convertBracketAssign(std::string(node->Name()), node->getCount());
        s += " =" + v;
        addRow(s);
    }
    globalsCollector_.add(name);
    localVarCollector_.add(name);
}

void Converter::visit(OpParam* node)
{
//    std::string s = std::string(node->Name()) + " = " +  popAsStr();
//    addRow(s);
//    node->Print();
    paramStack_.push(std::string(node->Name()));
}

void Converter::visit(OpCall* node)
{
    std::string name(node->Name());
    int arity = node->arity();

//    std::cout << "CALL name=" << name << " arity=" << arity << std::endl
    if (basicOperators_.find(name) != basicOperators_.end()) {
        if (arity == 1) {
            std::string v = popAsStr();
            std::string s = name + " " + v;
            stack_.emplace(ConvertStackItem::makeString(s));
        } else if (arity == 2) {
            if (name == "+") {
                convertAdd2();
            } else if (name == "==") {
                convertEq2();
            } else if (name == "!=") {
                convertNotEq2();
            } else {
                std::string v2 = popAsStr();
                std::string v1 = popAsStr();
                std::string s = v1 + " " + name + " " + v2;
                stack_.emplace(ConvertStackItem::makeString(s));
            }
        } else {
            //warning
        }
    } else if (name == "[]") {
        convertBracket(arity);
    } else if (name == "&") {
        convertConcat(arity);
    } else if (name == "definition") {
        convertDefinition(arity);
    } else if (name == "list") {
        convertList(arity);
    } else if (name == "_vector") {
        convertVector(arity);
    } else if (name == "vector") {
        convertVectorFunction(arity);
    } else if (name == "parenthesis") {
        convertParenthesis(arity);
    // any other call
    } else if (name == "string") {
        convertStringFunction(arity);
    } else if (name == "length") {
        convertLength(arity);
    } else if (name == "now") {
        convertNowFunction(arity);
    } else {
        // some built in Macro functions are available in Python,
        // we need to treat them as user functions
        auto it = builtinToPythonFunctions_.find(name);
        if (it != builtinToPythonFunctions_.end()) {
            convertCallFunction(it->second, arity, true);
        //
        } else {
            Function* f = findfunction(name, context_);
            // if not found tries to find it in the top level context. This happens
            // when request functions are called from a user function.
            if (f == nullptr && rootContext_ != nullptr && rootContext_ != context_) {
                f = findfunction(name, rootContext_);
            }
            if (f != nullptr) {
                if (f->isRequestFunction()) {
                    convertCallRequestFunction(name, arity);
                } else {
                    convertCallFunction(name, arity, f->isUserFunction());
                }
            // the function is either defined in an included macro or a
            // macro library function, or just undefined
            } else {
                // we assume it is a Macro Library function
                bool user = !context_->isLibraryFunction(name.c_str());
                convertCallFunction(name, arity, user);
            }
        }
    }
}

void Converter::visit(OpReturn* /*node*/)
{
    std::string s = "return " + popAsStr();
    addRow(s);
}

void Converter::visit(OpConvert* node)
{
    if (node->Name() == nullptr) {
        return;
    }

    std::string name(node->Name());

    if (name == "if") {
        convertBeginIf();
    } else if (name == "else_if") {
        convertBeginElseIf();
    } else if (name == "else") {
        convertBeginElse();
    } else if (name == "end_if") {
        convertEndIf();
    } else if (name == "repeat") {
        convertBeginRepeat();
    }  else if (name == "until") {
        convertEndRepeat();
    } else if (name == "begin_while") {
        convertBeginWhile();
    } else if (name == "end_while") {
        convertEndWhile();
    } else if (name == "begin_case") {
        convertBeginCase();
    } else if (name == "end_case") {
        convertEndCase();
    } else if (name == "begin_choice") {
        convertBeginChoice();
    } else if (name == "end_choice") {
        convertEndChoice();
    } else if (name == "begin_otherwise") {
        convertBeginOtherwise();
    } else if (name == "end_otherwise") {
        convertEndOtherwise();
    } else if (name == "end_for") {
        convertEndFor();
    } else if (name == "end_loop") {
        convertEndLoop();
    } else if (name == "begin_when") {
        convertBeginWhen();
    } else if (name == "end_when") {
        convertEndWhen();
    } else if (name == "begin_selection") {
        convertBeginSelection();
    } else if (name == "end_selection") {
        convertEndSelection();
    } else if (name == "include") {
        convertInclude();
    }
}

void Converter::visit(OpConvertFor* node)
{
    convertBeginFor(std::string(node->Name()));
}

void Converter::visit(OpConvertLoop* node)
{
    convertBeginLoop(std::string(node->Name()));
}

void Converter::visit(UserFunction* node)
{
    if (node->Name() != nullptr && strcmp(node->Name(), ".init_globals") == 0) {
        return;
    }
    auto fn = FunctionConverter(node, tab_, globalsCollector_);
    addFunctionResult(&fn);
    //res_.insert(res_.end(), fn.res_.begin(), fn.res_.end());
}

void Converter::visit(ExternFunction* node)
{
    hasInline_ = true;

    // Get function signature
    Converter fn = FunctionConverter(node, tab_, {});
    res_.insert(res_.end(), fn.res_.begin(), fn.res_.end());

    // get extern/inline contents
    changeTab(1);
    std::string s = "inline";
    if (node->getCmd() != nullptr) {
        s += "_" + std::string(node->getCmd());
    }
    s += " = \"\"\"";
    addRow(s);

    changeTab(-1);
    std::ifstream f(node->getFile());
    std::string line;
    while (std::getline(f, line)) {
        addRow(line);
    }

    changeTab(1);
    s = "\"\"\"";
    addRow(s);
    changeTab(-1);
    addEmptyRow();
}

void Converter::visit(OpComment* node)
{
    if (node->Name() != nullptr) {
        std::string s(node->Name());
        std::string t = s;
        toLower(t);
        if (t.find("#metview macro") == std::string::npos &&
            t.find("# metview macro") == std::string::npos) {
            addRow(s);
        }
    }
}

void Converter::visit(OpConvertFnStart* node)
{
    if (node->Name() != nullptr) {
        std::string s(node->Name());
        addRow(fnStartId_ + s);
    }
}

void Converter::convertAdd2()
{
    auto v2 = popAsItem();
    auto v1 = popAsItem();
    std::string s;
    if (v1->type() == PUSH_DATE && v2->type() == PUSH_TIME) {
        s = "datetime.datetime(";
        auto d = v1->date();
        s += std::to_string(d.Year()) + ", " +
             std::to_string(d.Month()) + ", " +
             std::to_string(d.Day());

        int h = 0, m = 0, sec = 0;
        if (v2->time(h, m, sec)) {
            s += ", " +
                 std::to_string(h) + ", " +
                 std::to_string(m) + ", " +
                 std::to_string(sec) + ")";
        }  else {
            s += ")";
        }
    } else {
        s = v1->str() + " + " + v2->str();
    }
    stack_.emplace(ConvertStackItem::makeString(s));
}

void Converter::convertEq2()
{
    std::string v2 = popAsStr();
    std::string v1 = popAsStr();
    std::string operName = (v2 != "None")?"==":"is";
    std::string s = v1 + " " + operName + " " + v2;
    stack_.emplace(ConvertStackItem::makeString(s));
}

void Converter::convertNotEq2()
{
    std::string v2 = popAsStr();
    std::string v1 = popAsStr();
    std::string operName = (v2 != "None")?"!=":"is not";
    std::string s = v1 + " " + operName + " " + v2;
    stack_.emplace(ConvertStackItem::makeString(s));
}

void Converter::convertBeginIf()
{
    std::string s = "if " + popAsStr() + ":";
    addRow(s);
    changeTab(1);
}

void Converter::convertBeginElseIf()
{
    std::string s = "elif " + popAsStr() + ":";
    changeTab(-1);
    addRow(s);
    changeTab(1);
}

void Converter::convertBeginElse()
{
    std::string s = "else:";
    changeTab(-1);
    addRow(s);
    changeTab(1);
}

void Converter::convertEndIf()
{
    changeTab(-1);
    addEmptyRow();
}

void Converter::convertBeginRepeat()
{
    std::string s = "while True:";
    addRow(s);
    changeTab(1);
}

// aka: until in the macro repeat-until block
void Converter::convertEndRepeat()
{
    std::string s = "if " + popAsStr() + ":";
    addRow(s);
    changeTab(1);
    addRow("break");
    changeTab(-2);
    addEmptyRow();
}

void Converter::convertBeginWhile()
{
    std::string s = "while " + popAsStr() + ":";
    addRow(s);
    changeTab(1);
}

void Converter::convertEndWhile()
{
    changeTab(-1);
    addEmptyRow();
}

// aka "switch" in c++
void Converter::convertBeginCase()
{
    std::string param = popAsStr();
    caseStack_.push(CaseInfo(param));
}

void Converter::convertEndCase()
{
    caseStack_.pop();
    addEmptyRow();
}

// aka "case" in c++ "switch" block
void Converter::convertBeginChoice()
{
    if (!caseStack_.empty()) {
        auto param = caseStack_.top().param_;
        std::string s = (caseStack_.top().choiceCnt_ == 0)?"if":"elif";
        s += " " + param + " == " + popAsStr() + ":";
        addRow(s);
        changeTab(1);
        caseStack_.top().choiceCnt_++;
    }
}

void Converter::convertEndChoice()
{
    changeTab(-1);
}

// aka "default" in c++ "switch" block
void Converter::convertBeginOtherwise()
{
    std::string s = "else:";
    addRow(s);
    changeTab(1);
}

void Converter::convertEndOtherwise()
{
    changeTab(-1);
}

void Converter::convertBeginFor(const std::string& id)
{
    auto byS = popAsStr();
    auto toS = popAsStr();
    std::string fromS;

    std::string t = res_.back();
    auto pos = t.find("=");
    if (pos != std::string::npos) {
        fromS = t.substr(pos+2);
    }

    auto fromSType=guessNumberType(fromS);
    auto toSType=guessNumberType(toS);
    auto bySType=guessNumberType(byS);

    res_.pop_back();

    // Float case: use np.arange()
    if (fromSType == FloatNumberType || toSType == FloatNumberType || bySType == FloatNumberType) {

        useNumpy_ = true;
        if (fromSType != FloatNumberType) {
            fromS = "float(" + fromS + ")";
        }
        if (toSType != FloatNumberType) {
            toS = "float(" + toS + ")";
        }
        if (bySType != FloatNumberType) {
            byS = "float(" + byS + ")";
        }

        if (!isNegative(byS)) {
            toS += " + " + byS + " / 2.";
        } else {
            toS += " + (" + byS + ") / 2.";
        }

        std::string s = "for " + id + " in np.arange(" + fromS + ", " + toS + ", " + byS + "):";
        addRow(s);
    // Other cases: use for()
    } else {

        if (fromSType == NotNumberType || toSType == NotNumberType || bySType == NotNumberType) {
            hasFor_ = true;
            addLocalForWarn();
        }

        if (!isNegative(byS)) {
            toS += " + " + byS;
        } else {
            toS += " + (" + byS + ")";
        }

        std::string s = "for " + id +
                " in range(" +  fromS +  ", " + toS;

        if (byS == "1") {
            s += "):";
        } else {
            s += ", " + byS + "):";
        }

        addRow(s);
    }

    changeTab(1);
}

void Converter::convertEndFor()
{
    changeTab(-1);
    addEmptyRow();
}

void Converter::convertBeginLoop(const std::string& id)
{
    std::string s = "for " + id  + " in " + popAsStr() + ":";
    addRow(s);
    changeTab(1);
}

void Converter::convertEndLoop()
{
    changeTab(-1);
    addEmptyRow();
}

void Converter::convertBeginWhen()
{
    whenStack_.push(WhenInfo());
}

void Converter::convertEndWhen()
{
    whenStack_.pop();
    addEmptyRow();
}

void Converter::convertBeginSelection()
{
    if (!whenStack_.empty()) {
        std::string s = (whenStack_.top().selectCnt_ == 0)?"if":"elif";
        s += " " + popAsStr() + ":";
        addRow(s);
        whenStack_.top().selectCnt_++;
    }
    changeTab(1);
}

void Converter::convertEndSelection()
{
    changeTab(-1);
}

void Converter::convertInclude()
{
    std::string name(popAsStr());
    dequote(name);
    auto pos = name.find(".");
    if (pos != std::string::npos) {
        name = name.substr(0, pos);
    }
    includes_.push_front(name);
}

std::string Converter::convertBracketAssign(const std::string& par, int arity)
{
    if (arity == 1) {
        std::string v1 = popAsStr();
        addDelta(v1, -1);
        std::string s = par + "[" + v1 + "]";
        return s;
    } else if (arity == 2) {
        std::string v2 = popAsStr();
        std::string v1 = popAsStr();
        addDelta(v1, -1);
        std::string s = par + "[" + v1 + ":" + v2 + "]";
        return s;
    } else if (arity == 3) {
        std::string v3 = popAsStr();
        std::string v2 = popAsStr();
        std::string v1 = popAsStr();
        addDelta(v1, -1);
        std::string s = par + "[" + v1 + ":" + v2 + ":" + v3 + "]";
        return s;
    } else {
        //warning
    }

    return {};
}

void Converter::convertBracket(int arity)
{
    if (arity == 2) {
        std::string v1 = popAsStr();
        std::string par = popAsStr();
        addDelta(v1, -1);
        std::string s = par + "[" + v1 + "]";
        stack_.emplace(ConvertStackItem::makeString(s));
    } else if (arity == 3) {
        std::string v2 = popAsStr();
        std::string v1 = popAsStr();
        std::string par = popAsStr();
        addDelta(v1, -1);
        std::string s = par + "[" + v1 + ":" + v2 + "]";
        stack_.emplace(ConvertStackItem::makeString(s));
    } else if (arity == 4) {
        std::string v3 = popAsStr();
        std::string v2 = popAsStr();
        std::string v1 = popAsStr();
        std::string par = popAsStr();
        addDelta(v1, -1);
        std::string s = par + "[" + v1 + ":" + v2 + ":" + v3 + "]";
        stack_.emplace(ConvertStackItem::makeString(s));
    } else if (arity == 5) {
        hasIndex4_ = true;
        std::string v4 = popAsStr();
        std::string v3 = popAsStr();
        std::string v2 = popAsStr();
        std::string v1 = popAsStr();
        std::string par = popAsStr();
        addDelta(v1, -1);
        std::string s = par + "[" + index4Method_ +
                "(" + par + ", " + v1 + ", " + v2 + ", " + v3 + ", " + v4 + ")]";
        stack_.emplace(ConvertStackItem::makeString(s));
    } else {
        //warning
    }
}

void Converter::convertConcat(int arity)
{
    if (arity == 2) {
        std::string s;
        auto item2 = popAsItem();
        auto item1 = popAsItem();
        auto c2 = item2->concatItem();
        auto c1 = item1->concatItem();

        if (c1 != nullptr) {
            c1->append(item2);
            stack_.emplace(item1);
        } else if (c2 != nullptr) {
            c2->prepend(item1);
            stack_.emplace(item2);
        } else {
            stack_.emplace(ConvertStackItem::makeConcat(item1, item2));
        }
    }
//        std::string v2 = popAsStr();
//        std::string v1 = popAsStr();

//        // try to concat strings in a nice way
//        if (isQuoted(v1) || isQuoted(v2) || v1.find("str(") == 0 || v2.find("str_") == 0) {
//           if (!isQuoted(v1) && v1.find("_concat(") == std) {
//               v1 = "str(" + v1 + +")";
//           }
//           if (!isQuoted(v2) && v2.find("_concat(") == std::string::npos) {
//               v2 = "str(" + v2  + ")";
//           }
//           s = v1 + " + " + v2;
//        } else {
//            if (v1.find("_concat(") == 0) {
//                s = v1;
//                s.back() = ' ';
//                s += ", " + v2 + ")";
//            } else {
//                s = "_concat("  + v1 + ", " + v2 + ")";
//            }
//            hasConcat_ = true;
//        }
//        stack_.emplace(ConvertStackItem::makeString(s));
//    }
}

void Converter::convertDefinition(int arity)
{
    std::string s = "{";
    std::vector<std::string> p;
    for (int i=0; i < arity; i++) {
        p.push_back(popAsStr());
    }
    for (int i=static_cast<int>(p.size())-1; i >= 1; i-=2) {
        std::string key = p[i];
//        dequote(key);
        s += key + ": " + p[i-1];
        if (i > 1) {
            s += ", ";
        }
    }
    s += "}";
    stack_.emplace(ConvertStackItem::makeString(s));
}

void Converter::convertList(int arity)
{
    std::string s = "[" + makeListFromStack(arity) + "]";
    stack_.emplace(ConvertStackItem::makeString(s));
}

void Converter::convertVector(int arity)
{
    std::string s = "np.array([" + makeListFromStack(arity) + "])";
    useNumpy_ = true;
    stack_.emplace(ConvertStackItem::makeString(s));
}

void Converter::convertVectorFunction(int arity)
{
    if (arity == 1) {
        std::string s = "np.zeros(" + popAsStr() + ")";
        useNumpy_ = true;
        stack_.emplace(ConvertStackItem::makeString(s));
    } else {
        //error
    }
}

void Converter::convertParenthesis(int arity)
{
    if (arity == 1) {
        std::string v = popAsStr();
        std::string s = "(" + v + ")";
        stack_.emplace(ConvertStackItem::makeString(s));
    } else {
        //error
    }
}

void Converter::convertStringFunction(int arity)
{
    if (arity == 1) {
        std::string s = "str(" + popAsStr() + ")";
        stack_.emplace(ConvertStackItem::makeString(s));
    } else if (arity == 2) {
        auto item2 = popAsStr();
        auto item1 = popAsStr();
        std::string s = "mv.string(" + item1 + ", " + item2 + ")";
        stack_.emplace(ConvertStackItem::makeString(s));
    } else {
        //error
    }
}

void Converter::convertLength(int arity)
{
    if (arity == 1) {
        std::string s = "len(" + popAsStr() + ")";
        stack_.emplace(ConvertStackItem::makeString(s));
    } else {
        //error
    }
}

void Converter::convertNowFunction(int arity)
{
    if (arity == 0) {
        std::string s = "datetime.datetime.now()";
        stack_.emplace(ConvertStackItem::makeString(s));
    } else {
        //error
    }
}

// call non-icon function
void Converter::convertCallFunction(const std::string& name, int arity, bool user)
{
    std::string s;
    if (!user) {
         s = "mv.";
    }

    if (name == "dialog") {
         s += "ui.";
    }
    s += name + "(" + makeListFromStack(arity) + ")";
    stack_.emplace(ConvertStackItem::makeString(s));

    if (name == "plot") {
        hasPlot_ = true;
    } else if (name == "newpage") {
        hasNewpage_ =  true;
    }
}

// call icon function
void Converter::convertCallRequestFunction(const std::string& name, int arity)
{
    std::string s = "mv.";
    if (std::find(uiFunctions_.begin(), uiFunctions_.end(), name) != uiFunctions_.end()) {
        s += "ui.";
    }
    s += name + "(";

    // e.g. read(), which is both an icon function and a built-in macro function
    if (arity == 0) {
        s += ")";
        stack_.emplace(ConvertStackItem::makeString(s));
    } else if (arity == 1) {
        auto item = popAsItem();
        auto itemName = item->str();
        if (item->type() == PUSH_IDENT) {
            dequote(itemName);
        }
        s += itemName + ")";
        stack_.emplace(ConvertStackItem::makeString(s));
    // icon function
    } else if (arity >= 1) {
        std::deque<ConvertStackItemPtr> p;
        for (int i=0; i < arity; i++) {
            p.emplace_front(popAsItem());
        }

        std::string leadingTab;
        bool singleLine = arity == 1 || (arity == 2 && p[0]->type() != PUSH_IDENT);
        if (!singleLine) {
            s += "\n";
            changeTab(1);
            leadingTab = std::string(tab_*tabCharNum_, ' ');
        }

        for (std::size_t i=0; i < p.size(); i++) {
            // definition reused
            if (p[i]->type() == PUSH_IDENT) {
                auto key = p[i]->str();
                dequote(key);
                s += leadingTab + "**" + key;
                if (i+1 < p.size()) {
                     s += ",";
                }
                if (!singleLine) {
                    s += "\n";
                }
            // keyword arguments
            } else if (i+1 < p.size()) {
                std::string key = p[i]->str();
                dequote(key);
                // class caoot be kwarg in python
                if (key == "class") {
                    key = "class_";
                }
                s += leadingTab + key + "=" + p[i+1]->str();
                if (i+2 < p.size()) {
                    s += ",";
                }
                if (!singleLine) {
                    s += "\n";
                }
                i += 1;
            }

        }

        if (!singleLine) {
            s += leadingTab + ")";
        } else {
            s += ")";
        }

        stack_.emplace(ConvertStackItem::makeString(s));
        if (!singleLine) {
            changeTab(-1);
        }
    }
}

void Converter::convertGlobals()
{
    auto oriNum = res_.size();

    globalsCollector_.setActive(true);
    //Context* cg = Context::Current->FindContext(Context::InitGlobals);
    Context* cg = context_->FindContext(Context::InitGlobals);
    if (cg) {
        auto s = cg->FirstStep();
        while(s) {
            s->accept(this);
            //s->Print();
            s = s->Next();
        }
    }

    globalsCollector_.setActive(false);
    if (res_.size() > oriNum) {
        addEmptyRow();
        hasGlobals_ = true;
    }
}

// Converts a whole user function definition
FunctionConverter::FunctionConverter(UserFunction* fn, int tab, const VarsCollector& globals) :
    functionName_(std::string(fn->Name()))
{
    tab_ = tab;

    changeTab(1);

    globalsCollector_.merge(globals);
    localVarCollector_.setActive(true);

    auto c = fn->getAddress();
    if (c == nullptr) {
        return;
    }

    convert(c);
    changeTab(-1);

    localVarCollector_.setActive(false);

    // build function signature
    std::string t = "def " + functionName_ + "(";
    std::deque<std::string> p;
    while(!paramStack_.empty()) {
        p.emplace_front(paramStack_.top());
        paramStack_.pop();
    }

    for (std::size_t i=0; i < p.size(); i++) {
        t += p[i];
        if (i < p.size()-1) {
            t += ", ";
        }
    }

    t += "):";

    // add global declarations
    changeTab(1);
    for (const auto &v: localVarCollector_.vars()) {
        if (globalsCollector_.contains(v)) {
            addRowFront("global " +  v);
        }
    }
    changeTab(-1);

    // add function signature
    addRowFront(t);

    addEmptyRow();
}

void Converter::addFunctionResult(FunctionConverter* fn)
{
    // try to add the commments preceding the function code to the function
    std::string id = fnStartId_ + fn->functionName();
    int pos = -1;
    for (std::size_t i=0; i < resMain_.size(); i++) {
        if (resMain_[i] == id) {
            pos = i;
            break;
        }
    }

    if (pos > 0) {
        std::deque<std::string> comments;
        for (int i=pos-1; i >= 0; i--) {
            // comment but not license
            if  (resMain_[i].find("#") == 0 && resMain_[i].find("#  ***") != 0) {
                comments.emplace_front(resMain_[i]);
                resMain_[i] = "";
            } else if (!resMain_[i].empty()) {
                break;
            }
        }
        resMain_[pos] = "";
        if (!comments.empty()) {
            res_.insert(res_.end(), comments.begin(), comments.end());
        }
    }

    res_.insert(res_.end(), fn->res_.begin(), fn->res_.end());
}

}  // namespace macro
}  // namespace metview


typedef struct data
{
    boolean serve;
} data;


static option opts[] = {
    {
        (char*)"serve",
        NULL,
        (char*)"-serve",
        (char*)"0",
        t_boolean,
        sizeof(boolean),
        OFFSET(data, serve),
    },
};

static data setup;

// This executable is not run as a metview module, but simply executed
// via popen/system. As a result marslog is not available for the caller so we need
// to write messages into the stdout/stderr in the code below.
int main(int argc, char** argv)
{
    marsinit(&argc, argv, &setup, NUMBER(opts), opts);

    // Minimum number of arguments
    if (argc < 3) {
        // Send an error message
        std::cerr << "macro2py: too few arguments (has to be >= 2)" << std::endl;
        marslog(LOG_EROR | LOG_PERR, "macro2py: too few arguments (has to be >= 2)\n");
        return 1;
    }

    const char* macroFile = argv[1];
    const char* pythonFile = argv[2];

    bool needWarnings = true;
    bool needLicense = true;
    bool useBlack = true;
    bool needHeader = true;

    if (argc > 3) {
        needHeader = !(strcmp(argv[3], "0") == 0);
    }
    if (argc > 4) {
        needWarnings = !(strcmp(argv[4], "0") == 0);
    }
    if (argc > 5) {
        needLicense = !(strcmp(argv[5], "0") == 0);
    }
    if (argc > 6) {
        useBlack = !(strcmp(argv[6], "0") == 0);
    }
    auto* s = new Script(mbasename("MYMACRO"), new ConvertCompiler);

    // store the current macro path - needs to be done *before* Compile(path)
    std::string fullPath = MakeAbsolutePath(macroFile);
    Script::MacroPath(fullPath.c_str());
    Script::MacroMainPath(fullPath.c_str());

    try {
        // Check if macro exits since Compile does not throw an
        // exception
        Path p(fullPath);
        if (!p.exists()) {
            throw MvException("Macro file=" + fullPath + " does not exist.");
        }

        if (s->Compile(fullPath.c_str()) == 0) {
            metview::macro::Converter converter;
            converter.setRootContext(s);
            converter.convert(s);
            converter.dumpResult(fullPath.c_str(), pythonFile, needHeader, needWarnings, needLicense);
        } else {
            throw MvException("Could not compile Macro.");
        }
    } catch (MvException& e) {
        std::cerr << "Failed to convert Macro to Python: " << std::string(e.what()) << std::endl;
//        std::cout << "Failed to convert Macro to Python: " << std::string(e.what()) << std::endl;
        marslog(LOG_EROR | LOG_PERR, "Failed to convert Macro to Python: ", e.what());
        return 1;
    }

    // try to use the black formatter on the result. We do not report
    // errors from it.
    if (useBlack) {
        std::string cmd = "black " + std::string(pythonFile) + " > /dev/null 2>&1";
        auto ret = system(cmd.c_str());
        if (ret == 0) {
            std::cout << "Sucessfully ran black formatter on generated Python file" << std::endl;
//            marslog(LOG_INFO, "Sucessfully ran black formatter on generated Python file");
        }
    }
    return 0;
}

