<%inc#include "create_model.h"
using namespace drogon_ctl;
%>
/**
 *
 *  [[className]].h
 *  DO NOT EDIT. This file is generated by drogon_ctl
 *
 */

#pragma once
#include <drogon/orm/Result.h>
#include <drogon/orm/Row.h>
#include <drogon/orm/Field.h>
#include <drogon/orm/SqlBinder.h>
#include <drogon/orm/Mapper.h>
#include <drogon/orm/BaseBuilder.h>
#ifdef __cpp_impl_coroutine
#include <drogon/orm/CoroMapper.h>
#endif
#include <trantor/utils/Date.h>
#include <trantor/utils/Logger.h>
#include <json/json.h>
#include <string>
#include <string_view>
#include <memory>
#include <vector>
#include <tuple>
#include <stdint.h>
#include <iostream>

namespace drogon
{
namespace orm
{
class DbClient;
using DbClientPtr = std::shared_ptr<DbClient>;
}
}
namespace drogon_model
{
namespace [[dbName]]
{
<%c++
auto &schema=@@.get<std::string>("schema");
    if(!schema.empty())
    {
        $$<<"namespace "<<schema<<"\n";
        $$<<"{\n";
    }
    std::vector<std::string> relationshipClassNames;
    auto &relationships=@@.get<std::vector<Relationship>>("relationships");
    for(auto &relationship : relationships)
    {
        auto &name = relationship.targetTableName();
        auto relationshipClassName = nameTransform(name, true);
        relationshipClassNames.push_back(relationshipClassName);
        if(relationship.type() == Relationship::Type::ManyToMany)
        {
            auto &pivotTableName = relationship.pivotTable().tableName();
            auto pivotTableClassName = nameTransform(pivotTableName, true);
            relationshipClassNames.push_back(pivotTableClassName);
        }
    }
    std::sort(relationshipClassNames.begin(), relationshipClassNames.end());
    relationshipClassNames.erase(std::unique(relationshipClassNames.begin(), relationshipClassNames.end()), relationshipClassNames.end());
    for(std::string &relationshipClassName : relationshipClassNames)
    {
%>
class {%relationshipClassName%};
<%c++
    }
%>

class [[className]]
{
  public:
    struct Cols
    {
<%c++
auto cols=@@.get<std::vector<ColumnInfo>>("columns");
    for(size_t i=0;i<cols.size();i++)
    {
        $$<<"        static const std::string _"<<cols[i].colName_<<";\n";
    }
%>
    };

    static const int primaryKeyNumber;
    static const std::string tableName;
    static const bool hasPrimaryKey;
<%c++if(@@.get<int>("hasPrimaryKey")<=1){%>
    static const std::string primaryKeyName;
<%c++if(!@@.get<std::string>("primaryKeyType").empty()){%>
    using PrimaryKeyType = [[primaryKeyType]];
    const PrimaryKeyType &getPrimaryKey() const;
<%c++}else{%>
    using PrimaryKeyType = void;
    int getPrimaryKey() const { assert(false); return 0; }
<%c++}%>
<%c++}else{
    auto pkTypes=@@.get<std::vector<std::string>>("primaryKeyType");
    std::string typelist;
    for(size_t i=0;i<pkTypes.size();i++)
    {
        typelist += pkTypes[i];
        if(i<(pkTypes.size()-1))
            typelist += ",";
    }
%>
    static const std::vector<std::string> primaryKeyName;
    using PrimaryKeyType = std::tuple<{%typelist%}>;//<%c++
    auto pkName=@@.get<std::vector<std::string>>("primaryKeyName");
    for(size_t i=0;i<pkName.size();i++)
    {
        $$<<pkName[i];
        if(i<(pkName.size()-1))
            $$<<",";
    }
%>

    PrimaryKeyType getPrimaryKey() const;
<%c++}%>

    /**
     * @brief constructor
     * @param r One row of records in the SQL query result.
     * @param indexOffset Set the offset to -1 to access all columns by column names,
     * otherwise access all columns by offsets.
     * @note If the SQL is not a style of 'select * from table_name ...' (select all
     * columns by an asterisk), please set the offset to -1.
     */
    explicit [[className]](const drogon::orm::Row &r, const ssize_t indexOffset = 0) noexcept;

    /**
     * @brief constructor
     * @param pJson The json object to construct a new instance.
     */
    explicit [[className]](const Json::Value &pJson) noexcept(false);

    /**
     * @brief constructor
     * @param pJson The json object to construct a new instance.
     * @param pMasqueradingVector The aliases of table columns.
     */
    [[className]](const Json::Value &pJson, const std::vector<std::string> &pMasqueradingVector) noexcept(false);

