/**
 *
 *  @file RestfulController.h
 *  @author An Tao
 *
 *  Copyright 2018, An Tao.  All rights reserved.
 *  https://github.com/an-tao/drogon
 *  Use of this source code is governed by a MIT license
 *  that can be found in the License file.
 *
 *  Drogon
 *
 */

#pragma once

#include <drogon/exports.h>
#include <drogon/drogon.h>
#include <drogon/orm/DbClient.h>
#include <drogon/orm/Mapper.h>
#include <trantor/utils/NonCopyable.h>
#include <string>
#include <functional>
#include <vector>

namespace drogon
{
/**
 * @brief this class is a helper class for the implementation of restful api
 * controllers generated by the drogon_ctl command.
 */
class DROGON_EXPORT RestfulController : trantor::NonCopyable
{
  public:
    void enableMasquerading(const std::vector<std::string> &pMasqueradingVector)
    {
        masquerading_ = true;
        masqueradingVector_ = pMasqueradingVector;
        for (size_t i = 0; i < masqueradingVector_.size(); ++i)
        {
            masqueradingMap_.insert(
                std::pair<std::string, size_t>{masqueradingVector_[i], i});
        }
    }

    void disableMasquerading()
    {
        masquerading_ = false;
        masqueradingVector_ = columnsVector_;
        for (size_t i = 0; i < masqueradingVector_.size(); ++i)
        {
            masqueradingMap_.insert(
                std::pair<std::string, size_t>{masqueradingVector_[i], i});
        }
    }

    void registerAJsonValidator(
        const std::string &fieldName,
        const std::function<bool(const Json::Value &, std::string &)>
            &validator)
    {
        validators_.emplace_back(fieldName, validator);
    }

    void registerAJsonValidator(
        const std::string &fieldName,
        std::function<bool(const Json::Value &, std::string &)> &&validator)
    {
        validators_.emplace_back(fieldName, std::move(validator));
    }

    /**
     * @brief make a criteria object for searching by ORM.
     *
     * @param pJson the json object presenting search criteria.
     * The json object must be an array of depth 3.
     * for example:
     * [
     *    [
     *       ["color","=","red"], //AND
     *       ["price","<",1000]
     *    ], //OR
     *    [
     *        ["color","=","white"], //AND
     *        ["price","<",800],  //AND
     *        ["brand","!=",null]
     *    ]
     * ]
     * @return orm::Criteria
     */

    orm::Criteria makeCriteria(const Json::Value &pJson) noexcept(false);

  protected:
    RestfulController(const std::vector<std::string> &columnsVector)
        : columnsVector_(columnsVector)
    {
    }

    std::vector<std::string> fieldsSelector(const std::set<std::string> &fields)
    {
        std::vector<std::string> ret;
        for (auto &field : masqueradingVector_)
        {
            if (!field.empty() && fields.find(field) != fields.end())
            {
                ret.emplace_back(field);
            }
            else
            {
                ret.emplace_back(std::string{});
            }
        }
        return ret;
    }

    template <typename T>
    Json::Value makeJson(const HttpRequestPtr &req, const T &obj)
    {
        auto &queryParams = req->parameters();
        auto iter = queryParams.find("fields");
        if (masquerading_)
        {
            if (iter != queryParams.end())
            {
                auto fields = utils::splitStringToSet(iter->second, ",");
                return obj.toMasqueradedJson(fieldsSelector(fields));
            }
            else
            {
                return obj.toMasqueradedJson(masqueradingVector_);
            }
        }
        else
        {
            if (iter != queryParams.end())
            {
                auto fields = utils::splitString(iter->second, ",");
                return obj.toMasqueradedJson(fields);
            }
            else
            {
                return obj.toJson();
            }
        }
    }

    bool doCustomValidations(const Json::Value &pJson, std::string &err)
    {
        for (auto &validator : validators_)
        {
            if (pJson.isMember(validator.first))
            {
                if (!validator.second(pJson[validator.first], err))
                {
                    return false;
                }
            }
        }
        return true;
    }

    bool isMasquerading() const
    {
        return masquerading_;
    }

    const std::vector<std::string> &masqueradingVector() const
    {
        return masqueradingVector_;
    }

  private:
    bool masquerading_{true};
    std::vector<std::string> masqueradingVector_;
    std::vector<
        std::pair<std::string,
                  std::function<bool(const Json::Value &, std::string &)>>>
        validators_;
    std::unordered_map<std::string, size_t> masqueradingMap_;
    const std::vector<std::string> columnsVector_;
};
}  // namespace drogon
