#include <maps/libs/json/include/builder.h>
#include <maps/libs/json/include/std.h>

#include <maps/libs/common/include/exception.h>
#include <maps/libs/common/include/base64.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/json/include/value.h>
#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/common/include/exception.h>

#include <mapreduce/yt/interface/client.h>
#include <library/cpp/string_utils/base64/base64.h>


#include <maps/wikimap/mapspro/services/mrc/libs/privacy_detector/include/privacy_detector_faster_rcnn.h>

#include <util/system/fs.h>
#include <util/generic/set.h>

#include <random>
#include <string>
#include <list>
#include <fstream>

using namespace NYT;

TSet<int64_t> loadFeatureIDs(const TString &path) {
    TSet<int64_t> featureIdsSet;
    std::ifstream ifs(path);
    if (!ifs.is_open())
        return featureIdsSet;
    for (; !ifs.eof();)
    {
        std::string line; std::getline(ifs, line);
        if (line.empty())
            continue;
        if ('#' == line[0])
            continue;
        featureIdsSet.insert(std::stoll(line));
    }
    for (int64_t t : featureIdsSet) {
        INFO() << t;
    }
    return featureIdsSet;
}

void SaveBBox(int64_t x1, int64_t y1, int64_t x2, int64_t y2, maps::json::ObjectBuilder &builder) {
    builder["bbox"] << [&](maps::json::ArrayBuilder builder) {
        builder << [&](maps::json::ArrayBuilder builder) {
                builder << x1;
                builder << y1;
            };
        builder << [&](maps::json::ArrayBuilder builder) {
                builder << x2;
                builder << y2;
            };
    };
}

void SaveObjects(const TNode& node, maps::json::ObjectBuilder &builder) {
    const TVector<TNode>& objectList = node.AsList();
    builder["objects"] << [&objectList](maps::json::ArrayBuilder builder) {
        for (const TNode& objectNode : objectList) {
            const TVector<TNode>& rcNode = objectNode["bbox"].AsList();
            const int64_t x1 = rcNode[0].AsList()[0].AsInt64();
            const int64_t y1 = rcNode[0].AsList()[1].AsInt64();
            const int64_t x2 = rcNode[1].AsList()[0].AsInt64();
            const int64_t y2 = rcNode[1].AsList()[1].AsInt64();
            const std::string num = objectNode["num"].AsString();
            for (size_t i = 0; i < num.size(); i++) {
                builder << [&](maps::json::ObjectBuilder builder) {
                    builder["type"] = num.substr(i, 1);
                    SaveBBox(x1 + i * (x2 - x1) / num.size(), y1, x1  + (i + 1) * (x2 - x1) / num.size(), y2, builder);
                };
            }
        };
    };
}

TString BlurCarNumber(const TString& encimageStr) {
    static const maps::mrc::privacy_detector::FasterRCNNDetector privacyDetector;
    std::vector<std::uint8_t> encimage(Base64DecodeBufSize(encimageStr.length()));
    size_t encimageSize = Base64Decode(encimage.data(), encimageStr.begin(), encimageStr.end());
    encimage.resize(encimageSize);
    cv::Mat image = cv::imdecode(encimage, 1);

    maps::mrc::privacy_detector::PrivacyImageBoxes objects = privacyDetector.detect(image);
    bool carNumberExists = false;
    for (size_t i = 0; i < objects.size(); i++) {
        if (objects[i].type == maps::mrc::db::ObjectInPhotoType::Face)
            continue;
        carNumberExists = true;
        const cv::Rect bbox = objects[i].box;
        cv::blur(image(bbox), image(bbox), bbox.size() / 2);
    }
    if (!carNumberExists)
        return encimageStr;

    std::vector<uint8_t> encodedImageData;
    cv::imencode(".jpeg", image, encodedImageData);
    return maps::base64Encode(std::string(encodedImageData.begin(), encodedImageData.end())).c_str();
}

void Download(IClientPtr client,
              const TString &inputTable,
              const TSet<int64_t> &setFeatureIDs,
              const TString &outputPath) {
    INFO() << "Make result json";

    std::ofstream ofs(outputPath);
    if (!ofs.is_open())
        ERROR() << "Unable to open output file: " << outputPath;
    maps::json::Builder builder(ofs);

    builder << [client, &inputTable, &setFeatureIDs](maps::json::ObjectBuilder builder) {
        builder["images_with_objects"] << [client, &inputTable, &setFeatureIDs](maps::json::ArrayBuilder builder) {
            TTableReaderPtr<TNode> reader = client->CreateTableReader<TNode>(inputTable);
            for (int processedItems = 0; reader->IsValid(); reader->Next(), processedItems++) {
                if ((processedItems + 1) % 1000 == 0) {
                    INFO() << "Processed " << (processedItems + 1) << " items";
                }

                const TNode& inpRow = reader->GetRow();

                int64_t featureID = inpRow["feature_id"].AsInt64();
                if (!setFeatureIDs.empty() && !setFeatureIDs.contains(featureID))
                    continue;
                //INFO() << featureID;
                builder << [&](maps::json::ObjectBuilder builder) {
                    builder["feature_id"] << featureID;
                    builder["image"] << BlurCarNumber(inpRow["image"].AsString());
                    builder["lat"] << inpRow["lat"].AsDouble();
                    builder["lon"] << inpRow["lon"].AsDouble();
                    SaveObjects(inpRow["objects"], builder);
                };
            };
        };
    };
}

int main(int argc, const char** argv) try {
    Initialize(argc, argv);

    maps::cmdline::Parser parser("Download house numbers dataset from YT table to json. Divide number bbox to digits bboxes");

    maps::cmdline::Option<std::string>
        intputTableName = parser.string("input")
                                .required()
                                .help("Input YT table name");

    maps::cmdline::Option<std::string>
        outputJsonPath = parser.string("output")
                               .required()
                               .help("Path to output json file");

    maps::cmdline::Option<std::string>
        filterFeatureID = parser.string("filter_featureid")
                                .defaultValue("")
                                .help("Path to text file with feature ids, one id on the line");

    parser.parse(argc, const_cast<char**>(argv));

    INFO() << "Connecting to yt::hahn";
    IClientPtr client = CreateClient("hahn");

    TSet<int64_t> featureIdsSet;
    if (filterFeatureID.defined())
        featureIdsSet = loadFeatureIDs(filterFeatureID.c_str());

    Download(client, intputTableName.c_str(), featureIdsSet, outputJsonPath.c_str());
    return EXIT_SUCCESS;
}
catch (const maps::Exception& e) {
    FATAL() << "Worker failed: " << e;
    return EXIT_FAILURE;
}
catch (const std::exception& e) {
    FATAL() << "Worker failed: " << e.what();
    return EXIT_FAILURE;
}
