#include "ft_helper.h"
#include "magic_strings.h"

namespace maps {

namespace {

const std::string CENTERS_CNT = "ft_centers_cnt";
const std::string POLYGONS_CNT = "ft_polygons_cnt";
const std::string IS_TRIVIAL = "is_trivial";
const std::string FT_ID = "ft_id";
const std::string FT_TYPE_ID = "ft_type_id";
const std::string FT_TYPE_IDS = "ft_type_ids";

std::string
centersCountSql(const std::string& ftId)
{
    return
        "(SELECT count(node_id) FROM ft_center "
        "WHERE ft_center.ft_id = " + ftId + ") ";
}

std::string
ftTypeIdSql(const std::string& ftId)
{
    return
        "(SELECT ft_type_id FROM ft as ft_ft "
        "WHERE ft_ft.ft_id = " + ftId + ") ";
}

bool
isFt(const std::string& origCategory)
{
    static std::set<std::string> ftCategories =
        {"ft", "ft_nm", "ft_fc", "ft_fc_el", "ft_fc_jc", "ft_ln_el", "ft_ln_jc"};
    return ftCategories.find(origCategory) != ftCategories.end();
}


} // namespace

std::string
isTrivialSql(const std::string& ftId)
{
    return
        "((SELECT count(edge_id) FROM ft_edge fe WHERE fe.ft_id = " + ftId + ") "
            "= 1 "
        "AND "
        "(SELECT count(nm_id) FROM ft_nm fn WHERE fn.ft_id = " + ftId + ")"
            " = 0 "
        "AND "
        "(SELECT ft_type_id FROM ft f WHERE f.ft_id = " + ftId + ") "
            "in (" + trivialFtTypes() + "))";
}

std::string
ftSql(const std::string& ftId, const std::string& ftPrefix)
{
    return
        ftId + " " + ftPrefix + FT_ID + ", " +
        ftTypeIdSql(ftId) + ftPrefix + FT_TYPE_ID + ", " +
        isTrivialSql(ftId) + ftPrefix + IS_TRIVIAL + ", " +
        centersCountSql(ftId) + ftPrefix + CENTERS_CNT + ", " +
        "CAST(NULL AS TEXT) " + ftPrefix + FT_TYPE_IDS;
}

std::string
ftMacroSql(const std::string& ftId, const std::string& ftPrefix)
{
    return
        ftId + " " + ftPrefix + FT_ID + ", " +
        ftTypeIdSql(ftId) + ftPrefix + FT_TYPE_ID + ", " +
        isTrivialSql(ftId) + ftPrefix + IS_TRIVIAL + ", " +
        "NULL " + ftPrefix + CENTERS_CNT + ", "
        "CAST(NULL AS TEXT) " + ftPrefix + FT_TYPE_IDS;
}

std::string
ftElSql(const std::string& ftId, const std::string& edgeId, const std::string& ftPrefix)
{
    return
        "(SELECT array_agg(DISTINCT ft.ft_type_id) FROM ft "
            "JOIN ft_edge USING (ft_id)"
            "WHERE ft_edge.edge_id =  " + edgeId + " "
            ") " + ftPrefix + FT_TYPE_IDS + ", " +
        ftId + " " + ftPrefix + FT_ID + ", "
        "NULL " + ftPrefix + FT_TYPE_ID + ", " +
        isTrivialSql(ftId) + ftPrefix + IS_TRIVIAL + ", "
        "NULL " + ftPrefix + CENTERS_CNT;
}

std::string
ftFcElSql(const std::string& edgeId, const std::string& ftPrefix)
{
    return
        "(SELECT array_agg(DISTINCT ft.ft_type_id) FROM ft,ft_face,face_edge "
            "WHERE face_edge.edge_id =  " + edgeId + " "
            "AND ft_face.face_id = face_edge.face_id "
            "AND ft.ft_type_id NOT IN (" + polygonFtTypes() + ") "
            "AND ft.ft_id = ft_face.ft_id) " + ftPrefix + FT_TYPE_IDS + ", "
        "NULL " + ftPrefix + FT_ID + ", "
        "NULL " + ftPrefix + FT_TYPE_ID + ", "
        "NULL " + ftPrefix + IS_TRIVIAL + ", "
        "NULL " + ftPrefix + CENTERS_CNT;
}

std::string
ftFcJcSql(const std::string& nodeId, const std::string& ftPrefix)
{
    return
        "(SELECT array_agg(DISTINCT ft.ft_type_id) "
            "FROM ft,ft_face,face_edge,edge "
            "WHERE (f_node_id=" + nodeId + " OR t_node_id=" + nodeId + ") "
            "AND face_edge.edge_id = edge.edge_id "
            "AND ft_face.face_id = face_edge.face_id "
            "AND ft.ft_type_id NOT IN (" + polygonFtTypes() + ") "
            "AND ft.ft_id = ft_face.ft_id) " + ftPrefix + FT_TYPE_IDS + ", "
        "NULL " + ftPrefix + FT_ID + ", "
        "NULL " + ftPrefix + FT_TYPE_ID + ", "
        "NULL " + ftPrefix + IS_TRIVIAL + ", "
        "NULL " + ftPrefix + CENTERS_CNT;
}


FtData::FtData(const pqxx::row& tuple, const std::string& ftPrefix)
    : id_(tuple.at(ftPrefix + FT_ID).as<DBID>(0))
    , ftType_(tuple.at(ftPrefix + FT_TYPE_ID).as<DBID>(0))
    , isTrivial_(tuple.at(ftPrefix + IS_TRIVIAL).as<bool>(false))
    , isPoint_((tuple.at(ftPrefix + CENTERS_CNT).as<DBID>(0) > 0)
        ? IsPoint::Yes
        : IsPoint::No)
{
    auto ftTypes = parseUniqueDBIDArray(
        tuple.at(ftPrefix + FT_TYPE_IDS).as<std::string>(STR_EMPTY_ARRAY));
    for(const auto& ftType: ftTypes) {
        if (ftType_ == 0) {
            ftType_ = ftType;
        }
        auto cat = category();
        auto fixCat = fixCategory(category::FT, ftType, isPoint_);
        REQUIRE(cat == fixCat,
            "id=" << id_ << ": ft_type_id " << ftType_
            << " conflicts with " << ftType << " : " << cat << " : " << fixCat);
    }
}

std::string
FtData::category(const std::string& origCategory) const
{
    return isFt(origCategory)
        ? fixCategory(origCategory, ftType_, isPoint_)
        : origCategory;
}

std::string
FtData::partRole() const
{
    return ftPartRole(category());
}

std::string
FtData::masterRole() const
{
    return ftMasterRole(category());
}

bool
FtData::hasFtTypeAttr() const
{
    return maps::hasFtTypeAttr(category());
}

Relation
FtData::relation(const Relation& ftRel) const
{
    Relation relation(ftRel);
    if (relation.role == role::PART && relation.master ==  category::FT) {
        relation.role = partRole();
    }
    relation.master = category(relation.master);
    relation.slave = category(relation.slave);
    return relation;
}

Relation
FtData::relation(const FtData& parentFt) const
{
    Relation relation = {
        parentFt.category(),
        masterRole(),
        category(),
        table::FT,
        RelationDirection::ToMaster
    };
    return relation;
}

} // namespace maps