    [[className]]() = default;

    void updateByJson(const Json::Value &pJson) noexcept(false);
    void updateByMasqueradedJson(const Json::Value &pJson,
                                 const std::vector<std::string> &pMasqueradingVector) noexcept(false);
    static bool validateJsonForCreation(const Json::Value &pJson, std::string &err);
    static bool validateMasqueradedJsonForCreation(const Json::Value &,
                                                const std::vector<std::string> &pMasqueradingVector,
                                                    std::string &err);
    static bool validateJsonForUpdate(const Json::Value &pJson, std::string &err);
    static bool validateMasqueradedJsonForUpdate(const Json::Value &,
                                          const std::vector<std::string> &pMasqueradingVector,
                                          std::string &err);
    static bool validJsonOfField(size_t index,
                          const std::string &fieldName,
                          const Json::Value &pJson,
                          std::string &err,
                          bool isForCreation);

<%c++
    for(const auto &col:cols)
    {
        $$<<"    /**  For column "<<col.colName_<<"  */\n";
        if(!col.colType_.empty())
        {
            $$<<"    ///Get the value of the column "<<col.colName_<<", returns the default value if the column is null\n";
            $$<<"    const "<<col.colType_<<" &getValueOf"<<col.colTypeName_<<"() const noexcept;\n";
            if(col.colType_=="std::vector<char>")
            {
                $$<<"    ///Return the column value by std::string with binary data\n";
                $$<<"    std::string getValueOf"<<col.colTypeName_<<"AsString() const noexcept;\n";
            }
            $$<<"    ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null\n";
            $$<<"    const std::shared_ptr<"<<col.colType_<<"> &get"<<col.colTypeName_<<"() const noexcept;\n";

            $$<<"    ///Set the value of the column "<<col.colName_<<"\n";
            $$<<"    void set"<<col.colTypeName_<<"(const "<<col.colType_<<" &p"<<col.colTypeName_<<") noexcept;\n";
                if(col.colType_=="std::string")
                    $$<<"    void set"<<col.colTypeName_<<"("<<col.colType_<<" &&p"<<col.colTypeName_<<") noexcept;\n";
                if(col.colType_=="std::vector<char>")
                {
                    $$<<"    void set"<<col.colTypeName_<<"(const std::string &p"<<col.colTypeName_<<") noexcept;\n";
                }
                if(!col.notNull_)
                {
                    $$<<"    void set"<<col.colTypeName_<<"ToNull() noexcept;\n";
                }

        }
        else
            $$<<"    //FIXME!!"<<" getValueOf"<<col.colTypeName_<<"() const noexcept;\n";
        $$<<"\n";
    }
%>

    static size_t getColumnNumber() noexcept {  return {% cols.size() %};  }
    static const std::string &getColumnName(size_t index) noexcept(false);

