#include "sublayerstraverse.h"

#include <maps/infra/yacare/include/error.h>
#include <maps/libs/common/include/exception.h>
#include <util/string/cast.h>

#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <cctype>

namespace maps {
namespace wiki {
namespace renderer {

namespace {

const std::string MPRO_LAYER_TYPE = "mproLayerType";
const std::string LAYER_TYPE_SELECTION = "selection";
const std::string LAYER_TYPE_FILTER = "filter";
const std::string LAYER_TYPE_TEXT = "text";
const std::string LAYER_TYPE_INDOOR = "indoor";

const std::string DELIMITER_LAYERS = ",";
const std::string DELIMITER_PARAMS = ":";

const std::string MPRO_SELECTION_LAYER_FILTER_CLAUSE = "mproSelectionLayerFilterClause";
const std::string MPRO_SELECTION_LAYER_FILTER_CLAUSE2 = "mproSelectionLayerFilterClause2";

const std::string MPRO_INDOOR_LAYER_FILTER_CLAUSE = "mproIndoorLayerFilterClause";

} // namespace

SubLayersTraverse::SubLayersTraverse(
        const ParsedSubLayers& parsedSubLayers,
        const std::string& token,
        maps::tilerenderer4::IOnlineRenderer& renderer,
        const Filters* filters)
    : requestedSubLayersToParams_(parsedSubLayers)
    , renderer_(renderer)
    , filters_(filters)
    , token_(token)
{
    REQUIRE(!requestedSubLayersToParams_.empty(),
            yacare::errors::BadRequest() << "Empty sublayers");
}

bool
SubLayersTraverse::onEnterGroupLayer(const maps::tilerenderer4::LayerInfo& layer)
{
    auto type = layer.metadata(MPRO_LAYER_TYPE);
    if (type == LAYER_TYPE_FILTER) {
        return onVisitFilterLayer(layer);
    }

    if (type == LAYER_TYPE_INDOOR) {
        return onVisitIndoorLayer(layer);
    }

    groupLayersStack_.push(
            { (bool)requestedSubLayersToParams_.count(layer.id()),
              boost::optional<std::string>(),
              type });
    return true;
}

bool
SubLayersTraverse::onVisitLayer(const maps::tilerenderer4::LayerInfo& layer)
{
    auto layerId = layer.id();
    auto subLayerIt = requestedSubLayersToParams_.find(layerId);
    if (subLayerIt == requestedSubLayersToParams_.end()
        && !groupLayersStack_.top().visible) {
        return false;
    }

    auto layerType = layer.metadata(MPRO_LAYER_TYPE);
    if (layerType == LAYER_TYPE_SELECTION) {
        if (subLayerIt == requestedSubLayersToParams_.end()) {
            return false;
        }
        const auto& params = subLayerIt->second;
        return onVisitSelectionLayer(layer, params);
    }

    if (groupLayersStack_.top().filterClause) {
        if( groupLayersStack_.top().type &&
           *groupLayersStack_.top().type == LAYER_TYPE_INDOOR ) {
            appendFilterClause(layerId, *groupLayersStack_.top().filterClause);
        }
        else {
            setFilterClause(layerId, *groupLayersStack_.top().filterClause);
        }
    }
    return true;
}

bool
SubLayersTraverse::onVisitSelectionLayer(
        const maps::tilerenderer4::LayerInfo& layer,
        const SubLayerParams& params)
{
    if (params.empty()) {
        return false;
    }

    auto selectionLayerFilterClause =
        layer.metadata(MPRO_SELECTION_LAYER_FILTER_CLAUSE);
    auto selectionLayerFilterClause2 =
        layer.metadata(MPRO_SELECTION_LAYER_FILTER_CLAUSE2);

    auto nParams = params.size();
    auto needSelectionLayerFilterClause2 =  (nParams > 1) && selectionLayerFilterClause2;
    REQUIRE(selectionLayerFilterClause || needSelectionLayerFilterClause2,
            yacare::errors::BadRequest() <<
                "selection layer filter clause not found for layer: " << layer.id());

    boost::format pfcFormatter(
        needSelectionLayerFilterClause2
        ? *selectionLayerFilterClause2
        : *selectionLayerFilterClause );

    for (const auto& param : params) {
        REQUIRE(
            std::all_of(param.begin(), param.end(), ::isdigit),
            yacare::errors::BadRequest() <<
                "SubLayer parameter is not integer: " << param);
    }

    if (needSelectionLayerFilterClause2){
        pfcFormatter
            % params[0]
            % std::string(
                         boost::algorithm::join(
                             boost::make_iterator_range(
                                 std::next(params.begin()),params.end()), ",")
                         );
    } else if (nParams == 1) {
        pfcFormatter % params[0];
    } else {
        std::string arraySql = " ANY (ARRAY[" + boost::algorithm::join(params, ",") + "])";
        pfcFormatter % arraySql;
    }
    setFilterClause(layer.id(), pfcFormatter.str());
    return true;
}

bool
SubLayersTraverse::onVisitFilterLayer(const maps::tilerenderer4::LayerInfo& layer)
{
    if (!filters_) {
        return false;
    }

    auto subLayerIt = requestedSubLayersToParams_.find(layer.id());
    if (subLayerIt == requestedSubLayersToParams_.end()) {
        return false;
    }
    const auto& params = subLayerIt->second;

    REQUIRE(params.size() == 1,
            yacare::errors::BadRequest() <<
                "Bad number of params for datafilter layer: " << params.size());

    groupLayersStack_.push(
            { true,
              filters_->filterClause(params[0], token_),
              layer.metadata(MPRO_LAYER_TYPE) });

    return true;
}

bool
SubLayersTraverse::onVisitIndoorLayer(
    const maps::tilerenderer4::LayerInfo& layer)
{
    auto subLayerIt = requestedSubLayersToParams_.find(layer.id());
    if (subLayerIt == requestedSubLayersToParams_.end()) {
        return false;
    }
    const auto& params = subLayerIt->second;

    REQUIRE(params.size() == 1,
            yacare::errors::BadRequest() <<
                "Bad number of params for indoor layer: " << params.size());

    auto indoorLayerFilterClause =
        layer.metadata(MPRO_INDOOR_LAYER_FILTER_CLAUSE);
    REQUIRE(indoorLayerFilterClause,
            yacare::errors::BadRequest() <<
                "indoor layer filter clause not found for layer: " << layer.id());

    boost::format pfcFormatter(*indoorLayerFilterClause);
    pfcFormatter % params.front();
    groupLayersStack_.push(
        { true,
          pfcFormatter.str(),
          layer.metadata(MPRO_LAYER_TYPE) });

    return true;
}

void SubLayersTraverse::appendFilterClause(LayerId layerId, std::string filterClause) {
    processFilterClause(layerId, filterClause, ProcessFilterClauseMode::Append);
}

void SubLayersTraverse::setFilterClause(LayerId layerId, std::string filterClause) {
    processFilterClause(layerId, filterClause, ProcessFilterClauseMode::Set);
}

void SubLayersTraverse::processFilterClause(LayerId layerId, std::string filterClause, ProcessFilterClauseMode mode)
{
    renderer5::postgres::PfcParams dummyPfcParams;
    dummyPfcParams.layerId = layerId;
    dummyPfcParams.sourceTableName = "dummy";

    // renderer hasn't got getPfcParams() method so we emulate it by
    // calling setPfcParams() with dummy value first. sourceTableName
    // must not be empty.
    auto oldPfcParams = renderer_.setPfcParams(dummyPfcParams);
    auto newPfcParams = oldPfcParams;
    if(mode == ProcessFilterClauseMode::Set) {
        newPfcParams.filterClause = std::move(filterClause);
    }
    else {
        newPfcParams.filterClause += " and " + std::move(filterClause);
    }
    renderer_.setPfcParams(newPfcParams);

    oldPfcParams_.push_back(oldPfcParams);
}

void
SubLayersTraverse::onLeaveGroupLayer(const maps::tilerenderer4::LayerInfo& /*layer*/)
{
    groupLayersStack_.pop();
}

bool
SubLayersTraverse::isDone()
{
    return false;
}

SubLayersTraverse::~SubLayersTraverse()
{
    for (const auto& pfcParam : oldPfcParams_) {
        renderer_.setPfcParams(pfcParam);
    }
}


TextLayersTraverse::TextLayersTraverse() = default;
TextLayersTraverse::~TextLayersTraverse() = default;

bool
TextLayersTraverse::onEnterGroupLayer(const maps::tilerenderer4::LayerInfo& layer)
{
    auto type = layer.metadata(MPRO_LAYER_TYPE);
    bool isText = (type == LAYER_TYPE_TEXT);
    if (isText) {
        textGroupLayerIds_.insert(layer.id());
    }
    visibleGroupLayersStack_.push(isText);
    return true;
}

bool
TextLayersTraverse::onVisitLayer(const maps::tilerenderer4::LayerInfo& /*layer*/)
{
    return visibleGroupLayersStack_.top();
}

void
TextLayersTraverse::onLeaveGroupLayer(const maps::tilerenderer4::LayerInfo& /*layer*/)
{
    visibleGroupLayersStack_.pop();
}

bool
TextLayersTraverse::isDone()
{
    return false;
}


ParsedSubLayers
parseSubLayers(const std::string& subLayers)
{
    ParsedSubLayers result;

    std::vector<std::string> splittedSubLayers;
    boost::split(splittedSubLayers, subLayers, boost::is_any_of(DELIMITER_LAYERS));

    for (const auto& subLayer : splittedSubLayers) {
        std::vector<std::string> subLayerWithParam;
        boost::split(subLayerWithParam, subLayer, boost::is_any_of(DELIMITER_PARAMS));
        REQUIRE(!subLayerWithParam.empty(),
                yacare::errors::BadRequest() << "Empty sublayer id");

        const auto& layerIdStr = subLayerWithParam.front();
        if (layerIdStr.empty()) {
            continue;
        }

        LayerId layerId;
        REQUIRE(TryFromString(layerIdStr, layerId),
                yacare::errors::BadRequest() <<
                    "Wrong sublayer id '" << layerIdStr << "'");

        subLayerWithParam.erase(subLayerWithParam.begin());
        result.emplace(layerId, std::move(subLayerWithParam));
    }
    return result;
}

} //renderer
} //wiki
} //maps
