#include "style2_renderer.h"
#include "searchpath.h"
#include "dbwrapper.h"
#include "filters.h"
#include "indoor_plan.h"

#include <maps/wikimap/mapspro/services/renderer/src/data_sets/include/data_set.h>
#include <maps/renderer/libs/design/include/loader.h>
#include <maps/renderer/libs/style2_renderer/include/render.h>
#include <maps/renderer/libs/style2_renderer/include/vec3_render.h>
#include <maps/libs/common/include/profiletimer.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/pgpool/include/pgpool3.h>
#include <maps/libs/xml/include/xml.h>
#include <contrib/libs/yaml-cpp/include/yaml-cpp/yaml.h>
#include <boost/format.hpp>

using namespace maps;
using namespace maps::renderer;

namespace maps::wiki::renderer {

namespace {

const std::string DATASET_SOURCE_ID = "nmaps";

std::shared_ptr<image::ImageStorage> imageStorage()
{
    static const auto imageStorage = std::make_shared<image::ImageStorage>();
    return imageStorage;
}

const style2_renderer::MapRenderer& mapRenderer()
{
    static const style2_renderer::MapRenderer renderer(imageStorage());
    return renderer;
}

const style2_renderer::Vec3Renderer& vec3Renderer()
{
    static const style2_renderer::Vec3Renderer renderer;
    return renderer;
}

style2_renderer::DataSets loadDatasets(
    const std::string& groupsFileName,
    const std::string& layersFileName)
{
    YAML::Node groupsYaml = YAML::LoadFile(groupsFileName);
    YAML::Node layersYaml = YAML::LoadFile(layersFileName);

    auto datasetHolder = std::make_unique<DataSet>(layersYaml, groupsYaml);
    style2_renderer::DataSets datasets;
    datasets.emplace(DATASET_SOURCE_ID, std::move(datasetHolder));
    return datasets;
}

std::shared_ptr<design::Design> loadDesign(const std::string& designFilename)
{
    const std::string iconsTarOrDir = (std::filesystem::path(designFilename).parent_path() / "icons.tar").string();
    return std::make_shared<design::Design>(
        design::loadDesign(
            designFilename,
            iconsTarOrDir,
            nullptr, // const LayerProcessor& process
            imageStorage()));
}

/*Helper to walk around multidesign specific*/
const xml3::Node extractNode(
        const xml3::Doc& config,
        const std::string& nodeName,
        const std::string& layerName )
{
    auto commonXPath = str( boost::format("/config/%1%") % nodeName);
    auto mdXPath = str( boost::format("/config/multidesign/layer[@name='%1%']/%2%")
                        % layerName
                        % nodeName );
    auto node = config.node(commonXPath, true);
    if (node.isNull()){
        node = config.node(mdXPath);
    }
    return node;
}

} // namespace

Style2Renderers Style2Renderer::create(
    const std::string& layerName,
    const std::filesystem::path& layersPath)
{
    Style2Renderers renderers;
    const auto configFileName = (layersPath / layerName / "config.xml").string();
    xml3::Doc xml(configFileName);

    auto createRenderer = [&] (const std::string& layerName) {
        auto renderer = std::make_shared<Style2Renderer>(
            layerName, layersPath, configFileName);
        renderers.insert({layerName, std::move(renderer)});
    };

    auto multiDesignLayerNodes = xml.nodes("/config/multidesign/layer", true);
    if (!multiDesignLayerNodes.size()) {
        createRenderer(layerName);
    } else {
        for (size_t i = 0; i < multiDesignLayerNodes.size(); ++i) {
            auto layerName = multiDesignLayerNodes[i].attr<std::string>("name");
            createRenderer(layerName);
        }
    }
    return renderers;
}

Style2Renderer::Style2Renderer(
    const std::string& layerName,
    const std::filesystem::path& layersPath,
    const std::string& configFileName)
    : layerName_(layerName)
    , layersPath_(layersPath)
{
    ProfileTimer pt;
    xml3::Doc xml(configFileName);

    auto optNode = extractNode(xml, "options", layerName);
    REQUIRE(!optNode.attr<bool>("layerDisabled", false),
            "Layer: " << layerName << " disabled");
    expires_ = std::chrono::seconds(optNode.attr<unsigned int>("expires", 0));

    auto imageNode = extractNode(xml, "image", layerName);
    transparent_ = imageNode.attr<bool>("transparent");

    auto limitNode = xml.node("/config/objectsQuantityLimit", true);
    objectsQuantityLimit_ =
        limitNode.isNull()
            ? DEFAULT_OBJECTS_QUANTITY_LIMIT
            : limitNode.attr<size_t>("value", DEFAULT_OBJECTS_QUANTITY_LIMIT);

    dbWrapper_.reset(new maps::wiki::renderer::DbWrapper(xml.nodes("/config/db"), layerName));
    searchPath_.reset(new SearchPath(xml.node("/config/searchPath", true)));

    auto filtersNode = xml.node("/config/filters", true);
    if (!filtersNode.isNull()) {
        filters_.reset(new Filters(filtersNode));
    }

    INFO() << "Style2 renderer: load fonts. elapsed... " << pt.getElapsedTime();
    fonts_ = font::loadSysFonts();
    INFO() << "Style2 renderer: : loaded fonts. elapsed... " << pt.getElapsedTime();

    const auto groupsFileName = (layersPath_ / "v2" / "groups.yaml").string();
    const auto layersFileName = (layersPath_ / "v2" / "layers.yaml").string();
    datasets_ = loadDatasets(groupsFileName, layersFileName);
    INFO() << "Style2 renderer: : loaded datasets. elapsed... " << pt.getElapsedTime();

    reloadDesign();
    INFO() << "Style2 renderer for layer " << layerName << ": created. elapsed... " << pt.getElapsedTime();
}

Style2Renderer::~Style2Renderer()
{}

void Style2Renderer::reloadDesign()
{
    const auto designFileName =
        (layersPath_ / "v2" / ("design." + layerName_ + ".json")).string();

    auto design = loadDesign(designFileName);
    std::unique_lock lock(designMutex_);
    design_ = std::move(design);
}

pgpool3::TransactionHandle getTransaction(
    maps::wiki::renderer::DbWrapper& dbWrapper_,
    const std::string& token,
    const TBranchId branchId)
{
    auto scopes = dbWrapper_.allowedScopes(branchId);
    Scope scope = scopes.contains(Scope::All) ? Scope::All : Scope::Objects;

    auto connection = dbWrapper_.getConnection(token, branchId, scope);
    return pgpool3::makeNonTransaction(std::move(connection));
}

AuxData Style2Renderer::createAuxData(const RequestParams& params) const
{
    AuxData auxData;
    auxData.searchPath = searchPath_->resolve(params.branchId);

    auxData.getTransaction = [this, token = params.token, branchId = params.branchId]() {
        return getTransaction(*dbWrapper_, token, branchId);
    };

    // todo: test getFilter function
    auxData.getFilter = [this, token = params.token](const std::string& filterId) {
        auto filterClause = filters_->filterClause(filterId, token);
        INFO() << "filter id: " << filterId;
        INFO() << "filter clause: " << filterClause;
        return filterClause;
    };
    auxData.objectsQuantityLimit = objectsQuantityLimit_;
    auxData.shiftGeometry = {params.dx, params.dy};
    return auxData;
}

image::png::PngBuffer Style2Renderer::renderTile(
    const RequestParams& reqParams) const
{
    std::unique_lock lock(designMutex_);
    const auto design = design_;
    lock.unlock();

    const auto& mode = design->basicMode;

    style2_renderer::RenderParams params(
        reqParams.x, reqParams.y, reqParams.z,
        mode.styleSet, mode.resources);

    params.auxParams[SUBLAYERS] = reqParams.subLayers;
    params.auxData = createAuxData(reqParams);
    params.scale = reqParams.scale;
    params.labelingType = style2_renderer::LabelingType::RTLabeler;
    params.fonts = fonts_;

    auto pngFormat = transparent_
        ? style2_renderer::MapRenderer::PngFormat::Rgba
        : style2_renderer::MapRenderer::PngFormat::Palette;

    return mapRenderer().renderPng(datasets_, params, pngFormat).png;
}

std::vector<char> Style2Renderer::renderVec3Tile(
    const RequestParams& reqParams,
    vecdata3::ResultFormat format) const
{
    std::unique_lock lock(designMutex_);
    const auto design = design_;
    lock.unlock();

    const auto& mode = design->basicMode;

    style2_renderer::Vec3RenderParams params(
        style2_renderer::RenderParams(
            reqParams.x, reqParams.y, reqParams.z,
            mode.styleSet, mode.resources)
    );
    if (reqParams.zoomRange) {
        params.ftZoomRange = reqParams.zoomRange.value();
    }

    params.auxParams[SUBLAYERS] = reqParams.subLayers;
    params.auxData = createAuxData(reqParams);
    params.format = format;

    return vec3Renderer().render(datasets_, params);
}

std::string Style2Renderer::indoorPlan(
    TIndoorPlanId planId,
    const TBranchId branchId,
    const std::string& token) const
{
    auto transaction = getTransaction(*dbWrapper_, token, branchId);
    auto searchPath = searchPath_->resolve(branchId);
    if (!searchPath.empty()) {
        transaction.get().set_variable("search_path", searchPath);
    }
    return ::maps::wiki::renderer::indoorPlan(transaction, planId);
}

std::string Style2Renderer::dataSetSchema() const
{
    const auto& dataset = datasets_.at(DATASET_SOURCE_ID);
    return dataset->schema().value();
}

} // namespace maps::wiki::renderer