    Json::Value toJson() const;
    Json::Value toMasqueradedJson(const std::vector<std::string> &pMasqueradingVector) const;
    /// Relationship interfaces
<%c++
    for(auto &relationship : relationships)
    {
        if(relationship.targetKey().empty() || relationship.originalKey().empty())
        {
            continue;
        }
        auto &name=relationship.targetTableName();
        auto relationshipClassName=nameTransform(name, true);
        auto relationshipValName=nameTransform(name, false);
        auto alias=relationship.targetTableAlias();
        auto aliasValName=nameTransform(alias, false);
        if(relationship.type() == Relationship::Type::HasOne)
        {
            if(!alias.empty())
            {
                if(alias[0] <= 'z' && alias[0] >= 'a')
                {
                    alias[0] += ('A' - 'a');
                }
                std::string alind(alias.length(), ' ');
%>
    {%relationshipClassName%} get{%alias%}(const drogon::orm::DbClientPtr &clientPtr) const;
    void get{%alias%}(const drogon::orm::DbClientPtr &clientPtr,
            {%alind%} const std::function<void({%relationshipClassName%})> &rcb,
            {%alind%} const drogon::orm::ExceptionCallback &ecb) const;
<%c++
            }
            else
            {
                std::string relationshipClassInde(relationshipClassName.length(), ' ');
%>
    {%relationshipClassName%} get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr) const;
    void get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr,
            {%relationshipClassInde%} const std::function<void({%relationshipClassName%})> &rcb,
            {%relationshipClassInde%} const drogon::orm::ExceptionCallback &ecb) const;
<%c++
            }
        }
        else if(relationship.type() == Relationship::Type::HasMany)
        {
            if(!alias.empty())
            {
                if(alias[0] <= 'z' && alias[0] >= 'a')
                {
                    alias[0] += ('A' - 'a');
                }
                std::string alind(alias.length(), ' ');
%>
    std::vector<{%relationshipClassName%}> get{%alias%}(const drogon::orm::DbClientPtr &clientPtr) const;
    void get{%alias%}(const drogon::orm::DbClientPtr &clientPtr,
            {%alind%} const std::function<void(std::vector<{%relationshipClassName%}>)> &rcb,
            {%alind%} const drogon::orm::ExceptionCallback &ecb) const;
<%c++
            }
            else
            {
                std::string relationshipClassInde(relationshipClassName.length(), ' ');
%>
    std::vector<{%relationshipClassName%}> get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr) const;
    void get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr,
            {%relationshipClassInde%} const std::function<void(std::vector<{%relationshipClassName%}>)> &rcb,
            {%relationshipClassInde%} const drogon::orm::ExceptionCallback &ecb) const;
<%c++
            }
        }
        else if(relationship.type() == Relationship::Type::ManyToMany)
        {
            auto &pivotTableName=relationship.pivotTable().tableName();
            auto pivotTableClassName=nameTransform(pivotTableName, true);
            if(!alias.empty())
            {
                if(alias[0] <= 'z' && alias[0] >= 'a')
                {
                    alias[0] += ('A' - 'a');
                }
                std::string alind(alias.length(), ' ');
%>
    std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>> get{%alias%}(const drogon::orm::DbClientPtr &clientPtr) const;
    void get{%alias%}(const drogon::orm::DbClientPtr &clientPtr,
            {%alind%} const std::function<void(std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>)> &rcb,
            {%alind%} const drogon::orm::ExceptionCallback &ecb) const;
<%c++
            }
            else
            {
                std::string relationshipClassInde(relationshipClassName.length(), ' ');
%>
    std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>> get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr) const;
    void get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr,
            {%relationshipClassInde%} const std::function<void(std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>)> &rcb,
            {%relationshipClassInde%} const drogon::orm::ExceptionCallback &ecb) const;
<%c++
            }
        }
    }
%>
  private:
    friend drogon::orm::Mapper<[[className]]>;
    friend drogon::orm::BaseBuilder<[[className]], true, true>;
    friend drogon::orm::BaseBuilder<[[className]], true, false>;
    friend drogon::orm::BaseBuilder<[[className]], false, true>;
    friend drogon::orm::BaseBuilder<[[className]], false, false>;
#ifdef __cpp_impl_coroutine
    friend drogon::orm::CoroMapper<[[className]]>;
#endif
    static const std::vector<std::string> &insertColumns() noexcept;
    void outputArgs(drogon::orm::internal::SqlBinder &binder) const;
    const std::vector<std::string> updateColumns() const;
    void updateArgs(drogon::orm::internal::SqlBinder &binder) const;
    ///For mysql or sqlite3
    void updateId(const uint64_t id);
