#pragma once

#include <string>
#include <unordered_map>

#include "Swordfish.h"

using namespace ddb;

class DDBPlugin {
    SessionSP session_;
    string pluginName_;
    unordered_map<string, FunctionDefSP> functionDefs_;

  public:
    DDBPlugin(const string &pluginName) : pluginName_(pluginName) {
        session_ = DolphinDBLib::createSession();
        if (session_.isNull()) {
            throw RuntimeException("Failed to create session");
        }

        init();
    }

    DDBPlugin(const SessionSP &session, const string &pluginName) : pluginName_(pluginName) {
        if (session.isNull()) {
            throw RuntimeException("Invalid session");
        }
        session_ = session;

        init();
    }

    string getPluginName() { return pluginName_; }

    FunctionDefSP getFunctionDef(const string &name) {
        auto iter = functionDefs_.find(name);
        if (iter == functionDefs_.end()) {
            throw RuntimeException("No such function: " + name);
        }
        return iter->second;
    }

    vector<string> getFunctionNames() {
        vector<string> names;
        for (auto iter = functionDefs_.begin(); iter != functionDefs_.end(); ++iter) {
            names.push_back(iter->first);
        }
        return names;
    }

    ConstantSP callFunction(const string &name, vector<ConstantSP> args = {}) {
        string functionName = name;
        if (functionName.find(pluginName_) != 0) {
            functionName = pluginName_ + "::" + functionName;
        }
        FunctionDefSP functionDef = getFunctionDef(functionName);
        int size = args.size();
        if (size < functionDef->getMinParamCount() || size > functionDef->getMaxParamCount()) {
            throw RuntimeException("Function " + functionName + " takes " +
                                   std::to_string(functionDef->getMinParamCount()) + " to " +
                                   std::to_string(functionDef->getMaxParamCount()) + " parameters.");
        }
        return functionDef->call(session_->getHeap().get(), args);
    }

    SessionSP getSession() { return session_; } 
private:
    void init() {
        VectorSP functionNames = DolphinDBLib::execute(session_, "exec name from defs('%" + pluginName_ + "::%')");

        if (functionNames->size() == 0) {
            VectorSP functionDefs = DolphinDBLib::execute(session_, "loadPlugin('" + pluginName_ + "')");
            for (auto i = 0; i < functionDefs->size(); ++i) {
                FunctionDefSP functionDef = functionDefs->get(i);
                functionDefs_[functionDef->getString()] = functionDef;
            }
        } else {
            for (auto i = 0; i < functionNames->size(); ++i) {
                FunctionDefSP functionDef = DolphinDBLib::execute(
                    session_, "funcByName('" + functionNames->get(i)->getString() + "')");
                functionDefs_[functionDef->getString()] = functionDef;
            }
        }
    }
};