<%c++
    for(auto col:cols)
    {
        if(!col.colType_.empty())
            $$<<"    std::shared_ptr<"<<col.colType_<<"> "<<col.colValName_<<"_;\n";
    }
    %>
    struct MetaData
    {
        const std::string colName_;
        const std::string colType_;
        const std::string colDatabaseType_;
        const ssize_t colLength_;
        const bool isAutoVal_;
        const bool isPrimaryKey_;
        const bool notNull_;
    };
    static const std::vector<MetaData> metaData_;
    bool dirtyFlag_[{%cols.size()%}]={ false };
  public:
    static const std::string &sqlForFindingByPrimaryKey()
    {
<%c++
auto rdbms=@@.get<std::string>("rdbms");
if(@@.get<int>("hasPrimaryKey")<=1){
        if(!@@.get<std::string>("primaryKeyType").empty()){%>
        static const std::string sql="select * from " + tableName + " where [[primaryKeyName]] = {%(rdbms=="postgresql"?"$1":"?")%}";
<%c++}else{%>
        static const std::string sql="";
<%c++}%>
<%c++}else{
        auto pkName=@@.get<std::vector<std::string>>("primaryKeyName");
%>
        static const std::string sql="select * from " + tableName + " where <%c++
        for(size_t i=0;i<pkName.size();i++)
        {
            if(rdbms=="postgresql")
            {
                $$<<pkName[i]<<" = $"<<i+1;
            }
            else
            {
                $$<<pkName[i]<<" = ?";
            }
            if(i<(pkName.size()-1))
                $$<<" and ";
        }
        $$<<"\";\n";
    }
%>
        return sql;
    }

    static const std::string &sqlForDeletingByPrimaryKey()
    {
<%c++
if(@@.get<int>("hasPrimaryKey")<=1){
        if(!@@.get<std::string>("primaryKeyType").empty()){%>
        static const std::string sql="delete from " + tableName + " where [[primaryKeyName]] = {%(rdbms=="postgresql"?"$1":"?")%}";
<%c++}else{%>
        static const std::string sql="";
<%c++}%>
<%c++}else{
        auto pkName=@@.get<std::vector<std::string>>("primaryKeyName");
%>
        static const std::string sql="delete from " + tableName + " where <%c++
        for(size_t i=0;i<pkName.size();i++)
        {
            if(rdbms=="postgresql")
            {
                $$<<pkName[i]<<" = $"<<i+1;
            }
            else
            {
                $$<<pkName[i]<<" = ?";
            }
            if(i<(pkName.size()-1))
                $$<<" and ";
        }
        $$<<"\";\n";
    }
%>
        return sql;
    }
    std::string sqlForInserting(bool &needSelection) const
    {
        std::string sql="insert into " + tableName + " (";
        size_t parametersCount = 0;
        needSelection = false;
<%c++
        bool selFlag=false;
        for(size_t i=0;i<cols.size();i++)
        {
            if(cols[i].isAutoVal_)
            {
                if(@@.get<std::string>("rdbms")=="sqlite3")
                {
                    continue;
                }
                if(@@.get<int>("hasPrimaryKey")>0)
                {
                    selFlag = true;
                }
                $$<<"            sql += \""<<cols[i].colName_<<",\";\n";
                $$<<"            ++parametersCount;\n";
                continue;
            }
            if(cols[i].colType_.empty())
                continue;
            if(cols[i].hasDefaultVal_)
            {
                if(@@.get<std::string>("rdbms")!="sqlite3")
                {
                    $$<<"        sql += \""<<cols[i].colName_<<",\";\n";
                    $$<<"        ++parametersCount;\n";
                }
                else
                {
%>
        if(dirtyFlag_[{%i%}])
        {
            sql += "{%cols[i].colName_%},";
            ++parametersCount;
        }
<%c++
                }
                if(@@.get<int>("hasPrimaryKey")>0||@@.get<std::string>("rdbms")=="postgresql")
                {
%>
        if(!dirtyFlag_[{%i%}])
        {
            needSelection=true;
        }
<%c++
                }
            }
            else
            {
%>
        if(dirtyFlag_[{%i%}])
        {
            sql += "{%cols[i].colName_%},";
            ++parametersCount;
        }
<%c++
            }
        }
        if(selFlag)
        {
            $$<<"        needSelection=true;\n";
        }
%>
        if(parametersCount > 0)
        {
            sql[sql.length()-1]=')';
            sql += " values (";
        }
        else
            sql += ") values (";

<%c++
if(@@.get<std::string>("rdbms")=="postgresql")
{
%>
        int placeholder=1;
        char placeholderStr[64];
        size_t n=0;
<%c++
}
        for(size_t i=0;i<cols.size();i++)
        {
        if(cols[i].isAutoVal_)
        {
            if(@@.get<std::string>("rdbms")!="sqlite3")
            {
%>
        sql +="default,";
<%c++
            }
            continue;
        }
        if(cols[i].colType_.empty())
        continue;
%>
        if(dirtyFlag_[{%i%}])
        {
<%c++
                if(@@.get<std::string>("rdbms")=="postgresql")
                {
%>
            n = snprintf(placeholderStr,sizeof(placeholderStr),"$%d,",placeholder++);
            sql.append(placeholderStr, n);
<%c++
                }else
                {
%>
            sql.append("?,");

<%c++
                }
%>
        }
<%c++
if(cols[i].hasDefaultVal_&&@@.get<std::string>("rdbms")!="sqlite3")
{
%>
        else
        {
            sql +="default,";
        }
<%c++
}
        }
%>
        if(parametersCount > 0)
        {
            sql.resize(sql.length() - 1);
        }
<%c++
        if(rdbms=="postgresql")
        {
%>
        if(needSelection)
        {
            sql.append(") returning *");
        }
        else
        {
            sql.append(1, ')');
        }
<%c++
        }
        else
        {
            $$<<"        sql.append(1, ')');\n";
        }
%>
        LOG_TRACE << sql;
        return sql;
    }
};
<%c++
if(!schema.empty())
{
    $$<<"} // namespace "<<schema<<"\n";
}
%>
} // namespace [[dbName]]
} // namespace drogon_model
